Java Time, 제대로 사용하기

2022. 9. 14. 23:56Spring/Java

package java.time 을 이해하고 자유자재로 사용하는 것이 본 포스팅의 목표입니다.

 

 

안녕하세요.

이번 포스팅은 미루고 미루고 미루고... 미뤘던 Java Time 총정리입니다.

미룬 만큼 잘 정리해보려고 하긴 했는데, 만약 부족한 부분이 있다면 댓글로 말씀해주세요 ☺️

 

 

 

java.time.Instant

: 기계 시간 표현

 

먼저, Instant 클래스를 살펴보겠습니다.

Instant는 인간이 읽을 수 없는 시간의 정수표기법입니다.

 

가령 1663166273 라는 타임스탬프를 보고 년, 월, 일, 시, 분, 초를 알 수 있는 사람이 존재할까요?

기계는 위의 타임 스탬프 값을 읽어 시간을 표현합니다.

Instant를 통해 바로 이러한 타임스탬프 값을 나타내는 객체를 생성할 수 있습니다.

 

Instant의 java doc을 보면 아래와 같습니다.

 

Instant class - Java doc

An instantaneous point on the time-line.This class models a single instantaneous point on the time-line. This might be used to record event time-stamps in the application. The range of an instant requires the storage of a number larger than a long. To achieve this, the class stores a long representing epoch-seconds and an int representing nanosecond-of-second, which will always be between 0 and 999,999,999.The epoch-seconds are measured from the standard Java epoch of 1970-01-01T00:00:00Z where instants after the epoch have positive values, and earlier instants have negative values.

 

해석해보자면 아래와 같습니다.

 

타임라인 위의 한 순간의 지점을 의미하며, 타임스탬프를 기록하는 데 사용할 수 있습니다.

Instant의 범위는 long보다 큰 숫자를 저장해야 하는데,

이를 위해 클래스는 epoch 초를 나타내는 long의 길이와 나노-초를 나타내는 int를 저장하며,

0에서 999,999,999 사이의 범위를 갖습니다.

 

epoch 초는 1970년 1월 1일 표준 자바 epoch 시간부터 측정되며,

epoch 초를 기준으로 이후 Instant는 양의 값을 가지며 이전 Instant는 음의 값을 갖습니다.

 

Instant now = Instant.now();
ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());
 
System.out.println(now);                                // 2022-09-14T05:55:37.764304600Z
System.out.println(now.atZone(ZoneId.of("UTC")));       // 2022-09-14T05:55:37.764304600Z[UTC]
System.out.println(zonedDateTime);                      // 2022-09-14T14:55:37.764304600+09:00[Asia/Seoul]

 

 

java.time.LocalDate*

: 인류용 일시를 표현하는 방법

 

Java 8 이후부터 굉장히 자주 사용할 LocalDate, LocalDateTime을 소개합니다.

Instant와 달리 인간이 읽을 수 있는 시간을 표시합니다.

특징을 정리하면 아래 리스트와 같습니다.

 

- 흔히 사용하는 연,월,일,시,분,초 등을 표현

- 특정 날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)를 사용

- DateTimeFormatter를 사용해서 일시를 특정한 문자열로 포매팅해서 사용

 

기본적으로 현재 시간을 출력하는 방식은 LocalDateTime.now() 이며,

이름에 붙은 Local이 의미하는 대로, 현재 시스템 Zone에 해당하는 일시를 리턴합니다.

 

LocalDateTime now = LocalDateTime.now();
System.out.println(now);       // 2022-01-18T16:08:37.386460
 
LocalDateTime birthday = LocalDateTime.of(1998, Month.AUGUST, 12, 12, 0, 0);
System.out.println(birthday);  // 1998-08-12T12:00

 

이하부터 Date와 LocalDateTime 사이의 변경 방법을 알아봅니다.

LocalDate와 LocalDateTime은 거의 비슷하기 때문에 대표적으로 LocalDateTime만을 예시로 가져왔습니다.

 

 

 

 

📌  Date → LocalDateTime

: java.util.Date → java.util.LocalDate

 

기본적으로 Date에서 LocalDate or LocalDateTime으로 바로 전환 불가능합니다.

Instant를 통해 아래의 2가지 방식으로 변경할 수 있습니다.

 

 

✔ 1. LocalDateTime.ofInstant()

LocalDateTime.ofInstant( /* Instant , ZoneId */ )

 

Date date = new Date();
 
LocalDateTime localDateTime = LocalDateTime.ofInstant(
            Instant.ofEpochMilli(date.getTime()),       // date -> Instant
            ZoneId.systemDefault()                      // java.time.ZoneId
        );

 

 

✔ 2. Instant.ofEpochMilli().toLocalDateTime()

Instant.ofEpochMilli( /* long */).toLocalDateTime()

 

Date 객체를 long 타입의 epochMilli로 변환하여 변경할 수 있습니다.

Date date = new Date();
 
LocalDateTime localDateTime = Instant
            .ofEpochMilli(date.getTime())       // date -> long (epochMilli) -> Instant
            .atZone(ZoneId.systemDefault())     // Instant -> ZonedDateTime
            .toLocalDateTime();                 // ZonedDateTime -> LocalDateTime

 

 

 

📌  LocalDateTime → Date

java.util.Date → java.util.LocalDate

 

기본적으로 LocalDate or LocalDateTime 에서 Date로 바로 전환 불가능합니다.

Instant를 통해 아래의 4가지 방식으로 변경할 수 있습니다.

 

 

✔  1. LocalDateTime.atZone().toInstant()

LocalDateTime.atZone( /* ZoneId */ ).toInstant()

 

ZoneId를 지정하고 싶은 경우 사용할 수 있습니다.

atZone(...) 메소드를 통해 ZonedDateTime을 생성 후, toInstant로 Instant 객체를 생성합니다.

LocalDateTime localDateTime = LocalDateTime.now();
 
Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date date = Date.from(instant);

 

 

✔  2. LocalDateTime.toInstant()

LocalDateTime.toInstant( /* ZoneOffset */ )

 

ZoneOffset을 입력해  Instant 객체로 변경할 수 있습니다.

LocalDateTime localDateTime = LocalDateTime.now();
 
Instant instant = localDateTime.toInstant(ZoneOffset.UTC);
Date date = Date.from(instant);

 

 

✔  3. Timestamp.valueOf().toInstant()

Timestamp.valueOf( LocalDateTime | String ).toInstant()

 

Timestamp를 통해 Instant 객체로 변경할 수 있습니다.

Timestamp.valueOf()는 입력 Format에 맞춰 입력합니다.

 

LocalDateTime format : yyyy-mm-dd hh:mm:ss.fffffffff (fffffffff indicates nanoseconds.)

String Format :  yyyy-[m]m-[d]d hh:mm:ss[.f...]. (The fractional seconds may be omitted.)

 

LocalDateTime localDateTime = LocalDateTime.now();

Instance instant = Timestamp.valueOf(localDateTime).toInstant();
Date date = Date.from(instant);

 

혹은 String formatting 후 변경할 수도 있습니다.

 

LocalDateTime localDateTime = LocalDateTime.now();
 
String formatted = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Instant instant = Timestamp.valueOf(formatted).toInstant();
Date date = Date.from(instant);

 

 

✔  4. Timestamp.valueOf().getTime()

Timestamp.valueOf( LocalDateTime | String ).getTime()

 

Timestamp를 통해 long 타입으로 변경할 수 있습니다.

Timestamp.valueOf()는 입력 Format에 맞춰 입력합니다.

 

LocalDateTime format : yyyy-mm-dd hh:mm:ss.fffffffff (fffffffff indicates nanoseconds.)

String Format :  yyyy-[m]m-[d]d hh:mm:ss[.f...]. (The fractional seconds may be omitted.)

 

LocalDateTime localDateTime = LocalDateTime.now();
 
Timestamp timestamp = Timestamp.valueOf(localDateTime);
Date date = new Date(timestamp.getTime());

 

 

📌  LocalDate → LocalDateTime

아래와 같이 atTime() 을 통해 변경할 수 있습니다.

 

LocalDate localDate = LocalDate.now();

LocalDateTime startDay = localDate.atTime(0, 0, 0);
LocalDateTime endDay = localDate.atTime(23, 59, 59);

 

 

 

