ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java/Kotlin 에서의 timezone 처리
    카테고리 없음 2024. 10. 27. 16:42

    JVM 기반의 애플리케이션을 설치해서 사용하는 경우 해당 애플리케이션이 기본적으로 사용하게 되는 타임존은 애플리케이션이 띄워져있는 OS의 시간 설정을 따르게 된다. 모바일 기기의 경우 OS에 의해 주기적으로 타임존이 변경된다. 해외 여행을 가게되면 핸드폰의 시간이 자동으로 그 지역 타임존으로 변경되어 설정되어 있는 경험을 하는것도 바로 그러한 이유 때문이다.

     

    분산환경에서 애플리케이션을 구동하는 경우에는 기본값을 그대로 사용하는 경우 원치않는 결과를 얻게될 수 있다. 여러 지역에 걸쳐 서비스가 구동되고 있는 경우라면 동일한 애플리케이션이라도 사용하는 타임존의 값이 달라질 수 있다.

    이런 환경에서 가장 많이 사용하는 방법은 모든 시간을 UTC (혹은 GMT) 를 기준으로 사용하는 것이다. 이 경우 데이터베이스에 저장된 값이나 로그 파일에 있는 시간값을 조회할 때 우리는 클라이언트의 타임존을 UTC 시간으로 변환하는 과정이 필요하다.


    Java 에서 date/time 정보를 표현하는 타입은 정말 다양하게 존재한다.

     

    Java 8 이전 날짜/시간 API

    java.sql.Date, java.sql.Timestamp

    JDBC API 에서 date/time 정보를 표현하기 위한 타입이다. 패키지명에서도 알 수 있듯이 JDBC 와 관련된 작업을 위해서 존재하는 만큼 일반적인 비즈니스 로직에서 사용하기에 적합하지 않다. (JDBC 와의 결합도가 증가된다)

     

    java.util.Date

    특정 시점을 밀리세컨드단위까지의 정확도로 표현할 수 있는 타입으로 원래 의도는 UTC 를 반영한 시간을 표현하는 것이다. JPA 를 사용하다보면 다음과 같이 @Temporal 애노테이션을 통해 어떤 형태로 Date 클래스를 다룰건지 선택할 수 있다. (앞서 살펴본 sql.Date 와 sql.Timestamp 와의 호환성을 위해 존재하는 것으로 보인다.)

    @Temporal(TemporalType.DATE)
    private Date date;
    
    @Temporal(TemporalType.TIMESTAMP)
    private Date time;

     

    java.util.Calendar

    Calendar 타입은 시간의 특정 시점을 캘린더의 특정 날짜로 변환하기 위한 메서드를 제공하는 추상 클래스이다. 이를 통해 2024년 10월 27일 15시와 같이 달력 상의 날짜처럼 타임스탬프로 표현되는 시간을 변환해줄 수 있다.

     

    최근에는 앞서 살펴본 Date 타입이나 Calendar 타입을 많이 사용하지 않는데, 그 이유는 1) 해당 타입들은 대부분의 경우 thread-safe 하지 않으며, 2) 타임존이 존재하는 경우 이를 적용하거나 변환하는 것이 명확하지 않기 때문이다.

     

    Java8  이후 날짜/시간 API

    Java8 이 릴리즈 되면서 JSR 310 명세로 java.time 패키지가 추가되는데 이는 앞서 본 타입들의 문제점을 보완하였기 때문에 최근 대부분의 경우에는 java.time 패키지를 사용해 날짜/시간을 표현한다. LocalDate, LocalTime, LocalDateTime, ZonedDateTime 과 같이 날짜와 시간을 별도로 분리해 사용할 수 있도록 하였으며 기존과는 다르게 immutable 하며 따라서 thread-safe 한 클래스들이다.

     

    java.time.ZonedDateTime

    그 중에서도 이전까지 가장 큰 고통을 주었던 타임존 관련 문제를 해결하기 위해 등장한 클래스가 바로 ZonedDateTime 이다. 이는 2007-12-03T10:15:30+01:00 Europe/Paris 과 같은 ISO-8601 캘린더 시스템내의 datetime 을 다룬다.

     

    java.time.Instant

    타임라인에서 특정한 시점을 표현하는 클래스로 일반적으로는 timestamp 를 표현하는데 사용된다.

    그렇다면 언제 어떤 타입을 사용하는 것이 좋을까?

    java.time 패키지의 날짜/시간 관련 클래스를 사용하는 경우에는 다음과 같은 기준을 두고 사용하면 좀 더 일관성있게 사용할 수 있다.

     

    Instant 클래스는 컴퓨터, 그 외 날짜/시간 클래스들은 사람이 읽기쉬운 타입으로 처리하자

    두 클래스의 값을 로그로 찍어보면 보여지는 형태는 사실 큰 차이가 없다. 왜냐면 java.time 패키지들은 대부분 ISO-8601 형식을 준수하려 하기 때문에 Instant 의 toString 이 ISO-8601 로 보여주기 때문이다. 그렇지만 타임라인 상의 특정 시점을 표현한다는 점을 염두해두면 컴퓨터에게는 시간을 표현하는 가장 자연스러운 방식이 된다. 다만 사람은 시간을 보통 그런식으로 인식하지 않고, 특정 타임존에서 시계를 볼 때 보는 시점으로 인식하기에 이를 같이 사용하기엔 어려움이 있다.

     

    데이터베이스와 관련된 곳에서는 항상 UTC 를 사용하자

    데이터베이스 연결에서는 UTC를 기준으로 하는것이 분산환경에서 접근할 때 어려움을 최소화 할 수 있는 방식이라고 할 수 있다.

    JPA 를 사용하는 경우라면 hibernate.jdbc.time_zone 속성을 UTC 로 설정하면 UTC로의 변환을 자동으로 수행해줄 수 있다.

    이렇게 하면 매번 클라이언트와 데이터베이스 사이에서 그에 맞는 타임존으로 변경하는 코드를 매번 작성할 필요가 사라지게된다.

    추가로 DB 컬럼으로 날짜/시간을 다룰 때는 MySQL 을 기준으로 timestamp 와 date 타입을 사용할 수 있는데, 좀 더 포괄적이라는

    이유만으로 모든 타입을 timestamp 로 사용하지 말자. 이는 불필요한 복잡도를 증가시키고, 저장공간을 좀 더 사용하게 된다. (예를 들어 생년월일과 같은 값을 저장하는 경우에는 date 면 충분하다)

     

    엔티티 클래스, JSON 요청/응답 필드와 같이 사용처에 따라 일관성있는 타입을 사용하자

    • JPA 엔티티의 시간 필드는 DB에 저장되는 값이며 컴퓨터가 사용하는 타입이므로 Instant 를 사용하는 것이 좋다.
    • JSON 요청/응답을 처리하기 위한 DTO 클래스에서는 LocalDateTime 이나 String 타입을 사용한 뒤 이를 파싱해서필요한 타입으로 변환해서 사용하면 좀 더 유연하게 클라이언트의 요청을 처리할 수 있다.
      • 필요한 경우 타임존 정보를 추가 필드로 전달받거나 클라이언트에 대한 설정정보를 저장하는 테이블에이를 저장해두고 활용할 수 있다. (사용자 브라우저의 기본 설정을 따르거나, 휴대폰의 타임존을 따른다거나)

    몇 가지 케이스를 나누어 살펴보았지만 결론적으로는 UI와 같이 사용자가 눈으로 확인할 수 있거나 타임존이 중요하게 동작하는 곳이라면ZonedDateTime 을 사용해 타임존 정보를 확실하게 처리해주는 것이 좋고, Instant 클래스는 DST나 타임존에 대한 처리를 개발자가 따로 해줄 필요가 없기 때문에 그 외의 경우에서 사용하면 개발시의 복잡도를 줄여주는데 도움이 될 수 있다.

    참고

    kotlinx.datetime

    추가로 코틀린의 멀티플랫폼 특성을 지원하기 위해 kotlinx.datetime 이라는 라이브러리가 존재한다.

    일반적인 JVM 기반 애플리케이션 개발 시 Kotlin 을 사용하는 경우에는 앞서 살펴본 java.time 패키지의 클래스를 활용하겠지만 만약 멀티플랫폼을 지원해야 하는 환경에서는 그에 맞는 라이브러리를 사용해야 지원하는 플랫폼에서 호환될 수 있다.

     

    제공되는 대부분의 API 는 java.time 패키지와 동일하다고 볼 수 있다. ISO-8601 포맷을 기반으로 날짜/시간을 표현하는 것도 대부분의 시간관련 라이브러리의 특성과 유사하다고 할 수 있다.

     

    Jetbrains 에서 지정한 날짜/시간 관련 멀티플랫폼 공식 라이브러리이지만 현시점에는 알파버전이기 때문에 멀티플랫폼을 구현해야 하는 경우가 아니라면 접해볼 수 있는 일은 드물 것 같다.

Designed by Tistory.