Intl.DateTimeFormat API

외부 라이브러리 없이 모든 로케일에 맞게 날짜와 시간을 포맷하세요

소개

각 나라에서는 날짜가 다르게 표시됩니다. 예를 들어, 2024년 1월 15일은 미국에서는 1/15/2024, 영국에서는 15/1/2024, 일본에서는 2024/1/15처럼 나타납니다. 시간 표현도 다릅니다. 미국은 AM/PM이 있는 12시간제를 사용하고, 대부분의 다른 나라는 24시간제를 사용합니다.

여러 로케일에 맞게 직접 날짜 포맷팅 로직을 구현하는 것은 복잡하고 오류가 나기 쉽습니다. 날짜와 월의 순서, 구분자, 시간 표시 형식, 타임존 변환, 그레고리력 외의 달력 같은 특수 케이스까지 고려해야 합니다.

Intl.DateTimeFormat API가 이러한 문제를 해결해줍니다. 이 API는 최신 브라우저에서 내장된 로케일 기반 날짜와 시간 포맷팅 기능을 제공합니다. 별도의 외부 라이브러리가 필요하지 않습니다.

기본 사용법

가장 간단한 날짜 포맷팅 방법은 Intl.DateTimeFormat 인스턴스를 만들고, 그 객체의 format() 메서드를 호출하는 것입니다.

const date = new Date(2024, 0, 15, 14, 30);

const formatter = new Intl.DateTimeFormat("en-US");
formatter.format(date);
// "1/15/2024"

로케일을 바꿔서 다양한 포맷팅 방식을 확인해보세요.

const ukFormatter = new Intl.DateTimeFormat("en-GB");
ukFormatter.format(date);
// "15/01/2024"

const japanFormatter = new Intl.DateTimeFormat("ja-JP");
japanFormatter.format(date);
// "2024/1/15"

같은 날짜라도 로컬 규칙에 따라 세 가지 방식으로 다르게 포맷됩니다.

로케일 이해하기

로케일이란 언어와 지역별 환경을 식별하는 문자열입니다. BCP 47 표준을 따르며, 언어 코드를 기본으로 하고 선택적으로 지역 코드가 붙습니다.

자주 사용하는 로케일 패턴:

"en"      // English (generic)
"en-US"   // English (United States)
"en-GB"   // English (United Kingdom)
"es"      // Spanish (generic)
"es-MX"   // Spanish (Mexico)
"es-ES"   // Spanish (Spain)
"zh-CN"   // Chinese (China, Simplified)
"zh-TW"   // Chinese (Taiwan, Traditional)

로케일 파라미터를 생략하면 브라우저는 사용자의 기본 로케일을 사용합니다.

const formatter = new Intl.DateTimeFormat();
formatter.format(date);
// Output varies based on user's browser locale

로케일의 배열도 전달할 수 있습니다. 브라우저는 지원하는 첫 번째 로케일을 사용합니다.

const formatter = new Intl.DateTimeFormat(["es-MX", "es", "en"]);
// Uses Spanish (Mexico) if available, falls back to generic Spanish, then English

포맷 옵션 개요

Intl.DateTimeFormat 생성자는 옵션 객체를 받아 포맷된 출력에 포함될 내용을 정할 수 있습니다. 포맷팅에는 두 가지 방법이 있습니다.

첫 번째 방법은 스타일 단축키를 사용하는 것입니다. 이 방법은 빠르고 일반적인 포맷팅을 제공합니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "full",
  timeStyle: "short"
});

formatter.format(date);
// "Monday, January 15, 2024 at 2:30 PM"

두 번째 방법은 구성 요소 옵션을 사용합니다. 이를 통해 날짜의 각 부분을 세밀하게 제어할 수 있습니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

formatter.format(date);
// "January 15, 2024 at 2:30 PM"

스타일 단축키와 구성 요소 옵션은 함께 사용할 수 없습니다. 포매터마다 하나의 방식만 선택하세요.

스타일 단축키

dateStyletimeStyle 옵션은 미리 설정된 네 가지 포맷 레벨을 제공합니다.

dateStyle 옵션은 날짜 부분을 포맷합니다.

const date = new Date(2024, 0, 15);

const full = new Intl.DateTimeFormat("en-US", { dateStyle: "full" });
full.format(date);
// "Monday, January 15, 2024"

const long = new Intl.DateTimeFormat("en-US", { dateStyle: "long" });
long.format(date);
// "January 15, 2024"

const medium = new Intl.DateTimeFormat("en-US", { dateStyle: "medium" });
medium.format(date);
// "Jan 15, 2024"

const short = new Intl.DateTimeFormat("en-US", { dateStyle: "short" });
short.format(date);
// "1/15/24"

timeStyle 옵션은 시간 부분을 포맷합니다.

const date = new Date(2024, 0, 15, 14, 30, 45);

const full = new Intl.DateTimeFormat("en-US", { timeStyle: "full" });
full.format(date);
// "2:30:45 PM Eastern Standard Time"

const long = new Intl.DateTimeFormat("en-US", { timeStyle: "long" });
long.format(date);
// "2:30:45 PM EST"

const medium = new Intl.DateTimeFormat("en-US", { timeStyle: "medium" });
medium.format(date);
// "2:30:45 PM"

const short = new Intl.DateTimeFormat("en-US", { timeStyle: "short" });
short.format(date);
// "2:30 PM"

두 옵션을 함께 사용하면 날짜와 시간을 동시에 포맷할 수 있습니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
  timeStyle: "short"
});

formatter.format(date);
// "Jan 15, 2024, 2:30 PM"

스타일 단축키는 자동으로 로케일 규칙에 맞게 적용됩니다.

const usFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "short",
  timeStyle: "short"
});
usFormatter.format(date);
// "1/15/24, 2:30 PM"

const deFormatter = new Intl.DateTimeFormat("de-DE", {
  dateStyle: "short",
  timeStyle: "short"
});
deFormatter.format(date);
// "15.1.24, 14:30"

독일어 포맷은 점(.)을 구분자로 쓰고, 일과 월의 순서를 바꾸며, 24시간제를 사용합니다. 미국식 포맷은 슬래시(/)를 사용하고, 월-일-년 순서에 12시간제를 AM/PM과 함께 씁니다. 동일한 옵션이더라도 로케일에 따라 결과가 달라집니다.

구성 요소 옵션

구성 요소 옵션을 사용하면 표시 방식과 내용을 세밀하게 설정할 수 있습니다. 각 옵션은 날짜나 시간의 한 부분을 지정합니다.

year 옵션은 연도를 표시합니다.

const formatter = new Intl.DateTimeFormat("en-US", { year: "numeric" });
formatter.format(date);
// "2024"

const twoDigit = new Intl.DateTimeFormat("en-US", { year: "2-digit" });
twoDigit.format(date);
// "24"

month 옵션은 월을 다양한 포맷으로 표시합니다.

const numeric = new Intl.DateTimeFormat("en-US", { month: "numeric" });
numeric.format(date);
// "1"

const twoDigit = new Intl.DateTimeFormat("en-US", { month: "2-digit" });
twoDigit.format(date);
// "01"

const long = new Intl.DateTimeFormat("en-US", { month: "long" });
long.format(date);
// "January"

const short = new Intl.DateTimeFormat("en-US", { month: "short" });
short.format(date);
// "Jan"

const narrow = new Intl.DateTimeFormat("en-US", { month: "narrow" });
narrow.format(date);
// "J"

day 옵션은 해당 월의 일(day)을 표시합니다.

const formatter = new Intl.DateTimeFormat("en-US", { day: "numeric" });
formatter.format(date);
// "15"

const twoDigit = new Intl.DateTimeFormat("en-US", { day: "2-digit" });
twoDigit.format(date);
// "15"

weekday 옵션은 요일을 표시합니다.

const long = new Intl.DateTimeFormat("en-US", { weekday: "long" });
long.format(date);
// "Monday"

const short = new Intl.DateTimeFormat("en-US", { weekday: "short" });
short.format(date);
// "Mon"

const narrow = new Intl.DateTimeFormat("en-US", { weekday: "narrow" });
narrow.format(date);
// "M"

여러 구성 요소 옵션을 조합해 원하는 날짜 형식을 만들 수 있습니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

formatter.format(date);
// "Monday, January 15, 2024"

브라우저는 로케일 관습에 따라 공백과 구두점을 자동으로 처리합니다.

시간 형식 지정

시간 구성 요소 옵션으로 시, 분, 초 표시를 제어할 수 있습니다.

const date = new Date(2024, 0, 15, 14, 30, 45);

const formatter = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric"
});

formatter.format(date);
// "2:30:45 PM"

hour12 옵션은 12시간제 또는 24시간제 중 무엇을 사용할지 결정합니다.

const hour12 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  hour12: true
});
hour12.format(date);
// "2:30 PM"

const hour24 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  hour12: false
});
hour24.format(date);
// "14:30"

hourCycle 옵션은 시간 표시를 더 세밀하게 제어합니다. 네 가지 선택지가 있습니다.

// h11: 0-11
const h11 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h11"
});
h11.format(new Date(2024, 0, 15, 0, 30));
// "0:30 AM"

// h12: 1-12
const h12 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h12"
});
h12.format(new Date(2024, 0, 15, 0, 30));
// "12:30 AM"

// h23: 0-23
const h23 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h23"
});
h23.format(new Date(2024, 0, 15, 0, 30));
// "0:30"

// h24: 1-24
const h24 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h24"
});
h24.format(new Date(2024, 0, 15, 0, 30));
// "24:30"

자정에는 이 차이가 중요합니다. h11 주기는 0을 사용하고, h12는 12를 사용합니다. 마찬가지로, h23는 0을 사용하고 h24는 24를 사용합니다.

dayPeriod 옵션은 12시간제에 설명적인 시간대 마커를 추가합니다.

const morning = new Date(2024, 0, 15, 10, 30);
const afternoon = new Date(2024, 0, 15, 14, 30);
const night = new Date(2024, 0, 15, 22, 30);

const formatter = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  dayPeriod: "long"
});

formatter.format(morning);
// "10:30 in the morning"

formatter.format(afternoon);
// "2:30 in the afternoon"

formatter.format(night);
// "10:30 at night"

dayPeriod 옵션은 12시간제 형식일 때만 사용할 수 있습니다.

fractionalSecondDigits 옵션은 초의 소수점 이하 정밀도를 표시합니다.

const date = new Date(2024, 0, 15, 14, 30, 45, 123);

const oneDigit = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 1
});
oneDigit.format(date);
// "2:30:45.1 PM"

const threeDigits = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 3
});
threeDigits.format(date);
// "2:30:45.123 PM"

소수점 이하 자릿수를 한 자리, 두 자리, 세 자리로 지정할 수 있습니다.

타임존 처리

timeZone 옵션은 날짜를 특정 시간대로 변환합니다.

const date = new Date("2024-01-15T14:30:00Z"); // UTC time

const newYork = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "full",
  timeStyle: "long"
});
newYork.format(date);
// "Monday, January 15, 2024 at 9:30:00 AM EST"

const tokyo = new Intl.DateTimeFormat("ja-JP", {
  timeZone: "Asia/Tokyo",
  dateStyle: "full",
  timeStyle: "long"
});
tokyo.format(date);
// "2024年1月15日月曜日 23:30:00 日本標準時"

const london = new Intl.DateTimeFormat("en-GB", {
  timeZone: "Europe/London",
  dateStyle: "full",
  timeStyle: "long"
});
london.format(date);
// "Monday, 15 January 2024 at 14:30:00 GMT"

동일한 시점도 시간대에 따라 다르게 표시됩니다. IANA 시간대 식별자인 America/New_York, Europe/London 또는 Asia/Tokyo 등을 사용하세요.

timeZoneName 옵션은 시간대 이름을 표시합니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "short"
});
formatter.format(date);
// "9:30 AM EST"

const long = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "long"
});
long.format(date);
// "9:30 AM Eastern Standard Time"

const shortOffset = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "shortOffset"
});
shortOffset.format(date);
// "9:30 AM GMT-5"

const longOffset = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "longOffset"
});
longOffset.format(date);
// "9:30 AM GMT-05:00"

timeZoneName 값에 따라 시간대 정보의 상세 수준이 달라집니다.

달력 시스템과 숫자 시스템

calendar 옵션은 그레고리력이 아닌 달력도 지원합니다.

const date = new Date(2024, 0, 15);

const gregorian = new Intl.DateTimeFormat("en-US", {
  calendar: "gregory",
  year: "numeric",
  month: "long",
  day: "numeric"
});
gregorian.format(date);
// "January 15, 2024"

const japanese = new Intl.DateTimeFormat("ja-JP", {
  calendar: "japanese",
  year: "numeric",
  month: "long",
  day: "numeric"
});
japanese.format(date);
// "令和6年1月15日"

const islamic = new Intl.DateTimeFormat("ar-SA", {
  calendar: "islamic",
  year: "numeric",
  month: "long",
  day: "numeric"
});
islamic.format(date);
// "٦ رجب ١٤٤٥"

const chinese = new Intl.DateTimeFormat("zh-CN", {
  calendar: "chinese",
  year: "numeric",
  month: "long",
  day: "numeric"
});
chinese.format(date);
// "2023甲辰年腊月初五"

같은 날짜라도 달력 시스템에 따라 다르게 변환됩니다. 사용 가능한 달력에는 gregory, japanese, islamic, islamic-umalqura, islamic-tbla, islamic-civil, islamic-rgsa, chinese, hebrew, indian, persian 등이 있습니다.

numberingSystem 옵션을 사용하면 숫자가 다양한 문자 체계로 표시됩니다.

const date = new Date(2024, 0, 15);

const western = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "latn",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
western.format(date);
// "1/15/2024"

const arabic = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "arab",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
arabic.format(date);
// "١‏/١٥‏/٢٠٢٤"

const devanagari = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "deva",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
devanagari.format(date);
// "१/१५/२०२४"

const bengali = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "beng",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
bengali.format(date);
// "১/১৫/২০২৪"

지원되는 숫자 체계로는 latn(서양 숫자), arab(아라비아-인도 숫자), arabext(확장 아라비아-인도 숫자), beng(벵골어 숫자), deva(데바나가리 숫자) 등이 있습니다.

날짜 범위 포맷팅

formatRange() 메서드는 중복 정보를 자동으로 생략하여 날짜 범위를 포맷합니다.

const start = new Date(2024, 0, 15);
const end = new Date(2024, 0, 20);

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

formatter.formatRange(start, end);
// "January 15 – 20, 2024"

포맷터가 중복 정보를 생략합니다. 두 날짜가 모두 2024년 1월에 있으면, 결과에는 월과 연도가 한 번만 포함됩니다.

범위가 서로 다른 월에 걸쳐 있으면 둘 다 표시됩니다.

const start = new Date(2024, 0, 15);
const end = new Date(2024, 1, 20);

formatter.formatRange(start, end);
// "January 15 – February 20, 2024"

범위가 서로 다른 연도에 걸치면 모든 정보가 표시됩니다.

const start = new Date(2024, 0, 15);
const end = new Date(2025, 1, 20);

formatter.formatRange(start, end);
// "January 15, 2024 – February 20, 2025"

formatRange() 메서드는 시간 범위에도 사용할 수 있습니다.

const start = new Date(2024, 0, 15, 14, 30);
const end = new Date(2024, 0, 15, 16, 45);

