오늘은 시간을 표현하기 위한 개념들을 알아볼 것이다.
데이터베이스의 시간을 표현하는 타입이나 프로그래밍 언어에서 시간을 표현하는 타입에 관련된 오류를 맞닥뜨리게 되면 생각보다 사전 지식이 없다면 오류를 찾기가 힘들다.
그래서 오늘은 타임존 도메인 관련해서 지식을 알아보고 PostgreSQL에서 사용되는 두 가지 타입인 timestamp, timestamptz, 그리고 이를 실제 JPA 엔티티에 매핑하고 이를 DTO로 변환해 요청된 포맷과 타임존에 따른 시간을 반환하도록 하는 데 사용되는 jackson 라이브러리와 spring이 제공해주는 @JsonFormat과 @DateFormat에 대해 알아보는 것이 목표이다. 먼길을 가야 하기 때문에 바로 시작해보자.
1. 타임존이란 무엇일까?
타임존은 해당 국가에 의해 법적으로 지정되는 동일한 시간을 따르는 지역을 의미한다.
영토가 큰 나라인 경우는 지역별로 실제 각기 다른 타임존을 사용하기도 한다.
즉 지역마다 시간이 다를 수 있다는 것이다.
한국의 타임존이라고 하면 경도 0도에 위치한 그리니치 천문대를 기준으로 하는 태양 시간에서 동쪽으로 9시간을 계산한
GMT +09:00 으로 표현된다.
하지만 이러한 GMT는 지구의 자전 주기의 흐름이 늦어지고 있는 문제를 해결하지 못하는데, 이를 해결하기 위해 UTC라는 세슘 원자의 진동수에 기반한 국제 원자시를 기준으로 다시 지정된 시간이 정의되었다.
즉 정확한 시간측정을 위해 GMT를 대체하기 위한 새로운 표준이다.
UTC + 09:00에서 + 09:00의 의미는 UTC 기준시간보다 9시간이 빠르다는 말이다. 이렇게 UTC와의 차이를 표현한 것이 바로 오프셋이라고 하고 표현식은 UTC 기준 (+, -) 시, 분으로 표현된다.
여기서 그럼 타임존과 오프셋이 동일하게 생각될 수 있다. 한국 시를 나타내는 KST는 보통 타임존으로 지칭하는데 이를 오프셋과 동일한 의미로 사용되기에는 애매한 이유가 존재한다.
첫 번째는 하절기에 표준시를 원래 시간보다 한 시간 앞당긴 시간으로 이용하는 서머타임(DST)때문이고
두 번째는 여러 정책에 의해 실제 타임존이 변경될 수 있고, 국가 표준시도 변경될 수 있다는 점 때문이다.
이 말은 한 지역의 타임존은 여러 개의 오프셋을 가질 수 있다는 것을 의미하고 그 결정 시점은 변할 수 있다는 것이다.
그래서 우리는 현재 KST를 사용하고 일본은 JST, 뉴욕은 PST를 사용한다라고 말할 수 있고 정확히 특정 지역의 타임존을 오프셋이 아닌 하나의 지역으로 표기하여 처리하고 있는데 이때 사용되는 개념이 IANA time zone database이다.
이름은 우리가 Area / Location의 규칙을 사용하고 Area는 보통 대륙이나 대양명을 사용하고 Location은 주로 큰 도시 위주로 지정된다.
따라서 대한민국의 타임존은 Asia / Seoul이고 일본은 Asia / Tokyo인데, 현재 두 지역 모두 UTC +09:00을 표준시로 사용하고 있지만 다른 국가에 소속되고 역사적인 변경내역이 달라 별도의 타임존으로 관리되고 있다.
PostgreSQL에 적용되는 Time Zones
PostgreSQL은 IANA 타임존 데이터베이스를 사용한다.
그리고 시간과 날짜를 저장하는 타입은 단 두 가지밖에 존재하지 않는다
TIMESTAMP WITHOUT TIME ZONE(timestamp), TIMESTAMP WITH TIME ZONE (timestamptz)이다.
실제 이 둘 데이터가 저장되는 방식은 동일하다. 바로 8바이트 정수이다.
먼저 TIMESTAMP에 대해 이해해야 한다. 이는 타임존의 값을 명시하지 않는 시간과 날짜를 저장한다.
2000-01-01 00:00:00
TIMESTAMPTZ도 마찬가지로 TIMESTAMP의 날짜와 시간을 저장하되 어느 타임존에서 저장되었는지를 같이 기술한다.
그렇지만 저장되는 바이트는 동일하다.
2000-01-01 00:00:00 +09:00
이는 현재 데이터베이스 혹은 서버의 타임존에 영향을 받아(SHOW TIMEZONE) 출력만 된다. 저장할 때는 동일하게 UTC 기준으로 저장을 한다는 이야기다.
절대적인 UTC가 필요하다면 TIMESTAMP를 특정 지역에 대한 시간을 추출하려고 한다면 TIMESTAMPTZ를 사용하면 될 것 같다.
이제 애플리케이션 단으로 올라와보자.
Java 8부터 지원하는 date 패키지에는 실제 날짜와 시간을 저장하는 클래스가 존재한다.
나는 이 중 LocalDateTime을 엔티티에 매핑하여 사용한다. 실제 애플리케이션에서는 분, 초단위까지 필요하기 때문인데
이러한 데이터를 클라이언트에게 전달해 줄 때 발생하는 문제는 다음과 같다.
클라이언트에서는 `T`와 같은 문자와 실제 소수점까지의 데이터는 필요가 없다. 단순히 분까지만 내려줬으면 좋겠다.
이는 데이터를 포맷팅 하는 무언가가 필요하다.
나는 이 경우 @JsonFormat 에노 테이션을 사용한다.
이는 jackson 라이브러리가 제공해주는 에노 테이션으로 DTO 변환 이후 클라이언트에게 응답 시 적절한 포맷으로 변환을 해준다.
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createdDatetime;
이후 timezone이라는 부분이 존재하는데 이는 역직렬화에 사용되는 조건이 아니다.
따라서 이에 대해 고민하는 독자는 다음을 살펴보아야 한다.
ec2를 사용하는 경우 서버의 설정이 기본 utc이고 그렇기 때문에 애플리케이션도 해당 타임존을 사용한다..
나는 이 경우 두 가지 방법을 생각했는데 첫 번째는 서버의 타임존을 변경하는 것. 두 번째는 JVM 설정하는 것이었고
큰 변경점보다 작은 변경점이 경험상 좋기 때문에 -Duser.timezone=Asia/Seoul로 지정하여 처리했다.
혹은 애플리케이션 시작하는 Main 쪽에서 타임존을 설정할 수 있으니 해당 방법으로 처리해도 된다.
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime requestDateTime;
DateTimeFormat을 사용해 클라이언트가 전달하는 날짜 형태를 포맷팅 하여 받을 수 있습니다.
이는 스프링에서 제공해주는 어노테이션으로 @ModelAttribute를 사용한다면 이는 스프링이 지원하는 컨버터를 사용해 객체에 매핑하게 됩니다. 또 url 파라미터로 요청이 왔을 때도 마찬가지로요.
가끔 우리는 json 데이터를 받기 위해 @RequestBody 어노테이션을 사용합니다.
이때 우리는 jackson 라이브러리를 사용하게 됩니다.
ObjectMapper가 동작을 하는 것이죠 따라서 우리는 이 경우 다음을 예상할 수 있습니다.
요청 시 직렬화를 하려면 다음 조건을 따른다.
- 두 어노테이션 모두가 있으면 @JsonFormat이 진행된다
- @JsonFormat이 틀리면 @DateTimeFormat이 맞더라도 직렬화는 실패한다
- 단, @DateTimeFormat이 있다면 @DateTimeFormat의 포맷으로 직렬 화가 진행된다.
응답 시에는 역직렬화에서는 @JsonFormat만 가능하다입니다.
Jackson은 json 데이터를 역/직렬 화하기 위해 필요한 라이브러리입니다.
form-data나 url parameter를 파싱 하는 데는 아무런 소용이 없습니다. 이를 기억해야 합니다.
즉
JSON 직렬화 외에는 Jackson이 사용되지 않기 때문에 @JsonFormat은 효과가 없습니다.
그래서 RequestParameter나 ModelAttribute에선 @DateTimeFormat 만 적용될 수 있습니다.
'느리게 변하는 지식' 카테고리의 다른 글
lambda in computer programming (0) | 2023.07.14 |
---|---|
CDN (0) | 2022.12.17 |
아파치 웹 서버를 리눅스에서 설치할 때 왜 여러 패키지를 받을까? (0) | 2022.11.12 |
데드락 (0) | 2022.08.04 |
CPU 작동 원리 (0) | 2022.04.08 |
댓글