TechFeedTechFeed
Backend

타임스탬프, 타임존, 타임스탬프츠, 한국 표준시 | 로그가 아홉 시간 미래로 찍히는 이유는?

로그가 아홉 시간 미래면 서버가 틀린 게 아니다. 지금 시각에 아홉 시간을 더한 뒤 아이소 문자열을 만들면 Z가 협정시로 읽힌다. 타임스탬프츠, 세션 타임존, 아이소 오프셋, 슈퍼베이스, 알디에스 서울, 노드, PostgreSQL, API, 백엔드, 개발자 기준으로 저장과 표시를 나눈다. 오토배큠 글과 피지바운서 글과 자리를 섞지 않는다.

by

모니터링이 오늘 오후 네 시 장애를 내일 새벽 한 시로 보여 주면, 서버 시계가 틀린 게 아닙니다. 지금 시각에 아홉 시간을 더한 뒤 아이소 문자열을 만들면 Z가 협정시로 읽혀 아홉 시간이 한 번 더 갑니다. 컬럼은 타임스탬프츠로 두고 협정시로 저장합니다. 로그 문자열은 서울 시각에 +09:00을 붙입니다. 저는 12개 사이트 운영 로그를 그렇게 만들었다가 대시보드가 미래를 가리키는 걸 봤습니다.


네이버 클라우드나 알디에스 서울도 세션 타임존이 협정시면 콘솔과 앱이 어긋납니다. 죽은 튜플은 오토배큠 글을, 풀 고갈은 피지바운서 케이스를 보면 자리가 겹치지 않습니다. 숫자는 2026년 8월 포스트그레 18 문서와 MDN 데이트 기준입니다.


화면이 내일로 뜨면 저장이 틀린가

화면이 틀린 경우가 더 많습니다. 컬럼이 타임스탬프츠면 한 순간이 들어가 있고, 문자열의 Z와 오프셋이 그 순간을 다른 시계로 읽습니다.


포스트그레 위키는 타임존 없는 타임스탬프를 쓰지 말라고 적습니다. 타임스탬프츠는 이름과 달리 타임존 이름을 저장하지 않습니다. 2000년 1월 1일 협정시 이후 마이크로초 한 값입니다. 넣을 때 어떤 오프셋이든 받고, 꺼낼 때 세션 타임존으로 보여 줍니다. 타임존 없는 타입은 달력과 시계 사진입니다. 그 숫자가 서울인지 협정시인지는 컬럼이 모릅니다.


제가 후배 레포에서 먼저 여는 칸은 세 곳입니다. 테이블 정의, 노드가 만드는 문자열, 차트 라이브러리가 파싱하는 형식입니다. 세 칸이 같으면 화면이 내일이어도 디비부터 의심합니다. 세 칸이 다르면 디비를 키우지 않습니다.


맡는 일틀리면 보이는 증상
컬럼 타입한 순간을 저장하거나, 벽시계 숫자만 저장서머타임·리전 이동 때 산술이 어긋남
세션 타임존타임스탬프츠를 어떤 시계로 보여줄지피에스큐엘과 앱 콘솔의 숫자가 다름
아이소 문자열Z 또는 +09:00으로 그 순간을 설명로그가 아홉 시간 미래 또는 과거
화면 포맷사람이 읽는 서울 시각액션 러너는 협정시, 노트북은 서울
하루 범위서울 자정부터 다음 자정 미만자정 행이 이틀에 잡히거나 하루가 비움

실행 계획이 느린 자리는 쿼리 성능 글에 있습니다. 스키마를 나누는 밤은 무중단 마이그레이션을 같이 봅니다.


한 줄 가드 | 화면이 아홉 시간 틀리다고 인스턴스 타임존을 바꾸지 않습니다. 문자열 끝의 Z와 +09:00부터 봅니다.


Z를 붙이면 한국 시간이 아니다

투아이소스트링은 항상 협정시와 Z를 붙입니다. 서울 숫자를 먼저 만든 뒤 Z를 붙이면 그 숫자는 협정시로 다시 읽힙니다.


