Skip to content

feat(core): read typed dates in every calendar - #9792

Open
nynexman4464 wants to merge 7 commits into
mainfrom
nynexman4464/feat/calendar-date-parser
Open

nynexman4464 wants to merge 7 commits into
mainfrom
nynexman4464/feat/calendar-date-parser

Conversation

@nynexman4464

@nynexman4464 nynexman4464 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Base is main (#9791 and #9622 are merged). #9643 (typed entry in DateInput and DateTimeInput) and #9840 (parser fixes for more locales) stack on this branch.

This implements the parser behind spec:AST-111 (approved in #9605) FR9–FR13: reading dates written in a calendar, years of the calendar, rejecting dates the calendar does not have, completeness for FR12, and committed text that reads back as the same date. It also adds the helper for AR2's example date. No component uses it yet; #9643 wires it into the fields.

User need

People who use a calendar other than the Gregorian calendar need to type dates the way their locale writes them in that calendar: ۱۴۰۵/۸/۱۱, 1 Adar 5784, 令和元年5月1日. The existing parseDateInput reads English month names and ASCII digits in Gregorian only. One parser that works for every calendar keeps the fields' behavior the same across calendars.

Problem and solution fit

  • utils/calendarDateParser.ts, built from Intl output (through Core's plainDateFormat and plainDateFormatToParts) and validated by Temporal:
    • per-locale lexicons of month, era and weekday names, field order and digits, cached;
    • any Unicode decimal digits, mixed scripts, the locale's and English names in every width, Hebrew positional months, Japanese eras, and the leap markers of the Chinese and Dangi calendars;
    • distinct results for empty, partial, unreadable, nonexistent and out-of-range input, and whether more typing could still change the date;
    • the committed text, the format hint and the calendar's display name.
  • Years are years of the calendar (FR10, DEC-4): a Buddhist 2026 is Buddhist year 2026. The committed text and the format hint add the short era where the locale's format omits it and the era keeps the field order, in every calendar with eras other than the Chinese and Dangi calendars. fix(utils): read back calendar text in Latvian, Amharic and Hindi #9840 adds the narrow-era and earlier-era cases that FR10 needs for japanese and roc.
  • Never adjusts a date: calendarDateFromFields rejects 30 Esfand in a common Persian year.
  • nearestAcceptedDate in utils/plainDate.ts: today when a field accepts it, else the nearest date within min, max and the disabled days. feat(DateInput, DateTimeInput): type dates in the active calendar #9643's format hint uses it. It is not exported from @astryxdesign/core/utils.
  • parseDateInput is unchanged.
  • Invisible characters: the parser drops the shared INVISIBLE_CHARS and, for pasted text, the bidi embedding and override characters (U+202A–U+202E). The shared set in utils/unicodeDigits.ts is unchanged, so NumberInput's number parser and the time parser in utils/timeFormat.ts read text as before.
  • Apostrophes: a straight or curly apostrophe reads as the ʻ (U+02BB) that Intl writes in the English Islamic month names, such as Dhuʻl-Hijjah; U+02BF folds the same way.
  • Lint: the raw-Intl rule approves the parser at its own path, with valid and invalid cases.

Current specification

Public delta and caller burden

  • No public API. The parser and nearestAcceptedDate are internal and not exported. No rendered output changes, and NumberInput and time text read as before.

Design and alternatives

  • One parser for every calendar, Gregorian included (spec:AST-111 DEC-4). DEC-4 rejects segmented fields.
  • Names, field order and digits come from Intl for the locale and calendar, so the parser reads what the same runtime writes.
  • Committed text in the Chinese and Dangi calendars comes from Temporal's day plus Intl names, so display and parsing agree in one runtime.

Evidence

  • Round trip: the committed text and the format hint read back as the same date for every day from 1900 through 2100, for all 16 calendars, in each native locale plus en and sv-SE, with zero failures. calendarDateParser.roundtrip.test.ts samples this matrix in CI, plus locales with weekday words that name months or eras, month names with digits, and cycle years. Dates before 1900 are best effort.
  • Unit tests: reading, years, rejection kinds, completeness, committed text and calendar names, including Persian digits, a plain Adar in a leap year, a year outside its Japanese era, bidi embeddings in pasted text, and Dhu'l-Hijjah 10, 1447 AH typed with a straight, curly or ʻ apostrophe in islamic-umalqura.
  • Speed: a lexicon builds in 14–100 ms on first use, and a parse takes 7–30 µs (287 µs for the Chinese calendar under the polyfill).

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

  • pnpm exec vitest run --maxWorkers=2 over the test files this pull request changes (the raw-Intl rule, the parser, its round trip and plainDate) and the other tests beside its changed sources in utils, plus docPropLiterals.test.ts: 56 files, 1,084 of 1,085 tests pass. The one failure, hitArea.test.ts "batches every pending control into one frame: all reads, then all writes", fails the same way on the main commit the stack sits on (d23d1fe438). NumberInput and timeFormat tests pass too.
  • pnpm exec tsc --noEmit -p packages/core: clean.
  • pnpm check:knowledge, pnpm check:changesets: pass.
  • node scripts/pre-review.mjs --base main: 1,997 authored lines.

Writing rubric results

No knowledge record is touched.

  • R1 narration: none; no record changed.
  • R2 research: none.
  • R3 evidence: none; no record changed.
  • R4 rejected alternatives: none in a record.
  • R5 change references: none.
  • R6 open questions: none.

@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Building Building Preview Nov 4, 2026 11:15pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🔄 In progress (for maintainers only) Nov 4, 2026, 11:15 PM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 3, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold for a spec decision, and five fixes are needed either way.

This parser implements draft spec:AST-111 FR9–FR13 and DEC-4. On main, architecture:internationalization INV10 still keeps date parsing Gregorian, and INV8's Owning code does not list the new module. So an engineering owner needs to approve this addition ahead of those records, or identify the current record that governs it. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.

Required fixes:

  • The parser reads its own text back as a different date: several European locales write the Chinese and Korean hint with the 60-year cycle number (2/12/41 for 1 Jan 2025) or with no year, and Hungarian and Serbian Islamic month names contain a digit (Réb. 1) (calendarDateParser.ts:1284–1288, :427–437).
  • Committed text that cannot be read back: Spanish, French, Italian and Romanian date_weekday text fails on Tuesdays because mar is also a month name, Vietnamese weekday digits (Thứ 4) count as date numbers, and the parser never learns some era spellings in hints, such as French EB and H.
  • The new RuleTester cases are spliced into two existing objects without closing them, so two calendarSystem.ts boundary cases are overwritten (no-raw-intl-locale.test.mjs:155–158, :310–316).
  • normalizeDateText copies the digit folding and invisible-character stripping that utils/unicodeDigits.ts already gives numberParser.ts and timeFormat.ts; import toAsciiDigits and INVISIBLE_CHARS instead.
  • The changeset says the parser reads a date "the way a locale writes it", but the round-trip matrix covers only native locales, en and sv-SE; extend the matrix to the shipped locales or narrow the wording.

[Automated review]

Comment on lines +1284 to +1288
if (!parts.some(p => partType(p) === 'relatedYear' || p.type === 'year')) {
// Some locales write only the cycle year name; the long form also has
// the Gregorian year.
resolved = options.weekday ? LONG_WITH_WEEKDAY : LONG;
parts = partsOf(locale, calendar, resolved, mid);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In ca-ES, cs-CZ, de-DE, fi-FI, fr-FR, it-IT, nl-NL, pl-PL, pt-PT and ru-RU, the numeric year part here is the 60-year cycle number. The hint is 2/12/41 for 1 Jan 2025, and the parser reads it back as 24 Dec 2041. In de-DE, pt-BR and pt-PT the long form also has only yearName (2. M12 jia-chen), so the text reads back in the current year. Always write the related Gregorian year.

Comment on lines +427 to +437
if (/\d/.test(name)) {
// A month number with affixes: "11月", "9:e mån", "윤6", "6bis".
const match = /^(\D*)(\d+)(\D*)$/.exec(name);
if (match != null && (match[1] || match[3])) {
templates.set(`${match[1]}#${match[3]}`, {
pre: match[1],
post: match[3],
isLeap: code.endsWith('L'),
});
}
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hungarian and Serbian Islamic month abbreviations contain a digit (Réb. 1, Реб. 1 = Rabīʿ I). They become a numeric template here, so MF 1447. Réb. 1 2. (26 Aug 2025) reads as month 1 (28 Jun 2025). Treat the name as a month name unless the digit is that month's own number.

Comment on lines +586 to +592
for (const word of [
...months.keys(),
...eras.keys(),
...yearAliases.keys(),
]) {
skip.delete(word);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Month names win over weekday words, and every lexicon includes English (line 403). So mar (Tuesday in es, fr, it, ro) becomes a second month, and every Tuesday's date_weekday text is unreadable; no-NO tir. and pt-BR ter. collide the same way. Read a weekday word as a weekday when a month is already present, or when it sits at the pattern's weekday position.

Comment on lines +286 to +287
.replace(BIDI_MARKS, '')
.replace(/\p{Nd}/gu, asciiDigit)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utils/unicodeDigits.ts already owns this for numberParser.ts and timeFormat.ts (INVISIBLE_CHARS, toAsciiDigits). BIDI_MARKS and asciiDigit have already drifted from it. Import the shared helpers, add U+202A–U+202E to INVISIBLE_CHARS if needed, and delete the copies.

{
code: `new Intl.NumberFormat(locale, {useGrouping: false}).format(day);`,
filename: CALENDAR_SYSTEM_INFRA_FILE,
code: `new Intl.DateTimeFormat(locale, {calendar, timeZone: 'UTC'}).formatToParts(ms);`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object above is still open, so this code and filename overwrite the existing calendarSystem.ts NumberFormat case. Close it with }, and open a new { first. The same applies at line 314. The suite runs 111 cases, not 113.

code: `const {NumberFormat} = Intl;`,
filename: CALENDAR_SYSTEM_INFRA_FILE,
errors: [rawIntlReference],
code: `new Intl.DateTimeFormat(undefined, {calendar: 'persian'});`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same splice: this overwrites the invalid const {NumberFormat} = Intl; case for calendarSystem.ts. Close the object with }, and open a new { before this line.

Comment thread .changeset/calendar-date-parser.md Outdated
'@astryxdesign/core': patch
---

[feat] Core gains an internal parser for dates typed in a calendar system. It reads a date the way a locale writes it in any `CalendarSystem`, in any decimal digits, with month and era names, leap months such as Adar I, and Japanese eras such as `令和元年`, and returns a Gregorian date. A date the calendar does not have is rejected and never adjusted. It also writes the committed text and the format hint. No component uses it yet, and no public API changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"reads a date the way a locale writes it in any CalendarSystem" is broader than the evidence. The round trip covers each calendar's native locale, en and sv-SE, and the parser cannot read its own text in shipped locales such as es-ES, fr-FR, it-IT, ro-RO and vi-VN. Fix the parser and widen the matrix, or narrow the wording.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold for a spec decision, and six fixes are needed either way.

This parser implements draft spec:AST-111 FR9–FR13 and DEC-4, and nearestAcceptedDate implements its draft AR2. On main, architecture:internationalization INV10 still keeps date parsing Gregorian, and INV8's Owning code does not list the new module. An engineering owner needs to approve these additions ahead of those records, or identify the current record that governs them; they record that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.

Required fixes:

  • The parser reads its own text back as a different date, because several European locales write the Chinese and Korean hint with the 60-year cycle number (2/12/41 for 1 Jan 2025) or with no year, and Hungarian and Serbian Islamic month names contain a digit (Réb. 1) (calendarDateParser.ts:1283–1288, :427–437).
  • Nine Unicode property escapes are written as regex literals (calendarDateParser.ts:129 and eight more), so unicodePropertyEscapes.source-build.test.mjs fails and a Next.js source build without a browserslist stops with Unknown property: Nd; build them with new RegExp(...) as utils/unicodeDigits.ts does.
  • Committed text cannot be read back in several shipped locales: Spanish, French, Italian and Romanian date_weekday text fails on Tuesdays because mar is also a month name, Vietnamese weekday digits (Thứ 4) count as date numbers, and hints such as French EB and H use era spellings the parser never learns.
  • The new RuleTester cases are spliced into two existing objects without closing them, so two calendarSystem.ts boundary cases are overwritten (no-raw-intl-locale.test.mjs:154–159, :310–317).
  • normalizeDateText copies the digit folding and invisible-character stripping that utils/unicodeDigits.ts already gives numberParser.ts and timeFormat.ts, so import toAsciiDigits and INVISIBLE_CHARS instead.
  • The changeset says the parser reads a date "the way a locale writes it", but the round-trip matrix covers only native locales, en and sv-SE, so extend the matrix to the shipped locales or narrow the wording.

[Automated review]

weekday: 'short',
};
const BIDI_MARKS = /[\u061c\u200b-\u200f\u202a-\u202e\u2066-\u2069]/g;
const DECIMAL_DIGIT = /\p{Nd}/u;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and eight more \p{…} regex literals (lines 130, 131, 135, 287, 291, 298, 415, 509) fail unicodePropertyEscapes.source-build.test.mjs, and Next.js's bundled Babel stops on the file with Unknown property: Nd when an app sets no browserslist. Build each one once with new RegExp('\\p{Nd}', 'u'), as utils/unicodeDigits.ts does.

@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-systems-core branch from 53da574 to fe3c951 Compare November 3, 2026 22:39
@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-date-parser branch from 3bfcb3f to 96920a3 Compare November 3, 2026 22:40

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold for a spec decision, and one fix is needed either way.

The parser and nearestAcceptedDate implement draft spec:AST-111 FR9–FR13, AR2 and DEC-4, while architecture:internationalization INV10 on main still keeps date parsing Gregorian and INV8's Owning code does not list the parser; the widened invisible-character set also lets NumberInput and DateTimeInput's time field accept text that no current clause covers. An engineering owner needs to approve these additions ahead of those records, or identify the current record that governs them, by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.

Required fix:

  • calendarDateParser.ts:193–227 re-implements the UTC date-only formatting path that plainDateFormatToParts and plainDateFormat already provide (and calendarSystem.ts already calls), so route partsOf and formatInCalendar through them and delete epochMs.

The read-back, source-build, lint-case, digit-folding and changeset fixes from the last round all hold at this head.

[Automated review]

Comment on lines +206 to +227
function epochMs(pd: PlainDate): number {
const date = new Date(0);
date.setUTCFullYear(pd.year, pd.month - 1, pd.day);
return date.getTime();
}

function isoToPlainDate(iso: string): PlainDate {
const match = /^(-?\d+)-(\d+)-(\d+)$/.exec(iso);
if (match == null) {
throw new RangeError(`Bad sample date ${iso}`);
}
return {year: +match[1], month: +match[2], day: +match[3]};
}

function partsOf(
locale: Locale,
calendar: CalendarSystem,
options: Intl.DateTimeFormatOptions,
pd: PlainDate,
): Intl.DateTimeFormatPart[] {
return formatter(locale, calendar, options).formatToParts(epochMs(pd));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

epochMs and partsOf repeat the date-fields branch of plainDateFormatInput (plainDate.ts:494–500): a cached formatter with timeZone: 'UTC' and UTC midnight. plainDateFormatToParts(pd, {...options, calendar}, locale) already does this, and calendarSystem.ts:300–307 calls it for the same job. Call it here and plainDateFormat in formatInCalendar, then delete epochMs, so the parser always learns from the text the fields show.

}
const typedEra = eraCodes.size === 1 ? [...eraCodes][0] : undefined;

// Assign the numbers to fields.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking (INV29): this comment and // Resolve the year. at line 969 narrate the code. Delete them, or extract assignFields() and resolveYear().

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold for a spec decision, and one fix is needed either way.

The parser and nearestAcceptedDate implement draft spec:AST-111 FR9–FR13, AR2 and DEC-4, while architecture:internationalization INV10 on main still keeps date parsing Gregorian and INV8's Owning code does not list the parser; the wider invisible-character set also changes what NumberInput and DateTimeInput's time field accept, which no current clause covers. An engineering owner needs to approve these additions ahead of those records, or identify the current record that governs them, by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.

Required fix:

  • Miss: normalizeDateText (calendarDateParser.ts:232) does not map ʻ (U+02BB), the letter Intl writes in the English names Dhuʻl-Qiʻdah and Dhuʻl-Hijjah, so Dhu'l-Hijjah 10, 1447 AH typed with a straight or curly apostrophe is unreadable in every Islamic calendar; add U+02BB and U+02BF to that apostrophe class with a test.

The earlier formatting-path finding is fixed.

[Automated review]

.normalize('NFKC')
.replace(INVISIBLE_CHARS, '')
// People type an ASCII apostrophe or quote for Hebrew geresh and gershayim.
.replace(/[\u05f3\u2018\u2019\u02bc`]/g, "'")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss: ʻ (U+02BB) is missing from this class. Intl writes it in the English Islamic month names Dhuʻl-Qiʻdah and Dhuʻl-Hijjah (and Dhuʻl-Q., Dhuʻl-H.), so Dhu'l-Hijjah 10, 1447 AH and Dhu’l-Hijjah 10, 1447 AH are unreadable in every Islamic calendar. Adding U+02BB and U+02BF here makes both read, and the parser's unit and round-trip suites still pass.

@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-systems-core branch from 0041c70 to de34259 Compare November 4, 2026 19:47
@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-date-parser branch from f81d686 to 18150b8 Compare November 4, 2026 19:47

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold for a spec decision, and one fix is needed either way.

The parser and nearestAcceptedDate implement draft spec:AST-111 FR9–FR13, AR2 and DEC-4, while architecture:internationalization INV10 on main still keeps date parsing Gregorian and INV8's Owning code does not list the parser; the wider invisible-character set also changes what NumberInput and DateTimeInput's time field accept, which no current clause covers. An engineering owner needs to approve these additions ahead of those records, or identify the current records that govern them, by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.

Required fix:

  • Miss: normalizeDateText (calendarDateParser.ts:232) does not fold ʻ (U+02BB), the letter Intl writes in the English names Dhuʻl-Qiʻdah and Dhuʻl-Hijjah, so Dhu'l-Hijjah 10, 1447 AH typed with a straight or curly apostrophe is unreadable in every Islamic calendar; add U+02BB and U+02BF to that class with a test.

[Automated review]

.normalize('NFKC')
.replace(INVISIBLE_CHARS, '')
// People type an ASCII apostrophe or quote for Hebrew geresh and gershayim.
.replace(/[\u05f3\u2018\u2019\u02bc`]/g, "'")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss: ʻ (U+02BB) is missing from this class. Intl writes it in the English Islamic month names Dhuʻl-Qiʻdah and Dhuʻl-Hijjah (and Dhuʻl-Q., Dhuʻl-H.), so Dhu'l-Hijjah 10, 1447 AH and Dhu’l-Hijjah 10, 1447 AH are unreadable in every Islamic calendar. Adding U+02BB and U+02BF here makes both spellings read and keeps the parser's tests passing:

.replace(/[\u05f3\u2018\u2019\u02bb\u02bc\u02bf`]/g, "'")

Please add a test that Dhu'l-Hijjah 10, 1447 AH reads in en with islamic-umalqura.

}

function windowedYear(twoDigits: number, current: number): number {
// Eighty years back and nineteen ahead of the calendar's current year.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking (INV29): this comment restates the two comparisons below. Delete it, or move the window into a JSDoc on windowedYear: "Resolves a two-digit year to the century that puts it at most 80 years before and 19 years after the current year."

@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-systems-core branch from de34259 to babf230 Compare November 4, 2026 22:30
@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-date-parser branch from 18150b8 to f02913f Compare November 4, 2026 22:30
@nynexman4464

Copy link
Copy Markdown
Contributor Author

/approve-spec f02913f

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested. An engineering owner approved the calendar parser's direction at this head, and two fixes remain.

  • Miss: formatLunisolar in packages/core/src/utils/calendarDateParser.ts (lines 1241–1280) copies the lunisolar branch of formatCalendarDate in utils/calendarSystem.ts instead of extending it, so Calendar's formatter and the field already write different Chinese and Dangi text for the same day (24. M09 bing-wu against 24. M09 2026 in de-DE); give formatCalendarDate a related-year option, call it from formatInCalendar, and delete the copy.
  • The description still says that the shared INVISIBLE_CHARS set drops U+202A–U+202E and that NumberInput and the time fields now ignore those characters, but unicodeDigits.ts is unchanged at this head and only the calendar parser strips them, so please remove those two statements.

The inline comments add two non-blocking points: the ASCII fast path in normalizeDateText skips the backtick fold, and two comments only label the next block.

[Automated review]

* names. Intl and a polyfilled Temporal can start a month one day apart, so the
* month's name comes from its middle day and the day number from Temporal.
*/
function formatLunisolar(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss: this copies the lunisolar branch of formatCalendarDate (calendarSystem.ts:330–377). Both take the middle-of-month Intl parts, write Temporal's day through plainDateFormatDayNumber with the resolved numbering system, and take the weekday from the date. Only the related-year swap (lines 1258–1260 and 1273–1276) is new. With the same options, Calendar's formatter writes 24. M09 bing-wu and this function writes 24. M09 2026 for 2 Nov 2026 in de-DE. Give formatCalendarDate an option that writes the related Gregorian year, call it from formatInCalendar for every non-Gregorian calendar, and delete this function.

/** Text in one comparable form: ASCII digits, lower case, no Latin accents. */
export function normalizeDateText(text: string, locale: Locale): string {
// Most typed text is printable ASCII; skip the Unicode passes it does not need.
if (/^[\u0020-\u007e]*$/.test(text)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: this returns before the apostrophe fold at line 235, so the backtick there is folded only when the text also has a non-ASCII character. Dhu`l-Hijjah 10, 1447 AH is unreadable in en with islamic-umalqura, but the same text with a trailing U+200F reads as 27 May 2026. Fold the backtick here too, or drop it from the class.

}
}

// Literals, weekday names and year names: words to read past.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking (INV29): this comment and the one at line 494 only label the next block. Extract collectSkipWords() and collectEras(), or delete the comments.

@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-systems-core branch from babf230 to 5ec2dc4 Compare November 4, 2026 22:53

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval is on hold for a spec decision at this head, and one fix is needed either way.

The calendar parser, its raw-Intl lint admission and nearestAcceptedDate implement spec:AST-111 FR9–FR13, AR2 and DEC-4, which is still draft on main, and architecture:internationalization INV8's Owning code does not list the parser; the earlier /approve-spec names the previous head, and an approval counts only at its exact head. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner.

  • Miss: formatLunisolar in packages/core/src/utils/calendarDateParser.ts (lines 1241–1280) copies the lunisolar branch of formatCalendarDate in utils/calendarSystem.ts instead of extending it, so the Calendar label and the field already write different Chinese and Dangi text for the same day (24. M09 bing-wu against 24. M09 2026 in de-DE); give formatCalendarDate a related-year option, call it from formatInCalendar, and delete the copy.

Two earlier non-blocking points still apply: the ASCII fast path in normalizeDateText skips the backtick fold, and the comments at lines 428 and 494 only label the next block.

[Automated review]

Add calendarDateParser: per-locale lexicons, a typed-date parser with distinct rejection kinds, committed text, the format hint, and calendar display names. Unit and round-trip tests cover every calendar. The raw-Intl lint rule approves the parser at its own path.
…cales

Write the related Gregorian year for Chinese and Korean dates, read month names that hold a digit, read weekday words that are also month or era names, learn the era spellings of full dates, and keep a one-digit day open until it exceeds 31. Reuse unicodeDigits for digit folding and invisible characters, and build Unicode property escapes at runtime.
…eParser

Build formatters through dateTimeFormatFor instead of a second cache, and fold the new parser cases into the existing tests.
…matToParts

partsOf and formatInCalendar call plainDateFormatToParts and
plainDateFormat, which already format a date-only PlainDate at UTC
midnight in UTC, so epochMs and the local formatter go away. Lunisolar
day and year digits use plainDateFormatDayNumber with the formatter's
numbering system.
… in typed month names

The shared invisible-character set stays as on main, so NumberInput and time text read as before. The calendar parser strips U+202A-U+202E itself, and reads a straight or curly apostrophe for the ʻ that Intl writes in Islamic month names such as Dhuʻl-Hijjah.
@nynexman4464
nynexman4464 force-pushed the nynexman4464/feat/calendar-date-parser branch from e38dac1 to 0d0e06d Compare November 4, 2026 23:15
@nynexman4464
nynexman4464 changed the base branch from nynexman4464/feat/calendar-systems-core to main November 4, 2026 23:15

This branch was successfully deployed

1 active (outdated) deployment
Preview — e38dac1e Deployed Nov 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant