← 모든 글

라이브러리 없이 JavaScript에서 시간대가 올바른 날짜 포맷

JavaScript 내장 Intl.DateTimeFormat은 moment.js나 date-fns 없이 IANA 시간대 포맷을 처리합니다. 명시적 timeZone 옵션, 일광 절약에 안전한 표시, formatToParts, 벽시계 변환의 한계, 그리고 Temporal이나 시간대 라이브러리가 여전히 유용한 때를 배웁니다.

Date에 시간대 속성이 없는 이유

JavaScript의 Date는 항상 UTC 밀리초 카운트입니다. 객체 안에 저장된 시간대는 없습니다. 옵션 없이 .toString()이나 .toLocaleString()을 호출하면 JavaScript는 운영체제에서 가져온 런타임의 로컬 시간대를 씁니다. 같은 코드라도 뉴욕의 서버와 도쿄의 노트북에서는 기반 타임스탬프가 동일해도 다른 출력을 냅니다.

Intl.DateTimeFormat으로 포맷하기

Intl.DateTimeFormat은 로캘과 시간대를 인식하는 포맷을 위한 내장 API입니다. IANA 시간대 식별자를 지원하고, 일광 절약 시간 전환을 자동으로 처리하며, 현대 브라우저와 Node.js에서 사용할 수 있습니다. 핵심은 런타임 기본값에 의존하지 말고 timeZone 옵션을 명시적으로 넘기는 것입니다.

  • new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', dateStyle: 'full', timeStyle: 'long' }).format(date)
  • date.toLocaleString('en-GB', { timeZone: 'Europe/London', hour12: false })
  • date.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })
  • new Intl.DateTimeFormat('en-US', { timeZone: 'UTC', hour12: false }).format(date)

formatToParts로 개별 부분 추출하기

formatToParts()를 사용해 날짜의 개별 구성 요소를 {type, value} 객체로 얻어 사용자 정의 날짜 문자열을 구성하세요. 이는 로캘에 따라 구두점, 순서, 문자 체계가 다르기 때문에 로컬라이즈된 날짜 문자열을 쪼개는 것보다 낫습니다.

  • const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(date)
  • parts.find(p => p.type === 'year').value → '2023'
  • parts.find(p => p.type === 'month').value → '11'
  • Object.fromEntries(parts.map(p => [p.type, p.value])) → { year, month, day, hour, minute, second }

벽시계 시간을 UTC로 변환하기(어려운 방향)

주어진 시간대의 벽시계 시간에서 UTC로 가는 것은 고전적인 Date API로는 더 어렵습니다. 어떤 순간을 America/New_York으로 포맷하는 것은 쉽지만, America/New_York에서 2026-03-08 02:30이 나타내는 순간을 구성하는 것은 쉽지 않습니다. 그 로컬 시간이 일광 절약 시간 전환 동안 건너뛰어지거나 반복될 수 있기 때문입니다.

  • Temporal은 2026년에 Stage 4에 도달했지만 브라우저 네이티브 지원은 아직 보편적이지 않음
  • 오늘날 모든 브라우저에서 프로덕션에 쓰려면 Temporal 폴리필이나 date-fns-tz의 toDate()가 여전히 실용적 선택지
  • 수동 UTC 오프셋 산술을 피하세요 — 일광 절약 시간 전환은 해마다 다른 로컬 시각에 일어남
  • 이 사이트의 zonedToEpochMs()는 1회 반복 오프셋 보정을 사용 — src/timeUtils.ts 참고

올바른 시간대 식별자 고르기

America/New_York, Europe/London, Asia/Tokyo, UTC 같은 IANA 시간대 이름을 쓰세요. 사용자 대상 시간에는 UTC-5 같은 고정 오프셋을 피하세요. 오프셋은 일광 절약 시간에 따라 바뀌기 때문입니다. America/New_York은 1월에 UTC-5, 7월에 UTC-4일 수 있습니다. IANA 이름은 플랫폼이 선택된 날짜에 올바른 과거 규칙을 적용하게 합니다.

  • 좋음: America/Los_Angeles — 과거와 미래의 일광 절약 규칙 포함
  • 좋음: Europe/Berlin — 중앙유럽 여름 시간을 자동 처리
  • 좋음: Asia/Shanghai — 일광 절약 없는 안정적인 UTC+8 표시
  • 로그, API 저장, 데이터베이스 타임스탬프, 리전 간 비교에는 UTC를 사용
  • 제품 UI의 최종 표시에는 사용자의 IANA 시간대를 사용

시간대 포맷 FAQ

Two daylight-saving transitions a year break the assumption that "wall-clock time + timezone = a unique moment". Both are encoded in the IANA timezone database (still often called the Olson database) — the authoritative source of timezone rules used by every modern OS, browser, and language runtime.

  • DST gap: 02:00 to 02:59 local time does not exist on the spring-forward day
  • DST overlap: 01:00 to 01:59 local time occurs twice on the fall-back day
  • Ambiguous local time: in the overlap window you must pick "earlier" or "later" explicitly
  • Python uses a fold flag; JS Temporal uses a disambiguation option; java.time defaults to the earlier offset
  • Olson database / IANA tzdata / zoneinfo are three names for the same dataset, updated several times a year
  • Each IANA name (America/Los_Angeles, Europe/Berlin) maps to a list of (transition, offset, abbreviation) tuples
  • Stale tzdata on a device produces silently-wrong local times after any DST or zone change
  • Server best practice: do all date arithmetic in UTC, convert to local only at display

FAQ

Can JavaScript format a date in another timezone without a library?
Yes. Use Intl.DateTimeFormat or toLocaleString with a timeZone option, such as America/New_York or Asia/Tokyo.
Does Intl.DateTimeFormat handle daylight saving time?
Yes. When you use an IANA timezone identifier, the runtime applies the correct offset for that timezone and date.
Should I store timezone offsets or timezone names?
Store UTC for event instants. If you need the user's local context, store an IANA timezone name such as America/New_York, not only a numeric offset.
How do I get the user's timezone in JavaScript?
Use Intl.DateTimeFormat().resolvedOptions().timeZone, which returns an IANA name like America/New_York. Store that name, not a numeric offset, when you need to reconstruct the user's local time later.
What is the DST gap (spring-forward)?
The DST gap is the wall-clock hour that is skipped when daylight saving time begins. In most US zones, when the clock would tick from 02:00 to 02:59 it instead jumps to 03:00. Local times in that window do not exist on the transition day. Unix timestamps themselves never skip; only the wall-clock interpretation does.
What is the DST overlap (fall-back)?
The DST overlap is the wall-clock hour that occurs twice when daylight saving time ends. In most US zones, the clock ticks from 01:59 back to 01:00, so every minute from 01:00 to 01:59 happens twice. A wall-clock time in that window maps to two Unix timestamps; converters and schedulers must specify "earlier" or "later".