Hello

Daylight Saving Time(DST) in JAVA 본문

java

Daylight Saving Time(DST) in JAVA

nari0_0 2023. 4. 4. 18:23
728x90

데이터 포멧이 미국시인 데이터를 받아 한국시로 변환해 처리해야하는 일이 있었다.

단순하게  미국시+8을 생각했는데 ST(Summer time) 혹은, DST(Daylight saving time)라고 하는 시간제가 적용된 시간 처리가 필요했다.

 

먼저 DST에 대해 알아볼 필요가 있다. 하절기에 표준시를 원래 시간보다 한 시간 앞당긴 시간을 쓰는 것을 말한다.

필요하다면 링크를 통해 DST사용 국가 확인 후 적용하면 된다.

 

2023년 DST는 2023년3월12일 - 2023년11월05일에 종료된다.
여름에는 오전 2시부터 시작되는데 시계는 1시간 당겨쓰게 되어 오전 3시부터 시작되고,
가을에는 시계가 오전 2시부터 시작되어 당겨쓴 1시간을 반납해 1시로 되돌아 갑니다.
즉, 2023-03-12 02:00:00 시 부터 2023-03-12 03:00:00 시 로 사용된다.

 

java 8 에서 추가된 java.time을 사용해 DST를 처리하는 방법을 정리하고자 한다.

java.util을 사용해 처리하는 방법은 baeldung 블로그에 정리된 내용을 읽어보는 것을 추천합니다.

LocalDateTime, ZonedDateTime

LocalDateTime은 시간대가 없는 날짜-시간을 표현한다. 
ZonedDateTime은 시간대가 있는 날짜-시간을 표현한다. 애매한 현지 날짜-시간을 처리하는데 오프셋과 함께 날짜-시간을 저장합니다.
DST를 처리하기 위해서는 ZonedDateTime를 사용해야한다.


아래의 예시를 확인하면 dateTime 문자열은 시간대 정보를 포함하고 있지만 LocalDateTime은 오프셋, 시간대 정보나 time-line 에서 순간을 표현하지 못하는 것을 확인 할 수 있습니다.
ZonedDateTime은 두 가지 정보 -08:00, [America/Los_Angeles]가 같이 보여진다. 각각 ZoneOffset, ZoneId 입니다.

String dateTime = "2023-03-12 01:59:59 AM America/Los_Angeles";
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd h:mm:ss a VV", Locale.ENGLISH);
LocalDateTime localDateTime = LocalDateTime.parse(dateTime, dateTimeFormatter);
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTime, dateTimeFormatter);
Assert.assertEquals("2023-03-12T01:59:59", localDateTime.toString());
Assert.assertEquals("2023-03-12T01:59:59-08:00[America/Los_Angeles]", zonedDateTime.toString());

LocalDateTime은 zoneOffset, ZoneId 갖지 못하기 때문에 DST처리가 필요하다면 ZonedDateTime으로 변환이 필요하다.

ZonedDateTime change1 = ZonedDateTime.of(localDateTime, ZoneId.of("America/Los_Angeles"));
ZonedDateTime change2 = localDateTime.atZone(ZoneId.of("America/Los_Angeles")); //내부적으로 ZonedDateTime.of 호출

Assert.assertEquals("2023-03-12T01:59:59-08:00[America/Los_Angeles]", change1.toString());
Assert.assertEquals("2023-03-12T01:59:59-08:00[America/Los_Angeles]", change2.toString());

2023년 DST 시작 시간은 위에 작성해 두었다.

DST가 시작되기 전 2023-03-12 01:59:59 시간에 10분을 더하면 DST가 처리된 03:09:59시간, ZoneOffset(-07:00) 을 확인할 수 있습니다.

1시간 10분이 차이나는 것 처럼 보이나 실제 차이는 10분 차이나는 것을 확인할 수 있다.

ChronoUnit 은 TemporalUnit을 구현한 Enum으로 다양한 시간 단위를 갖고 있으며 시간과 관련된 다양한 API를 지원한다.

나는 분의 개념을 나타내는 단위로 두 파라메터의 시간을 계산하는 메소드를 사용해 차이를 계산했다.

