|
| 1 | +# Can `<input type="date">` and `<input type="time">` do the job? |
| 2 | + |
| 3 | +**Measured 2026-08-21** against Chromium 148 (Electron 42.9.2) on macOS 15.6.1, browser UI locale `en-GB`, system locale `en-AU`. Cross-browser support figures are from published compatibility data, not measured here — see [§Limits](#limits). |
| 4 | + |
| 5 | +## Verdict |
| 6 | + |
| 7 | +**Widely supported, and not locale-controllable.** The native pickers are available almost everywhere, but the page cannot influence how they render, they follow the *browser's* locale rather than the document's or the user's stated preference, and they have no concept of a non-Gregorian calendar. |
| 8 | + |
| 9 | +Three findings, in order of how much they matter: |
| 10 | + |
| 11 | +1. **`lang` is ignored entirely.** Six inputs carrying six different `lang` values rendered identically. There is no markup, attribute or CSS that changes the display format. |
| 12 | +2. **No calendar support.** `lang="th-TH-u-ca-buddhist"` rendered a Gregorian date. The platform *has* Buddhist, Japanese, Islamic and Persian calendars — `Intl` formats all four correctly in the same browser — the widget simply does not use them. |
| 13 | +3. **`input=time` does not even match its own browser's locale data.** For `en-GB`, `Intl` says `14:30`; the widget renders `02:30 pm`. |
| 14 | + |
| 15 | +## Part 1 — support |
| 16 | + |
| 17 | +Published compatibility data puts `date`, `time` and `datetime-local` at **96.39% global support**. |
| 18 | + |
| 19 | +| Browser | `date` / `time` | `month` / `week` | |
| 20 | +|---|---|---| |
| 21 | +| Chrome 25+, Edge 13+, Opera | full | full | |
| 22 | +| Safari iOS 18.2+ | full | full | |
| 23 | +| Safari desktop 14.1+ | supported | **falls back to `text`** | |
| 24 | +| Firefox 57+ | supported | **falls back to `text`** | |
| 25 | +| Opera Mini | none | none | |
| 26 | +| IE 11 and earlier | none | none | |
| 27 | + |
| 28 | +The "partial support" both Firefox and Safari desktop carry is `month` and `week` degrading to a plain text field, which pushes the user into typing a value by hand in the exact format the server expects. Feature-detect by setting `type` and reading it back: an unsupported type reports `"text"`. |
| 29 | + |
| 30 | +Verified in Chromium: all five of `date`, `time`, `month`, `week` and `datetime-local` report their own type. |
| 31 | + |
| 32 | +**For `date` and `time` specifically, availability is not the problem.** |
| 33 | + |
| 34 | +## Part 2 — the page cannot control the locale |
| 35 | + |
| 36 | +Six date inputs and six time inputs, identical values, differing only in `lang`: |
| 37 | + |
| 38 | +```html |
| 39 | +<input type="date" value="2026-03-22"> |
| 40 | +<input type="date" lang="de-DE" value="2026-03-22"> |
| 41 | +<input type="date" lang="en-US" value="2026-03-22"> |
| 42 | +<input type="date" lang="ja-JP" value="2026-03-22"> |
| 43 | +<input type="date" lang="ar-SA" value="2026-03-22"> |
| 44 | +<input type="date" lang="th-TH-u-ca-buddhist" value="2026-03-22"> |
| 45 | +``` |
| 46 | + |
| 47 | +**Every one rendered `22/03/2026`. Every time input rendered `02:30 pm`.** |
| 48 | + |
| 49 | +For comparison, `Intl.DateTimeFormat` in the same browser, same instant: |
| 50 | + |
| 51 | +| locale | `Intl` date | `Intl` time | the widget | |
| 52 | +|---|---|---|---| |
| 53 | +| `de-DE` | `22.03.26` | `14:30` | `22/03/2026` · `02:30 pm` | |
| 54 | +| `en-US` | `3/22/26` | `2:30 PM` | `22/03/2026` · `02:30 pm` | |
| 55 | +| `ja-JP` | `2026/03/22` | `14:30` | `22/03/2026` · `02:30 pm` | |
| 56 | +| `ar-SA` | `٢٢/٣/٢٠٢٦` | `٢:٣٠ م` | `22/03/2026` · `02:30 pm` | |
| 57 | + |
| 58 | +The format comes from the **browser UI locale** — here `en-GB`, which is neither the document's `lang` nor the system locale (`en-AU`) nor anything the server sent. It is a setting inside the browser, invisible to the page and unreadable by it. |
| 59 | + |
| 60 | +The [wire value is unaffected](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/date) and always ISO 8601 — `"2026-03-22"`, `"14:30"`, `"2026-W12"` — which is the one part of this that is well specified and dependable. |
| 61 | + |
| 62 | +A [Blink intent to make form controls respect `lang`](https://groups.google.com/a/chromium.org/g/blink-dev/c/QpEoCwU0Ttg/m/DVHHm28IKVYJ) has been open for years. It has not shipped: this measurement is Chromium 148. |
| 63 | + |
| 64 | +## Part 3 — no calendar systems |
| 65 | + |
| 66 | +`lang="th-TH-u-ca-buddhist"` renders a Gregorian date. So would `-u-ca-japanese`, `-u-ca-islamic` or `-u-ca-persian`; there is no attribute for it and no mechanism to add one. |
| 67 | + |
| 68 | +This is not a data problem. The same browser, same instant, through `Intl`: |
| 69 | + |
| 70 | +| calendar | rendered | |
| 71 | +|---|---| |
| 72 | +| Thai Buddhist | `22 มีนาคม 2569` | |
| 73 | +| Japanese imperial | `令和8年3月22日` | |
| 74 | +| Islamic | `٣ شوال ١٤٤٧ هـ` | |
| 75 | +| Persian | `۲ فروردین ۱۴۰۵` | |
| 76 | + |
| 77 | +The Buddhist year is **2569** against Gregorian 2026 — a 543-year difference. Persian is 1405, Islamic 1447. A user who reads dates in one of these sees a number that is not merely formatted differently but is a different year entirely. |
| 78 | + |
| 79 | +Every one of those calendars ships in the browser. The native picker reaches none of them. |
| 80 | + |
| 81 | +## Part 4 — `input=time` disagrees with its own browser |
| 82 | + |
| 83 | +The sharpest single finding. With the browser UI locale at `en-GB`: |
| 84 | + |
| 85 | +``` |
| 86 | +navigator.language "en-GB" |
| 87 | +Intl.DateTimeFormat("en-GB", {timeStyle: "short"}) "14:30" |
| 88 | +Intl resolved hourCycle "h23" |
| 89 | +
|
| 90 | +<input type="time" value="14:30"> renders 02:30 pm |
| 91 | +``` |
| 92 | + |
| 93 | +The date agrees — `Intl` and the widget both give `22/03/2026`. **The time does not.** `Intl` reports a 24-hour cycle for this locale and formats accordingly; the widget renders a 12-hour clock with a lowercase `pm` that matches no CLDR convention for `en-GB`. |
| 94 | + |
| 95 | +So `input=time` is not "locale-aware but uncontrollable". It is inconsistent with the locale data the same browser exposes one API call away. |
| 96 | + |
| 97 | +## What this means |
| 98 | + |
| 99 | +Use the native input when the audience is one locale, the calendar is Gregorian, and the exact rendering does not matter. It is free, accessible, and the mobile pickers are good. |
| 100 | + |
| 101 | +Reach for a server-rendered, locale-aware component when any of the following is true — and each of these is a routine requirement, not an exotic one: |
| 102 | + |
| 103 | +* **The rendering must match the rest of the page.** A form showing `22/03/2026` in the picker and `22 March 2026` in the summary beside it is the default outcome, not an edge case. |
| 104 | +* **The locale is chosen by the application**, from a user profile, an `Accept-Language` header, or a URL segment. The native picker will ignore all three and follow a browser setting instead. |
| 105 | +* **The user does not read Gregorian dates.** No amount of markup will help. |
| 106 | +* **Time is displayed at all**, given Part 4. |
| 107 | +* **`month` or `week` is needed** on Firefox or Safari desktop, where it is a bare text field. |
| 108 | + |
| 109 | +That is the case for `localize_datetime_inputs`, and it is stronger than "the native control is ugly": the native control cannot be made correct. |
| 110 | + |
| 111 | +## Limits |
| 112 | + |
| 113 | +* **One browser measured.** Chromium 148 via Electron. Firefox and Safari were not tested directly; their rows in Part 1 come from published compatibility data. Their `lang` behaviour is very likely the same — no engine implements the proposal — but that is inference, not measurement here. |
| 114 | +* **One vantage point.** Browser UI locale `en-GB`, system locale `en-AU`. A different browser locale changes what the widget renders but not the finding, which is that the *page* cannot influence it. |
| 115 | +* **Point in time.** The Blink intent could ship; if it does, Part 2 needs revisiting. Parts 3 and 4 are not addressed by that proposal. |
| 116 | +* Rendering was read from a screenshot of the running widget, which is what a user sees, rather than from an internal API — there is no API that reports the displayed format. |
| 117 | + |
| 118 | +## Reproduction |
| 119 | + |
| 120 | +Save as an HTML file and open it. No server needed. |
| 121 | + |
| 122 | +```html |
| 123 | +<input type="date" value="2026-03-22"> |
| 124 | +<input type="date" lang="de-DE" value="2026-03-22"> |
| 125 | +<input type="date" lang="ja-JP" value="2026-03-22"> |
| 126 | +<input type="date" lang="th-TH-u-ca-buddhist" value="2026-03-22"> |
| 127 | +<input type="time" value="14:30"> |
| 128 | +<script> |
| 129 | + const d = new Date(2026, 2, 22, 14, 30); |
| 130 | + console.log(navigator.language, |
| 131 | + new Intl.DateTimeFormat(navigator.language, {timeStyle: "short"}).format(d), |
| 132 | + new Intl.DateTimeFormat("th-TH-u-ca-buddhist", {dateStyle: "long"}).format(d)); |
| 133 | +</script> |
| 134 | +``` |
| 135 | + |
| 136 | +Compare what the four date inputs display against each other, and what the time input displays against the `Intl` output logged beside it. |
0 commit comments