Java

LocalDateTime와 Instant (+ ZonedDateTime)

작은별._. 2025. 2. 27. 23:10
728x90

현업에서 LocalDateTime과 Instant를 사용하다가 문제를 봉착하게 되어서 해당 블로그를 쓰게 되었습니다. Java에서는 LocalDateTime, Instant, ZonedDateTime과 같은 날짜와 시간을 다루기 위한 클래스들을 제공합니다. 하지만 각 클래스는 다른 용도와 특성을 가지고 있습니다. 이들을 잘 활용하는 것이 중요합니다. 

 

1. LocalDateTime

LocalDateTime은 시간대 정보를 포함하지 않는 날짜와 시간을 다룰 때 사용합니다. 즉, 시간대가 중요하지 않고 단순히 날짜와 시간만 필요한 경우에 적합합니다.

주요 특징:

  • 시간대 정보 없음: LocalDateTime은 시간대 정보 없이 로컬 날짜와 시간만을 표현합니다.
  • 불변 객체: LocalDateTime은 불변 객체이므로 값을 변경하려면 새로운 객체를 생성해야 합니다.

2. Instant

Instant는 UTC 기준의 타임스탬프를 다룰 때 사용됩니다. 즉, 절대적인 시점을 표현하며 시간대 정보를 포함하지 않습니다. 주로 타임스탬프를 기록하거나 두 시점 간의 차이를 계산할 때 사용됩니다.

주요 특징:

  • UTC 기준: Instant는 UTC를 기준으로 시간을 표현합니다.
  • 시간대 정보 없음: Instant는 LocalDateTime처럼 시간대 정보가 없습니다.
  • 타임스탬프: 보통 초 또는 나노초 단위로 절대 시간을 기록하는 데 사용됩니다.
    •  이 값은 날짜와 시간을 하나의 정수로 표현할 수 있으므로 날짜와 시간의 차이를 계산하거나 순서를 비교하는데 유리해서 데이터베이스에 많이 사용됩니다.

3. ZonedDateTime

ZonedDateTime은 시간대 정보를 포함하는 날짜와 시간을 다룹니다. ZonedDateTime은 특정 시간대와 관련된 날짜와 시간을 다룰 수 있습니다. 타임존에 따라 변환이 가능하고, 세계 여러 지역에서 동일한 시간대를 기준으로 작업할 때 유용합니다.

주요 특징:

  • 시간대 정보 포함: ZonedDateTime은 날짜와 시간뿐만 아니라 시간대 정보도 함께 포함합니다.
  • 타임존 변환: 쉽게 다른 시간대로 변환할 수 있습니다.
  • 표준화된 날짜와 시간: 다른 시간대 간의 날짜와 시간 계산이 가능합니다.

 

코드 예시

public class DateTimeExample {
    public static void main(String[] args) {
 	LocalDateTime currentDateTime = LocalDateTime.now();  // 시스템 기본 시간대 기준
        System.out.println("현재 날짜와 시간: " + currentDateTime);
        
   	Instant now = Instant.now();  // 현재 UTC 시간
        System.out.println("현재 Instant: " + now);
        
        ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));  // 서울 시간 기준
        System.out.println("현재 서울 시간: " + zonedDateTime);
        
        // 다른 시간대로 변환
        ZonedDateTime utcTime = zonedDateTime.withZoneSameInstant(ZoneId.of("UTC"));
        System.out.println("현재 UTC 시간: " + utcTime);
    }
}

 

제 로컬 타임존은 Asia/Seoul입니다. 따라서 코드를 실행하면 아래와 같이 결과가 나옵니다.

현재 날짜와 시간: 2025-02-27T22:57:59.828653
현재 Instant: 2025-02-27T13:57:59.828793Z
현재 서울 시간: 2025-02-27T22:57:59.828913+09:00[Asia/Seoul]
현재 UTC 시간: 2025-02-27T 13:57:59.828913Z [UTC]

 

제가 봉착한 문제는 Instant와 LocalDateTime 변환 시 내부 동작을 잘 이해하지 못해서 발생한 문제입니다.

현업에서 LocalDateTime.now()로 서울 기준 시각을 생성하고, Instant로 변환 시 아래 메서드를 사용하면 자동으로 9시간이 느린 UTC 시각으로 변경되는 줄 알았습니다.


// 현재 날짜와 시간으로 객체 생성 
LocalDateTime currentDateTime = LocalDateTime.now(); // 서울 시각 (시스템 디폴트)

Instant instant = currentDateTime.toInstant(ZoneOffset.UTC);

 

 

