Java/개념

Java 8 LocalDateTime vs Instant 어떤 상황에서 쓰는게 적합한가?

TheWing 2022. 8. 9. 02:42

Java 8 LocalDateTime vs Instant 어떤 상황에서 쓰는게 적합한가?

들어가기 전에

  • 본 글은 세션 공유용 자료이며 LocalDateTime, Instant 의 개념에 관한 짧은 글이 아니므로 양해 부탁드립니다.

포스팅 계기

  • 최근 새 회사(이커머스 도메인)에 이직하게 되었고 코프링(코틀린 + 스프링)기반 프로젝트를 진행하고 있었다.
  • 프로젝트를 살펴보던 중 시간 관련 데이터들을 DB에 LocalDateTime 로 넣고 있었다.
    Instant 클래스가 아닌 LocalDateTime로 넣는 이유가 궁금해서 팀 내에 공유할 겸 오랜만에 포스팅하게 되었다.

그냥 LocalDateTime 쓰면 안되나요?

독자들은 LocalDateTime 이나 Instant 아무거나 쓰면 안 되나? 이런 궁금증이 들 수 있다. LocalDateTime 클래스는 개인 프로젝트를 진행할 때 시간 관련 Type으로 사용했던 경험이 있을 것이다. 필자 또한 개인 프로젝트 하면서 해당 클래스를 사용했었다. 정확히 Instant 클래스를 알기 전까지 사용했다. (전 회사에서는 Instant 클래스를 사용했고 현 회사에서는 LocalDateTime 을 사용하고 있었기에 의문이 들었다.)

LocalDateTime, LocalDate, LocalTime 간단한 사용법들은 이전글 에서 포스팅 하였고 개념글 위주로 LocalDateTime이 좋은지 Instant 가 좋은지 더 깊게 공부해 보았고 포스팅하려고 한다.
(혹시나 Java 8 이상 사용 중이고 DateCalandar클래스를 사용하고 있다면 지양하는 것을 추천한다.)

아래 링크 참고 하길 바란다

Instant

위 사진은 이해를 돕기 위한 이미지이다. (날짜 + 시간 + UTC)

  • Instant 클래스는 단어의 의미와 같이 순간, 즉시 를 의미한다.
  • 컴퓨팅을 하기 위해 Timestamp로 기술적인 표현을 한 것이다.
    • 즉, 인간보다는 기계에 친화적이다.
  • long 형태로 Unix Timestamp를 저장하기 때문에 연산이 빠르다.
    • Unix Timestamp를 사용하면 되지 않나? 라는 의문을 가진 독자가 있을 것이다. Unix Timestamp2038년 문제가 있다. 1970년 1월 1일 자정에서부터 2147483647 초가 지난 2038년 1월 19일 화요일 03:14:07 UTC 까지 표현이 가능하다. 이것을 보완한 것이 Instant 클래스이다
  •   private static final long MIN_SECOND = -31557014167219200L; 
      private static final long MAX_SECOND = 31556889864403199L;
  • 지원할 수 있는 범위는 아래와 같다.
    • MIN_SECOND = -10000000-01-01T00:00Z
    • MAX_SECOND = 1000000000-12-31T23:59:59.99999999Z
  • 현재 순간을 찍으려면 아래와 같이 객체를 생성하면 된다.
    • Instant now = Instant.now();
      println(now); 
      // 2022-08-08T16:09:17.105081Z -- 2022년 08월 09일 01시 09분 17초
    • 나노초까지 표현이 가능하다.
    • 뒤에 Z를 표현하는 것을 볼 수 있다.
      • ISO_8601은 날짜와 시간과 관련된 데이터 교환을 다루는 국제 표준이며 링크를 참고하면 된다.
    • 앞에 “인간보단 기계에 친화적이라고 했는데 읽기 편한데?”라고 생각할 수 있다.
      • Timestamp는 본래 long 타입이다. toString() 을 재정의하여 DateTimeFormatter 로 읽기 쉽게 해준 것이다.
    • 어떻게 UTC 기반으로 타임을 찍는가?
    •     public static Instant now() { 
              return Clock.currentInstant(); 
          } 
          public abstract class Clock { 
              private static final long OFFSET_SEED = System.currentTimeMillis() / 1000 - 1024; 
              private static long offset = OFFSET_SEED; 
              // 시스템의 현재 시간을 가져온다 나노초로 가져오는 것을 밀리초로 표현해준다. 
              static Instant currentInstant() { 
                  long localOffset = offset; long adjustment = VM.getNanoTimeAdjustment(localOffset); 
                  // 밀리초를 나노초로 변환해서 가져온다 
                  ... 
                  return Instant.ofEpochSecond(localOffset, adjustment); 
              } 
          }

