본문 바로가기
빌드노트 검색

빌드노트

PostgreSQL에서 하루를 더했는데 24시간 뒤와 시각이 다르다

timestamptz에 더하는 1 day와 24 hours는 뉴욕의 DST 전환에서 다른 순간에 도달한다. PostgreSQL 18.6에서 봄 23시간·가을 25시간과 UTC 대조를 확인하고, 지역 달력의 다음 날과 경과 24시간 중 필요한 계산을 고른다.

PostgreSQL의 timestamptz에 interval '1 day'를 더했더니 다음 날 정오인데, interval '24 hours'를 더하면 오후 1시가 될 수 있다. DST, 즉 일광 절약 시간제 전환을 건너는 계산에서 생기는 차이다. 1 day는 계산하는 연결의 시간대를 기준으로 달력 날짜를 하루 옮기고, 24 hours는 24시간을 더한다. 따라서 “하루 뒤”가 지역 달력의 다음 날인지, 실제로 86,400초가 지난 뒤인지부터 정해야 한다.

예를 들어 “뉴욕의 다음 날 정오까지”라는 조건과 “요청한 순간부터 24시간 동안”이라는 조건은 서로 다른 만료 시각을 요구할 수 있다. 전자에는 업무 지역을 지정한 달력 계산이, 후자에는 interval '24 hours'가 맞는다. 이 글에서는 다음 날 정오가 존재하고 한 가지 순간으로 정해지는 입력으로 둘을 비교한다.

정오에서 출발해 봄과 가을을 비교한다

접속 가능한 PostgreSQL 실습 DB와 SQL 클라이언트를 준비한다. 새 연결에서 아래 SQL 전체를 실행하면 연결의 원래 시간대를 확인한 뒤, 뉴욕을 기준으로 두 덧셈을 비교할 수 있다. 테이블은 만들지 않는다. SET LOCAL은 현재 트랜잭션에만 적용되며 마지막 ROLLBACK에서 끝난다. SET LOCAL의 적용 범위 (새 탭에서 열림)

두 입력은 뉴욕의 2024년 봄·가을 전환 전날 정오다. TIMESTAMPTZ 타입과 당시의 오프셋 -05, -04를 함께 써서 출발 순간을 고정했다. 실행 도구가 각 문장을 별도 연결에서 처리하지 않도록 같은 연결과 트랜잭션을 유지한다.

SELECT current_setting('server_version') AS server_version,
       current_setting('TimeZone') AS session_timezone;

BEGIN;
SET LOCAL DateStyle = 'ISO, YMD';
SET LOCAL TIME ZONE 'America/New_York';

WITH sample(season, started_at) AS (VALUES
    ('spring', TIMESTAMPTZ '2024-03-09 12:00:00-05'),
    ('fall',   TIMESTAMPTZ '2024-11-02 12:00:00-04')
), calculated AS (
    SELECT season, started_at,
           started_at + INTERVAL '1 day' AS day_result,
           started_at + INTERVAL '24 hours' AS hours_result
    FROM sample
)
SELECT season, day_result, hours_result,
       (EXTRACT(epoch FROM day_result) - EXTRACT(epoch FROM started_at))::bigint AS day_seconds,
       (EXTRACT(epoch FROM hours_result) - EXTRACT(epoch FROM started_at))::bigint AS hours_seconds
FROM calculated
ORDER BY season DESC;

ROLLBACK;

별도로 만든 PostgreSQL 18.6, Debian 12 컨테이너에서 실행했다. 서버가 사용하는 시스템 tzdata는 2026c였고, 새 연결의 원래 시간대는 Etc/UTC였다. 아래는 뉴욕으로 설정한 트랜잭션의 SELECT 출력이다. 열 구분은 CSV로 옮겼으며 숫자는 직접 관찰한 값이다.

season,day_result,hours_result,day_seconds,hours_seconds
spring,2024-03-10 12:00:00-04,2024-03-10 13:00:00-04,82800,86400
fall,2024-11-03 12:00:00-05,2024-11-03 11:00:00-05,90000,86400

봄에는 출발 시각의 -05가 결과에서 -04로 바뀌었다. 뉴욕의 시계가 3월 10일 새벽 2시에서 3시로 넘어가므로, 전날 정오에서 다음 날 정오까지는 23시간이다. 1 day는 정오를 유지하면서 82,800초 뒤에 도달했다. 실제 24시간을 더한 결과는 한 시간 늦은 오후 1시였다.

가을에는 반대다. 11월 3일 새벽 시계가 한 시간 되돌아가며 오프셋이 -04에서 -05가 된다. 다음 날 정오까지는 25시간, 즉 90,000초가 필요했다. 24시간만 더하면 그보다 한 시간 이른 오전 11시에 도달했다. 같은 1 day가 봄에는 짧고 가을에는 길어진 것이다.

