java, date 관련 api - Date, LocalDateTime, ZonedDateTime, Instant와 지역시간 설정 팁

java.util.Date의 문제

가변성과 비일관성

  • Date가 time을 출력한다.
  • time의 출력 결과가 기계를 위한 시간인 EPOCK(long)로 출력된다.
Date date = new Date();
long time = date.getTime();
  • Date 객체가 가변적이다. 쓰레드의 안정성 문제 등.
Date now = new Date();
now.setTime(335983742343245l);

월이 숫자이며 0부터 시작한다.

  • 1월은 0이다.
  • 99월 등 말도 안되는 값을 입력할 수 있다.
GregorianCalendar calendar = new GregorianCalendar(2021, 99, 23);

java.time의 LocalDate, LocalDateTime, Instant

  • 자바 8부터 사용되는 시간 api 이다.
  • 전반적으로 이해하기 쉽도록 구현되어 있다.
    • Instant : 컴퓨터용 시간
    • LocalDate : (현 시스템의) 날짜
    • LocalDateTime : (현 시스템의) 날짜와 시간
    • ZonedDateTiem : (설정한 ZoneId의) 날짜와 시간
  • LocalDateTime은 현재 시스템의 시간을 나타낸다. 커널 및 시스템의 시간에 따르므로 설정에 주의한다.
Instant instant = Instant.now(); // 컴퓨터용 시간
LocalDate localDate = LocalDate.now(); // 사람용 날짜
LocalDateTime localDateTime = LocalDateTime.now(); // 사람용 날짜 + 시간
ZonedDateTime zonedDateTime = ZonedDateTime.now(); // ZoneId에 따른 사람용 날짜 + 시간
  • 날짜 설정이 명확하다.
  • 특히 월은 enum으로 설정한다.
LocalDateTime.of(2021, Month.JULY,7, 10,15);
  • 시간 비교를 위한 별도의 api가 존재한다.
LocalDateTime target = LocalDateTime.of(2023, Month.JANUARY, 1, 12, 00);
Duration between = Duration.between(target, LocalDateTime.now());
System.out.println(between.toDays());

Period period = LocalDate.now().until(LocalDate.of(2023, Month.JANUARY, 1));
System.out.println(period.get(ChronoUnit.DAYS));

ZonedId 설정과 모호함

  • LocalDateTime을 한국 현재 시간을 기준으로 독일/베를린의 시간을 구한다고 가정한다. 그럼 어떻게 구할까?
  • 나의 경우 처음에는 아래와 같이 접근했다. LocalDateTime.now().atZone(베를린)을 넣으면 될 것이라 예상했다.
  • 하지만 예상과 다른 결과가 발생했다.
LocalDateTime now = LocalDateTime.now(); // 2023-01-31T23:33:08.334684900
ZonedDateTime systemDefault = now.atZone(ZoneId.systemDefault()); // 2023-01-31T23:33:08.334684900+09:00[Asia/Seoul]
ZonedDateTime berlin = now.atZone(ZoneId.of("Europe/Berlin")); // 2023-01-31T23:33:08.334684900+01:00[Europe/Berlin]
  • 결과는 세 개의 시간 모두 동일하게 ‘2023-01-31T23:33:08.334684900’ 를 출력한다. 반대로, 로컬시간을 특정 존의 시간으로 간주한다.
  • 개인적으로 atZone의 의미가 모호하다고 생각한다. 그러므로 해외의 시간을 조회할 때는 LocalDateTime으로부터 시작하면 안된다고 생각한다.
  • 나의 경우 Instant로 시작하는 것이 분명해보였다.
Instant utcNow = Instant.now(); // 2023-01-31T14:38:01.908973700Z
ZonedDateTime berlin = utcNow.atZone(ZoneId.of("Europe/Berlin")); // 2023-01-31T15:38:01.908973700+01:00[Europe/Berlin]

시간에 대한 국제적 합의와 한계

  • 위에 사용된 zone, offset, UTC, +1:00 등을 이해하기 위해서는 불가피하게 국제적으로 정한 시간에 대한 측정 방식을 이해해야 한다.

시간을 정의하는 규칙

  • GMT : 시간을 정하는 기준. 그리니치. 레거시
  • UTC : 자전주기를 반영한 더 과학적인 방법
  • 소프트웨어는 UTC를 채택한다.

지역 시간과 offset

  • offset이란 특정 지역의 시간이 UTC를 기준으로 얼마나 느리거나 빠른지를 표기하는 방식이다. 한국은 +9:00 이다.
  • offset은 time zone과 반드시 일치하지 않으며, 동일한 좌표라도 time zone 자체가 변경될 수 있다. 그 이유는
    • 정치 경제적 상황에 따라 특정 타임존의 offset이 변경된다.
    • 서머타임 등 기간에 따라 offset이 변경된다.
    • 특정 이유로 특정 지역의 타임존 자체가 변경될 수 있다.
  • UTC에 대해서는 거의 오차가 없는 동일한 시간을 언제 어디서나 누구나 측정할 수 있다. 하지만 offset은 역사와 정치, 문화에 따라 계속 변한다.

IANA time zone database와 소프트웨어의 시간 설정

  • UTC를 수학적으로 정의하더라도 지역에 따른 타임존과 오프셋은 가변적이다.
  • 다만 이미 지난 타임존과 오프셋은 명확하고 기록 가능하다. 이러한 타임존과 오프셋을 관리하고 수집하는 기관 중 IANA database가 가장 신뢰할 수 있는 기관이다.

자바의 IANA 적용의 한계

  • 자바의 경우 IANA을 수용하였다. 하지만 특정 시점에서 포크한 IANA의 데이터로 미래를 완벽하게 예측할 수 없다. 그렇다고 JDK가 통신 API를 통해 실시간 IANA과 통신할 수는 없다. 그러므로 자바가 제공하는 ZonedDateTime은 완전하게 일치하지 않는다.
  • 자바 이외의 자바스크립트 등 여러 언어는 정확한 지역 시간 설정을 개발자에게 미룬다.
  • 그러므로 엄격한 시간 관리가 필요한 경우 내장 ZoneId나 offset을 사용해서는 안된다.

참고

  • https://jaimemin.tistory.com/1537
  • https://meetup.nhncloud.com/posts/125\
  • https://stackoverflow.com/questions/45592878/java-doesnt-have-information-about-all-iana-time-zones