Lesson 08·Unit 3 · The Intl toolbox·Calendar systems & week conventions·Lv 201·~8 min
Calendar systems, eras & the shape of the week
Thailand's official year is 2569. Japan's paperwork says Reiwa 8. A Saudi contract may be dated 1448. The Gregorian calendar is one calendar among many that CLDR ships, and the week doesn't start on the same day everywhere either.
By the end
- ·Render one date in another calendar with a
-u-ca-extension, and askresolvedOptions()which one a locale picked. - ·Explain why one day carries four different year numbers (2026, Reiwa 8, 2569 BE, 1448 AH), and why an era resets the count.
- ·Read a locale's week shape from
Intl.Localeinstead of hardcoding it, and say why "W1" means two different weeks.
The problem
Date code tends to hardcode three assumptions: the year number is universal, the era is ancient history, and the week runs Monday-to-Sunday with a Saturday–Sunday weekend. All three are locale data. A birth-year dropdown that only offers Gregorian years, a “this year” filter that compares against 2026, or a booking grid that shades the wrong weekend each ships a real bug in Bangkok, Tokyo, Riyadh, or Tel Aviv.
How it works
The year number is an output, not a fact
Most date code treats the year as a constant that locales decorate: the same 2026 everywhere, with the day and month shuffled around it. A calendar system sits a level below that. It decides what turns a moment into a year, month, and day at all, and locales disagree about it. Take July 29, 2026. Thailand's official Buddhist calendar calls that year 2569. The Umm al-Qura calendar used in Saudi Arabia says 1448, the Hebrew calendar 5786, and the Japanese one Reiwa 8.
BCP 47 carries the choice in the -u-ca- extension: en-u-ca-hebrew means English text, Hebrew calendar. When the tag has no extension, CLDR data picks the default for the region. That is how plain th-TH resolves to buddhist and fa-IR to persian with no code changes on your side. The design point of putting it in the tag is that the choice travels with the request: a server, a database export, and a client that see the same tag produce the same rendering.
The default itself is data, and data gets revised. Older CLDR releases defaulted ar-SA to the Umm al-Qura calendar. Current ones default it to gregory, reflecting Saudi Arabia's 2016 switch to Gregorian dates for civil purposes. Two browsers on your users' desks can disagree about this today. That is why the demo computes its locale table in your runtime instead of shipping a screenshot. resolvedOptions().calendar tells you what happened, and Intl.supportedValuesOf("calendar") lists what the runtime ships (18 systems in Node 22).
How it works
Offset, astronomy, or decree
Calendars differ in how much arithmetic you can safely do on them. The Buddhist and Minguo calendars are pure offsets on Gregorian months: add 543 for Thailand, subtract 1911 for Taiwan, and the month structure never moves. The lunar and lunisolar families are not offsets at all. An Islamic calendar year runs 354 or 355 days, so every Hijri date lands about 10 to 11 days earlier on the Gregorian calendar each year. The Umm al-Qura variant follows a table published in Saudi Arabia rather than a formula you can inline. The Hebrew calendar keeps lunar months aligned with solar seasons by inserting an entire leap month, Adar I, in 7 years of every 19.
The Japanese calendar adds a third mechanism: the era, a named span that resets the year counter by decree. January 7, 1989 was Shōwa 64, and the next morning was Heisei 1. April 30, 2019 was Heisei 31, the next day was Reiwa 1, and Shōwa 64 itself lasted seven days. A government announces an era name. No formula computes it. Reiwa became public one month before it took effect. That is why hardcoded era tables are a time bomb, and calendar data channels (CLDR, ICU, OS updates) are the fix.
The transferable rule: only the offset calendars are safe to compute by hand, and even there you should not, because Intl.DateTimeFormat with -u-ca- already knows all three mechanisms. Store ISO 8601 internally, convert at the edge.
const d = new Date(Date.UTC(2026, 6, 29));
for (const ca of ["gregory", "japanese", "buddhist", "islamic-umalqura"]) {
console.log(ca, new Intl.DateTimeFormat("en-u-ca-" + ca,
{ dateStyle: "full", timeZone: "UTC" }).format(d));
}
// gregory Wednesday, July 29, 2026
// japanese Wednesday, July 29, 8 Reiwa
// buddhist Wednesday, July 29, 2569 BE
// islamic-umalqura Wednesday, Safar 15, 1448 AHHow it works
The week has a shape, and it is not yours
Three more values come with the calendar: which day the week starts on, which days are the weekend, and how many days of January a week needs before it counts as week 1. Intl.Locale is where you ask. One wrinkle is a lesson in reading runtimes. The current spelling is getWeekInfo(), but older engines, Node 22's V8 among them, expose the same data as a weekInfo property, so defensive code checks for both. Node reports minimalDays while current Chrome omits it. That is why the table below prints a dash when your engine stays quiet. Days run 1 for Monday through 7 for Sunday.
The values move more than people expect. The US starts its grid on Sunday with a Saturday-Sunday weekend. Germany starts Monday. Saudi Arabia and Israel also start Sunday, but their weekend is Friday and Saturday. A booking UI that shades Saturday-Sunday as "the weekend" is wrong in both markets in different directions: it greys out a working day and leaves a rest day bookable.
The quiet one is minimalDays, the week-numbering rule. The ISO 8601 convention used across most of Europe wants week 1 to hold at least 4 days of the new year. The US convention accepts 1. January 1, 2027 falls on a Friday. An American report calls that week "week 1 of 2027" while an ISO report files the same days under week 53 of 2026. Two dashboards can sit a full week apart on what "W1" means, and both are right for their locale.
See it yourself
Try these, in order
Each step triggers a specific failure you should recognize on sight.
- 1In One day, eight year counters, set Date (Gregorian) to 2019-05-01. The japanese row flips to 1 Reiwa while gregory still reads 2019. Step back one day to 2019-04-30 and the same row reads 31 Heisei: the year counter reset mid-week, by decree.
- 2Set the date to 2026-06-16. The islamic-umalqura row reads Muharram 1, 1448: a new year in the middle of June. Set the same date in 2027 and the Hijri year has already rolled to 1449, because a 354-day year drifts about 10 to 11 days against Gregorian every cycle.
- 3Read the ar-SA row in What your browser ships, then open this page in another browser if you have one handy. The default calendar is CLDR data, not a constant: current runtimes report gregory for ar-SA, older ones report islamic-umalqura. resolvedOptions().calendar is how code finds out instead of assuming.
- 4In The shape of the week, compare the ar-SA and he-IL rows against en-US. All three start their week on Sunday, but the shaded weekend is Friday and Saturday. A calendar grid that hardcodes a Saturday-Sunday weekend ships wrong in Riyadh and Tel Aviv in different directions.
- 5Read the Min. days column for en-US and de-DE. In an engine that reports it you see 1 versus 4. Current Chrome shows a dash because its getWeekInfo() omits the value. The number is the week-numbering rule: the US calls the week containing January 1 week 1, while the ISO rule (minimalDays 4) can file the first days of January under the previous year's week 53. "W1" in a report header is ambiguous until you name the rule.
- 6In What your browser ships, type am-ET into the locale field and press Add locale. A new row appears with the calendar your engine resolves for Amharic in Ethiopia, rendered in Ge'ez script. Any well-formed BCP 47 tag works, and a tag with no data still resolves through fallback. This table is your runtime's CLDR snapshot, live.
One day, eight year counters
Pick a date. Every row is the same day, formatted by Intl.DateTimeFormat with a different -u-ca- calendar. Only the first row agrees with your filing system about what year it is.
| Calendar | Year reads | Full date (en) |
|---|---|---|
| gregoryThe ISO / trade default. Not the world's only civil calendar. | 2026 | Wednesday, July 29, 2026 |
| japaneseGregorian months, but the year counter resets per emperor. | 8 Reiwa | Wednesday, July 29, 8 Reiwa |
| buddhistThailand's official calendar. Gregorian + 543. | 2569 BE | Wednesday, July 29, 2569 BE |
| islamic-umalquraSaudi Arabia's calendar. Lunar: a year is 354–355 days. | 1448 AH | Wednesday, Safar 15, 1448 AH |
| hebrewLunisolar: a leap year inserts a whole month (Adar I). | 5786 | Wednesday, 15 Av 5786 |
| persianIran and Afghanistan. Solar, new year at the March equinox. | 1405 AP | Wednesday, Mordad 7, 1405 AP |
| rocTaiwan's official era: year 1 = 1912. | 115 Minguo | Wednesday, July 29, 115 Minguo |
| indianIndia's civil Śaka calendar, published alongside Gregorian. | 1948 Śaka | Wednesday, Sravana 7, 1948 Śaka |
The full list comes from Intl.supportedValuesOf("calendar").
What your browser ships
No -u-ca- extension here: each row asks resolvedOptions().calendar which calendar the plain locale tag gets by default. The demo computes this table in your browser on purpose. The ar-SA default changed from islamic-umalqura to gregory in recent CLDR releases, so two browsers can disagree about it today.
| Locale tag | Default calendar | Your browser renders |
|---|---|---|
| en-US | — | — |
| th-TH | — | — |
| fa-IR | — | — |
| ar-SA | — | — |
| ja-JP | — | — |
| ja-JP-u-ca-japanese | — | — |
Thailand runs on the Buddhist calendar by default: a th-TH user reads this year as 2569 BE. Iran resolves to the Persian calendar. Users can also override the calendar per-device, so treat the default as a starting point, not a fact about the user. A tag your engine has no data for still resolves: it falls back, and the row shows what the fallback ships.
An era boundary, frame by frame
The Japanese calendar restarts its year counter when an emperor accedes. Both modern transitions, rendered by -u-ca-japanese: Shōwa 64 lasted seven days, and April 30 → May 1, 2019 took the year from Heisei 31 to Reiwa 1 mid-week.
Last day of Shōwa · 1989-01-07
January 7, 64 Shōwa
昭和64年1月7日
First day of Heisei · 1989-01-08
January 8, 1 Heisei
平成元年1月8日
Last day of Heisei · 2019-04-30
April 30, 31 Heisei
平成31年4月30日
First day of Reiwa · 2019-05-01
May 1, 1 Reiwa
令和元年5月1日
Walk across a boundary yourself
May 1, 1 Reiwa
令和元年5月1日
May 1, 2019 · boundary day
The 2019 transition was announced one month in advance. Unicode 12.1.0 shipped with exactly one new character for it: U+32FF ㋿, the square Reiwa era sign.
The shape of the week
Which day a calendar grid starts on and which days are the weekend are CLDR data, read here from Intl.Locale week info in your browser. Shaded cells are that locale's weekend. The first column is that locale's first day of the week.
Reading week data…
Min. days is the week-numbering rule: how many days of January a week must contain to count as week 1. Most of Europe says 4 (the ISO 8601 rule), the US says 1. January 1, 2027 falls on a Friday. A US calendar files it in week 1, an ISO calendar in the last week of 2026. “Week 1” in a report header is ambiguous until you name the rule. A dash in that column means your engine's week info omits the value (current Chrome does). The CLDR data behind it exists either way.
Things you almost certainly believe that are wrong
- Everyone agrees what year it is. A Thai government form says 2569 BE. A Saudi contract may be dated 1448 AH. A Japanese tax office says 8 Reiwa.
- A year is 365 days, give or take a leap day. An Islamic calendar year is 354 or 355 days, so every Hijri date lands about 10–11 days earlier on the Gregorian calendar each year. A Hebrew leap year inserts an entire extra month.
- Eras are ancient history. Reiwa 1 began May 1, 2019. Japanese public paperwork uses era years today, and birth-year dropdowns that only offer Gregorian years are a localization bug there.
- The weekend is Saturday and Sunday. Saudi Arabia and Israel rest Friday–Saturday. A booking UI that shades Sat–Sun as “the weekend” is wrong in both markets.
- You can convert between calendars with arithmetic. Buddhist is Gregorian + 543. But Umm al-Qura month lengths follow a published Saudi table, and Hebrew leap months follow a 19-year cycle. Ask the calendar API. Do not do date math.
If you remember one thing
The year number, the era, and the shape of the week are all locale data. Store ISO 8601, format through the calendar the locale resolves, and never do arithmetic on a formatted year.
What to do about it
- Store dates in ISO 8601 / proleptic Gregorian internally, and convert at the display edge with
Intl.DateTimeFormat. The locale (or an explicit-u-ca-extension) picks the calendar; your storage format never changes. - Never do arithmetic on a formatted year. “Gregorian + 543” works for Thailand and for nothing else: Hijri months follow a published table and Japanese eras change by accession.
- Read the week's shape from
Intl.Localeweek info (getWeekInfo(), or the olderweekInfoproperty) instead of hardcoding first day and weekend. - Treat era tables as data with an update path. The Reiwa era was announced one month before it took effect; systems with hardcoded era lists needed emergency patches, systems reading CLDR/ICU data needed a data update.
Use this with
Stakeholders
Moments
- ·Designing date pickers, calendars, or scheduling features
- ·Entering markets with a non-Gregorian civil calendar (TH, JP, SA, IR, IL, TW)
- ·Defining “week 1” for reports and analytics
Field note
The Reiwa transition was a live test for the whole industry. Japan announced the era name on April 1, 2019, and it took effect on May 1, 2019, thirty days later. In that window Unicode shipped release 12.1.0 with exactly one new character, U+32FF, the square Reiwa era sign, so receipts and forms could keep the single-glyph era abbreviation convention. Teams that read era data from CLDR/ICU took an ordinary data update. Teams with hardcoded era tables took an emergency release.
Quick check
3 questions · pass at 2+
Question 1/3
A user in Bangkok submits a form dated year 2569. What happened?
Question 2/3
Your date library ships a hardcoded table of Japanese eras. Why is that a time bomb rather than a fact?
Question 3/3
Your booking calendar renders Monday-first columns and shades Saturday and Sunday as the weekend. Which markets file bugs, and what is the fix?