MDN은 데이트의 투아이소스트링이 아이소 8601을 단순화한 형식이고, 타임존은 항상 협정시이며 접미사는 Z라고 적습니다. Z는 오프셋 0입니다. +09:00과 같은 칸이 아닙니다. 자바스크립트 데이트는 내부가 밀리초 한 값입니다. 로컬 게터는 실행 환경 타임존으로 보여 줄 뿐입니다. 깃허브 액션 러너는 대개 협정시입니다. 같은 코드가 노트북과 시아이에서 다른 문자열을 만듭니다.


자주 보는 실수는 두 줄입니다. 첫째, Date.now() + 9 * 3600 * 1000으로 밀리초를 옮긴 뒤 투아이소스트링을 부릅니다. 절대 시각이 아홉 시간 앞으로 가고, Z가 그 앞당긴 시각을 협정시라고 주장합니다. 읽는 쪽이 서울로 바꾸면 열여덟 시간이 됩니다. 둘째, 서울 벽시계 숫자만 찍고 접미사를 생략합니다. 브라우저와 러너가 각자 자기 타임존으로 파싱합니다.


밀리초에 아홉 시간을 더하지 않는다
// 금지. 절대 시각을 옮긴 뒤 Z를 붙임 const bad = new Date(Date.now() + 9 * 3600 * 1000).toISOString() // 허용. 서울 벽시계 + 오프셋. 시스템 타임존과 무관 function nowKSTiso() { const s = new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Seoul' }) return s.replace(' ', 'T') + '+09:00' } // 허용. 저장·전송은 협정시 그대로 const utcIso = new Date().toISOString()
서울 16:00에 +09:00을 붙인 시각과 같은 숫자에 Z를 붙여 내일 01:00으로 읽히는 비교
서울 숫자를 만든 뒤 Z를 붙이면 그 숫자는 협정시가 된다. 읽는 쪽은 아홉 시간을 한 번 더 더한다

컬럼은 타임스탬프츠로 두고 표시만 바꾼다

위키도 타임존 없는 타임스탬프를 쓰지 말라고 적습니다. 한 순간은 타임스탬프츠에 두고, 서울 시각은 조회나 화면에서만 만듭니다.


알디에스와 슈퍼베이스 기본 세션은 협정시인 경우가 많습니다. 피에스큐엘에서 now()를 치면 협정시로 보입니다. 앱이 +09:00 문자열을 넣으면 디비는 그 순간을 올바로 저장합니다. 다시 꺼낼 때 세션이 협정시면 Z로 나갑니다. 콘솔이 서울로 보이길 원하면 세션만 Asia/Seoul로 바꿉니다. 인스턴스 파라미터를 함부로 바꾸면 백업 작업과 복제 슬롯 로그까지 같이 움직입니다.


에이티 타임 존은 방향이 타입마다 다릅니다. 타임스탬프츠에 서울을 적용하면 타임존 없는 벽시계가 나옵니다. 타임존 없는 값에 서울을 적용하면 그 숫자를 서울로 해석한 한 순간이 나옵니다. 같은 구문을 두 번 쓰면 자정이 하루 밀립니다. 위키는 고정 오프셋 문자열을 타임존 이름으로 쓰지 말라고도 적습니다. +09를 이름으로 주면 포식스 규칙이 아이소와 반대로 기울 수 있습니다. 이름은 Asia/Seoul을 씁니다.


표현결과 타입쓰는 자리
타임스탬프츠 컬럼한 순간생성, 수정, 이벤트, 만료
타임존 없는 타임스탬프벽시계 숫자신규 컬럼으로 쓰지 않음
date달력 하루생년월일, 영업일. 시각이 없을 때
AT TIME ZONE 'Asia/Seoul' on 타임스탬프츠서울 벽시계리포트, 일자 버킷
AT TIME ZONE INTERVAL '09:00'오프셋 산술이름 대신 간격이 필요할 때만

