How to switch between 12-hour and 24-hour time format

Use JavaScript to display times with AM/PM or in 24-hour format according to user preferences

Introduction

Time appears differently around the world. Americans typically see 2:30 PM, while Germans see 14:30, and both represent the same moment. When you display times in only one format, you assume all users follow the same convention.

Showing times in an unfamiliar format creates friction. A user who expects 14:30 but sees 2:30 PM must mentally convert the time. A user who expects 2:30 PM but sees 14:30 faces the same problem. This cognitive load repeats for every time displayed in your application.

JavaScript provides the Intl.DateTimeFormat API to handle time formatting automatically. This lesson explains why time formats vary, how the API controls 12-hour versus 24-hour display, and when to override locale defaults.

Why time formats vary by locale

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

In the United States, Canada, Australia, and the Philippines, 12-hour format with AM/PM indicators is standard. The afternoon hour appears as 2:30 PM.

In most of Europe, Latin America, and Asia, 24-hour format is standard. The same time appears as 14:30 with no AM/PM indicator needed.

Some regions use both formats depending on context. The United Kingdom uses 24-hour format for transportation schedules but 12-hour format for everyday conversation.

When you display times, you need to match the user's expectations for their region and context.

What 12-hour and 24-hour formats mean

The 12-hour format divides the day into two 12-hour periods. Hours run from 12 to 11, then start over. The system uses AM (ante meridiem) for midnight to noon and PM (post meridiem) for noon to midnight. Midnight starts at 12:00 AM and noon occurs at 12:00 PM.

12:00 AM → midnight
1:00 AM → 1 hour after midnight
11:59 AM → 1 minute before noon
12:00 PM → noon
1:00 PM → 1 hour after noon
11:59 PM → 1 minute before midnight

The 24-hour format counts hours continuously from 0 to 23. Midnight starts at 00:00 and the day ends at 23:59. No AM/PM indicator is needed because each hour has a unique number.

00:00 → midnight
01:00 → 1 hour after midnight
11:59 → 1 minute before noon
12:00 → noon
13:00 → 1 hour after noon
23:59 → 1 minute before midnight

Different locales use different formats by default. The Intl.DateTimeFormat API respects these defaults but allows you to override them when needed.

Using hour12 option to control time format

The hour12 option controls whether to use 12-hour format. Set it to true for 12-hour format with AM/PM, or false for 24-hour format.

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

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

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

console.log(format12.format(date));
// Output: "2:30 PM"

console.log(format24.format(date));
// Output: "14:30"

The hour12 option overrides the locale's default behavior. Even though US English typically uses 12-hour format, setting hour12: false forces 24-hour format.

You must include either hour in the options or use timeStyle for the hour12 option to take effect. Without a time component in the output, the option has no impact.

Using hourCycle option for fine-grained control

The hourCycle option provides more control than hour12 by specifying exactly how hours should be counted. It accepts four values: "h11", "h12", "h23", and "h24".

const date = new Date('2025-03-15T00:30:00'); // 12:30 AM

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

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

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

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

console.log(h11.format(date));
// Output: "0:30 AM"

console.log(h12.format(date));
// Output: "12:30 AM"

console.log(h23.format(date));
// Output: "00:30"

console.log(h24.format(date));
// Output: "24:30"

Each hour cycle value produces different output for the same time. The differences become most apparent at midnight and noon.

Understanding the four hourCycle values

The four hourCycle values define how hours are numbered within each period.

The "h12" value uses 12-hour format with hours from 1 to 12. Midnight appears as 12:00 AM and noon appears as 12:00 PM. This is the standard 12-hour format used in the United States.

The "h11" value uses 12-hour format with hours from 0 to 11. Midnight appears as 0:00 AM and noon appears as 0:00 PM. This format is less common but appears in some contexts.

The "h23" value uses 24-hour format with hours from 0 to 23. Midnight appears as 00:00 and the day ends at 23:59. This is the standard 24-hour format used in most of Europe and Asia.

The "h24" value uses 24-hour format with hours from 1 to 24. Midnight appears as 24:00 from the previous day's perspective. This format is rare but appears in some technical contexts.

Most applications use either "h12" for 12-hour format or "h23" for 24-hour format.

Comparing hours at midnight across hour cycles

Midnight demonstrates the differences between hour cycle values most clearly.

const midnight = new Date('2025-03-15T00:00:00');

const cycles = ['h11', 'h12', 'h23', 'h24'];

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

  console.log(`${cycle}: ${formatter.format(midnight)}`);
});

// Output:
// h11: 0:00:00 AM
// h12: 12:00:00 AM
// h23: 00:00:00
// h24: 24:00:00

The h12 and h23 values produce the most familiar representations for their respective formats.

Comparing hours at noon across hour cycles

Noon also shows how hour cycles differ.

const noon = new Date('2025-03-15T12:00:00');

const cycles = ['h11', 'h12', 'h23', 'h24'];

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

  console.log(`${cycle}: ${formatter.format(noon)}`);
});

// Output:
// h11: 0:00:00 PM
// h12: 12:00:00 PM
// h23: 12:00:00
// h24: 12:00:00

Again, h12 and h23 produce standard representations while h11 uses 0 for the noon hour.

How hour12 and hourCycle interact

When you specify both hour12 and hourCycle, the hour12 option takes precedence and the hourCycle option is ignored.

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

const formatter = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: true,
  hourCycle: 'h23'  // This is ignored
});

console.log(formatter.format(date));
// Output: "2:30 PM" (12-hour format from hour12: true)

The hour12: true setting forces 12-hour format, overriding the hourCycle: 'h23' setting that would normally produce 24-hour format.

In practice, use either hour12 for simple control or hourCycle for precise control, but not both together.

Respecting locale defaults

When you omit both hour12 and hourCycle, the formatter uses the locale's default time format.

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

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

const deFormatter = new Intl.DateTimeFormat('de-DE', {
  hour: 'numeric',
  minute: 'numeric'
});

const jpFormatter = new Intl.DateTimeFormat('ja-JP', {
  hour: 'numeric',
  minute: 'numeric'
});

console.log(usFormatter.format(date));
// Output: "2:30 PM" (US default: 12-hour)

console.log(deFormatter.format(date));
// Output: "14:30" (German default: 24-hour)

console.log(jpFormatter.format(date));
// Output: "14:30" (Japanese default: 24-hour)

The US locale defaults to 12-hour format while German and Japanese locales default to 24-hour format. Respecting these defaults provides the most familiar experience for users in each region.

Overriding locale defaults

You can override a locale's default time format by explicitly setting hour12 or hourCycle.

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

// Force German locale to use 12-hour format
const de12 = new Intl.DateTimeFormat('de-DE', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: true
});

// Force US locale to use 24-hour format
const us24 = new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: 'numeric',
  hour12: false
});

console.log(de12.format(date));
// Output: "2:30 PM"

console.log(us24.format(date));
// Output: "14:30"

This approach works when you need to enforce a specific time format regardless of the user's locale.

When to respect locale defaults

Most applications should respect locale defaults. Users in each region expect times to appear in their familiar format. Overriding these defaults creates confusion.

Let the formatter use the locale's default time format when displaying times to users in their own region.

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

const time = new Date('2025-03-15T14:30:00');
console.log(formatter.format(time));
// Output varies by user's locale
// For en-US: "2:30 PM"
// For de-DE: "14:30"
// For ja-JP: "14:30"

This approach adapts automatically to each user's expectations without requiring configuration.

When to override locale defaults

Override locale defaults when your application requires consistency across all users. Transportation systems, military applications, and technical tools often need 24-hour format regardless of locale.

// Always use 24-hour format for flight times
const formatter = new Intl.DateTimeFormat(navigator.language, {
  hour: 'numeric',
  minute: 'numeric',
  hour12: false
});

const departureTime = new Date('2025-03-15T14:30:00');
console.log(`Departure: ${formatter.format(departureTime)}`);
// Output: "Departure: 14:30" (for all locales)

This ensures consistency when showing times that users need to reference precisely, such as schedules, logs, or timestamps.

You can also override defaults when users explicitly select a preference. If your application offers a setting to choose between 12-hour and 24-hour format, use that preference instead of the locale default.

function formatTime(date, userPrefers24Hour) {
  const formatter = new Intl.DateTimeFormat(navigator.language, {
    hour: 'numeric',
    minute: 'numeric',
    hour12: !userPrefers24Hour
  });

  return formatter.format(date);
}

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

console.log(formatTime(time, false));
// Output: "2:30 PM"

console.log(formatTime(time, true));
// Output: "14:30"

This pattern respects user choice while still localizing other aspects of time display like separator characters and text direction.

Using hour12 with timeStyle

The hour12 option works with the timeStyle preset styles. This lets you control the time format while still using convenient preset styles.

const date = new Date('2025-03-15T14:30:45');

const short12 = new Intl.DateTimeFormat('en-US', {
  timeStyle: 'short',
  hour12: true
});

const short24 = new Intl.DateTimeFormat('en-US', {
  timeStyle: 'short',
  hour12: false
});

const medium12 = new Intl.DateTimeFormat('en-US', {
  timeStyle: 'medium',
  hour12: true
});

const medium24 = new Intl.DateTimeFormat('en-US', {
  timeStyle: 'medium',
  hour12: false
});

console.log(short12.format(date));
// Output: "2:30 PM"

console.log(short24.format(date));
// Output: "14:30"

console.log(medium12.format(date));
// Output: "2:30:45 PM"

console.log(medium24.format(date));
// Output: "14:30:45"

This approach simplifies formatting by combining preset styles with explicit hour format control.

Formatting times for multiple locales

When your application serves users in multiple regions, format times according to each locale's conventions.

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

const locales = [
  { code: 'en-US', name: 'United States' },
  { code: 'en-GB', name: 'United Kingdom' },
  { code: 'de-DE', name: 'Germany' },
  { code: 'ja-JP', name: 'Japan' }
];

locales.forEach(locale => {
  const formatter = new Intl.DateTimeFormat(locale.code, {
    hour: 'numeric',
    minute: 'numeric'
  });

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

// Output:
// United States: 2:30 PM
// United Kingdom: 14:30
// Germany: 14:30
// Japan: 14:30

Each locale uses its preferred format automatically. You do not need to know which regions use which format.

Checking the resolved hour cycle

You can check which hour cycle the formatter actually uses by calling resolvedOptions().

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

const options = formatter.resolvedOptions();
console.log(options.hourCycle);
// Output: "h12"

console.log(options.hour12);
// Output: true

This method returns the actual settings the formatter uses after resolving all locale defaults and explicit options. The returned object includes both hourCycle and hour12 properties when time components are present.

Formatting times in templates

You can use formatted times anywhere you display temporal information to users. This includes inserting times into HTML, showing timestamps in logs, or displaying schedules.

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

const meetingTime = new Date('2025-03-15T14:30:00');
const deadlineTime = new Date('2025-03-15T17:00:00');

document.getElementById('meeting').textContent = formatter.format(meetingTime);
document.getElementById('deadline').textContent = formatter.format(deadlineTime);

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

What to remember

Use the hour12 option to switch between 12-hour and 24-hour format. Set it to true for 12-hour format with AM/PM or false for 24-hour format.

Use the hourCycle option for precise control over hour numbering. The "h12" value provides standard 12-hour format and "h23" provides standard 24-hour format.

Respect locale defaults in most applications. Users expect times to appear in their region's familiar format. Override defaults only when consistency across all users matters more than respecting regional conventions.

Combine hour12 with timeStyle presets to control the hour format while using convenient preset styles for other time components.