LocalDateTime

  • LocalDateTime날짜 + 시간 정보를 가지고 있다. Timezone이 없는 것을 볼 수 있다.
  • LocalDateTime은 인간에게 친화적인 타입이다.
  • Timezone이 없는데 Instant 와 같이 UTC로 넣는게 아닌가?라는 생각을 할 수 있다.
    • LocalDateTime은 어떻게 시간을 나타내는지 보자
      •   LocalDateTime now = LocalDateTime.now(); 
          println(now); 
          // 2022-08-09T01:09:17.113332 2022년 08월 09일 01시 09분 17초
    • LocalDateTime 은 현재 로컬 시간에 맞춰서 시간을 표현하고 있다.
      • 내부 코드를 살펴보자
        •    public final class LocalDateTime { 
                 public static LocalDateTime now() { 
                     return now(Clock.systemDefaultZone()); 
                 } 
             } 
             public static Clock systemDefaultZone() { 
                 // 시스템의 ZoneId를 가져온다 
                 return new SystemClock(ZoneId.systemDefault()); 
             } 
             public static LocalDateTime now(Clock clock) { 
                 Objects.requireNonNull(clock, "clock"); 
                 final Instant now = clock.instant(); 
                 // ZoneId를 기반으로 Offset 정보를 가져온다. 
                 ZoneOffset offset = clock.getZone().getRules().getOffset(now); 
                 return ofEpochSecond(now.getEpochSecond(), now.getNano(), offset); 
             } 
             public static LocalDateTime ofEpochSecond(long epochSecond, int nanoOfSecond, ZoneOffset offset) { 
                     Objects.requireNonNull(offset, "offset"); 
                   NANO_OF_SECOND.checkValidValue(nanoOfSecond); 
                   long localSecond = epochSecond + offset.getTotalSeconds(); 
                   long localEpochDay = Math.floorDiv(localSecond, SECONDS_PER_DAY); 
                   int secsOfDay = Math.floorMod(localSecond, SECONDS_PER_DAY); 
                   LocalDate date = LocalDate.ofEpochDay(localEpochDay); 
                   LocalTime time = LocalTime.ofNanoOfDay(secsOfDay * NANOS_PER_SECOND + nanoOfSecond); 
                   return new LocalDateTime(date, time); 
             }
        • 인스턴스의 Timezone 기반으로 Offset을 생성한 후 현재 시점 기준으로 LocalDate, LocalTime 객체를 생성 후 LocalDateTime 을 생성 해주는 것을 볼 수 있다.

LocalDateTime 을 사용 했을 때 문제점?

  • LocalDateTime 코드를 살펴보았으니 한 가지 추론을 해보자 글로벌 서비스를 Seoul(Asia/Seoul), LA(America/Los_Angeles) , Tokyo(Asia/Tokyo) 에 운영하고 있다고 가정하면 각각 Region마다 서버를 둘 것이고 타임존이 각각 다를 것이다. 각 인스턴스에서 LocalDateTime.now()로 시간을 입력한다고 해보자 그럼 DB에는 어떻게 들어갈 것인가? 인스턴스의 TimezoneUTC로 설정해 주지 않는 이상 각각 Timezone에 맞춰 데이터가 들어갈 것이다.

어떤 상황에 사용하는 것이 적합한가?

Instant 클래스를 사용하기 적합한 곳

  • Timestamp를 UTC 형식으로 저장하는 곳 즉 DB, 벡엔드 비즈니스 로직, 데이터 교환, 직렬화 시나리오에 적합하다. (연산하기 쉽기 때문)
  • 글로벌 런칭한 서비스 비즈니스 앱 개발 시 InstantZonedDateTime 클래스 (ZoneId+ Instant )를 많이 사용한다.
    •  

LocalDateTime 을 사용하기 적합한 곳

  • 글로벌 서비스가 아닌 단일 리전 서비스 일 때(타임존 X)
    • 즉, 특정 날짜와 시간을 여러 리전에서 적용하려는 경우
  • FrontEnd Service, 즉 Display(View) 에 좋다고 한다.

개인적인 견해

  • 글로벌 서비스일 경우 LocalDateTime보다는 InstantZonedDateTime 을 사용하는 것이 좋다고 생각한다.
  • Instant 클래스를 사용자 입장에서 long 형태로 받기 때문에 읽기 힘들다고 할 수 있는데 toString()으로 재정의가 되어있고 Converting 하거나 Serialize 하여 보내주면 된다고 생각한다.
  • UTC 기반으로 DB에 넣어주고 Timezone 기반으로 보여주면 될 것 같다고 생각이 든다.
    • User의 Region , User가 속한 그룹의 Region 들이 다르고 Timezone을 다양하게 가져갈 수 있기 때문에 적절하게 사용하면 된다고 생각

후기

  • 이직 후 첫 세션 공유 자리이므로 준비하는 시간이 생각보다 오래 걸렸지만 재미 있었다.

Reference