다양한 시간대에서 날짜를 포맷팅하는 방법

JavaScript의 Intl.DateTimeFormat API를 사용하여 모든 시간대의 날짜와 시간 표시하기

소개

동일한 시간이 전 세계적으로 다른 시계 시간으로 표시됩니다. 뉴욕에서 회의가 오후 3시에 시작될 때, 런던의 시계는 오후 8시를 가리키고 도쿄의 시계는 다음 날 오전 5시를 가리킵니다. 애플리케이션이 시간대를 고려하지 않고 시간을 표시하면 사용자는 잘못된 정보를 보게 됩니다.

캘리포니아에 있는 사용자가 오후 2시에 출발하는 항공편을 예약할 때 오후 2시로 표시되기를 기대합니다. 애플리케이션이 버지니아에 있는 서버의 시간대를 사용하여 모든 시간을 형식화한다면, 사용자는 오후 5시를 보게 되고 공항에 3시간 늦게 도착하게 됩니다. 이러한 혼란은 애플리케이션에 표시되는 모든 시간에 걸쳐 복합적으로 발생합니다.

자바스크립트의 Intl.DateTimeFormat API는 모든 시간대에 대해 날짜와 시간을 형식화할 수 있는 timeZone 옵션을 제공합니다. 이 강의에서는 시간대가 존재하는 이유, 자바스크립트가 내부적으로 시간대를 처리하는 방법, 그리고 전 세계 어디에서나 사용자를 위해 날짜를 올바르게 형식화하는 방법을 설명합니다.

시간대가 존재하는 이유

지구는 회전하면서 서로 다른 위치에서 서로 다른 시간에 낮과 밤을 만듭니다. 뉴욕에서 태양이 머리 위에 있을 때, 런던에서는 이미 해가 졌고 도쿄에서는 아직 해가 뜨지 않았습니다. 각 위치는 서로 다른 순간에 정오를 경험합니다.

표준화된 시간대가 생기기 전에는 각 도시가 태양의 위치에 따라 자체적인 현지 시간을 유지했습니다. 이는 멀리 떨어진 도시들을 연결하는 철도와 전신에 문제를 일으켰습니다. 1884년, 여러 국가들은 세계를 시간대로 나누기로 합의했으며, 각 시간대는 대략 15도의 경도 폭을 가지며 지구 회전의 1시간에 해당합니다.

각 시간대는 협정 세계시(Coordinated Universal Time, UTC)로부터 표준 오프셋을 가집니다. 뉴욕은 일광 절약 시간에 따라 UTC-5 또는 UTC-4를 사용합니다. 런던은 UTC+0 또는 UTC+1을 사용합니다. 도쿄는 연중 UTC+9를 사용합니다.

사용자에게 시간을 표시할 때, 보편적인 순간에서 사용자가 보기를 기대하는 현지 시계 시간으로 변환해야 합니다.

JavaScript가 내부적으로 시간을 저장하는 방법

JavaScript Date 객체는 1970년 1월 1일 자정 UTC 이후의 밀리초 수로 특정 시점을 표현합니다. 이 내부 표현에는 시간대가 없습니다.

const date = new Date('2025-03-15T20:00:00Z');
console.log(date.getTime());
// 출력: 1742331600000

ISO 문자열 끝의 Z는 UTC 시간을 나타냅니다. Date 객체는 타임스탬프 1742331600000을 저장하며, 이는 시간대에 관계없이 전 세계 모든 사람에게 동일한 순간을 나타냅니다.

Date 객체에서 toString()과 같은 메서드를 호출하면 JavaScript는 표시를 위해 UTC 타임스탬프를 사용자의 로컬 시간대로 변환합니다. 이러한 자동 변환은 다른 시간대의 시간을 표시하려고 할 때 혼란을 일으킬 수 있습니다.

Intl.DateTimeFormat API와 timeZone 옵션을 사용하면 포맷팅에 사용할 시간대를 명시적으로 제어할 수 있습니다.

timeZone 옵션 사용하기

'timeZone' 옵션은 날짜 포맷팅 시 사용할 시간대를 지정합니다. 포맷터를 생성할 때 옵션 객체의 일부로 이 옵션을 전달합니다.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  dateStyle: 'short',
  timeStyle: 'short'
});

console.log(formatter.format(date));
// 출력: "3/15/25, 3:00 PM"

이 날짜는 UTC 기준 오후 8시를 나타냅니다. 뉴욕은 표준시 동안 UTC-5이거나 일광 절약 시간 동안 UTC-4입니다. 3월에는 일광 절약 시간이 적용되므로 뉴욕은 UTC-4입니다. 포맷터는 UTC 오후 8시를 현지 시간 오후 4시로 변환해야 하지만, 예제에서는 오후 3시를 보여주고 있어 이 특정 시나리오에서는 표준시가 적용되고 있음을 시사합니다.