세션 타임존과 하루 범위를 같이 확인한다
SHOW timezone; SELECT now() AS session_now, now() AT TIME ZONE 'Asia/Seoul' AS seoul_wall; -- 서울 하루. 닫힌 BETWEEN 대신 반열린 구간 SELECT id, created_at FROM events WHERE created_at >= TIMESTAMPTZ '2026-08-17 00:00:00+09' AND created_at < TIMESTAMPTZ '2026-08-18 00:00:00+09'; -- 컬럼 -- created_at timestamptz NOT NULL DEFAULT now()
타임존 없는 타임스탬프는 벽시계 사진, 타임스탬프츠는 협정시 한 순간이라는 비교
타임스탬프츠는 타임존 이름을 보관하지 않는다. 세션 타임존이 표시만 바꾼다

아홉 시간을 더하고 문자열을 만들면

밀리초에 아홉 시간을 더하면 절대 시각이 이동합니다. 그 위에 Z를 붙이면 모니터링이 미래를 가리킵니다. 로케일 문자열에 +09:00을 붙입니다.


제가 12개 사이트 로그에 남긴 사고는 이 두 줄이었습니다. Date.now() + 9 * 3600 * 1000 다음에 투아이소스트링. 모니터링 도구는 Z를 협정시로 읽고, 서울 대시보드는 다시 아홉 시간을 더했습니다. 오후 배포가 내일 새벽에 찍혔습니다. 고친 헬퍼는 시스템 타임존을 보지 않습니다. toLocaleString('sv-SE', { timeZone: 'Asia/Seoul' })로 벽시계를 만든 뒤 공백을 T로 바꾸고 +09:00을 붙입니다. 에스브이-에스이 로케일은 연-월-일 순이라 아이소 날짜와 맞습니다.


저장과 큐 페이로드는 투아이소스트링 그대로 둡니다. 사람이 읽는 운영 로그만 서울 오프셋을 붙입니다. 두 칸을 한 함수로 섞지 않습니다. 아웃박스에 넣는 이벤트 시각은 아웃박스 글처럼 디비 기본값 now()를 믿습니다. 앱이 만든 문자열을 다시 파싱해 넣지 않습니다.


