Next.js + FastAPI + PostgreSQL 환경에서 안전하게 한국 시간(KST)을 다루는 법
🔍 들어가며
애플리케이션을 개발하다보면 모든 데이터의 기준 시간을 어떻게 관리하는 게 좋을지 고민이 생깁니다.
이번 글에서는 PostgreSQL의 2가지 데이터 타입에 대해서 알아보고, 한국에서만 운영되는 서비스의 경우와 글로벌 서비스를 고려한 DateTime을 다루는 방법을 다뤄보겠습니다.
🧩 기본 모델 구조
from datetime import datetime, timezone, timedelta
from sqlmodel import SQLModel, Field
from typing import Optional
from sqlalchemy import Column, DateTime
class BaseModel(SQLModel):
__abstract__ = True
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
class Deposit(BaseModel, table=True):
__tablename__ = "deposit"
id: Optional[int] = Field(default=None, primary_key=True)
deposit_date: datetime = Field(sa_column=Column(DateTime))
amount: int
portfolio_id: Optional[int] = Field(default=None)
위 예시처럼 created_at, updated_at, deposit_date 등 datetime 필드가 여러 테이블에 존재합니다.
문제는 이 DateTime 값이 어떤 시간대 기준으로 저장되고 표시되는가입니다.
이제 이를 한국 전용 서비스와 글로벌 서비스로 나누어 살펴보겠습니다.
🧱 PostgreSQL의 DateTime 타입 이해하기
PostgreSQL에서는 날짜와 시간을 저장할 때 두 가지 주요 타입을 제공합니다.
🕓 1. TIMESTAMP WITHOUT TIME ZONE
- 흔히
timestamp로 불리며, 시간대(Timezone) 정보를 저장하지 않습니다. - 즉,
'2025-11-03 10:00:00'은 그냥 문자열처럼 저장됩니다. - 이 값이 실제 어느 시간대를 의미하는지는 DB나 애플리케이션이 해석하는 방식에 따라 달라집니다.
예시
CREATE TABLE event ( start_time TIMESTAMP WITHOUT TIME ZONE ); INSERT INTO event VALUES ('2025-11-03 10:00:00');이 값은 “한국 10시”인지 “UTC 10시”인지 PostgreSQL은 알 수 없습니다.
✅ 한국 서비스(KST 고정)
→ TIMESTAMP WITHOUT TIME ZONE 타입을 사용해도 무방합니다.
DB 자체를 Asia/Seoul로 설정하면, 모든 시간이 KST로 저장되기 때문입니다.
🌐 2. TIMESTAMP WITH TIME ZONE
- PostgreSQL 내부적으로 UTC 기준으로 변환해 저장합니다.
- 하지만 조회 시에는 클라이언트의
timezone설정에 따라 변환되어 표시됩니다. - 즉,
"2025-11-03 10:00:00+09"와 같은 형태로 명확히 시간대가 포함된 값을 다룹니다.
예시
CREATE TABLE global_log ( created_at TIMESTAMP WITH TIME ZONE DEFAULT now() );
now()는 서버의 로컬 시간대를 반영하지만, 내부적으로는 UTC로 변환되어 저장됩니다.
✅ 글로벌 서비스(UTC 기준)
→ 반드시 TIMESTAMP WITH TIME ZONE 타입을 사용해야 합니다.
서버와 클라이언트, 로그 간의 시간 일관성을 유지하기에 가장 안전합니다.
⚠️ 주의: 타입 이름이 오해를 부를 수 있음
PostgreSQL의 TIMESTAMP WITH TIME ZONE은 “시간대 정보를 함께 저장한다”기보다는
“입력된 값을 UTC로 변환해 저장하고, 조회 시 다시 변환해 표시”하는 방식입니다.
즉, DB 내부에는 UTC 값이 저장되고, 클라이언트의 timezone 설정에 따라 다르게 보여집니다.
따라서 FastAPI나 프론트엔드에서 변환을 잘못하면 이중 변환(Double Conversion) 문제가 발생할 수 있습니다.
🇰🇷 1. 한국에서만 서비스하는 경우 — 가장 단순한 KST 방식
한국 사용자만을 대상으로 한 서비스라면 FastAPI 코드에는 타임존 변환 로직을 넣지 않아도 됩니다.
가장 간단하고 확실한 방법은 DB와 프론트엔드에서 KST로 고정하는 것입니다.
✅ 1-1. PostgreSQL 타임존 설정
Docker 환경에서는 docker-compose.yml 파일에 아래 설정을 추가합니다.

