자바, SQL 날짜/시간 정리

자바, SQL 날짜/시간 정리

Category
Published
May 17, 2024
Last updated
Last updated October 25, 2024

TL;DR

  • Java
    • Date는 immutable하지 않고, 레거시
    • LocalDate = 날짜, LocalTime = 시간, LocalDateTime = LocalDate + LocalTime
    • ZoneId: 지역 정보(Asia/Seoul), ZoneOffset: 시간대 정보(+09:00)
    • OffsetDateTime = LocalDateTime + ZoneOffset 로 시간대 정보가 포함됨
    • ZoneDateTime = LocalDateTime + ZoneOffset + ZoneId 로 지역 정보가 포함되어 써머타임등 관리 가능
    • Instant: 1970-01-01T00:00:00Z 를 기준으로 지난 시간을 측정. OffsetDateTime과 비슷하다
  • SQL
    • DATE: 날짜만 저장하며 시간 정보는 포함되지 않습니다.
    • DATETIME: 날짜와 시간을 저장하며 시간대 정보를 포함하지 않습니다.
    • TIMESTAMP: 날짜와 시간을 저장하며, UTC 기반으로 저장되고 타임존 변환을 지원합니다.

자바의 시간

로컬 시간

java.util.Date

  • 레거시

java.sql.TimeStamp

  • 레거시

java.time.LocalDate

java.time.LocalTime

java.time.LocalDateTime

  • LocalDate + LocalTime

Timezone-aware을 다루기 전에

ZoneId

val timeZone = ZoneId.of("America/Guatemala")

ZoneOffset

val zoneOffset = ZoneOffset.of("-06:00")

Timezone-aware

java.time.Instant

  • 유닉스 타임스탬프 시작 시간인 1970-01-01T00:00:00Z 를 기준으로 지난 시간을 저장한다
private Instant(long epochSecond, int nanos) { this.seconds = epochSecond; // 1970-01-01T00:00:00Z 이후 지난 시간(초) this.nanos = nanos; // 소수점 시간, 음수일 수 없다 }

java.time.OffsetTime

  • LocalTime + ZoneOffset

java.time.OffsetDateTime

  • LocalDateTime + ZoneOffset
  • LocalDateTime에 시간대(-12h ~ UTC ~ +12h)를 포함한 것
  • Instant와 사용 측면에서 유사

java.time.ZoneDateTime

  • LocalDateTime + ZoneOffset + ZoneId
  • OffsetDateTime과 달리 ZoneId가 들어간다
    • 즉, +09:00 만이 아닌, Asia/Seoul 정보도 저장한다
    • DST(Daylight Saving Time)와 같은 써머타임의 정보도 들어가 있는 것
  • Instant와 달리 시간대(ZoneId) 정보를 포함함
 
 
ZoneDateTime 생성자, of 구조
private ZonedDateTime(LocalDateTime dateTime, ZoneOffset offset, ZoneId zone) { this.dateTime = dateTime; this.offset = offset; this.zone = zone; } public static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone) { return ofLocal(localDateTime, zone, null); } public static ZonedDateTime ofLocal(LocalDateTime localDateTime, ZoneId zone, ZoneOffset preferredOffset) { if (zone instanceof ZoneOffset) { return new ZonedDateTime(localDateTime, (ZoneOffset) zone, zone); } ZoneRules rules = zone.getRules(); List<ZoneOffset> validOffsets = rules.getValidOffsets(localDateTime); ZoneOffset offset; if (validOffsets.size() == 1) { offset = validOffsets.get(0); } else if (validOffsets.size() == 0) { ZoneOffsetTransition trans = rules.getTransition(localDateTime); localDateTime = localDateTime.plusSeconds(trans.getDuration().getSeconds()); offset = trans.getOffsetAfter(); } else { if (preferredOffset != null && validOffsets.contains(preferredOffset)) { offset = preferredOffset; } else { offset = Objects.requireNonNull(validOffsets.get(0), "offset"); // protect against bad ZoneRules } } return new ZonedDateTime(localDateTime, offset, zone); }
ZoneDateTime.now() 호출 구조
public static ZonedDateTime now() { return now(Clock.systemDefaultZone()); } public static ZonedDateTime now(ZoneId zone) { return now(Clock.system(zone)); } public static ZonedDateTime now(Clock clock) { Objects.requireNonNull(clock, "clock"); final Instant now = clock.instant(); // called once return ofInstant(now, clock.getZone()); }

SQL

DATE

  • 형식: YYYY-MM-DD
  • 범위: 1000-01-01 ~ 9999-12-31
날짜만 저장할 때 사용합니다. 시간 정보는 포함되지 않습니다.

DATETIME

  • 형식: YYYY-MM-DD HH:MM:SS
  • 범위: 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
날짜와 시간을 함께 저장할 때 사용합니다. 시간대 정보는 포함되지 않습니다.

TIMESTAMP

  • 형식: YYYY-MM-DD HH:MM:SS
  • 범위: 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC (범위는 MySQL 기준으로 다를 수 있습니다)
저장 시 서버의 타임존 설정에 따라 UTC로 변환되어 저장되고, 읽을 때는 다시 로컬 타임존으로 변환됩니다.
자동으로 CURRENT_TIMESTAMP로 초기화되거나 업데이트되는 특성을 가질 수 있습니다.
 

References

notion image