Reddit

Postgres AT TIME ZONE 'UTC' does NOT do what you think it does

Postgres의 AT TIME ZONE 'UTC'가 실제로 바꾸는 것

PostgreSQL의 `AT TIME ZONE`은 입력 타입에 따라 `timestamp`와 `timestamptz` 사이를 서로 반대 방향으로 변환합니다. UTC 기준으로 월 단위 계산을 하려면 시간대를 명시해야 하며, 변환 전후의 타입과 비교 방식을 함께 살펴야 합니다.

AI 요약

PostgreSQL의 timestamp는 시간대 정보가 없는 날짜·시간 값이고, timestamptz는 특정 시점을 나타냅니다. 이름과 달리 timestamptz가 입력된 시간대 이름을 계속 보관하는 것은 아닙니다. AT TIME ZONE은 입력 타입에 따라 결과 타입이 달라져 혼동을 부릅니다.

월 단위 계산에서 생기는 차이

글은 월별 시계열의 변화량을 구하는 조인 쿼리를 예로 듭니다. b.month_start + INTERVAL '1 months'처럼 timestamptz에 한 달을 더하면 세션의 TimeZone 설정에 따라 결과가 달라질 수 있습니다. 글의 사례에서는 세션 시간대가 태평양 시간대(PT)이고 서비스 기준은 UTC입니다. UTC의 2026-03-01 00:00:00+00은 PT에서 2026-02-28 16:00:00-08로 표현됩니다. 이 값에 한 달을 더하면 2026-03-28 16:00:00-08이 되어, UTC 기준으로 월을 계산하려는 의도와 어긋납니다.

글은 먼저 b.month_start AT TIME ZONE 'UTC'를 적용해 UTC 시각을 시간대 없는 timestamp로 바꾸고, 그 결과에 한 달을 더하라고 제안합니다. 이때 타입은 여전히 timestamp입니다. 따라서 timestamptz인 a.month_start와 비교할 때는 결과 타입까지 확인해야 합니다. 글이 제시한 최종 식은 ((b.month_start AT TIME ZONE 'UTC') + INTERVAL '1 months') AT TIME ZONE 'UTC'입니다. 두 번째 변환은 시간대 없는 값을 UTC 기준의 시점인 timestamptz로 해석합니다.

`AT TIME ZONE`의 양방향 동작

timestamp AT TIME ZONE 'UTC'는 시간대 없는 날짜·시간을 UTC 시각으로 해석해 timestamptz를 만듭니다. 반대로 timestamptz AT TIME ZONE 'UTC'는 시점을 UTC 달력 표현으로 바꿔 시간대 정보가 없는 timestamp를 돌려줍니다. 같은 연산자 문법이 입력 타입에 따라 반대 역할을 합니다. 글의 핵심 사례에서는 먼저 timestamptz를 UTC의 timestamp로 바꾸고 월을 더한 뒤, 다시 UTC 기준 timestamptz로 변환합니다.

다만 글의 “timestamp와 timestamptz 비교는 항상 false”라는 문장은 그대로 일반화하기 어렵습니다. PostgreSQL은 두 타입을 비교할 때 시간대 없는 값을 세션 시간대 기준으로 해석하므로, 결과는 세션 설정과 값에 따라 달라집니다. 따라서 비교가 언제나 실패한다고 단정하기보다, 양쪽 타입과 TimeZone 설정을 명시적으로 점검해야 합니다. 글이 지적하는 타입 변환 문제는 유효하지만, 비교 결과에 관한 설명은 주의해서 읽어야 합니다.

Reddit 반응

  • @u/AlternativePeace1121 — 요약하면, 항상 시간대가 있는 timestamp를 쓰세요.
    • @u/tanin47 — 요약은 실제로 AT TIME ZONE 'UTC'가 시간대가 있는 timestamp를 시간대가 없는 timestamp로 바꾼다는 뜻입니다. 글 맨 위에 요약을 추가했습니다.
    • @u/ArmpitNoise — 항상 UTC를 쓰고 그에 맞게 계산하라는 말인가요?
    • @u/MagicalVagina — timestamptz가 바로 그 역할을 합니다.
    • @u/6502zx81 — TAI는 연속적입니다.
    • @u/good_live — UTC만 저장하면 정보가 사라집니다. 이벤트가 발생한 장소를 따로 저장하면 괜찮지만, 계산을 직접 해야 합니다. 어떤 데이터를 저장하느냐에 따라 다르다고 봅니다.
    • @u/Merry-Lane — 저장한 타임스탬프에 보통 사용자의 시간대를 적용하는데, 왜 문제가 되나요?
  • @u/jonathancast — 저는 연산이 그렇게 동작한다고 생각했습니다. timestamptz AT TIME ZONE 'UTC'는 어떤 시점을 UTC 시간대로 표현하고, timestamp AT TIME ZONE 'UTC'는 그 표현을 UTC 시간대로 해석해 시점을 만듭니다. 양방향에 같은 문법을 써서 헷갈리지만, 입력 타입을 알면 이해됩니다.
    • @u/tanin47 — timestamptz AT TIME ZONE 'UTC'의 결과는 timestamptz가 아니라 시간대 없는 timestamp입니다. 글 맨 위 요약을 더 명확하게 고쳤습니다.
    • @u/jonathancast — 맞습니다. timestamp는 연·월·일·시·분·초와 소수 초로 이루어진 값이지만 특정 시점을 나타내지는 않습니다. timestamptz는 시간대 표시와 timestamp를 짝으로 저장하는 타입이 아니라, 특정 시간대에서 timestamp를 해석해 얻은 시점입니다. 그래서 timestamptz 값만으로는 어떤 시간대에서 해석해야 하는지 알 수 없습니다. 특정 시간대에서 그 시점에 해당하는 timestamp를 구하려면 AT TIME ZONE이 필요합니다.
  • @u/Worried_Meaning4694 — 이 동작을 처음 본 사람이라면 꽤 위험한 함정이라고 느낄 만합니다. AT TIME ZONE 'UTC'가 시간대가 없는 값이 아니라 시간대가 있는 값을 돌려준다고 예상해도 이상하지 않습니다.
  • @u/piderman — 제 기억으로는 lead(value, 1, 0) OVER (PARTITION BY 1 ORDER BY month_start) - value를 쓰면 됩니다. 윈도 함수로 이런 문제를 피할 수 있습니다. 시계열 데이터의 변화량 계산은 윈도 함수가 잘 처리하는 작업입니다. 시계열이 여러 개라면 1 대신 구분하는 열을 지정하세요.

원문: Book of Revenue / 번역·요약: Trawling