이렇게 하면 PostgreSQL 내부에서 now()를 호출할 때 자동으로 Asia/Seoul(KST) 기준으로 시간이 저장됩니다.
즉, FastAPI가 UTC나 다른 표준시로 변환할 필요가 없습니다.
✅ 1-2. FastAPI에서는 “로직 없음”
FastAPI 코드에는 별도의 타임존 처리를 넣지 않습니다.
단순히 datetime.now()로 생성된 값이 그대로 DB에 들어가고, 그대로 조회됩니다.

이렇게 하면 FastAPI는 단순히 DB에서 온 datetime을 그대로 반환합니다.
즉, 시간대 일관성이 DB에서 관리되므로 백엔드는 순수하게 유지됩니다.
✅ 1-3. 프론트엔드(Next.js)에서 시간 표시
프론트엔드에서는 브라우저의 로캘에 따라 표시가 달라질 수 있으므로,
KST 기준으로 고정 표시하고 싶다면 다음과 같이 처리합니다.
// utils/formatDate.ts
export function formatKST(dateString: string): string {
const date = new Date(dateString);
return date.toLocaleString("ko-KR", { timeZone: "Asia/Seoul" });
}
// 예시
<Text>{formatKST(deposit.deposit_date)}</Text>
이렇게 하면 언제 어디서 접속하더라도 한국 시간 기준으로 고정 표시됩니다.
✅ 1-4. 이 방식의 장점
- 설정이 단순하고 유지보수 부담이 적음
- 서비스 지역이 한국으로 고정된 경우 문제 발생 확률 낮음
- DB, BE, FE 모두 “한 가지 시간대(KST)”로 일관성 유지
⚠️ 단점
- 추후 글로벌 확장 시 UTC 기반으로 전환이 필요
- 서버가 해외 리전에 있을 경우, 시스템 시간과 DB 시간 불일치 가능성 있음
🌎 2. 글로벌 서비스 — UTC 기준 관리 + 변환 표시
글로벌 서비스를 운영한다면, 모든 시간은 UTC(Universal Time Coordinated) 기준으로 저장해야 합니다.
사용자 화면에서만 KST 또는 현지 시간대로 변환하여 표시하는 것이 원칙입니다.
✅ 2-1. PostgreSQL 설정
PostgreSQL에서는 타임존을 UTC로 유지합니다.
Docker 설정에서 TZ를 빼거나, 명시적으로 UTC로 지정합니다.
environment:
TZ: UTC
그리고 DB 컬럼 타입은 TIMESTAMP WITH TIME ZONE을 사용하는 것이 안전합니다.
✅ 2-2. FastAPI에서 UTC 저장
FastAPI 모델 정의 시 datetime.now(timezone.utc)를 사용하면 UTC 기준 시간이 저장됩니다.
from datetime import datetime, timezone
class BaseModel(SQLModel):
__abstract__ = True
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
✅ 2-3. 프론트엔드에서 사용자 로캘로 변환
Next.js에서 UTC로 저장된 데이터를 가져와 사용자 시간대 또는 KST로 변환해 표시할 수 있습니다.
export function formatLocalTime(dateString: string, locale = "ko-KR") {
const date = new Date(dateString);
return date.toLocaleString(locale, { timeZone: "Asia/Seoul" });
}
이렇게 하면 한국 사용자는 “한국 시간”,
미국 사용자는 “미국 현지 시간”으로 보게 할 수 있습니다.
✅ 2-4. UTC 기반 설계의 장점
- 글로벌 타임존 호환성 확보
- 시간대 변환 시 혼동 최소화
- 로그 및 데이터 분석 시 표준 시간으로 일관성 유지
⚠️ 단점
- 초기 설정이 다소 복잡
- 프론트엔드에서 변환 로직 필수
🧠 실무 팁
- KST 전용 서비스 → Docker에서
TZ=Asia/Seoul, FastAPI는 아무 로직 없음 - 글로벌 서비스 → UTC로 저장, 표시 시 변환
- DB와 서버의 시간대를 혼합하지 말 것
- DB는 UTC면 UTC, KST면 KST로 일관되게 유지해야 함
- 날짜 비교, 정렬 시 항상 표준화된 형식 사용 (ISO 8601 권장)
- 예:
"2025-10-10T04:22:00+09:00"
- 예:
📚 마무리
한국 전용 서비스라면 Docker에서 타임존만 KST로 설정하고 FastAPI는 건드리지 않는 것이 가장 단순하고 안정적인 방법입니다.
하지만 글로벌 서비스로 확장할 계획이 있다면, UTC를 표준으로 저장하고 프론트에서 변환 표시하는 구조로 설계해야 합니다.
시간대는 단순한 설정 같지만, 실제 운영 중에는 로그 불일치, 정렬 오류, 예약 작업 시간 혼동 등
다양한 문제의 원인이 될 수 있습니다.
따라서 서비스 성격에 맞게 초기에 올바른 기준을 정하는 것이 중요합니다.