본문 바로가기
Language/Java

[Java 8] 날짜 API - 2

by ocwokocw 2021. 2. 11.

- 출처: https://www.oracle.com/technical-resources/articles/java/jf14-date-time.html

- 출처: 자바 8 인 액션

- Truncation

신규 API 는 날짜, 시간, 날짜 및 시간을 표현하는 타입을 제공하여 정밀한 시간을 지원하지만, 이것보다 더 세밀하게 정의된 정밀도의 개념을 지원한다. truncatedTo 메소드는 이런 경우에 사용하는데, DB의 truncate 와 비슷하게 해당 필드에 대한 값을 비운다.

 

LocalDateTime timePoint = LocalDateTime.now();
LocalDateTime truncatedTime = timePoint.truncatedTo(ChronoUnit.SECONDS);
	
System.out.println("timePoint: " + timePoint);
System.out.println("truncatedTime: " + truncatedTime);

 

- 실행결과

 

timePoint: 2020-09-25T23:50:22.651
truncatedTime: 2020-09-25T23:50:22

 

실행결과에서 truncatedTime 변수 값이 timePoint 에서 초 이하 단위가 절삭(truncate) 된것을 볼 수 있다.


- Instant

사람은 주, 날짜, 시간, 분으로 날짜와 시간을 계산하지만 기계에서는 특정지점을 숫자로 표현하는것이 자연스러운 방법이다. Instant는 1970년 1월 1일 0시 0분 0초 UTC 로 부터 특정 지점까지 흐른 시간을 초로 표현한다. 

 

System.out.println("Instant : " + Instant.now());
System.out.println("Day of month : " + Instant.now().get(ChronoField.DAY_OF_MONTH));
Instant : 2020-09-29T05:32:13.502Z
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
	at java.time.Instant.get(Instant.java:566)
	at java8.JavaTest.main(JavaTest.java:16)

 

Instant 도 현재를 표현하는 now 메소드를 제공하지만, 사람이 읽을 수 있는 DayOfMonth 와 같은 필드는 지원하지 않는다.


- Time Zones

우리가 앞서 살펴본 local 클래스들은 time zone 과 같은 복잡한 정보는 없었다. time zone 은 표준 시간이 같은 지역들에 대응하는 규칙들의 집합이라고 할 수 있다. 대략 40개 정도가 있으며, Coordinated Universal Time(협정 세계 표준시 - UTC) 을 기준으로 하여 차이값(offset)에 따라 정의되어 있다. 특별한 경우를 제외하고는 거의 맞물려 움직인다.

 

Time zone 들은 2개의 식별자에 의해 참조된다. 예를 들면 함축형(ex - "PLT")과 더 긴 (ex - "Asia/Karachi") 와 같은 식이다. 어플리케이션을 설계할 때 타임존을 사용하기에 적절한 시나리오와 오프셋을 사용하기 적절한 경우를 고려해야 한다.

 

ZoneId 는 지역에 대한 식별자이다. 각 ZoneId 는 위치를 위한 타임존을 정의하는 몇몇 규칙에 부합한다. 소프트웨어를 설계할 때, 만약 "PLT" 나 "Asia/Karachi" 와 같은 문자열을 반환하려고 한다면 이렇게 처리하는 대신 도메인 클래스를 사용해야 한다. 

 

아래는 특정 타임존을 변수에 저장하는 코드이다.

 

LocalDateTime timePoint = LocalDateTime.now();
	
ZoneId id = ZoneId.of("Europe/Paris");
ZonedDateTime zoned = ZonedDateTime.of(timePoint, id);
	
System.out.println("timePoint: " + timePoint);
System.out.println("zoned: " + zoned);

 

- 실행결과 

 

timePoint: 2020-09-26T00:20:15.462
zoned: 2020-09-26T00:20:15.462+02:00[Europe/Paris]

 

timePoint 의 값을 타임존 정보와 함께 저장하였다. 아래는 LocalDate, LocalTime, LocalDateTime, ZonedDateTime, ZoneId 와의 관계를 나타낸것이다.

  • LocalDate + LocalTime = LocalDateTime
  • LocalDateTime + ZoneId = ZonedDateTime

