Skip to content

Implement non-ISO calendar support for Temporal (Chinese, Dangi, Hebrew, Persian)#2342

Merged
lahma merged 6 commits into
sebastienros:mainfrom
lahma:icu-libs
Mar 21, 2026
Merged

Implement non-ISO calendar support for Temporal (Chinese, Dangi, Hebrew, Persian)#2342
lahma merged 6 commits into
sebastienros:mainfrom
lahma:icu-libs

Conversation

@lahma

@lahma lahma commented Mar 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Implement calendar arithmetic for Chinese, Dangi, Hebrew, and Persian calendars using .NET's built-in System.Globalization.Calendar classes
  • Fix Japanese era boundaries (Meiji starts at 1873 per Temporal spec, not 1868)
  • Fix month/monthCode consistency validation across all constructors for non-ISO calendars
  • Add calendar-aware field accessors, with(), add()/subtract(), since()/until() for all Temporal types
  • Remove 55 test262 exclusions; +408 newly passing tests (93,300 total, 0 failures)

Architecture

New NonIsoCalendars.cs adapter (~1,100 lines) wraps .NET calendar classes:

Calendar .NET Class Range
chinese ChineseLunisolarCalendar 1901–2100
dangi KoreanLunisolarCalendar 918–2050
hebrew HebrewCalendar 1583–2239
persian PersianCalendar 622–9999

Key operations:

  • IsoToCalendarDate — ISO date → calendar year/month/monthCode/day with leap month detection
  • CalendarDateToIso — Calendar fields → ISO date with monthCode (M04L) resolution
  • CalendarDateAdd — Year addition preserving monthCode semantics (leap month → reject/constrain)
  • CalendarDateUntil — Calendar-aware date difference for year/month/day units

Graceful fallback to ISO-like behavior for dates outside .NET calendar ranges. Algorithmic Hebrew leap year detection (19-year Metonic cycle) extends support beyond .NET's range.

Changes by file

  • NonIsoCalendars.cs (new) — Central calendar adapter
  • TemporalHelpers.cs — Calendar-aware field accessors (CalendarYear, CalendarMonth, CalendarMonthCode, CalendarDay, CalendarDaysInMonth, CalendarMonthsInYear, CalendarInLeapYear, CalendarEra, CalendarEraYear); dispatch non-ISO calendars in CalendarDateToISO, CalendarDateAdd, CalendarDateUntil; fix ComputeYearFromEra for Japanese eras
  • 5 prototype files — Calendar-aware getters and with() for PlainDate, PlainDateTime, PlainYearMonth, PlainMonthDay, ZonedDateTime
  • 5 constructor files — Pass monthCode to CalendarDateToISO; skip ISO-specific month/monthCode consistency check for non-ISO calendars
  • Test262Harness.settings.json — Remove 55 file exclusions

Test plan

  • Full test262 suite: 93,300 passed, 0 failures (was 92,892)
  • Main xUnit test suite: 2,764 passed, 0 failures
  • Chinese calendar: monthCode, inLeapYear, monthsInYear, add, subtract, since, until, with (leap dates)
  • Dangi calendar: same operations as Chinese
  • Hebrew calendar: era (am), leap month (M05L), add/subtract across leap years
  • Persian calendar: toString produces correct ISO date (e.g., 1395-01-01 → 2016-03-20)
  • Japanese calendar: pre-Meiji dates use "ce" era, Meiji starts at 1873
  • No regressions on ISO/Gregorian calendar operations

🤖 Generated with Claude Code

lahma and others added 6 commits March 21, 2026 17:14
Add calendar arithmetic for Chinese, Dangi, Hebrew, and Persian
calendars using .NET's built-in System.Globalization.Calendar classes.

New NonIsoCalendars adapter handles ISO↔calendar date conversion,
calendar-aware field accessors (month, monthCode, day, daysInMonth,
monthsInYear, inLeapYear), date arithmetic preserving monthCode
semantics across leap months, and calendar date difference calculation.

Key changes:
- NonIsoCalendars.cs: Central adapter wrapping ChineseLunisolarCalendar,
  KoreanLunisolarCalendar, HebrewCalendar, PersianCalendar
- TemporalHelpers.cs: Calendar-aware field accessors, era support for
  Hebrew/Persian, Japanese era cutoff fix (1873 not 1868)
- All prototype files: Calendar-aware getters and with() methods
- All constructor files: monthCode passthrough for non-ISO calendars
- CalendarDateAdd/CalendarDateUntil dispatch to non-ISO adapter
- Graceful fallback to ISO-like fields for out-of-range dates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… validation

- Remove exclusion for numbering-systems.js (all digit mappings now work)
- Fix display month validation: reject monthCode with display month > 12
  even with overflow "constrain" (M13L is never valid)
- Fix HebrewCalendar out-of-range year handling in GetLeapMonthOrdinal
  and GetMonthsInYear with try/catch guards
- Fix Japanese Meiji era cutoff from 1868 to 1873 (when Japan adopted
  the Gregorian calendar per Temporal spec)
- Fix PlainDateTime/ZonedDateTime/PlainYearMonth/PlainMonthDay constructors
  to pass monthCode through to CalendarDateToISO for non-ISO calendars
- Fix PlainDateTime with() monthCode parsing to use ParseMonthCode
  (supports "L" suffix) instead of int.TryParse

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hebrew calendar uses a 19-year Metonic cycle where years 3, 6, 8, 11,
14, 17, 19 are leap years. This allows correct leap year detection
for Hebrew years outside .NET HebrewCalendar's range (5343-5999).

Fixes GetLeapMonthOrdinal and GetMonthsInYear to use algorithmic
detection as fallback, enabling CalendarDateAdd to correctly reject
adding years to leap months when the target year isn't a leap year.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ions

- PlainMonthDay.prototype.with() now uses calendar-aware field defaults
  and CalendarDateToISO for non-ISO calendars
- Remove exclusions for PlainYearMonth.with(non-iso-calendar-fields),
  PlainMonthDay.with(non-iso-calendar-fields), PlainMonthDay.from(fields-object)
- Fix HebrewDateToIso to use algorithmic leap year for out-of-range years

93,300 test262 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For non-ISO calendars, ordinal month number does not equal the display
month in monthCode (e.g., month 5 = M04L in Chinese calendar with leap
month). The ISO-specific validation that checks month == monthFromCode
must be skipped for non-ISO calendars where CalendarDateToISO handles
the resolution.

Applied to all 5 constructors: PlainDate, PlainDateTime, PlainMonthDay,
PlainYearMonth, ZonedDateTime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update CreatePlainDateFromFields and CreateZonedDateTimeFromFields
in TemporalHelpers to use CalendarDateToISO for non-ISO calendars
and skip ordinal/display month consistency checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lahma
lahma enabled auto-merge (squash) March 21, 2026 16:45
@lahma
lahma merged commit 57bd88e into sebastienros:main Mar 21, 2026
4 checks passed
@lahma
lahma deleted the icu-libs branch March 21, 2026 16:46
@sebastienros

Copy link
Copy Markdown
Owner

Super interesting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants