타임존 처리와 AT TIME ZONE

TIMESTAMP WITH TIME ZONE, AT TIME ZONE 구문, 세션 타임존 설정, UTC 저장 원칙, 서머타임 함정, DB별 타임존 변환 패턴을 설명합니다.

· 5 min read · PALDYN Team

지난 글에서 날짜·시간 함수와 INTERVAL 산술을 살펴봤다. 이번에는 타임존(Time Zone) 처리—UTC 저장 원칙, TIMESTAMP WITH TIME ZONE, AT TIME ZONE 변환 구문, 세션 TZ 설정, 서머타임(DST) 함정—를 정리한다.


왜 타임존이 까다로운가

날짜·시간 값은 **“어떤 기준으로 측정한 시각인가”**를 명시하지 않으면 무의미하다. 같은 2026-05-05 12:00:00이라도 서울·뉴욕·런던에서 각각 다른 절대 시각을 가리킨다. 글로벌 서비스라면:

  • 여러 나라 사용자가 동일 데이터를 서로 다른 로컬 시각으로 조회한다.
  • 서머타임(DST) 전환 시 1시간이 사라지거나 중복된다.
  • DB 서버 위치와 앱 서버 위치, 클라이언트 위치가 각각 다를 수 있다.

황금 원칙: DB에는 UTC로 저장하고, 표시(display) 시점에만 로컬 TZ로 변환한다.


TIMESTAMP 타입의 두 갈래

타임존 처리 개요

타입TZ 정보내부 저장표준 명칭
TIMESTAMP WITH TIME ZONE있음UTC로 변환·저장SQL:1999
TIMESTAMP WITHOUT TIME ZONE없음있는 그대로SQL:1992

PostgreSQL은 TIMESTAMPTZ(= TIMESTAMP WITH TIME ZONE), Oracle은 TIMESTAMP WITH TIME ZONE / TIMESTAMP WITH LOCAL TIME ZONE을 제공한다. SQL Server는 2016부터 AT TIME ZONE 연산자를 지원하며, datetimeoffset 타입이 오프셋 정보를 포함한다.


AT TIME ZONE 구문

AT TIME ZONE 코드 패턴

PostgreSQL

-- TIMESTAMPTZ → 특정 TZ로 변환 (표시용)
SELECT now() AT TIME ZONE 'Asia/Seoul';

-- TIMESTAMP (TZ 없음) → TZ 있는 타입으로 해석 후 변환
SELECT '2026-05-05 12:00:00'::timestamp AT TIME ZONE 'UTC'
                                         AT TIME ZONE 'Asia/Seoul';

-- 세션 타임존 변경 (연결 단위)
SET timezone = 'Asia/Seoul';
SELECT now();  -- KST로 표시

AT TIME ZONE두 번 연속 사용하면: 첫 번째 적용은 TIMESTAMP → TIMESTAMPTZ 해석(지정 TZ를 기준으로), 두 번째 적용은 TIMESTAMPTZ → TIMESTAMP 변환(다른 TZ 기준으로 표시)이다.

Oracle

-- SYSTIMESTAMP는 이미 TIMESTAMP WITH TIME ZONE
SELECT SYSTIMESTAMP AT TIME ZONE 'Asia/Seoul' FROM DUAL;

-- DATE/TIMESTAMP를 TZ-aware로 변환 후 전환
SELECT FROM_TZ(CAST(SYSDATE AS TIMESTAMP), 'UTC')
       AT TIME ZONE 'Asia/Seoul'
FROM DUAL;

-- 세션 TZ 변경
ALTER SESSION SET TIME_ZONE = 'Asia/Seoul';

SQL Server (2016+)

-- GETUTCDATE()를 특정 TZ로 변환
SELECT GETUTCDATE() AT TIME ZONE 'UTC'
                   AT TIME ZONE 'Korea Standard Time';

-- datetimeoffset 타입 활용
SELECT SYSDATETIMEOFFSET() AT TIME ZONE 'Korea Standard Time';

서머타임(DST) 함정

DST가 적용되는 타임존(예: America/New_York)은 시간이 이중으로 존재하거나 사라지는 구간이 있다.

-- 뉴욕 DST 전환 직전·직후 (2026-03-08 02:00 → 03:00)
SELECT '2026-03-08 02:30:00 America/New_York'::timestamptz;
-- ERROR: 이 시각은 존재하지 않음 (시계가 03:30으로 건너뜀)

-- 안전한 방법: UTC로 저장 후 변환
INSERT INTO events(ts) VALUES ('2026-03-08 07:30:00+00');
SELECT ts AT TIME ZONE 'America/New_York' FROM events;

UTC 저장 시 DST 문제를 DB 레이어에서 완전히 피할 수 있다.


세션 TZ vs 컬럼 TZ

방법범위주의
SET timezone (PostgreSQL)세션 전체ORM 커넥션 풀에서 누락 위험
ALTER SESSION SET TIME_ZONE (Oracle)세션 전체같음
AT TIME ZONE 함수쿼리 수준명시적이고 안전
컬럼 기본값 CURRENT_TIMESTAMP삽입 시세션 TZ 영향받음

커넥션 풀 환경에서는 세션 TZ 설정이 다른 세션에 영향을 주지 않도록 커넥션 획득 시점에 TZ를 강제 설정하거나, AT TIME ZONE을 쿼리에 명시하는 것이 안전하다.


실무 권장 패턴

-- 1. 테이블은 TIMESTAMPTZ로 정의
CREATE TABLE orders (
    id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 2. 삽입은 UTC 또는 오프셋 명시
INSERT INTO orders DEFAULT VALUES;

-- 3. 조회 시 AT TIME ZONE으로 변환
SELECT id,
       created_at AT TIME ZONE 'Asia/Seoul' AS created_kst
FROM orders;

DB별 타임존 확인 쿼리

-- PostgreSQL
SHOW timezone;
SELECT current_setting('timezone');

-- Oracle
SELECT DBTIMEZONE, SESSIONTIMEZONE FROM DUAL;

-- MySQL / MariaDB
SELECT @@global.time_zone, @@session.time_zone;

-- SQL Server
SELECT GETDATE(), SYSDATETIMEOFFSET();

지난 글: 날짜·시간 함수와 INTERVAL 연산

다음 글: UNION · INTERSECT · EXCEPT 집합 연산


읽어주셔서 감사합니다. 😊