ZonedDateTime beforeDst = ZonedDateTime.of(localDateTime, ZoneId.of("America/Los_Angeles"));
Assert.assertEquals("2023-03-12T01:59:59-08:00[America/Los_Angeles]", beforeDst.toString()); //DST가 시작되기 전 +8로 계산 됨
ZonedDateTime afterDst = beforeDst.plusMinutes(10); //10분을 추가하게 되면 DST가 시작되는 시간을 넘김
Assert.assertEquals("2023-03-12T03:09:59-07:00[America/Los_Angeles]", afterDst.toString()); // DST가 적용되어 +7로 계산 됨

long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES.between(beforeDst, afterDst);
Assert.assertEquals(10L, deltaBetweenDatesInMinutes);

A TimeZone 을 받아 B TimeZone으로 계산

나는 미국시를 받아 한국시로 계산해 DB 저장이 필요했다.

ZonedDateTime.withZoneSameInstant 메소드는 다른 타임존을 가진 복사본을 만든다. 시간대를 변경하고 동일한 순간을 유지하는 것입니다.

미국 로스앤젤레스 2023-03-12T03:09:59 시간은 한국시로 2023-03-12T19:09:59 인 것을 확인 할 수 있다.

ZonedDateTime beforeDst = ZonedDateTime.of(localDateTime, ZoneId.of("America/Los_Angeles"));
ZonedDateTime afterDst = beforeDst.plusMinutes(10);
Assert.assertEquals("2023-03-12T01:59:59-08:00[America/Los_Angeles]", beforeDst.toString());
Assert.assertEquals("2023-03-12T03:09:59-07:00[America/Los_Angeles]", afterDst.toString());

ZonedDateTime beforeDstToKor = beforeDst.withZoneSameInstant(ZoneId.of("Asia/Seoul"));
ZonedDateTime afterDstToKor = afterDst.withZoneSameInstant(ZoneId.of("Asia/Seoul"));
Assert.assertEquals("2023-03-12T18:59:59+09:00[Asia/Seoul]",beforeDstToKor.toString());
Assert.assertEquals("2023-03-12T19:09:59+09:00[Asia/Seoul]",afterDstToKor.toString());

현지 날짜, 시간을 유지 하고 시간대가 다른 경우가 필요하다면 ZonedDateTime.withZoneSameLocal 메소드를 사용하면 된다.

미국 로스앤젤레스 2023-03-12T03:09:59[America/Los_Angeles] -> 한국 2023-03-12T03:09:59[Asia/Seoul]로 만들 수 있다.

ZonedDateTime beforeDst = ZonedDateTime.of(localDateTime, ZoneId.of("America/Los_Angeles"));
Assert.assertEquals("2023-03-12T01:59:59-08:00[America/Los_Angeles]", beforeDst.toString());
ZonedDateTime beforeDstToKor = beforeDst.withZoneSameLocal(ZoneId.of("Asia/Seoul"));
Assert.assertEquals("2023-03-12T01:59:59+09:00[Asia/Seoul]",beforeDstToKor.toString());

Three-letter time zone IDs

JDK 1.1.x 버전에서 호환성을 위해 "PST","AST" 등 세글자 시간대 ID를 지원했으나, 동일한 약어로 여러시간대가 존재해 의도와 다르게 동작할 수 있어 권장하지 않는 방법입니다.

String dateTime = "2023-03-12 01:59:59 AM America/Los_Angeles";
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd h:mm:ss a VV", Locale.ENGLISH);
ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTime,dateTimeFormatter);
Assert.assertEquals("2023-03-12T01:59:59-08:00[America/Los_Angeles]", zonedDateTime.toString());

ZonedId docs를 참고하면 Map으로 갖고있는 Three-letter time zone 리스트를 확인할 수 있다.

 

추가로, DateTimeFormatter 사용 시 정의된 패턴을 사용하는 것이 아닌 사용자 지정 패턴을 만들 경우 기호의 의미 확인 후 패턴 작성 사용이 필요하다.

 

참고 : 

https://www.baeldung.com/java-daylight-savings

https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html

https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html

https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html

https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html

https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

https://unicode-org.github.io/icu/userguide/format_parse/datetime

https://stackoverflow.com/questions/30710829/java-time-datetimeformatter-pattern-for-timezone-offse

 

728x90