하지만, 이는 크나큰 착각이었습니다. ㅎㅎ 원인을 찾다가 LocalDateTime의 공식문서에 아래와 같은 내용이 적혀있는 것을 확인했는데요,

This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.

 

즉, LocalDateTime.now()는 타임존 정보(시간대 정보)를 포함하지 않은 현재 날짜와 시간을 반환합니다. 이 메서드는 시스템의 기본 타임존을 기준으로 LocalDateTime 객체를 생성하지만, LocalDateTime 자체는 타임존 정보를 가지고 있지 않기 때문에 toInstant(ZoneOffset.UTC)를 사용해도 9시간이 더해지는 것이 아니라 시간정보 끝에 `z`를 붙임으로써 (Z Zero Offset) 그냥 UTC로 나타낸 시각이다라고만 표시해 주게 됩니다.

2025-02-27T22:30:57.271865 // LocalDateTime.now()
2025-02-27T22:30:57.271865Z // LocalDateTime.now().toInstant(ZoneOffset.UTC);

 

 

만약 시스템의 기본 타임존 상관없이 UTC 시각을 얻고 싶다면 아래처럼 LocalDateTime 객체를 생성해야 합니다.


LocalDateTime localDateTime = LocalDateTime.now(ZoneOffset.UTC);

 

 

추가로, 위에서 사용했던  Instant instant = currentDateTime.toInstant(ZoneOffset.UTC); 코드를 타고 들어가 보면 아래의 시그니처를 가집니다.

  default Instant toInstant(ZoneOffset offset) {
     return Instant.ofEpochSecond(toEpochSecond(offset), toLocalTime().getNano());
   }

 

Parameter의 ZoneOffset은 Instant로 변환 시 더해지는 오프셋입니다. 

참고로, ZoneOffset.UTC 는 0이라는 값을 가집니다.

public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);

 

따라서 위에서 사용했던 Instant instant = currentDateTime.toInstant(ZoneOffset.UTC); 가 currentDateTime 값에 어떤 값도 더하지 않고 timezone만 UTC로 변환 후 그대로 반환하는 것입니다.

 

시간 변환 코드 예시

void LocalDateTimeToInstant() {
    LocalDateTime localDateTime = LocalDateTime.now();
    System.out.println("localDateTime: " + localDateTime);

    Instant instant = localDateTime.toInstant(ZoneOffset.UTC);
    System.out.println("instant1: " + instant);

    //ZoneOffset을 바로 지정하여 Instant를 생성 -> 9시간만큼의 초(3600 * 9) 만큼을 현재시간에 뺀 시간을 생성한다.
    instant = localDateTime.toInstant(ZoneOffset.of("+0900"));
    System.out.println("instant2: " + instant);

    //시스템에 적용된 timezone이 적용된 ZonedDateTime 인스턴스를 이용한다.
    ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
    instant = zonedDateTime.toInstant();
    System.out.println("instant3: " + instant);

    ZoneOffset offset = zonedDateTime.getOffset();
    instant = localDateTime.toInstant(offset);
    System.out.println("instant4: " + instant);
  }
  
  /*
localDateTime: 2025-02-27T23:04:31.172737
instant1: 2025-02-27T23:04:31.172737Z
instant2: 2025-02-27T14:04:31.172737Z
instant3: 2025-02-27T14:04:31.172737Z
instant4: 2025-02-27T14:04:31.172737Z
  */
  
  
void InstantToLocalDateTime() {
    Instant instant = Instant.now();
    System.out.println("instant now: " + instant);

    //변환할 timezone을 설정
    ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
    System.out.println("time zone: " + zonedDateTime.getZone());
    LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
    System.out.println("localDateTime: " + localDateTime);
  }
  
  
  /*
instant now: 2025-02-27T14:07:02.176906Z
time zone: Asia/Seoul
localDateTime: 2025-02-27T23:07:02.176906
*/

 

더 다양한 시간 변환은 아래 블로그에서 확인하면 좋을 것 같습니다.

https://devel-repository.tistory.com/37

 

java time convert (시간 변환)

애플리케이션 개발을 하다 보면 종종 문자열 타입의 시간 정보를 자바의 LocalDateTime 혹은 LocalDate, LocalTime과 같은 인스턴스로 변환을 하거나 역으로 원하는 시간 형식으로 문자열로 변환을 해야

devel-repository.tistory.com

 

결론

  • 해당 서버 기준 시스템 시간을 가져올 때
    -> LocalDateTime 클래스 사용
  • UTC 기준 시간을 가져올 때
    -> Instant 클래스 사용
  • 특정 기준 시의 시간을 가져올 때
    -> ZonedDateTime 클래스 사용

코드를 사용할 때 내부 동작을 잘 확인하자!!

728x90
반응형