로그용 서울 시각과 전송용 협정시를 나눈다
function nowKST() { return new Date() .toLocaleString('sv-SE', { timeZone: 'Asia/Seoul' }) .replace('T', ' ') .slice(0, 16) } function nowKSTiso() { const s = new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Seoul' }) return s.replace(' ', 'T') + '+09:00' } console.log('log', nowKST()) console.log('iso', nowKSTiso()) console.log('utc', new Date().toISOString())

막히는 자리 | 모니터링이 미래를 가리키면 서버 엔티피를 먼저 의심하지 않습니다. 로그 한 줄을 복사해 Z로 끝나는지, +09:00으로 끝나는지 봅니다. Z인데 숫자가 서울이면 그 줄이 원인입니다.


슈퍼베이스 콘솔과 노드 로그가 어긋날 때

콘솔은 세션 타임존으로 보여 주고, 에이피아이는 보통 Z 문자열을 줍니다. 프론트가 브라우저 타임존으로 다시 그리면 액션 러너와 노트북이 갈립니다.


슈퍼베이스 테이블을 타임스탬프츠로 만들면 대시보드는 브라우저 로케일로 예쁘게 보여 줍니다. 포스트지레스트 응답은 같은 값을 2026-08-17T07:00:00.000Z처럼 줍니다. 서울 오후 네 시입니다. 넥스트 서버 컴포넌트가 new Date(row.created_at).toLocaleString()을 타임존 없이 부르면, 버셀 함수는 협정시로, 로컬 넥스트는 서울로 그립니다. 같은 행이 페이지마다 다릅니다. timeZone: 'Asia/Seoul'을 명시하거나, 표시를 클라이언트로 미룹니다.


로우 레벨 보안 정책에 시각을 넣을 때도 같습니다. now()는 타임스탬프츠입니다. 문자열 리터럴 '2026-08-17 16:00:00'은 세션 타임존을 따릅니다. 정책 테스트가 로컬에선 통과하고 호스티드에선 실패하면, 정책 본문보다 세션부터 봅니다. 정책 문법 자체는 슈퍼베이스 알엘에스 글에 있습니다. 여기는 시각 리터럴만 봅니다.


차트와 알림은 같은 칸이 아니다

저장은 한 순간, 차트 버킷은 서울 달력, 알림 문구는 사람이 읽는 시각입니다. 하루 범위를 비트윈으로 닫으면 자정이 두 번 셉니다.


위키는 타임스탬프에 비트윈을 쓰지 말라고 적습니다. 닫힌 구간이라 끝 시각이 자정이면 그 행이 다음 날에 또 들어갑니다. 서울 하루는 >= 그날 00:00+09 그리고 < 다음날 00:00+09입니다. 차트 라이브러리가 카테고리 눈금을 문자열로 받으면, Z를 자른 날짜만 넘기지 않습니다. 협정시 날짜로 버킷이 하루 밀립니다. 버킷 키는 Asia/Seoul로 자른 연-월-일을 씁니다.


슬랙 알림은 반대입니다. 원본 페이로드는 Z를 유지하고, 문구 한 줄만 서울로 바꿉니다. 온콜이 원문을 복사해 디비에서 찾을 때 Z가 남아 있어야 합니다. 1인 팀은 이 세 칸을 함수 이름으로 나눕니다. 저장, 버킷, 문구. 한 헬퍼가 세 일을 하면 다음 분기에도 같은 아홉 시간이 돌아옵니다.


디비는 타임스탬프츠로 저장하고, 문자열은 +09:00 또는 협정시, 화면만 서울로 그리는 세 단계
저장은 협정시, 사람은 서울. 밀리초에 아홉 시간을 더해 Z를 붙이는 줄만 지운다

참고 자료


세션 타임존 기본값과 콘솔 표시는 호스트마다 다릅니다. 위 표는 2026년 8월 공개 문서 기준 점검용이며, 최종 근거는 클러스터의 SHOW timezone과 로그 한 줄의 접미사입니다.


자주 묻는 질문

이미 들어간 timestamp 컬럼을 지금 바꿔야 하나?

새로 만드는 컬럼은 타임스탬프츠로 둡니다. 이미 들어간 칸은 그 숫자가 서울인지 협정시인지 앱 계약을 먼저 적습니다. 계약이 없으면 ALTER만으로 순간이 아홉 시간 이동합니다. 스테이징에서 샘플 열 시각과 로그를 맞춘 뒤에 바꿉니다.


투아이소스트링을 쓰면 안 되나?

저장과 에이피아이 전송에는 그대로 씁니다. MDN도 이 함수가 협정시와 Z를 붙인다고 적습니다. 금지인 건 서울 숫자를 만든 뒤 Z를 붙이는 줄입니다. 사람이 읽는 로그는 +09:00을 붙입니다.


알디에스 타임존을 아시아/서울로 바꾸면 끝나나?

콘솔 숫자는 맞을 수 있습니다. 앱이 Z를 잘못 붙이면 화면은 그대로 틀립니다. 인스턴스 타임존은 백업·확장 작업 로그까지 움직입니다. 세션이나 표시 함수만 바꾸는 편이 안전합니다.


슈퍼베이스 무료 프로젝트가 항상 협정시인가?

호스티드 기본은 협정시인 경우가 많습니다. 프로젝트마다 SHOW timezone으로 확인합니다. 대시보드가 서울로 보여 준다고 응답 JSON도 서울인 건 아닙니다. 응답 문자열 끝을 봅니다.


하루 통계가 자정에만 어긋나면?

비트윈으로 닫힌 구간을 쓰지 않습니다. 서울 자정 이상, 다음 서울 자정 미만으로 자릅니다. 차트 버킷 키도 협정시 날짜가 아니라 아시아/서울로 자른 연-월-일을 씁니다.


자바 인스턴트와 오프셋데이트타임은?

같은 실수입니다. 로컬데이트타임에 아홉 시간을 더해 스트링으로 내보내면 Z와 충돌합니다. 전송은 인스턴트, 화면은 존아이디 아시아/서울입니다. 히카리 풀 이야기는 가상 스레드 글에 있고, 여기는 시각 칸만 봅니다.


타임스탬프타임존타임스탬프츠한국 표준시PostgreSQL슈퍼베이스알디에스백엔드API개발자노드ISO

함께 보면 좋은 문제 해결

EXPLORE / Backend

이어서 읽어보기

전체 토픽 둘러보기