How to format times in user's locale

Use JavaScript to display times according to each user's regional conventions

Introduction

Times appear differently around the world. Americans typically see 3:30 PM displayed as 3:30 PM, while most Europeans expect 15:30. When you hardcode a time format, you assume all users follow the same convention.

Showing times in an unfamiliar format creates confusion. A user accustomed to 24-hour time who sees 3:30 PM must mentally convert it to understand whether it represents morning or afternoon. This cognitive load compounds across every time in your application.

JavaScript provides the Intl.DateTimeFormat API to handle time formatting automatically. This lesson explains why time formats vary across cultures, how the API works, and how to format times correctly for any locale.

Why time formats vary by locale

Different regions developed different conventions for displaying times. These conventions reflect historical practices, educational systems, and cultural preferences. No single format is universal.

In the United States, Canada, Australia, and the Philippines, times use 12-hour format with AM and PM indicators. 3:30 in the afternoon appears as 3:30 PM.

In most European countries, Latin America, and Asia, times use 24-hour format without AM or PM indicators. The same time appears as 15:30.

The separator character between hours and minutes also varies. English-speaking countries use colons, while some locales use periods or other punctuation.

The way AM and PM appear also differs. English uses AM and PM, Spanish uses a.m. and p.m., and some locales place these indicators before the time instead of after.

When you display times, you need to match the user's expectations for both the hour format and the specific formatting conventions.

Using Intl.DateTimeFormat to format times

The Intl.DateTimeFormat constructor creates a formatter that applies locale-specific conventions. To format times, pass a locale identifier as the first argument and specify time-related options in the second argument.

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

const date = new Date('2025-03-15T15:30:00');
console.log(formatter.format(date));
// Output: "3:30 PM"

This creates a formatter for US English that displays hours and minutes. The hour and minute options tell the formatter to include these components. The format() method converts the Date object to a string with appropriate formatting.

The Date constructor accepts an ISO 8601 datetime string like 2025-03-15T15:30:00. This creates a Date object representing 3:30 PM on March 15, 2025. The formatter then converts this to a locale-specific time string.

Formatting the same time for different locales

You can format the same time for different locales by changing the locale identifier passed to the constructor.

const date = new Date('2025-03-15T15:30:00');

const usFormatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(usFormatter.format(date));
// Output: "3:30 PM"

const gbFormatter = new Intl.DateTimeFormat('en-GB', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(gbFormatter.format(date));
// Output: "15:30"

const deFormatter = new Intl.DateTimeFormat('de-DE', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(deFormatter.format(date));
// Output: "15:30"

const frFormatter = new Intl.DateTimeFormat('fr-FR', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(frFormatter.format(date));
// Output: "15:30"

Each formatter applies different conventions. The US formatter uses 12-hour format with AM/PM. The UK, German, and French formatters all use 24-hour format without AM/PM indicators.

You do not need to know which format each locale uses. The API handles these details automatically based on the locale identifier.

Including seconds in time display

You can add the second option to display seconds along with hours and minutes.

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

const date = new Date('2025-03-15T15:30:45');
console.log(formatter.format(date));
// Output: "3:30:45 PM"

The second option works the same way as hour and minute. Set it to 'numeric' to include seconds in the output.

Controlling digit padding with 2-digit

The hour, minute, and second options accept two values: 'numeric' and '2-digit'. The 'numeric' value displays digits without padding, while '2-digit' always displays two digits with leading zeros.

const date = new Date('2025-03-15T09:05:03');

const numericFormatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric'
});
console.log(numericFormatter.format(date));
// Output: "9:05:03 AM"

const twoDigitFormatter = new Intl.DateTimeFormat('en-US', {
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit'
});
console.log(twoDigitFormatter.format(date));
// Output: "09:05:03 AM"

The numeric formatter displays 9:05:03 AM with a single digit for the hour. The two-digit formatter displays 09:05:03 AM with a leading zero for the hour. Both display two digits for minutes and seconds because these values are typically padded regardless of the setting.

Forcing 12-hour or 24-hour format

By default, the API uses the time format preferred by the locale. You can override this with the hour12 option.

const date = new Date('2025-03-15T15:30:00');

const hour12Formatter = new Intl.DateTimeFormat('en-GB', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: true
});
console.log(hour12Formatter.format(date));
// Output: "3:30 pm"

const hour24Formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: false
});
console.log(hour24Formatter.format(date));
// Output: "15:30"