별도 대조에서는 이미 전환을 지난 2024-03-10 12:00:00-04와 2024-11-03 12:00:00-05에서도 같은 계산을 실행했다. 이 두 입력에서 다음 날 정오까지는 모두 86,400초였고 두 식의 결과도 같았다. 차이는 DST를 쓰는 지역의 모든 날짜에서 생기는 것이 아니라, 계산 구간이 오프셋 변화를 건널 때 드러난다.

interval 자체보다 더한 뒤의 순간을 비교한다

PostgreSQL은 interval의 월, 일, 마이크로초 성분을 따로 보관한다. 일 성분은 지역 달력의 날짜를 옮기는 데 쓰이고, 시간 성분은 경과 시간을 더하는 데 쓰인다. 그래서 같은 길이처럼 보이는 1 day와 24 hours가 timestamptz 덧셈에서 다른 결과를 낼 수 있다. interval의 저장 성분 (새 탭에서 열림), 날짜·시간 덧셈의 DST 규칙 (새 탭에서 열림)

위 쿼리의 day_seconds와 hours_seconds는 결과의 epoch에서 출발 시각의 epoch를 뺀 값이다. timestamptz의 epoch는 UTC 기준의 초이므로 표시된 시각의 차이와 실제 경과 시간을 구별할 수 있다. EXTRACT(epoch FROM INTERVAL '1 day')만 조회해서는 특정 날짜와 지역의 DST 경계를 반영한 경과 시간을 알 수 없다. EXTRACT의 epoch 정의 (새 탭에서 열림)

자기 데이터에서 확인할 때도 덧셈 전후의 timestamptz로 이 차이를 구한다. 애플리케이션 결과가 이 예제와 다르다면 실제 덧셈을 실행하는 연결의 current_setting('TimeZone')을 먼저 확인한다. 이어서 덧셈 왼쪽 식을 pg_typeof(...)로 감싸 timestamp with time zone인지 확인한다. 열이 그 타입이어도 중간에 ::timestamp로 변환했다면 같은 조건의 계산이 아니다.

입력에 -05를 썼어도 뉴욕을 기억하지는 않는다

같은 출발 순간으로 UTC 대조를 해 보자. 앞 SQL에서 SET LOCAL TIME ZONE 'America/New_York'; 한 줄을 SET LOCAL TIME ZONE 'UTC';로 바꾸고 전체를 다시 실행한다. 입력의 타입과 오프셋은 그대로 둔다. 이번에 직접 얻은 결과는 다음과 같다.

season,day_result,hours_result,day_seconds,hours_seconds
spring,2024-03-10 17:00:00+00,2024-03-10 17:00:00+00,86400,86400
fall,2024-11-03 16:00:00+00,2024-11-03 16:00:00+00,86400,86400

이것은 뉴욕에서 계산해 둔 결과를 UTC로 표시한 것이 아니라, UTC를 기준으로 덧셈을 다시 한 결과다. UTC에는 이 DST 전환이 없어서 두 식이 같은 순간에 도달했다. 예를 들어 뉴욕에서 계산한 봄의 1 day 결과는 12:00-04, UTC로는 16:00+00이었다. UTC 연결에서 다시 계산한 1 day 결과는 위 출력처럼 17:00+00이므로, 단순한 표시 차이보다 한 단계 앞선 산술의 차이다.

timestamptz는 입력을 UTC로 환산해 저장하고 원래 시간대 이름이나 오프셋을 보존하지 않는다. 입력의 -05는 출발 순간을 정할 뿐, 이후 덧셈에도 뉴욕의 규칙을 쓰라는 뜻은 아니다. 달력상의 다음 날을 계산하려면 그 계산을 실행하는 트랜잭션의 시간대를 업무 지역인 America/New_York로 명시해야 한다. 지역 이름은 날짜별 DST 규칙을 포함하므로 고정 오프셋으로 대신하면 같은 요구를 표현할 수 없다. 시간대 입력·저장·출력 규칙 (새 탭에서 열림), 지역 시간대와 오프셋의 차이 (새 탭에서 열림)

“다음 날 같은 현지 시각”이 요구라면 업무 시간대와 1 day를 함께 지정하고, “86,400초 뒤”가 요구라면 24 hours를 사용한다. 원래 업무 지역 이름이 필요하면 timestamptz와 별도로 보관한다. 이 실험은 2024년 뉴욕의 정오 입력과 전환 뒤 정오를 검증했다. 새벽의 존재하지 않거나 두 번 나타나는 현지 시각을 어떻게 처리할지, 다른 지역이나 미래 시간대 규칙에서 무엇을 택할지는 검증하지 않았다.

← 전체 글 보기