const formatter = new Intl.DateTimeFormat("en-US", {
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

formatter.formatRange(start, end);
// "January 15, 2:30 – 4:45 PM"

두 시간이 같은 날에 있기 때문에 날짜가 한 번만 표시됩니다.

포맷된 부분에 접근하기

formatToParts() 메서드는 포맷된 날짜의 각 부분을 객체 배열로 반환합니다. 이를 통해 커스텀 포맷팅 로직을 구현할 수 있습니다.

const date = new Date(2024, 0, 15, 14, 30);

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

const parts = formatter.formatToParts(date);

배열의 각 객체는 typevalue 속성을 가집니다.

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2024" },
  { type: "literal", value: " at " },
  { type: "hour", value: "2" },
  { type: "literal", value: ":" },
  { type: "minute", value: "30" },
  { type: "literal", value: " " },
  { type: "dayPeriod", value: "PM" }
]

이러한 부분을 필터링하거나 조작해서 원하는 포맷을 직접 구성할 수 있습니다.

const dateParts = parts.filter(part =>
  ["month", "day", "year"].includes(part.type)
);

const dateString = dateParts.map(part => part.value).join("/");
// "January/15/2024"

formatRangeToParts() 메서드는 날짜 범위에 대해서도 동일한 기능을 제공합니다.

const start = new Date(2024, 0, 15);
const end = new Date(2024, 0, 20);

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const parts = formatter.formatRangeToParts(start, end);

각 part 객체에는 해당 속성이 시작 날짜에서 왔는지, 종료 날짜에서 왔는지를 나타내는 source 속성이 포함되어 있습니다.

[
  { type: "month", value: "January", source: "startRange" },
  { type: "literal", value: " ", source: "startRange" },
  { type: "day", value: "15", source: "startRange" },
  { type: "literal", value: " – ", source: "shared" },
  { type: "day", value: "20", source: "endRange" },
  { type: "literal", value: ", ", source: "shared" },
  { type: "year", value: "2024", source: "shared" }
]

모범 사례

동일한 옵션으로 여러 날짜를 포맷할 때는 포매터 인스턴스를 재사용하세요. 포매터를 생성할 때는 로케일 협상 및 옵션 해석이 이루어지며, 이는 약간의 성능 비용이 발생합니다.

// Less efficient
dates.forEach(date => {
  const formatted = new Intl.DateTimeFormat("en-US").format(date);
  console.log(formatted);
});

// More efficient
const formatter = new Intl.DateTimeFormat("en-US");
dates.forEach(date => {
  const formatted = formatter.format(date);
  console.log(formatted);
});

가능하다면 로케일 매개변수를 생략하여 사용자의 브라우저 로케일을 사용하세요. 이렇게 하면 사용자의 환경 설정을 존중하게 됩니다.

const formatter = new Intl.DateTimeFormat();
// Uses browser locale automatically

특정 지역을 대상으로 할 때는 대체 로케일을 제공하세요. 선호하는 로케일을 사용할 수 없는 경우 브라우저는 다음 옵션을 사용합니다.

const formatter = new Intl.DateTimeFormat(["fr-CA", "fr", "en"]);
// Prefers French (Canada), falls back to French, then English

일반적인 날짜 및 시간 표시에는 스타일 단축키를 사용하세요. 이 방식은 로케일 규칙에 맞게 자동으로 조정되며, 설정도 간단합니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
  timeStyle: "short"
});

출력 형식을 세밀하게 제어해야 할 때는 컴포넌트 옵션을 사용하세요.

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

원격 사용자 또는 예약된 이벤트의 시간을 표시할 때는 항상 타임존을 명시하세요. 타임존이 없으면 사용자의 로컬 타임존으로 표시되어 의도와 다를 수 있습니다.

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

날짜 범위를 표시할 때는 각 날짜를 따로 포맷해서 연결하지 말고 formatRange()를 사용하세요. 이 메서드는 중복 정보를 자동으로 생략해줍니다.

// Less clear
const startFormatted = formatter.format(start);
const endFormatted = formatter.format(end);
const range = `${startFormatted} to ${endFormatted}`;

// Better
const range = formatter.formatRange(start, end);

dayPeriod, fractionalSecondDigits, 그리고 일부 timeZoneName 값과 같은 고급 기능의 브라우저 호환성을 확인하세요. 모든 최신 브라우저는 핵심 기능을 지원하지만, 일부 최신 옵션은 구형 브라우저에서 호환되지 않을 수 있습니다.