포맷터는 변환을 자동으로 처리합니다. UTC 시점과 대상 시간대를 제공하면 API가 올바른 현지 시간을 생성합니다.

IANA 시간대 이름 이해하기

timeZone 옵션은 IANA 시간대 데이터베이스의 시간대 식별자를 허용합니다. 이러한 식별자는 America/New_York, Europe/London 또는 Asia/Tokyo와 같이 지역/도시 형식을 사용합니다.

const date = new Date('2025-03-15T20:00:00Z');

const zones = [
  'America/New_York',
  'Europe/London',
  'Asia/Tokyo',
  'Australia/Sydney'
];

zones.forEach(zone => {
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone: zone,
    dateStyle: 'short',
    timeStyle: 'long'
  });

  console.log(`${zone}: ${formatter.format(date)}`);
});

// 출력:
// America/New_York: 3/15/25, 4:00:00 PM EDT
// Europe/London: 3/15/25, 8:00:00 PM GMT
// Asia/Tokyo: 3/16/25, 5:00:00 AM JST
// Australia/Sydney: 3/16/25, 7:00:00 AM AEDT

각 시간대는 동일한 순간에 대해 서로 다른 현지 시간을 보여줍니다. 뉴욕은 3월 15일 오후 4시를 표시합니다. 런던은 3월 15일 오후 8시를 표시합니다. 도쿄와 시드니는 이미 3월 16일로 진행되어 각각 오전 5시와 오전 7시를 표시합니다.

IANA 이름은 일광 절약 시간을 자동으로 처리합니다. 포맷터는 America/New_York가 동부 표준시와 동부 일광 절약 시간 사이를 전환한다는 것을 알고 있으며 모든 날짜에 대해 올바른 오프셋을 적용합니다.

유효한 IANA 시간대 이름 찾기

IANA 데이터베이스에는 수백 개의 시간대 식별자가 포함되어 있습니다. 일반적인 패턴은 다음과 같습니다:

  • America/New_York - 뉴욕시
  • America/Los_Angeles - 로스앤젤레스
  • America/Chicago - 시카고
  • Europe/London - 런던
  • Europe/Paris - 파리
  • Europe/Berlin - 베를린
  • Asia/Tokyo - 도쿄
  • Asia/Shanghai - 상하이
  • Asia/Kolkata - 인도
  • Australia/Sydney - 시드니
  • Pacific/Auckland - 오클랜드

식별자는 EST나 PST와 같은 시간대 약어 대신 도시 이름을 사용합니다. 이는 약어가 모호하기 때문입니다. EST는 북미에서는 동부 표준시를 의미하지만 호주 동부 표준시도 의미합니다. 도시 기반 이름은 모호하지 않게 유지됩니다.

IANA 시간대 데이터베이스 문서에서 전체 식별자 목록을 검색할 수 있습니다.

시간대로 UTC 사용하기

특별 식별자 UTC는 본초 자오선으로부터 오프셋이 없는 협정 세계시로 날짜를 포맷합니다.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'UTC',
  dateStyle: 'short',
  timeStyle: 'long'
});

console.log(formatter.format(date));
// 출력: "3/15/25, 8:00:00 PM UTC"

UTC 시간은 Date 객체에 저장된 내부 타임스탬프와 일치합니다. 포맷팅에 UTC를 사용하는 것은 서버 로그나 데이터베이스 타임스탬프와 같이 사용자의 위치에 따라 변경되지 않아야 하는 시간을 표시할 때 유용합니다.

사용자의 시간대 가져오기

resolvedOptions() 메서드는 포맷터가 사용하는 실제 옵션(시간대 포함)을 반환합니다. timeZone을 지정하지 않고 포맷터를 생성하면 기본값으로 사용자의 시스템 시간대가 사용됩니다.

const formatter = new Intl.DateTimeFormat();
const options = formatter.resolvedOptions();

console.log(options.timeZone);
// 출력: "America/New_York" (또는 사용자의 실제 시간대)

이는 사용자의 현재 시간대에 대한 IANA 식별자를 제공합니다. 이 식별자를 사용하여 동일한 영역에서 다른 날짜를 포맷하거나 사용자의 시간대 기본 설정을 저장할 수 있습니다.

const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: userTimeZone,
  dateStyle: 'full',
  timeStyle: 'long'
});

const date = new Date('2025-03-15T20:00:00Z');
console.log(formatter.format(date));
// 출력은 사용자의 시간대에 따라 다름

이 패턴은 날짜가 자동으로 사용자의 현지 시간으로 표시되도록 보장합니다.

여러 시간대에 대해 동일한 순간 포맷팅하기

사용자에게 이벤트가 다른 위치에서 몇 시에 발생하는지 보여주기 위해 여러 시간대에 대해 동일한 Date 객체를 포맷할 수 있습니다.

const meetingTime = new Date('2025-03-15T20:00:00Z');

const zones = [
  { name: 'New York', zone: 'America/New_York' },
  { name: 'London', zone: 'Europe/London' },
  { name: 'Tokyo', zone: 'Asia/Tokyo' }
];

zones.forEach(({ name, zone }) => {
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone: zone,
    dateStyle: 'long',
    timeStyle: 'short'
  });

  console.log(`${name}: ${formatter.format(meetingTime)}`);
});

// 출력:
// New York: March 15, 2025 at 4:00 PM
// London: March 15, 2025 at 8:00 PM
// Tokyo: March 16, 2025 at 5:00 AM

이는 사용자가 자신의 시간대와 다른 참가자의 시간대에서 회의나 이벤트가 언제 발생하는지 이해하는 데 도움이 됩니다.

시간대 간 시간 없는 날짜 형식 지정

시간 없이 날짜만 형식을 지정할 때, 시간대는 표시되는 달력 날짜에 영향을 줄 수 있습니다. UTC 자정의 날짜는 다른 시간대에서 다른 달력 날짜로 표시됩니다.

const date = new Date('2025-03-16T01:00:00Z');

const formatter1 = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/Los_Angeles',
  dateStyle: 'long'
});

const formatter2 = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Tokyo',
  dateStyle: 'long'
});

console.log(`Los Angeles: ${formatter1.format(date)}`);
console.log(`Tokyo: ${formatter2.format(date)}`);

// Output:
// Los Angeles: March 15, 2025
// Tokyo: March 16, 2025

3월 16일 UTC 기준 오전 1시는 로스앤젤레스에서는 3월 15일 오후 5시, 도쿄에서는 3월 16일 오전 10시에 해당합니다. 두 시간대 사이에 달력 날짜가 하루 차이가 납니다.

이는 예약된 이벤트, 마감일 또는 사용자가 현지 달력을 기준으로 해석하는 모든 날짜를 표시할 때 중요합니다.

시간대 오프셋 사용하기

IANA 식별자 대신 +01:00 또는 -05:00과 같은 오프셋 문자열을 사용할 수 있습니다. 이는 UTC로부터의 고정 오프셋을 나타냅니다.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: '+09:00',
  dateStyle: 'short',
  timeStyle: 'long'
});

console.log(formatter.format(date));
// Output: "3/16/25, 5:00:00 AM GMT+9"

오프셋 문자열은 정확한 오프셋은 알지만 특정 위치는 모를 때 작동합니다. 그러나 일광 절약 시간제를 처리하지 않습니다. 뉴욕을 나타내기 위해 -05:00을 사용하면, 뉴욕이 실제로 -04:00을 사용하는 일광 절약 시간 동안에는 시간이 잘못됩니다.

IANA 식별자는 일광 절약 시간제를 자동으로 처리하기 때문에 선호됩니다.

일광 절약 시간제 작동 방식 이해하기

많은 지역에서는 일광 절약 시간제를 위해 일년에 두 번 시계 오프셋을 변경합니다. 봄에는 시계가 한 시간 앞으로 이동하고, 가을에는 시계가 한 시간 뒤로 이동합니다. 이는 위치에 따른 UTC 오프셋이 연중 변경된다는 것을 의미합니다.

IANA 시간대 식별자를 사용하면 Intl.DateTimeFormat API가 모든 날짜에 대해 올바른 오프셋을 자동으로 적용합니다.

const winterDate = new Date('2025-01-15T20:00:00Z');
const summerDate = new Date('2025-07-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  dateStyle: 'long',
  timeStyle: 'long'
});

console.log(`Winter: ${formatter.format(winterDate)}`);
console.log(`Summer: ${formatter.format(summerDate)}`);

// Output:
// Winter: January 15, 2025 at 3:00:00 PM EST
// Summer: July 15, 2025 at 4:00:00 PM EDT

1월에는 뉴욕이 동부 표준시(UTC-5)를 사용하여 오후 3시를 표시합니다. 7월에는 뉴욕이 동부 일광 절약 시간(UTC-4)을 사용하여 오후 4시를 표시합니다. 동일한 UTC 시간이라도 일광 절약 시간제 적용 여부에 따라 다른 현지 시간이 표시됩니다.

어떤 날짜에 어떤 오프셋을 사용하는지 추적할 필요가 없습니다. API가 이를 자동으로 처리합니다.

