Skip to content

Getting 24-Hour or 12-Hour Time Right in AI-Generated Text by Locale

8 min read · updated August 11, 2026

A German meeting invitation that says 2:30 PM is not wrong in the sense of being unreadable. It is wrong in the sense that no German document has ever said it, and that is enough for a reader to conclude the text was not written for them.

The symptom

The pattern is consistent enough to be diagnostic: the surrounding prose is idiomatic in the target language, and the times inside it carry AM and PM markers in English, sometimes with a period after each letter, occasionally translated into a form the language does not use. Dates around them may be correct while the times are not, because the two are learned from different evidence.

The cause is the same imbalance behind the DD/MM versus MM/DD bug. Twelve-hour time with AM/PM markers is the dominant convention in English-language text, and English-language text dominates the formatting evidence, so the twelve-hour form is the strong prior even when the model is generating another language competently.

The claim that current models default to twelve-hour form is an observation about model behaviour, not a mechanism, and model behaviour changes between releases. Check it for the model you are using rather than assuming it holds.

Preferred hour cycle is published data

Which clock a region prefers is not folklore. The Unicode Common Locale Data Repository publishes it as supplemental time data: for each region it records a preferred hour cycle and a list of allowed ones. The specification for how that data is structured and used is in UTS #35, Part 4: Dates, published by the Unicode Consortium.

The broad picture, as CLDR has it: the United States, Canada, Australia, New Zealand, India, Mexico, the Philippines, Egypt and several others prefer the twelve-hour clock; most of Europe, most of South America, Japan, China, Korea and most of Africa prefer twenty-four hour. The United Kingdom is the row that catches people out — CLDR gives en-GB a preferred hour cycle of H, so Intl.DateTimeFormat("en-GB", { timeStyle: "short" }) produces 14:30, even though British speakers say “half two” and twelve-hour forms are entirely normal in British writing. The data records the formatting convention, not the speech.

These are CLDR values at the time of writing, August 2026. CLDR ships roughly twice a year and regional preferences are revised; read the current supplemental data rather than trusting this paragraph.

Four hour cycles, not two

“12-hour or 24-hour” is a simplification. There are four hour cycles, and ECMA-402 names them:

  • h12 — hours 1 to 12, midnight is 12 AM. The US convention.
  • h23 — hours 0 to 23, midnight is 00:00. The European convention and the one ISO 8601 uses.
  • h11 — hours 0 to 11, midnight is 0 AM. Used in Japanese twelve-hour contexts.
  • h24 — hours 1 to 24, midnight is 24:00. Rare, but it exists and is used in some contexts to denote the end of a day as distinct from the start of the next.

The distinction between h23 and h24 is where the end-of-day edge case lives: a shift that runs to midnight is 24:00 on the day it ends under one convention and 00:00 on the following day under the other. Both are ISO 8601 valid — the standard permits 24:00 as an end-of-day designation — and a system that mixes them silently loses or duplicates an interval. If you are generating schedules or timesheets, decide this explicitly.

There is also the separator. Most locales use a colon; some, including Finnish and Danish in some contexts, use a period, and CLDR carries that as part of the pattern rather than as a global rule.

And AM/PM is not the only day-period system. CLDR defines flexible day periods — morning, afternoon, evening, night, plus midnight and noon as distinct points — and exposes them through the B and b pattern characters, because several languages divide the day differently from the twelve-hour split and use those divisions in ordinary speech. Japanese 午前 and 午後 map onto AM and PM reasonably cleanly; Chinese 上午, 中午, 下午 and 晚上 do not, because 中午 is a separate midday band rather than a boundary. A model translating “2 PM” into Chinese has to pick a band, and picking the wrong one produces a time that a reader will silently re-interpret. This is another reason to carry an unambiguous twenty-four hour value through the system and let the formatter decide how to say it.

What to put in the prompt

If the model must produce the string, be explicit about all three components rather than naming a locale:

Write all times using the 24-hour clock, hours 00 to 23, zero-padded
to two digits, with a colon separator: 09:05, 14:30, 23:00.
Never write AM or PM. Midnight is 00:00 and noon is 12:00.

Each clause closes a specific failure. Naming the hour range stops h24 creeping in. Requiring zero-padding stops 9:05, which is the commonest half-correct output. Banning AM and PM explicitly matters because the model will otherwise sometimes produce 14:30 PM, which is a real and frequent artefact of applying two conventions at once. And pinning midnight and noon closes the ambiguity that produces 12:00 AM for midnight, which a substantial number of readers parse as noon.

Rendering instead of generating

As everywhere in this cluster, the durable answer is that the model produces a value and code produces the string. Have the model emit a time as HH:mm in twenty-four hour form, or better as a full RFC 3339 timestamp with an offset, and format it at the point of display:

const t = new Date("2026-08-11T14:30:00Z");
const opts = { timeStyle: "short", timeZone: "Europe/Berlin" };

new Intl.DateTimeFormat("de-DE", opts).format(t)  // "16:30"
new Intl.DateTimeFormat("en-US", opts).format(t)  // "4:30 PM"
new Intl.DateTimeFormat("en-GB", opts).format(t)  // "16:30"

new Intl.DateTimeFormat("en-GB").resolvedOptions().hourCycle  // "h23"

Two things that step also fixes. It forces you to carry a timezone, which a generated time string does not have — a model writing 14:30 has no opinion about which zone that is in, and a meeting time without a zone is an incident waiting for a customer in another country. And resolvedOptions() gives you a way to check what your runtime actually believes, which is how you catch a small-ICU build silently formatting everything as US English. The same formatter carries the other calendar conventions a generated schedule gets wrong, including which day the week starts on, which is locale data for the same reason and is guessed by a model for the same reason.