Why showing a date always involves three implicit decisions
Date representations feel trivial.
They are everywhere.
And because of that, they are rarely questioned.
MM/dd/yyyy
dd.MM.yyyy
dd. MMMM yyyy
They look harmless.
They look technical.
They look solved.
But they aren't.
Behind every date representation are decisions — and most systems make them implicitly.
The core insight
A date representation always consists of three parameters. Always.
- Format
- Language
- Region / cultural conventions
Even if we do not specify them explicitly.
Even if we rely on defaults.
Defaults do not remove these parameters.
They merely hide them.
1. Format — structure
The format defines how the date is structured:
dd.MM.yyyy
MM/dd/yyyy
dd. MMMM yyyy
It controls:
- order (day / month / year)
- separators (
.,/,,) - numeric vs textual representation
What it does not define:
- language
- region
- cultural expectation
A format describes shape, not meaning.
2. Language — words
Language determines which words are used:
- January / Januar / Jänner
- Monday / Montag
- March / März
But language alone says nothing about:
- date order
- separators
- customary notation
"German" is not enough.
"English" is not enough.
3. Region / cultural conventions — expectations
The region defines what people expect to see:
-
dd.MM.yyyyvsMM/dd/yyyy - dots vs slashes
- typical short and long forms
- week start
Two users can:
- speak the same language
- read the same month name
- and still expect different representations
These three parameters always exist
Even when we only specify one.
Example:
en
What does this actually mean?
In most systems, it implicitly becomes something like:
Language: English
Region: US
Format: MM/dd/yyyy
But these are assumptions, not facts.
No one explicitly chose them.
No one documented them.
No one can reliably explain them later.
Language + region as locale tags (IETF BCP 47)
So we have three parameters — but in practice, two of them (language and region) are usually expressed together as a locale tag.
A locale tag does not replace the format — it only covers language and region.
Most platforms follow a standard for this: IETF BCP 47.
So when we write de-AT or en-US, we are not just picking a language.
We are selecting a language and a regional set of conventions.
A (common) BCP 47 tag has the form:
language-region
-
Language: ISO 639 (usually lowercase), e.g.
de,en -
Region: ISO 3166 (usually uppercase), e.g.
AT,US,GB
The important part: language and region are independent dimensions.
Any combination can be meaningful:
-
en-AT— English language, Austrian conventions -
de-US— German language, US conventions
Also note the order: AT is a region, so it comes second (en-AT), not first.
Some systems use an underscore notation like en_AT.
In the browser Intl APIs, the standard form is the hyphen: en-AT.
But even a full locale tag only covers two of the three decisions. The format is still a separate choice.
Defaults are not decisions
When we specify only part of the information, the system fills in the rest:
- browser
- operating system
- runtime
- library
- framework
Each layer makes a reasonable guess.
Fallbacks do not replace missing parameters.
They only hide them.
Internally, there is still:
- a concrete format
- a concrete language
- a concrete region
They are just implicit.
Example 1: dd. MMMM yyyy
Format: dd. MMMM yyyy
This only means:
"Write the month name in full."
But which one?
- January
- Januar
- Jänner
Without language and region, the format alone is meaningless.
Example 2: German is not just German
Language: German
What does that imply?
-
de-DE→ Januar -
de-AT→ Jänner
Both are correct.
Both are German.
And yet, they are not interchangeable.
Language alone is insufficient.
Example 3: English is not a culture
Locale: en-AT
This means:
- Language: English
- Region: Austria
A perfectly valid and realistic scenario:
- English UI
- Austrian date conventions
English does not imply US culture.
It never did.
These parameters can come from different sources
In real systems, they rarely originate from one place.
Typical sources include:
- Browser / OS — implicit, uncontrolled
- User profile — explicit, but often incomplete
- Admin / organization — standardized, intentional
- Context / use case — UI, export, contract, audit log
Consider a real scenario:
A user's browser is set to de-DE.
Their organization is based in Austria.
An admin has configured dates to display in long format.
If the system resolves the locale from the browser, the user sees Januar. If it resolves from the organization, they see Jänner. Which one wins depends on the implementation — and that's rarely documented.
Either way, someone sees a date that doesn't match their expectation.
There is no single "correct" date.
Even the format is a decision
Most platforms offer predefined format levels:
short
medium
long
full
But you can also define a fully custom format like dd. MMMM yyyy.
Either way, the choice of format is not a technical detail.
It is a product decision:
In which context do users see a short date? When do they see a long one?
Who decides — the developer, the admin, the product?
These are not implementation details. They belong to product design, configuration, and context — not just code.
The common mistake
We say:
"We support localization."
And what we actually mean is:
- a format
- a default locale
- some Intl APIs
What we really have is:
a collection of implicit assumptions.
And those assumptions have consequences:
- A contract rendered with
01/02/2025— is that January 2nd or February 1st? - A user in Vienna sees Januar and wonders why the system doesn't know their country.
- An audit log stores dates in the server's locale — which changes after a migration.
These are not edge cases.
They are the natural result of treating three decisions as one.
The key takeaway
A date is only reasonably represented when
format, language, and region are all explicitly defined.
Everything else is guessing.
Once you accept that these three decisions always exist, the remaining question is no longer if you should define them — but where.
What to do
- Always pass format, language, and region explicitly — never rely on one to imply the others.
- Treat format selection (
short,long,full) as a product decision, not a developer default. - Define a clear priority for where each parameter comes from: user profile, tenant, admin policy, or context.
- Never persist or export dates in a locale-dependent format without documenting which locale.
- When in doubt, make the decision visible — in configuration, in documentation, in code.
In the browser: Intl.DateTimeFormat
The browser is one of the most common user interfaces — and it already has an API that models all three decisions explicitly: Intl.DateTimeFormat.
Format: dateStyle
The dateStyle option controls the shape of the output:
const date = new Date("2025-01-02");
new Intl.DateTimeFormat("de-AT", { dateStyle: "short" }).format(date)
// → "02.01.25"
new Intl.DateTimeFormat("de-AT", { dateStyle: "medium" }).format(date)
// → "02.01.2025"
new Intl.DateTimeFormat("de-AT", { dateStyle: "long" }).format(date)
// → "2. Jänner 2025"
new Intl.DateTimeFormat("de-AT", { dateStyle: "full" }).format(date)
// → "Donnerstag, 2. Jänner 2025"
Same locale. Only the format changes.
Language + Region: the locale tag
The locale tag is where language and region are combined (BCP 47).
In Intl, use the hyphen form: de-AT (not de_AT).
const date = new Date("2025-01-02");
new Intl.DateTimeFormat("de-DE", { dateStyle: "long" }).format(date)
// → "2. Januar 2025"
new Intl.DateTimeFormat("de-AT", { dateStyle: "long" }).format(date)
// → "2. Jänner 2025"
new Intl.DateTimeFormat("en-US", { dateStyle: "long" }).format(date)
// → "January 2, 2025"
new Intl.DateTimeFormat("en-GB", { dateStyle: "long" }).format(date)
// → "2 January 2025"
new Intl.DateTimeFormat("en-AT", { dateStyle: "long" }).format(date)
// → "2 January 2025"
Same date. Same format level. Completely different output — because language and region differ.
What happens when we leave things out?
new Intl.DateTimeFormat("en").format(new Date("2025-01-02"))
// → depends on the browser's region assumption
// Could be "1/2/2025" (US) or "02/01/2025" (GB)
new Intl.DateTimeFormat().format(new Date("2025-01-02"))
// → depends entirely on the browser/OS default locale
This is exactly the problem: the API works — but only if we pass all three decisions explicitly.
When we leave the locale vague or omit dateStyle, the browser fills in the gaps. Silently. Differently on every machine.
The API already supports this
Intl.DateTimeFormat does not force us to guess. It lets us express all three decisions in a single call:
new Intl.DateTimeFormat("de-AT", { dateStyle: "long" })
-
Format:
long - Language: German
- Region: Austria
Three parameters. One line. No ambiguity.
The tools exist. The question is whether we use them — or let the defaults decide for us.
Server-side runtimes follow the same model (for example: CultureInfo in .NET, Locale in Java) — but that's a topic for another post.
Conclusion
- Every date representation requires three parameters
- They exist even when we do not specify them
- Defaults are assumptions, not decisions
- Implicit decisions are the most dangerous ones
Localization does not start with APIs.
It starts where a product takes responsibility.
Top comments (0)