이벤트 일정을 위한 시간 포맷팅

이벤트 시간을 표시할 때는 이벤트가 열리는 위치의 시간과 선택적으로 사용자 위치의 시간을 포맷팅하세요.

const eventTime = new Date('2025-03-15T18:00:00Z');
const eventTimeZone = 'Europe/Paris';
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

const eventFormatter = new Intl.DateTimeFormat('en-US', {
  timeZone: eventTimeZone,
  dateStyle: 'full',
  timeStyle: 'short'
});

const userFormatter = new Intl.DateTimeFormat('en-US', {
  timeZone: userTimeZone,
  dateStyle: 'full',
  timeStyle: 'short'
});

console.log(`Event time: ${eventFormatter.format(eventTime)} (Paris)`);
console.log(`Your time: ${userFormatter.format(eventTime)}`);

// Output (for a user in New York):
// Event time: Saturday, March 15, 2025 at 7:00 PM (Paris)
// Your time: Saturday, March 15, 2025 at 2:00 PM

이 패턴은 사용자에게 이벤트가 해당 시간대에서 언제 발생하는지와 사용자의 위치에 따라 언제 참여해야 하는지를 모두 보여줍니다.

사용자 시간대로 서버 타임스탬프 포맷팅하기

서버 로그와 데이터베이스 레코드는 종종 UTC 타임스탬프를 사용합니다. 이를 사용자에게 표시할 때는 사용자의 현지 시간대로 변환하세요.

const serverTimestamp = new Date('2025-03-15T20:00:00Z');
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

const formatter = new Intl.DateTimeFormat(navigator.language, {
  timeZone: userTimeZone,
  dateStyle: 'short',
  timeStyle: 'medium'
});

console.log(`Activity: ${formatter.format(serverTimestamp)}`);
// 출력은 사용자의 시간대와 로케일에 따라 다릅니다
// 뉴욕의 en-US의 경우: "Activity: 3/15/25, 4:00:00 PM"

이렇게 하면 사용자가 UTC나 서버 시간이 아닌 익숙한 현지 시간으로 타임스탬프를 볼 수 있습니다.

timeZone과 다른 옵션 결합하기

timeZone 옵션은 모든 다른 Intl.DateTimeFormat 옵션과 함께 작동합니다. 개별 날짜 및 시간 구성 요소를 지정하거나, 스타일 프리셋을 사용하거나, 달력 시스템을 제어할 수 있습니다.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Tokyo',
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric',
  timeZoneName: 'long'
});

console.log(formatter.format(date));
// 출력: "Monday, March 16, 2025 at 5:00:00 AM Japan Standard Time"

timeZoneName 옵션은 출력에 시간대 이름이 표시되는 방식을 제어합니다. 이후 수업에서 이 옵션에 대해 자세히 다룰 것입니다.

피해야 할 사항

timeZone 옵션의 값으로 EST, PST 또는 GMT와 같은 시간대 약어를 사용하지 마세요. 이러한 약어는 모호하고 일관되게 지원되지 않습니다.

// 잘못된 방법 - 약어는 작동하지 않을 수 있음
const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'EST',  // 이는 오류를 발생시킬 수 있음
  dateStyle: 'short',
  timeStyle: 'short'
});

항상 America/New_York와 같은 IANA 식별자나 -05:00과 같은 오프셋 문자열을 사용하세요.

사용자의 시간대가 서버의 시간대와 일치한다고 가정하지 마세요. 항상 올바른 시간대로 명시적으로 시간을 포맷하거나 사용자의 감지된 시간대를 사용하세요.

여러 시간대에서 포맷터 재사용하기

여러 시간대에 대한 날짜를 포맷팅할 때, 많은 포맷터를 생성할 수 있습니다. 동일한 설정으로 많은 날짜를 포맷팅하는 경우, 더 나은 성능을 위해 포맷터 인스턴스를 재사용하세요.

const dates = [
  new Date('2025-03-15T20:00:00Z'),
  new Date('2025-03-16T14:00:00Z'),
  new Date('2025-03-17T09:00:00Z')
];

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Europe/Berlin',
  dateStyle: 'short',
  timeStyle: 'short'
});

dates.forEach(date => {
  console.log(formatter.format(date));
});

// 출력:
// "3/15/25, 9:00 PM"
// "3/16/25, 3:00 PM"
// "3/17/25, 10:00 AM"

포맷터를 생성하는 것은 옵션을 처리하고 로케일 데이터를 로드하는 작업을 포함합니다. 여러 날짜에 동일한 포맷터를 재사용하면 이러한 오버헤드를 방지할 수 있습니다.