Logo

[Java8 Time API] ZonedDateTime 사용법

Java8에서 추가된 ZonedDateTime 사용법에 대해서 알아보겠습니다. ZonedDateTimeLocalDateTime과 달리 타임존 또는 시차 개념을 가지고 있는 클래스입니다.

ZonedDateTime

ZonedDateTime 클래스는 타임존 또는 시차 개념이 필요한 날짜와 시간 정보를 나타내기 위해서 사용됩니다. public 생성자를 제공하지 않기 때문에 객체를 생성할 때는 now()나, of()와 같은 정적 메소드를 사용하도록 설계되어 있습니다.

ZonedDateTime nowLa = ZonedDateTime.now();
System.out.println("Now in LA is " + nowLa);

ZonedDateTime nowSeoul = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
System.out.println("Now in Seoul is " + nowSeoul);

ZonedDateTime worldCup = ZonedDateTime.of(2002, 6, 18, 20, 30, 0, 0, ZoneId.of("Asia/Seoul"));
System.out.println("World Cup is " + worldCup);
Now in LA is 2017-12-10T09:15:12.793-08:00[America/Los_Angeles]
Now in Seoul is 2017-12-11T02:15:12.862+09:00[Asia/Seoul]
World Cup is 2002-06-18T20:30+09:00[Asia/Seoul]

제 PC의 타임존이 LA로 세팅이 되어 있어서, 인자없이 ZonedDateTime.now()를 호출했을 때 LA 시간이 출력되었습니다. ZoneID를 통해서 타임존 정보를 넘기면 원하는 지역의 시간을 얻을 수 있습니다. ZonedDateTime.of() 메소드는 인자를 너무 많이 받아서 코드 가독성이 떨어질 수 있습니다. 그럴 경우, 아래와 같이 좀 더 명시적인 Fulent API를 사용할 수 있습니다.

ZonedDateTime worldCup = Year.of(2002).atMonth(6).atDay(18).atTime(20, 30).atZone(ZoneId.of("Asia/Seoul"));
System.out.println("World Cup is " + worldCup);
World Cup is 2002-06-18T20:30+09:00[Asia/Seoul]

ZonedDateTime = LocalDateTime + 타임존/시차

많은 분들이 ZonedDateTimeLocalDateTime이 어떻게 다른 건지 혼란스러워 하십니다. ZonedDateTimeLocalDateTime에 타임존 또는 시차 개념이 추가되어 있다고 생각하면 명쾌하게 이해할 수 있습니다. 따라서 다음과 같이 ZonedDateTimeLocalDateTime와 타임존아나 시차 개념을 더하거나 빼면서 상호 변환이 가능합니다.

LocalDate date = LocalDate.of(2002, 6, 18);
System.out.println("Date = " + date);
LocalTime time = LocalTime.of(20, 30);
System.out.println("Time = " + time);
LocalDateTime dateTime = LocalDateTime.of(date, time);
System.out.println("DateTime = " + dateTime);

ZonedDateTime zonedDateTime = ZonedDateTime.of(dateTime, ZoneId.of("Asia/Seoul"));
System.out.println("ZonedDateTime = " + zonedDateTime);

System.out.println("DateTime = " + zonedDateTime.toLocalDateTime());
System.out.println("Date = " + zonedDateTime.toLocalTime());
System.out.println("Time = " + zonedDateTime.toLocalDate());
Date = 2002-06-18
Time = 20:30
DateTime = 2002-06-18T20:30
ZonedDateTime = 2002-06-18T20:30+09:00[Asia/Seoul]
DateTime = 2002-06-18T20:30
Date = 20:30
Time = 2002-06-18

ZoneId vs. ZoneOffset

ZoneId은 타임존, ZoneOffset은 시차를 나타냅니다. ZoneOffset는 UTC 기준으로 고정된 시간 차이를 양수나 음수로 나타내는 반면에 ZoneId는 이 시간 차이를 타임존 코드로 나타냅니다. 예를 들어 서울의 경우 타임존 코드는 Asia/Seoul이고 시차는 +0900 입니다. 타임존을 사용하든 시차를 사용하든 동일한 시간을 얻을 수 있습니다.

ZoneOffset seoulZoneOffset = ZoneOffset.of("+09:00");
System.out.println("+0900 Time = " + ZonedDateTime.now(seoulZoneOffset));
ZoneId seoulZoneId = ZoneId.of("Asia/Seoul");
System.out.println("Seoul Time = " + ZonedDateTime.now(seoulZoneId));
+0900 Time = 2017-07-02T11:16:47.261+09:00
Seoul Time = 2017-07-02T11:16:47.261+09:00[Asia/Seoul]