Setting hour12: true forces 12-hour format even for locales that normally use 24-hour format. Setting hour12: false forces 24-hour format even for locales that normally use 12-hour format.

The locale still determines other formatting details like punctuation and spacing. The UK formatter with hour12: true displays 3:30 pm with lowercase pm, while a US formatter would display 3:30 PM with uppercase PM.

Formatting times for the user's locale

Instead of hardcoding a specific locale, you can use the user's preferred language from the browser. The navigator.language property returns the user's top language preference.

const userLocale = navigator.language;
const formatter = new Intl.DateTimeFormat(userLocale, {
  hour: 'numeric',
  minute: 'numeric'
});

const date = new Date('2025-03-15T15:30:00');
console.log(formatter.format(date));
// Output varies by user's locale
// For en-US: "3:30 PM"
// For en-GB: "15:30"
// For de-DE: "15:30"
// For fr-FR: "15:30"

This approach displays times according to each user's expectations without requiring them to manually select a locale. The browser provides the language preference, and the Intl API applies the appropriate formatting conventions.

You can also pass the entire array of preferred languages to enable fallback behavior.

const formatter = new Intl.DateTimeFormat(navigator.languages, {
  hour: 'numeric',
  minute: 'numeric'
});

const date = new Date('2025-03-15T15:30:00');
console.log(formatter.format(date));

The API uses the first locale it supports from the array. This provides better fallback handling when the user's top preference is unavailable.

Creating times to format

You can create Date objects with time information in several ways. The most reliable approach is to use ISO 8601 datetime strings.

const time1 = new Date('2025-03-15T09:00:00');
const time2 = new Date('2025-03-15T15:30:00');
const time3 = new Date('2025-03-15T23:45:30');

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

console.log(formatter.format(time1));
// Output: "9:00 AM"

console.log(formatter.format(time2));
// Output: "3:30 PM"

console.log(formatter.format(time3));
// Output: "11:45 PM"

ISO 8601 datetime strings use the YYYY-MM-DDTHH:MM:SS format. The T separates the date from the time. This format is unambiguous and works consistently across all locales and timezones.

Formatting times from timestamps

You can also create Date objects from Unix timestamps. A Unix timestamp represents the number of milliseconds since January 1, 1970 UTC.

const timestamp = 1710515400000; // March 15, 2025 at 3:30 PM
const date = new Date(timestamp);

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric'
});
console.log(formatter.format(date));
// Output: "3:30 PM"

This approach works when you receive timestamps from APIs, databases, or other systems that represent times as numbers.

You can also pass the timestamp directly to the format() method without creating a Date object first.

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

const timestamp = 1710515400000;
console.log(formatter.format(timestamp));
// Output: "3:30 PM"

The API accepts both Date objects and timestamps. Use whichever approach fits your code better.

Formatting the current time

To format the current time, create a Date object with no arguments. This creates a Date object representing the current moment.

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

const now = new Date();
console.log(formatter.format(now));
// Output: "3:45:12 PM" (or current time when run)

You can also pass Date.now() directly, which returns the current timestamp as a number.

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

console.log(formatter.format(Date.now()));
// Output: "3:45:12 PM" (or current time when run)

Both approaches produce identical results.

Reusing formatters for performance

Creating a new Intl.DateTimeFormat instance involves loading locale data and processing options. When you need to format multiple times with the same locale and settings, create the formatter once and reuse it.

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

const times = [
  new Date('2025-03-15T09:00:00'),
  new Date('2025-03-15T12:30:00'),
  new Date('2025-03-15T18:45:00')
];

times.forEach(time => {
  console.log(formatter.format(time));
});
// Output:
// "9:00 AM"
// "12:30 PM"
// "6:45 PM"

This approach is more efficient than creating a new formatter for each time. The performance difference becomes significant when formatting arrays with hundreds or thousands of time values.

Formatting times in templates

You can use Intl.DateTimeFormat anywhere you display times to users. This includes inserting formatted times into HTML templates, displaying times in tables, or showing timestamps in user interfaces.

const formatter = new Intl.DateTimeFormat(navigator.language, {
  hour: 'numeric',
  minute: 'numeric'
});

const eventStart = new Date('2025-03-15T14:00:00');
const eventEnd = new Date('2025-03-15T16:30:00');

document.getElementById('start-time').textContent = formatter.format(eventStart);
document.getElementById('end-time').textContent = formatter.format(eventEnd);

The formatted strings work like any other string value. You can insert them into text content, attributes, or any other context where you display information to users.