또 ZoneOffset 이라는 표현도 있는데, 이는 해당 타임존과 UTC 간의 시간차이를 표현한것이다. 

 

ZoneOffset offset = ZoneOffset.of("+02:00");

 

타임존을 사용할 때 헷갈리는 개념이 있는데, 바로 현재 시간을 다른 TimeZone 에서 표현하고자 할 때 LocalDateTime.now()로 현재 시간을 얻고, atZone 메소드로 해당 타임존의 시각을 얻으면 해당 지역에서의 현재 시간을 알 수 있지 않을까라고 생각하는 경우가 있다.

 

ZoneId parisZone = ZoneId.of("Europe/Paris");
ZoneId losAngelesZone = ZoneId.of("America/Los_Angeles");

LocalDateTime timePoint = LocalDateTime.now();
System.out.println("timePoint: " + timePoint);

ZonedDateTime zdt2 = timePoint.atZone(losAngelesZone);
ZonedDateTime ztd2WithParis = zdt2.withZoneSameInstant(parisZone);
System.out.println("zdt2: " + zdt2);
System.out.println("ztd2WithParis: " + ztd2WithParis);

Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(losAngelesZone);
ZonedDateTime ztd3WithParis = zdt3.withZoneSameInstant(parisZone);
System.out.println("zdt3 : " + zdt3);
System.out.println("ztd3WithParis: " + ztd3WithParis);
timePoint: 2020-09-29T20:03:41.394
zdt2: 2020-09-29T20:03:41.394-07:00[America/Los_Angeles]
ztd2WithParis: 2020-09-30T05:03:41.394+02:00[Europe/Paris]
zdt3 : 2020-09-29T04:03:41.414-07:00[America/Los_Angeles]
ztd3WithParis: 2020-09-29T13:03:41.414+02:00[Europe/Paris]

 

위에서 출력결과 2번행인 zdt2: 2020-09-29T20:03:41.394-07:00[America/Los_Angeles] 가 실행결과로 나올 때, 현재시간인 timePoint 에서 atZone 메소드를 통해 미국의 Zone 정보를 인자로 주었으니, 로스앤젤레스 시간이 나와야하는게 아닌가라고 생각할 수 있다.

 

LocalDateTime 은 마치 길을가다가 옆의 친구에게 지금 몇시냐 라고 물어보면 옆의 친구는 자신의 시계를 보고 답을 해주는 것과 같다. 대답을 해주는 사람은 현재 "몇시 몇분 UTC+9"라고 대답하지 않는다.

 

LocalDateTime 은 단지 날짜와 시간의 정보만 가지고있을 뿐이고, atZone 메소드로 해당 날짜와 시간에 단순하게 지역정보를 추가하여 새로운 ZonedDateTime 을 만들 뿐이다. 해당 지역으로 시간을 변환하지 않는다. 만약 로스앤잴레스 시간으로 변환하고자 한다면, 변환하기전의 타임존 정보가 어디여야 하는지를 알아야 하는데, LocalDateTIme은 Zone에 대한 정보가 없기 때문이다.

 

그래서 zdt2는 timePoint 날짜와 시간에 단순하게 로스앤젤레스 Zone 정보만 추가된것이다.

 

withZoneSameInstant 메소드로 Zone 정보를 넘겨서 생성한 zdt2WithParis는 사정이 달라진다. zdt2가 UTC-7 이었는데 UTC+2인 Paris 로 변환하라고 하였으니 상대적으로 +9시간을 더한 30일 오전 05시가 출력된것이다.

 

만약 설명이 너무 복잡해서 모르겠다면 LocalDateTime 에는 타임존의 정보가 없으며, 어떤 시간으로 부터 특정 타임존의 시간정보를 알아야 한다면, 변환하려는 시간은 어떤 타임존이었는가를 알아야 변환할 수 있다는 개념을 생각하면서 천천히 생각해보면 될 것이다.


- Time Zone 클래스

