해외 예약의 시간대 설계: UTC 저장부터 현지 시각 보존까지
해외 예약 시스템에서는 같은 예약을 여러 지역의 시각으로 표시하고, 알림과 예약 시작을 정확한 시점에 실행해야 한다. 여기에 예약을 받은 뒤 현지 정부가 시간대 규칙을 바꾸는 상황까지 고려하면, 저장 자료형을 선택하기 전에 서비스가 유지해야 할 시간의 의미부터 정리할 필요가 있다.
이 글은 PostgreSQL을 사용하는 개발자가 예약의 시간대 정책과 저장 방식을 검토할 때 참고할 수 있는 기준을 정리한다. 정부의 시간대 규칙이 바뀌어도 고객이 예약한 현지 날짜·시각을 유지하는 정책을 중심으로 설명하며, 특정 컬럼이나 구현 방식을 확정된 해법으로 전제하지 않는다.
1. UTC 시점, 현지 시각, 지역 시간대를 구분한다
시간대 설계에서는 다음 정보를 구분하면 요구사항을 명확하게 표현할 수 있다.
| 개념 | 의미 | 예시 |
|---|---|---|
| UTC 시점 | 세계 어디에서든 같은 순간을 가리키는 기준 | 2030-07-01 07:00 UTC |
| 현지 날짜·시각 | 해당 지역의 달력과 시계에 표시되는 값 | 2030-07-01 09:00 |
| UTC 오프셋(offset) | 특정 시점에서 UTC와 현지 시각의 차이 | +02:00 |
| 지역 시간대 식별자 | 지역의 시간 규칙을 찾는 식별자 | Europe/Amsterdam |
오프셋은 특정 시점의 차이를 나타낸다. 지역 시간대 식별자는 날짜에 따라 달라질 수 있는 규칙을 찾는 데 사용한다. 따라서 현재 오프셋 하나를 모든 미래 예약에 그대로 적용해서는 안 된다.
IANA 시간대 데이터베이스(tzdb)는 지역별 시간대 경계, UTC 오프셋, 서머타임 규칙의 변경을 반영해 갱신된다. 지역 식별자가 같더라도 그 지역에 적용되는 규칙 데이터는 바뀔 수 있다. IANA 시간대 데이터베이스
2. PostgreSQL의 timestamptz가 보존하는 정보를 확인한다
PostgreSQL의 시간대 포함 타임스탬프(timestamptz)는 timestamp with time zone의 줄임말이다. 입력을 UTC 시점으로 변환해 저장하며, 입력 당시의 지역 이름이나 원래 오프셋은 보존하지 않는다. 조회할 때는 세션 시간대에 맞춰 표시한다. PostgreSQL 날짜·시간 자료형 공식 문서
다음 두 표기는 같은 순간을 가리킨다.
| 표시 기준 | 날짜·시각 |
|---|---|
| UTC | 2026-09-09 00:00:00+00 |
| UTC+09:00 | 2026-09-09 09:00:00+09 |
데이터베이스 도구에서 +09:00이 보인다는 사실만으로 입력 당시의 시간대가 저장됐다고 판단할 수는 없다. 자료형, 조회 세션의 시간대, 클라이언트의 표시 설정을 함께 확인해야 한다.
반면 시간대 없는 타임스탬프(timestamp without time zone)는 날짜와 시각을 담는다. 그 값만으로는 전 세계에서 유일한 순간을 정할 수 없다. 현지 예약 시각으로 사용한다면 어느 지역의 날짜·시각인지 함께 해석해야 한다. PostgreSQL 날짜·시간 자료형
두 자료형의 선택은 보존하려는 정보의 의미에 따라 달라진다.
3. 알려진 서머타임 규칙과 미래의 규칙 변경을 나눠 본다
이미 알려진 규칙에 따라 여름과 겨울의 오프셋이 달라지는 경우에는 예약 날짜에 맞는 규칙으로 변환한다.
예약을 받은 뒤 정부가 미래의 시간대 규칙 자체를 바꾸는 경우에는 추가 판단이 필요하다. 예약 당시 계산한 UTC 시점과, 새 규칙에서 고객이 기대하는 현지 시각이 서로 맞지 않을 수 있기 때문이다. Temporal 공식 문서도 미래 일정에서 저장된 오프셋과 새 규칙이 충돌하는 상황을 설명한다. Temporal 시간대 규칙 변경 설명
다음은 실제 국가의 변경 사례가 아닌 설명용 가정이다.
어떤 지역에서 2030년 7월 1일 오전 9시 예약을 받았다. 예약 당시 그날의 오프셋을 +02:00으로 계산했다면 대응하는 UTC 시점은 오전 7시다. 이후 규칙이 바뀌어 해당 날짜의 오프셋이 +01:00이 됐다고 하자.
| 변경 후 처리 기준 | UTC 시점 | 변경된 규칙의 현지 시각 |
|---|---|---|
| 기존 UTC 시점을 유지 | 2030-07-01 07:00 UTC | 2030-07-01 08:00 |
| 예약한 현지 시각을 유지 | 2030-07-01 08:00 UTC | 2030-07-01 09:00 |
어느 결과가 올바른지는 고객과 약속한 시간의 의미에 따라 결정한다. 현지 오전 9시 만남을 보장하는 예약이라면 두 번째 처리가 정책에 맞는다.
4. 저장 구조보다 먼저 유지할 시간을 결정한다
시간대 정책은 다음과 같이 구분할 수 있다.
| 정책 | 유지하는 값 | 규칙 변경 시 검토할 처리 |
|---|---|---|
| UTC 시점 유지 | 이미 정한 순간 | 새 규칙에 따라 현지 표시가 달라지는지 확인 |
| 현지 날짜·시각 유지 | 고객이 선택한 지역의 날짜와 시각 | 새 규칙에 대응하는 UTC 시점을 다시 계산 |
이 글에서 다루는 예약 정책은 현지 날짜·시각 유지다. 앞선 예시에서는 고객과 약속한 오전 9시를 유지하고, 실행 기준인 UTC를 다시 계산한다.
이 정책을 구현하려면 규칙이 바뀐 뒤에도 고객이 원래 선택한 현지 날짜·시각을 식별할 수 있어야 한다.
UTC 오전 7시와 지역 식별자만 남아 있다면 새 규칙으로는 현지 오전 8시가 계산된다. 이 정보만으로는 고객이 원래 오전 8시를 선택했는지, 이전 규칙에서 오전 9시를 선택했는지 구분할 수 없다.
이 예시에서 도출되는 저장 요구사항은 원래 현지 날짜·시각을 직접 보존하거나, 이를 복원할 수 있는 정보를 남기는 것이다. 구체적인 컬럼과 자료형은 그다음에 선택한다.
5. 현지 날짜·시각을 보존하는 저장 방식을 비교한다
아래는 현지 시각 유지 정책을 구현하기 위해 검토할 수 있는 방향이다. 모든 정보를 중복 저장해야 한다는 뜻은 아니며, 각 방식의 운영 부담과 기존 데이터 구조를 함께 평가해야 한다.
| 검토 방향 | 보존 또는 복원 방법 | 설계 시 확인할 점 |
|---|---|---|
| 현지 날짜·시각과 지역 식별자를 보존 | 고객의 입력 의미를 직접 유지 | 계산된 UTC를 함께 저장한다면 갱신 기준과 일관성 관리 |
| UTC, 당시 적용한 오프셋, 지역 식별자를 보존 | UTC와 당시 오프셋으로 원래 현지 시각을 복원 | 재계산 후에도 원래 입력을 복원할 정보가 남는지 |
| UTC, 지역 식별자, 당시 규칙 버전을 보존 | 과거 규칙으로 원래 현지 시각을 복원 | 버전 이름뿐 아니라 해당 규칙 데이터를 실제로 다시 사용할 수 있는지 |
첫 번째 방식은 현지 날짜·시각을 직접 확인하기 쉽다. 두 번째와 세 번째 방식은 복원에 필요한 정보가 정확하게 유지되는지 검증해야 한다.
특히 과거 규칙을 기준으로 복원하는 방식에서 버전 문자열만 저장하고 해당 버전의 규칙을 사용할 수 없다면 복원 절차가 성립하지 않는다.
또한 timestamptz에 오프셋을 포함해 입력하는 것만으로는 원래 오프셋을 보존하는 방식이 되지 않는다. 입력값에서 어떤 정보가 저장 후에도 남는지 확인해야 한다. PostgreSQL 저장·출력 동작
현지 시각을 별도 컬럼에 저장할지, 다른 형태로 복원 정보를 유지할지는 구현 선택이다. 현지 시각 유지 정책에서 특정 컬럼 추가가 자동으로 결정되지는 않는다.
6. 존재하지 않거나 중복되는 현지 시각의 정책을 정한다
지역 시간대를 지정해도 모든 현지 날짜·시각이 하나의 UTC 시점에 대응하지는 않는다.
서머타임 전환으로 시계를 앞으로 당기면 일부 현지 시각이 건너뛰어지고, 뒤로 돌리면 같은 현지 시각이 두 번 나타날 수 있다. Temporal 공식 문서는 이러한 모호성을 처리하는 선택지를 설명한다. Temporal 모호한 현지 시각 설명
예약 서비스에서는 다음 항목을 결정해야 한다.
| 상황 | 결정할 정책 |
|---|---|
| 입력한 현지 시각이 존재하지 않음 | 입력을 거절할지, 다른 시각을 안내하고 다시 선택받을지 |
| 같은 현지 시각이 두 번 나타남 | 어느 시점을 선택할지, 사용자에게 어떻게 구분해 표시할지 |
| 기존 예약의 시각이 규칙 변경 후 유효하지 않음 | 자동 보정할지, 운영자 또는 고객의 재확인을 거칠지 |
현지 시각 유지라는 원칙도 존재하지 않는 시각을 만들어 낼 수는 없다. 라이브러리 기본 동작에 처리를 맡기기 전에 서비스의 예외 정책을 명확히 하는 편이 좋다.
7. 시작 시각과 종료 기준을 각각 정의한다
시작 시각 정책을 정한 다음에는 종료의 의미를 확인한다.
| 종료 기준 | 고객과의 약속 | 계산 기준 |
|---|---|---|
| 현지 종료 시각 | 현지 오전 9시부터 11시까지 이용 | 시작과 종료의 현지 날짜·시각을 각각 해석 |
| 실제 경과 이용시간 | 시작한 순간부터 2시간 이용 | 시작 UTC 시점에 경과시간을 더해 종료 계산 |
시계가 바뀌는 구간에서는 두 약속의 결과가 달라질 수 있다. 따라서 시작 시각을 현지 기준으로 유지한다고 해서 종료를 항상 시작에 이용시간을 더하는 방식으로 계산할 수 있는 것은 아니다.
기존 API가 시작과 종료를 받는지, 시작과 이용시간을 받는지도 확인해야 한다. 저장 구조를 검토하면서 API의 시간 계약까지 암묵적으로 바꾸지 않도록 주의한다.
8. 규칙 갱신 이후의 처리와 검증을 설계한다
현지 날짜·시각을 보존하더라도 규칙 변경이 미래 예약과 알림에 자동 반영되는 것은 아니다. 아래 항목은 실제 구현에서 확인할 검증 기준이다. 이 글에서 해당 처리를 구현하거나 테스트했다는 의미는 아니다.
| 확인 영역 | 검증할 내용 |
|---|---|
| 규칙 갱신 | 애플리케이션과 데이터베이스 등 변환을 수행하는 구성 요소가 어떤 규칙을 사용하는가 |
| 영향 예약 | 변경된 규칙으로 UTC 시점이 달라지는 미래 예약을 식별할 수 있는가 |
| 재계산 | 원래 현지 날짜·시각을 유지하면서 UTC를 갱신하는가 |
| 알림 | 예약 변경과 함께 알림 실행 시점도 갱신되며, 기존 작업이 중복 실행되지 않는가 |
| 입력 예외 | 존재하지 않는 시각과 중복 시각을 정한 정책대로 처리하는가 |
| 직렬화 | API와 저장 계층을 오갈 때 서버나 클라이언트의 시간대가 현지 날짜·시각을 바꾸지 않는가 |
| 종료 | 현지 종료 시각 또는 경과시간 중 정한 계약을 유지하는가 |
앞선 가정처럼 오프셋이 +02:00에서 +01:00으로 바뀌는 시나리오를 검증에 사용할 수 있다. 현지 오전 9시 유지 정책이라면 UTC가 오전 7시에서 오전 8시로 변경되는지, 예약 화면의 날짜·시각과 관련 알림이 일관되게 반영되는지 확인한다.
과거에 발생한 사건의 기록과 아직 실행되지 않은 예약도 구분해야 한다. 미래 예약 재계산이 이미 완료된 기록까지 일괄 변경하는 작업으로 확대되지 않도록 대상 범위를 정한다.
작은 SQL로 확인한 기본 변환과 예외
이번 글 수정에서 별도 로컬 PostgreSQL 18.4와 시간대 데이터 tzdata 2026b를 사용해 아래 쿼리를 실행했다. 운영 예약이나 미래의 정부 규칙 변경을 재현한 실험은 아니다. 같은 입력을 다른 환경에서 비교할 때는 PostgreSQL 버전뿐 아니라 설치된 시간대 데이터도 함께 기록해야 한다.
SET TIME ZONE 'UTC';
SELECT '2026-09-09 09:00:00+09'::timestamptz AS utc_value;
-- 2026-09-09 00:00:00+00
SET TIME ZONE 'Asia/Seoul';
SELECT '2026-09-09 00:00:00+00'::timestamptz AS seoul_value;
-- 2026-09-09 09:00:00+09
두 출력은 같은 순간을 세션 시간대에 맞춰 표시한 결과다. 다음 쿼리는 현지 시각을 UTC로 바꾼 뒤 다시 현지 시각으로 돌려본다.
SET TIME ZONE 'UTC';
SELECT local_time,
local_time AT TIME ZONE 'Europe/Amsterdam' AS instant,
(local_time AT TIME ZONE 'Europe/Amsterdam')
AT TIME ZONE 'Europe/Amsterdam' AS round_trip
FROM (VALUES
(timestamp '2026-03-29 02:30:00'),
(timestamp '2026-10-25 02:30:00')
) AS cases(local_time);
실제 출력은 다음과 같았다.
| 입력 현지 시각 | 계산된 UTC | 다시 계산한 현지 시각 |
|---|---|---|
| 2026-03-29 02:30:00 | 2026-03-29 01:30:00+00 | 2026-03-29 03:30:00 |
| 2026-10-25 02:30:00 | 2026-10-25 01:30:00+00 | 2026-10-25 02:30:00 |
첫 행에서는 존재하지 않는 02:30이 자동으로 03:30으로 바뀌었다. 둘째 행은 원래 시각으로 돌아왔지만, 유일한 시점이라는 뜻은 아니다.
SELECT timestamptz '2026-10-25 02:30:00+02' AS earlier,
timestamptz '2026-10-25 02:30:00+01' AS later;
-- earlier: 2026-10-25 00:30:00+00
-- later: 2026-10-25 01:30:00+00
PostgreSQL의 모호한 시각 처리 문서처럼 기본 변환은 이 입력들을 모두 거절하지 않는다. 따라서 SQL 변환에 성공했다는 사실만으로 예약 입력 검증이 끝나지는 않는다. 왕복 결과가 다른 누락 시각은 발견할 수 있지만, 왕복 결과가 같은 중복 시각은 별도 판정이 필요하다. 예약 확정 전에 적용할 오프셋을 구분해 안내할지, 모호한 입력을 거절할지 결정해야 한다. 알림 재계산이나 전체 예약 시스템의 동작은 이 작은 실험의 검증 범위에 포함하지 않았다.
설계에 적용하는 순서
시간대 요구사항은 다음 순서로 검토할 수 있다.
- 각 시간 필드가 실제 발생 시점, 현지 예약 시각, 경과시간 중 무엇을 의미하는지 정의한다.
- 정부의 규칙 변경 후 UTC 시점과 현지 날짜·시각 중 무엇을 유지할지 결정한다.
- 그 정책을 지킬 수 있는 저장 정보를 선택한다.
- 중복·누락 시각과 종료 기준의 예외 정책을 정한다.
- 규칙 갱신에 따른 재계산, 알림 갱신, 직렬화를 검증한다.
해외 예약에서 현지 날짜·시각을 유지해야 한다면, 고객이 선택한 값을 규칙 변경 후에도 식별할 수 있어야 한다. 이 요구사항을 기준으로 저장 방식과 갱신 절차를 비교하면, 자료형 선택부터 운영 처리까지 같은 정책으로 연결할 수 있다.
2026-09-14 보완: 로컬 SQL로 시간대 변환과 누락·중복 시각을 확인한 예제를 보완했다.