Intl.DateTimeFormat API
Format dates and times for any locale without external libraries
Introduction
Dates display differently around the world. January 15, 2024 appears as 1/15/2024 in the United States, 15/1/2024 in the United Kingdom, and 2024/1/15 in Japan. Time formats vary too. Americans use 12-hour clocks with AM and PM, while most of the world uses 24-hour time.
Building manual date formatting logic for different locales is complex and error-prone. You need to handle day and month ordering, separators, time formats, timezone conversions, and special cases like non-Gregorian calendars.
The Intl.DateTimeFormat API solves this problem. It provides built-in, locale-aware date and time formatting in all modern browsers. No external libraries required.
Basic usage
The simplest way to format a date is to create an Intl.DateTimeFormat instance and call its format() method.
const date = new Date(2024, 0, 15, 14, 30);
const formatter = new Intl.DateTimeFormat("en-US");
formatter.format(date);
// "1/15/2024"
Change the locale to see different formatting conventions.
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"
The same date, formatted three different ways based on local conventions.
Understanding locales
A locale is a string that identifies a language and regional preferences. The format follows the BCP 47 standard: language code, optionally followed by a region code.
Common locale patterns:
"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)
If you omit the locale parameter, the browser uses the user's default locale.
const formatter = new Intl.DateTimeFormat();
formatter.format(date);
// Output varies based on user's browser locale
You can also provide an array of locales. The browser uses the first supported locale.
const formatter = new Intl.DateTimeFormat(["es-MX", "es", "en"]);
// Uses Spanish (Mexico) if available, falls back to generic Spanish, then English
Formatting options overview
The Intl.DateTimeFormat constructor accepts an options object that controls what appears in the formatted output. There are two approaches to formatting.
The first approach uses style shortcuts. These provide quick, conventional formatting.
const formatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "full",
timeStyle: "short"
});
formatter.format(date);
// "Monday, January 15, 2024 at 2:30 PM"
The second approach uses component options. These give granular control over each part of the date.
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"
You cannot mix style shortcuts with component options. Choose one approach per formatter.
Style shortcuts
The dateStyle and timeStyle options provide four preset formatting levels.
The dateStyle option formats the date portion.
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"
The timeStyle option formats the time portion.
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"
Combine both options to format date and time together.
const formatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
timeStyle: "short"
});
formatter.format(date);
// "Jan 15, 2024, 2:30 PM"
Style shortcuts adapt to locale conventions automatically.
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"
German format uses dots as separators, reverses day and month order, and displays 24-hour time. American format uses slashes, month-first ordering, and 12-hour time with AM/PM. Same options, different output based on locale.
Component options
Component options give precise control over what displays and how. Each option specifies a part of the date or time.
The year option displays the 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"
The month option displays the month in various formats.
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"
The day option displays the day of the month.
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"
The weekday option displays the day of the week.
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"
Combine multiple component options to build custom date formats.
const formatter = new Intl.DateTimeFormat("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric"
});
formatter.format(date);
// "Monday, January 15, 2024"
The browser handles spacing and punctuation based on locale conventions.
Time formatting
Time component options control hour, minute, and second display.
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"
The hour12 option controls 12-hour versus 24-hour time display.
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"
The hourCycle option provides finer control over hour display. Four options exist.
// 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"
The difference matters at midnight. The h11 cycle uses 0, while h12 uses 12. Similarly, h23 uses 0, while h24 uses 24.
The dayPeriod option adds descriptive period markers for 12-hour time.
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"
The dayPeriod option only works with 12-hour time formats.
The fractionalSecondDigits option displays subsecond precision.
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"
You can specify one, two, or three fractional digits.
Timezone handling
The timeZone option converts dates to specific timezones.
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"
The same moment in time displays differently based on timezone. Use IANA timezone identifiers like America/New_York, Europe/London, or Asia/Tokyo.
The timeZoneName option displays the timezone name.
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"
Different timeZoneName values provide different levels of detail.
Calendar systems and numbering systems
The calendar option supports non-Gregorian calendars.
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甲辰年腊月初五"
The same date converts to different calendar systems. Available calendars include gregory, japanese, islamic, islamic-umalqura, islamic-tbla, islamic-civil, islamic-rgsa, chinese, hebrew, indian, persian, and others.
The numberingSystem option displays digits in different scripts.
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);
// "১/১৫/২০২৪"
Available numbering systems include latn (Western digits), arab (Arabic-Indic digits), arabext (Extended Arabic-Indic digits), beng (Bengali digits), deva (Devanagari digits), and many others.
Formatting date ranges
The formatRange() method formats a date range with smart omission of redundant information.
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"
The formatter omits repeated information. Both dates are in January 2024, so the output only includes the month and year once.
When the range spans different months, both display.
const start = new Date(2024, 0, 15);
const end = new Date(2024, 1, 20);
formatter.formatRange(start, end);
// "January 15 – February 20, 2024"
When the range spans different years, all information displays.
const start = new Date(2024, 0, 15);
const end = new Date(2025, 1, 20);
formatter.formatRange(start, end);
// "January 15, 2024 – February 20, 2025"
The formatRange() method works with time ranges too.
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"
The date displays once because both times occur on the same day.
Accessing formatted parts
The formatToParts() method returns an array of objects representing each part of the formatted date. This enables custom formatting logic.
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);
Each object in the array has a type and value property.
[
{ 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" }
]
You can filter and manipulate these parts to build custom formats.
const dateParts = parts.filter(part =>
["month", "day", "year"].includes(part.type)
);
const dateString = dateParts.map(part => part.value).join("/");
// "January/15/2024"
The formatRangeToParts() method provides the same functionality for date ranges.
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);
Each part object includes a source property indicating whether it came from the start or end date.
[
{ 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" }
]
Best practices
Reuse formatter instances when formatting multiple dates with the same options. Creating a formatter involves locale negotiation and option resolution, which has a small performance cost.
// 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);
});
Use the user's browser locale when possible by omitting the locale parameter. This respects user preferences.
const formatter = new Intl.DateTimeFormat();
// Uses browser locale automatically
Provide fallback locales when targeting specific regions. If the preferred locale is unavailable, the browser uses the next option.
const formatter = new Intl.DateTimeFormat(["fr-CA", "fr", "en"]);
// Prefers French (Canada), falls back to French, then English
Use style shortcuts for common date and time displays. They adapt to locale conventions automatically and require less configuration.
const formatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
timeStyle: "short"
});
Use component options when you need precise control over output format.
const formatter = new Intl.DateTimeFormat("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric"
});
Always specify timezone when displaying times to remote users or for scheduled events. Without a timezone, dates format in the user's local timezone, which may not match your intent.
const formatter = new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York",
dateStyle: "full",
timeStyle: "long"
});
Use formatRange() for date ranges instead of formatting each date separately and concatenating them. The method handles smart omission of redundant information.
// Less clear
const startFormatted = formatter.format(start);
const endFormatted = formatter.format(end);
const range = `${startFormatted} to ${endFormatted}`;
// Better
const range = formatter.formatRange(start, end);
Check browser compatibility for advanced features like dayPeriod, fractionalSecondDigits, and certain timeZoneName values. All modern browsers support core functionality, but newer options may require fallbacks for older browsers.