ZonedDateTime 은 타임존 정보와 함께 날짜와 시간을 표현한것이다. ZonedDateTime 은 시간의 어느 시점에서 offset을 표현할 수 있다. 일반적으로 만약 날짜와 시간을 특정 서버의 종속성없이 표현하길 원한다면 아래처럼 ZonedDateTime을 사용해야 한다.

 

ZonedDateTime timePoint = ZonedDateTime.parse("2020-09-28T10:15:30+01:00[Europe/Paris]");
	
System.out.println(timePoint);

 

- 실행결과 

 

2020-09-28T10:15:30+02:00[Europe/Paris]

 

OffsetDateTime 은 offset과 함께 날짜와 시간을 표현한 클래스이다. 이 클래스는 데이터를 DB로 직렬화 하거나 만약 타임존이 다른 서버들을 가지고있다면 로깅을 위한 타임스탬프 포맷 직렬화하는데 유용하게 사용될 수 있다.

 

ZoneOffset offset = ZoneOffset.of("+02:00");

OffsetTime time = OffsetTime.now();

OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(offset);

OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(offset);

OffsetTime changeTimeWithNewOffset2 = changeTimeWithNewOffset
		.withHour(0).plusSeconds(0);

OffsetTime changeTimeWithNewOffset3 = changeTimeWithNewOffset
		.withHour(3).plusSeconds(2);

System.out.println("time : " + time);
System.out.println("sameTimeDifferentOffset : " + sameTimeDifferentOffset);
System.out.println("changeTimeWithNewOffset : " + changeTimeWithNewOffset);
System.out.println("changeTimeWithNewOffset2 : " + changeTimeWithNewOffset2);
System.out.println("changeTimeWithNewOffset3 : " + changeTimeWithNewOffset3);

 

- 실행결과 

 

time : 11:56:58.564+09:00
sameTimeDifferentOffset : 04:56:58.564+02:00
changeTimeWithNewOffset : 11:56:58.564+02:00
changeTimeWithNewOffset2 : 00:56:58.564+02:00
changeTimeWithNewOffset3 : 03:57:00.564+02:00

- Periods

Period 는 3달 그리고 하루와 같은 시간대의 거리 값을 표현한다. 이같은 표현법은 시간대의 한 지점을 표현하는 다른 클래스들과는 대조적이다. 아래 에서 살펴보겠지만 Period는 Duration 과는 달리 년, 월, 일에 대한 표현을 한다.

 

Period period = Period.of(3, 2, 1);
	
LocalDate oldDate = LocalDate.now();
LocalDate newDate = oldDate.plus(period);

ZonedDateTime oldDateTime = ZonedDateTime.now();
ZonedDateTime newDateTime = oldDateTime.minus(period);

System.out.println("newDate: " + newDate);
System.out.println("newDateTime: " + newDateTime);

 

- 실행결과

 

newDate: 2023-11-30
newDateTime: 2017-07-28T13:52:54.953+09:00[Asia/Seoul]

- Durations

Duration 은 시간(시,분,초)의 관점에서 측정된 시간대의 거리인데, Period와 비슷한 목적이 있지만 정밀성에서 차이점이 있다.

 

Duration duration = Duration.ofSeconds(3, 5);
	
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minus(1, ChronoUnit.DAYS);

Duration oneDay = Duration.between(today, yesterday);

System.out.println("duraiton: " + duration);
System.out.println("today: " + today);
System.out.println("yesterday: " + yesterday);
System.out.println("oneDay: " + oneDay);
duraiton: PT3.000000005S
today: 2020-09-29T14:11:42.381
yesterday: 2020-09-28T14:11:42.381
oneDay: PT-24H

 

처음 duration은 3초 5나노초 라는 기간을 표현하고 있다. 그리고 각각 오늘과 어제의 LocalDateTime 간의 Between을 보면 -24H 로 두 시점의 기간을 계산한다.

 

만약 LocalDateTime을 LocalDate 로 형을 바꾸면 지원하지 않는 Type이라고 Exception을 던지니 주의하도록 하자.

댓글