반면에 많은 도시들이 고정된 시차를 사용하지 않습니다. 예를 들어 벤쿠버의 경우 보통은 시차가 -0800이지만 소위 Summer Time이라고 불리는 일괄절약타임을 시행하기 때문에 여름에는 한 시간 더 일찍 시간이 갑니다. 따라서 이런 도시를 대상으로 ZonedDateTime 객체를 만들 때는 ZoneOffset보다는 ZoneId를 사용하는 편이 훨씬 유리합니다. 계절에 따라 변하는 시차를 알아서 처리해주기 때문입니다.

ZoneOffset vancouverZoneOffset = ZoneOffset.of("-08:00");
System.out.println("-0800 Time     = " + ZonedDateTime.now(vancouverZoneOffset));
ZoneId vancouverZoneId = ZoneId.of("America/Vancouver"); // 1 hour earlier in summer
System.out.println("Vancouver Time = " + ZonedDateTime.now(vancouverZoneId));
-0800 Time     = 2017-07-01T18:16:47.263-08:00
Vancouver Time = 2017-07-01T19:16:47.263-07:00[America/Vancouver]

다른 타임존/시차 적용

withZoneSameInstant() 메소드를 사용하면 ZonedDateTime에 다른 타임존이나 시차를 적용할 수 있습니다. 아래는 2002년 월드컵 한국과 이탈리아 16강 경기의 서을 기준 시작 시간에 다른 도시의 타임존이나 다른 시차를 적용해본 예제입니다.

ZonedDateTime seoul = Year.of(2002).atMonth(6).atDay(18).atTime(20, 30).atZone(ZoneId.of("Asia/Seoul"));
System.out.println("Seoul =     " + seoul);

ZonedDateTime utc = seoul.withZoneSameInstant(ZoneOffset.UTC);
System.out.println("UTC =       " + utc);

ZonedDateTime london = seoul.withZoneSameInstant(ZoneId.of("Europe/London"));
System.out.println("London =    " + london);

ZonedDateTime newYork = seoul.withZoneSameInstant(ZoneOffset.of("-0400"));
System.out.println("NewYork =   " + newYork);

ZonedDateTime vancouver = seoul.withZoneSameInstant(ZoneId.of("America/Vancouver"));
System.out.println("Vancouver = " + vancouver);
Seoul =     2002-06-18T20:30+09:00[Asia/Seoul]
UTC =       2002-06-18T11:30Z
London =    2002-06-18T12:30+01:00[Europe/London]
NewYork =   2002-06-18T07:30-04:00
Vancouver = 2002-06-18T04:30-07:00[America/Vancouver]

ZonedDateTime과 String 간 변환

ZonedDateTimeformat() 메소드에 DateTimeFormatter 객체를 넘기면 다양한 포맷의 문자열로 변환할 수 있습니다. DateTimeFormatter 클래스에서 정적 필드로 정의해 놓은 표준 포맷터를 이용하거나 ofPattern() 정적 메소드를 통해 직접 포맷터를 정의할 수도 있습니다. 또한 ofLocalizedDateTime() 정적 메소드를 사용하면 Locale에 다국어 포맷터를 사용할 수 있습니다.

ZonedDateTime worldCup = Year.of(2002).atMonth(6).atDay(18).atTime(20, 30).atZone(ZoneId.of("Asia/Seoul"));

System.out.println(worldCup.format(DateTimeFormatter.ISO_DATE_TIME));
System.out.println(worldCup.format(DateTimeFormatter.ofPattern("yyyy/MM/dd/ HH:mm:ss z")));
System.out.println(worldCup.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)));
System.out.println(worldCup.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.KOREA)));
2002-06-18T20:30:00+09:00[Asia/Seoul]
2002/06/18/ 20:30:00 KST
Tuesday, June 18, 2002 8:30:00 PM KST
2002618일 화요일 오후 830분 00초 KST

반대로 문자열을 ZonedDateTime로 변환하기 위해서는 parse() 정적 메소드에 DateTimeFormatter 객체를 넘기면 됩니다.

ZonedDateTime zdt1 = ZonedDateTime.parse("2002-06-18T20:30:00+09:00[Asia/Seoul]");
ZonedDateTime zdt2 = ZonedDateTime.parse("2002/06/18/ 20:30:00 KST", DateTimeFormatter.ofPattern("yyyy/MM/dd/ HH:mm:ss z"));
ZonedDateTime zdt3 = ZonedDateTime.parse("Tuesday, June 18, 2002 8:30:00 PM KST", DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL));
ZonedDateTime zdt4 = ZonedDateTime.parse("2002년 6월 18일 화요일 오후 8시 30분 00초 KST", DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.KOREA));

System.out.printf("All the same? %s", zdt1.equals(zdt2) && zdt2.equals(zdt3) && zdt3.equals(zdt4));
All the same? true

이상으로 ZonedDateTime 사용법에 대해서 알아보았습니다.