📌  LocalDateTime   LocalDate

간단히 아래와 같이 toLocalDate() 을 통해 변경할 수 있습니다.

 

LocalDateTime nowTime = LocalDateTime.now();
LocalDate localDate = nowTime.toLocalDate();

 

 

 

 

DateTimeFormatter

Oracle에서는 자주 사용하는, 대표적이면서 자주 사용할 포맷을 미리 정의해두었습니다.

내용은 DateTimeFormatter - predefined 에서 확인할 수 있습니다.

 

DateTimeFormatter.ISO_LOCAL_DATE_TIME 와 같이 미리 포맷을 정의해두었기 때문에 활용할 수 있습니다.

아래는 위 링크를 캡처한 미리보기 내용입니다.

 

 

 

 

📌  .ofPattern(format)

DateTimeFormatter.ofPattern(format)

 

특정 패턴으로 LocalDate를 정의할 수 있습니다. 

LocalDate now = LocalDate.now();	// 2022-09-15

DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
now.format(MMddyyyy);			// 09/15/2022

 

 

LocalDateTime 도 동일하게 적용할 수 있습니다.

 

LocalDateTime nowTime = LocalDateTime.now();	// 2022-09-15T00:15:01.608071

DateTimeFormatter MMddyyyyHHmmss = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss");
nowTime.format(MMddyyyyHHmmss);			// 09/15/2022 00:15:01

 

 

 

📌  String → LocalDate*

LocalDate.parse(String, DateTimeFormatter)

 

특정 문자열의 패턴을 파싱해서 LocalDate로 정의할 수 있습니다.

 

LocalDate parse = LocalDate.parse("08/12/1998", MMddyyyy);
System.out.println(parse);     // 1998-08-12

 

시분초가 포함되어 있어도 LocalDateTime에서 동일하게 동작합니다. 

단, 위와 달리 LocalDateTime.parse() 일시 클래스를 사용한 파싱입니다.

 

DateTimeFormatter MMddyyyyHHmmss = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss");
LocalDateTime parse = LocalDateTime.parse("08/12/1998 10:43:22", MMddyyyy);
System.out.println(parse);		// 1998-08-12T10:43:22

 

 

 

 

java.time.Period

: 날짜를 기반으로 비교하는 인류 용 표기 방식

 

Period과 Duration은 기간을 표현하는 방법입니다.

 

 

📌  Period.between()

Period between = Period.between(today, birthDay);
// or
Period until = today.until(thisYearBirthday);
between.get(ChronoUnit.DAYS);
// or
between.getDays();

 

📌  Total Days 계산

LocalDate ~ LocalDate

 

특정 날짜부터 특정 날짜까지의 총 일자를 구하는 방식입니다.

LocalDate birthDay = LocalDate.of(1998, Month.AUGUST, 12);
long date = ChronoUnit.DAYS.between(birthDay, LocalDate.now());
 
System.out.println(date);   // 8799 -> 1998.08.12 부터 금일(2022.09.14)까지 총 8799일이 지났음

 

 

 

java.time.Duration

: 날짜를 기반으로 비교하는 기계 용 표기 방식

 

 

📌  Duration.between()

Instant instantNow = Instant.now();
Instant plus = instantNow.plus(10, ChronoUnit.SECONDS);
Duration between = Duration.between(instantNow, plus);
 
System.out.println(between.getSeconds()); // 10

 

 

 

 

LocalDateTime not supported

 

Java 8 date/time type java.time.LocalDateTime not supported by default

 

ObjectMapper를 통해 Json 으로 변환할 때 위와 같은 오류가 발생한다면 LocalDateTime 직렬화/역직렬화의 문제입니다.

JavaTimeModule을 통해 해결할 수 있습니다.

 

 

1. jackson Jsr310 dependency 추가

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

 

 

2. ObjectMapper에 JavaTimeModule 추가

import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
// ...
public static final ObjectMapper OM = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .registerModule(new JavaTimeModule());  // 해당 모듈 추가
// ...
String json = OM.writerWithDefaultPrettyPrinter().writeValueAsString(testDTO);

 

 

 

 

| Ref |

더 자바, Java 8