Display 12-Hour and 24-Hour Time in JavaScript

To force a clock style, pass hour12: true|false (or the finer hourCycle) to Intl.DateTimeFormat rather than relying on the locale default. Part of Mastering Intl.DateTimeFormat Options.

Why this scenario is tricky

Switching between 12-hour and 24-hour time is a place where two knobs — hour12 and hourCycle — overlap, and the finer edge cases (what midnight and noon look like) are exactly where they diverge. hour12: true gives "12:30 AM" and hour12: false gives "00:30", which covers the common case. But there is genuine ambiguity at the boundaries: is midnight "12:00 AM" or "00:00", and in 24-hour time is it "00:00" or "24:00"? Different locales and conventions answer differently, and hour12 alone cannot express the distinction — which is why hourCycle exists.

hourCycle names the exact convention: 'h11' (0–11), 'h12' (1–12), 'h23' (0–23), and 'h24' (1–24). It is the precise tool when the midnight/noon representation matters, and it overrides the coarser hour12. The trickiness is knowing that for most UIs hour12 is enough, but when a design or a locale requires a specific midnight convention, hourCycle is the option that controls it — and that letting the locale default choose is often the most correct behavior of all.

Whether a locale shows 12- or 24-hour time by default varies, so hard-coding an assumption breaks for someone. The subtlety is midnight: hourCycle distinguishes h11/h12 (12-hour, midnight as 0 or 12) and h23/h24 (24-hour, midnight as 0 or 24), and picking the wrong one prints '24:00' or '0 AM'.

Clock style is an explicit choiceLocale defaults differ; midnight is the trapClock style is an explicit choicerely on locale defaultsome users get the otherhour12 / hourCycle setpredictablehourCycle controls whether midnight prints as 0, 12, or 24.

Minimal working solution

Passing hour12: false to Intl.DateTimeFormat (alongside hour and minute) renders 24-hour time, and hour12: true renders 12-hour time with AM/PM. Always pair these with an explicit timeZone, because the hour shown depends entirely on the zone the instant is projected into — the same moment is a different hour in Tokyo and New York. Omitting the zone lets the host zone leak in and produces a different display on the server than in the user's browser.

For finer control, hourCycle: 'h23' forces 0–23 with midnight as "00:00", while 'h24' shows midnight as "24:00"; 'h11' and 'h12' are the 12-hour variants differing in whether the low bound is 0 or 12. Use hourCycle when the specific convention matters and hour12 for the everyday true/false switch. Often the best choice is to specify neither and let the locale decide, since a French user expects 24-hour and a US user 12-hour by default — overriding that can make your UI feel foreign.

const t = new Date('2024-03-15T00:30:00Z');
new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', hour12: false, timeZone: 'UTC' }).format(t); // '00:30'
new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', hour12: true, timeZone: 'UTC' }).format(t);  // '12:30 AM'

Pick the clockhour12 flips 12/24-hour outputPick the clockinstanthour12true/false'12:30 AM'or '00:30'

Full production version

A production formatter takes the locale, the zone, and an optional explicit hour convention, defaulting to the locale's own preference when the caller does not force one. Respecting the locale default is usually the most user-friendly behavior: it shows 24-hour time to users whose locale expects it and 12-hour to those who expect that, without your code encoding assumptions about who uses which. Provide the override only where a design genuinely requires a fixed convention across all locales, and reach for hourCycle rather than hour12 when that override must pin the midnight representation.

Cache the formatter per (locale, zone, convention) combination, since constructing Intl.DateTimeFormat is comparatively expensive and time displays often re-render. And keep the convention decision separate from the value: the instant is what it is, and 12- versus 24-hour is purely a presentation choice applied at render time. That separation means the same stored moment can be shown in either convention for different users or contexts without touching the underlying data, which is the whole point of formatting at the edge rather than baking a format into storage.

// hourCycle is finer than hour12: choose the midnight convention.
new Intl.DateTimeFormat('en-GB', { hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZone: 'UTC' });
// 'h23' → 00:00; 'h24' → 24:00; 'h11'/'h12' are 12-hour variants

hourCycle detailh11/h12/h23/h24 set the midnight rulehourCycle detailoptionshourCycleh23/h24/h11/h12midnight rule

Verification snippet

12h vs 24h Time assertionsKey cases assert correctlyAssertions that prove the edge casehour12 false'00:30'hour12 true'12:30 AM'h24 midnight'24:00'h11 midnight'0:30 AM'

Common pitfalls

12h vs 24h Time pitfallsCommon mistakes and their fixesWrongRighthard-code AM/PM stringsnot localizedhour12 / hourCycleexplicit clockassume 24h everywhereUS users confusedlet Intl format AM/PMlocalized

Frequently Asked Questions

How do I force 24-hour time in JavaScript?

Pass hour12: false to Intl.DateTimeFormat, or use hourCycle: 'h23' for the finer control where midnight is 00:00. For 12-hour output use hour12: true or hourCycle: 'h12'.

What is the difference between hour12 and hourCycle?

hour12 is a simple boolean for 12- vs 24-hour. hourCycle is more precise: h11 and h12 are 12-hour variants (midnight as 0 or 12) and h23 and h24 are 24-hour variants (midnight as 0 or 24), which matters for how midnight and noon are printed.

How do I switch between 12-hour and 24-hour time in JavaScript?

Pass hour12: true for 12-hour ('12:30 AM') or hour12: false for 24-hour ('00:30') to Intl.DateTimeFormat, along with hour, minute, and an explicit timeZone. For finer control over the midnight and noon representation, use hourCycle ('h11', 'h12', 'h23', 'h24'), which overrides hour12. Often it is best to specify neither and let the locale choose its expected convention.

What is the difference between hour12 and hourCycle?

hour12 is a coarse boolean: true gives AM/PM 12-hour time, false gives 24-hour. hourCycle names the exact convention — h11 (0–11), h12 (1–12), h23 (0–23), h24 (1–24) — and controls the ambiguous midnight and noon cases that hour12 cannot express, such as whether midnight is '00:00' or '24:00'. Use hour12 for the everyday switch and hourCycle when the specific boundary convention matters; hourCycle overrides hour12.

Should I force a time format or use the locale default?

Usually let the locale default decide, because it shows 24-hour time to users whose locale expects it and 12-hour to those who expect that, which feels native. Override with hour12 or hourCycle only where a design genuinely needs a fixed convention across all locales. Always pass an explicit timeZone regardless, since the displayed hour depends on the zone the instant is projected into.

How is midnight represented in each hour cycle?

It depends on the cycle: h23 shows midnight as '00:00' and h24 as '24:00', while in 12-hour cycles h12 shows '12:00 AM' and h11 shows '0:00 AM'. This is exactly the distinction hour12 alone cannot express, which is why hourCycle exists. If your design must pin the midnight representation across locales, set hourCycle explicitly rather than relying on hour12.

Does the displayed hour depend on the time zone?

Yes. The hour shown is the instant projected into a zone, so the same moment is a different hour in Tokyo and New York. Always pass an explicit timeZone alongside hour12 or hourCycle; omitting it lets the host zone leak in, so the time renders differently on a server than in the user's browser — a bug the hidden date makes easy to miss.