@astryxdesign/core 0.7.9-canary.4d7dca6 → 0.7.9-canary.52e65f3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -71,6 +71,8 @@ export interface CalendarDateLexicon {
|
|
|
71
71
|
readonly eras: ReadonlyMap<string, string>;
|
|
72
72
|
readonly yearAliases: ReadonlyMap<string, string>;
|
|
73
73
|
readonly trailers: ReadonlySet<string>;
|
|
74
|
+
readonly yearMarkers: ReadonlySet<string>;
|
|
75
|
+
readonly separators: ReadonlySet<string>;
|
|
74
76
|
readonly weekdays: ReadonlySet<string>;
|
|
75
77
|
readonly vocabulary: ReadonlyArray<string>;
|
|
76
78
|
readonly vocabularySet: ReadonlySet<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendarDateParser.d.ts","sourceRoot":"","sources":["../../src/utils/calendarDateParser.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"calendarDateParser.d.ts","sourceRoot":"","sources":["../../src/utils/calendarDateParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -291,6 +291,8 @@ function buildLexicon(locale, calendar) {
|
|
|
291
291
|
const yearAliases = new Map();
|
|
292
292
|
const skip = new Set();
|
|
293
293
|
const trailers = new Set();
|
|
294
|
+
const yearMarkers = new Set();
|
|
295
|
+
const separators = new Set();
|
|
294
296
|
const weekdays = new Set();
|
|
295
297
|
const addSkip = (text, words = skip) => {
|
|
296
298
|
for (const word of norm(text).replace(NOT_WORDS, ' ').split(/\s+/)) {
|
|
@@ -370,10 +372,13 @@ function buildLexicon(locale, calendar) {
|
|
|
370
372
|
for (const options of literalFormats) {
|
|
371
373
|
for (const pd of literalDates) {
|
|
372
374
|
const parts = partsOf(loc, calendar, options, pd);
|
|
373
|
-
for (const part of parts) {
|
|
375
|
+
for (const [k, part] of parts.entries()) {
|
|
374
376
|
if (part.type === 'literal' || partType(part) === 'yearName') {
|
|
375
377
|
addSkip(part.value);
|
|
376
378
|
}
|
|
379
|
+
if (part.type === 'literal' && parts[k - 1]?.type === 'year') {
|
|
380
|
+
addSkip(part.value, yearMarkers);
|
|
381
|
+
}
|
|
377
382
|
}
|
|
378
383
|
if (loc === locale) {
|
|
379
384
|
const tail = parts.at(-1);
|
|
@@ -405,6 +410,13 @@ function buildLexicon(locale, calendar) {
|
|
|
405
410
|
}
|
|
406
411
|
}
|
|
407
412
|
}
|
|
413
|
+
const hint = displayOptions(locale, calendar, NUMERIC);
|
|
414
|
+
for (const part of partsOf(locale, calendar, hint, literalDates[0])) {
|
|
415
|
+
const separator = norm(part.value).replace(/\s+/g, '');
|
|
416
|
+
if (part.type === 'literal' && separator) {
|
|
417
|
+
separators.add(separator);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
408
420
|
if (LUNISOLAR.has(calendar)) {
|
|
409
421
|
// Year names cycle every 60 years.
|
|
410
422
|
for (let year = 1996; year < 2057; year++) {
|
|
@@ -496,6 +508,8 @@ function buildLexicon(locale, calendar) {
|
|
|
496
508
|
eras,
|
|
497
509
|
yearAliases,
|
|
498
510
|
trailers,
|
|
511
|
+
yearMarkers,
|
|
512
|
+
separators,
|
|
499
513
|
weekdays,
|
|
500
514
|
vocabulary: [...vocabularySet].sort((a, b) => b.length - a.length),
|
|
501
515
|
vocabularySet,
|
|
@@ -517,8 +531,9 @@ function calendarEraYear(locale, calendar, pd) {
|
|
|
517
531
|
// Tokens
|
|
518
532
|
// =============================================================================
|
|
519
533
|
|
|
520
|
-
function tokenize(s, lex) {
|
|
534
|
+
function tokenize(s, lex, isDigitNameRead = true) {
|
|
521
535
|
const out = [];
|
|
536
|
+
let hasDigitName = false;
|
|
522
537
|
let i = 0;
|
|
523
538
|
const end = s.trimEnd().length;
|
|
524
539
|
while (i < end) {
|
|
@@ -533,6 +548,13 @@ function tokenize(s, lex) {
|
|
|
533
548
|
i = template.end;
|
|
534
549
|
continue;
|
|
535
550
|
}
|
|
551
|
+
const digitName = isDigitNameRead && /\d/.test(ch) && matchWord(s, i, lex);
|
|
552
|
+
if (digitName) {
|
|
553
|
+
out.push(wordToken(digitName, lex));
|
|
554
|
+
hasDigitName = true;
|
|
555
|
+
i += digitName.length;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
536
558
|
if (/\d/.test(ch)) {
|
|
537
559
|
let j = i;
|
|
538
560
|
while (j < s.length && /\d/.test(s[j])) {
|
|
@@ -588,6 +610,17 @@ function tokenize(s, lex) {
|
|
|
588
610
|
}
|
|
589
611
|
return 'unreadable';
|
|
590
612
|
}
|
|
613
|
+
// Latvian "1. adars" is a month, and also day 1 of "adars". It is the day
|
|
614
|
+
// when no other number can be the day. A number before the year word, as in
|
|
615
|
+
// "26. g.", is the year.
|
|
616
|
+
const canBeDay = (token, k) => {
|
|
617
|
+
const next = out.slice(k + 1).find(t => t.t !== 'sep');
|
|
618
|
+
const isYear = next?.t === 'word' && next.isYearMarker === true;
|
|
619
|
+
return token.t === 'num' && Number(token.v) <= 31 && !isYear;
|
|
620
|
+
};
|
|
621
|
+
if (hasDigitName && !out.some(canBeDay)) {
|
|
622
|
+
return tokenize(s, lex, false);
|
|
623
|
+
}
|
|
591
624
|
return out;
|
|
592
625
|
}
|
|
593
626
|
function matchTemplate(s, i, lex) {
|
|
@@ -681,7 +714,8 @@ function wordToken(word, lex) {
|
|
|
681
714
|
};
|
|
682
715
|
}
|
|
683
716
|
return {
|
|
684
|
-
t: 'word'
|
|
717
|
+
t: 'word',
|
|
718
|
+
isYearMarker: lex.yearMarkers.has(word)
|
|
685
719
|
};
|
|
686
720
|
}
|
|
687
721
|
|
|
@@ -803,7 +837,7 @@ export function parseCalendarDate(text, lex, options = {}) {
|
|
|
803
837
|
if (nums.length < 2) {
|
|
804
838
|
return INCOMPLETE;
|
|
805
839
|
}
|
|
806
|
-
if (nums.length > 3 || !sameSeparators(tokens)) {
|
|
840
|
+
if (nums.length > 3 || !sameSeparators(tokens, lex)) {
|
|
807
841
|
return UNREADABLE;
|
|
808
842
|
}
|
|
809
843
|
let order = nums.length === 2 ? lex.order.filter(field => field !== 'year') : lex.order;
|
|
@@ -912,7 +946,12 @@ function finish(date, isComplete, isDateDisabled) {
|
|
|
912
946
|
isComplete
|
|
913
947
|
};
|
|
914
948
|
}
|
|
915
|
-
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Whether the numbers share one separator. Two separators of the locale's own
|
|
952
|
+
* numeric pattern also match, such as Hindi Buddhist "1/1`/2443".
|
|
953
|
+
*/
|
|
954
|
+
function sameSeparators(tokens, lex) {
|
|
916
955
|
let previous = null;
|
|
917
956
|
let seenNumber = false;
|
|
918
957
|
let pending = null;
|
|
@@ -920,7 +959,7 @@ function sameSeparators(tokens) {
|
|
|
920
959
|
if (token.t === 'num') {
|
|
921
960
|
if (seenNumber) {
|
|
922
961
|
const separator = pending ?? '';
|
|
923
|
-
if (previous != null && separator !== previous) {
|
|
962
|
+
if (previous != null && separator !== previous && !(lex.separators.has(separator) && lex.separators.has(previous))) {
|
|
924
963
|
return false;
|
|
925
964
|
}
|
|
926
965
|
previous = separator;
|
|
@@ -1057,11 +1096,35 @@ function resolveDate(lex, fields) {
|
|
|
1057
1096
|
// =============================================================================
|
|
1058
1097
|
|
|
1059
1098
|
const displayOptionCache = new WeakMap();
|
|
1099
|
+
const eraChangeCache = new Map();
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Whether the era changes between 1900 and 2100. Only then can a year without
|
|
1103
|
+
* its era read as two dates.
|
|
1104
|
+
*/
|
|
1105
|
+
function hasEraChange(calendar) {
|
|
1106
|
+
let result = eraChangeCache.get(calendar);
|
|
1107
|
+
if (result == null) {
|
|
1108
|
+
const eraOf = date => calendarEraYear('en', calendar, date).era;
|
|
1109
|
+
result = eraOf({
|
|
1110
|
+
year: 1900,
|
|
1111
|
+
month: 1,
|
|
1112
|
+
day: 1
|
|
1113
|
+
}) !== eraOf({
|
|
1114
|
+
year: 2100,
|
|
1115
|
+
month: 12,
|
|
1116
|
+
day: 31
|
|
1117
|
+
});
|
|
1118
|
+
eraChangeCache.set(calendar, result);
|
|
1119
|
+
}
|
|
1120
|
+
return result;
|
|
1121
|
+
}
|
|
1060
1122
|
|
|
1061
1123
|
/**
|
|
1062
1124
|
* Adds the era to a format that omits it, so a year never reads as a
|
|
1063
1125
|
* Gregorian year. Keeps the locale's own pattern when the era would reorder
|
|
1064
|
-
* the fields.
|
|
1126
|
+
* the fields. Where the era changes, it uses a narrow era that keeps the
|
|
1127
|
+
* order instead, such as Amharic Japanese "1/1/8 R".
|
|
1065
1128
|
*/
|
|
1066
1129
|
function displayOptions(locale, calendar, options) {
|
|
1067
1130
|
if (WITHOUT_ERA.has(calendar)) {
|
|
@@ -1082,14 +1145,19 @@ function displayOptions(locale, calendar, options) {
|
|
|
1082
1145
|
};
|
|
1083
1146
|
const parts = partsOf(locale, calendar, options, sample);
|
|
1084
1147
|
result = options;
|
|
1148
|
+
const order = fieldOrder(parts).join();
|
|
1085
1149
|
if (!parts.some(p => p.type === 'era')) {
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1150
|
+
const widths = hasEraChange(calendar) ? ['short', 'narrow'] : ['short'];
|
|
1151
|
+
for (const era of widths) {
|
|
1152
|
+
const withEra = {
|
|
1153
|
+
...options,
|
|
1154
|
+
era
|
|
1155
|
+
};
|
|
1156
|
+
const eraParts = partsOf(locale, calendar, withEra, sample);
|
|
1157
|
+
if (fieldOrder(eraParts).join() === order) {
|
|
1158
|
+
result = withEra;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1093
1161
|
}
|
|
1094
1162
|
}
|
|
1095
1163
|
byLocale.set(key, result);
|
|
@@ -1102,10 +1170,30 @@ function formatInCalendar(pd, options, locale, calendar) {
|
|
|
1102
1170
|
relatedYear: true
|
|
1103
1171
|
});
|
|
1104
1172
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1173
|
+
const display = displayOptions(locale, calendar, options);
|
|
1174
|
+
const text = plainDateFormat(pd, {
|
|
1175
|
+
...display,
|
|
1107
1176
|
calendar
|
|
1108
1177
|
}, locale);
|
|
1178
|
+
// When every era width reorders the fields, such as the Amharic short-month
|
|
1179
|
+
// date, the text has no era. Text without an era reads in the latest era.
|
|
1180
|
+
// So a date of an earlier era adds the era. It goes at the start or the
|
|
1181
|
+
// end, as in the era format.
|
|
1182
|
+
const eraOf = date => calendarEraYear('en', calendar, date).era;
|
|
1183
|
+
if (!hasEraChange(calendar) || eraOf(pd) === eraOf({
|
|
1184
|
+
year: 9999,
|
|
1185
|
+
month: 12,
|
|
1186
|
+
day: 31
|
|
1187
|
+
}) || partsOf(locale, calendar, display, pd).some(p => p.type === 'era')) {
|
|
1188
|
+
return text;
|
|
1189
|
+
}
|
|
1190
|
+
const withEra = partsOf(locale, calendar, {
|
|
1191
|
+
...options,
|
|
1192
|
+
era: 'short'
|
|
1193
|
+
}, pd);
|
|
1194
|
+
const at = withEra.findIndex(p => p.type === 'era');
|
|
1195
|
+
const era = withEra.slice(at === 0 ? 0 : at - 1, at === 0 ? 2 : at + 1).map(p => p.value).join('');
|
|
1196
|
+
return at === 0 ? era + text : text + era;
|
|
1109
1197
|
}
|
|
1110
1198
|
|
|
1111
1199
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.9-canary.
|
|
3
|
+
"version": "0.7.9-canary.52e65f3",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -880,9 +880,9 @@
|
|
|
880
880
|
"react-dom": ">=19.0.0"
|
|
881
881
|
},
|
|
882
882
|
"devDependencies": {
|
|
883
|
-
"@astryxdesign/a11y-spec": "0.7.9-canary.
|
|
884
|
-
"@astryxdesign/cli": "0.7.9-canary.
|
|
885
|
-
"@astryxdesign/interaction-harness": "0.7.9-canary.
|
|
883
|
+
"@astryxdesign/a11y-spec": "0.7.9-canary.52e65f3",
|
|
884
|
+
"@astryxdesign/cli": "0.7.9-canary.52e65f3",
|
|
885
|
+
"@astryxdesign/interaction-harness": "0.7.9-canary.52e65f3",
|
|
886
886
|
"@babel/cli": "^7.29.7",
|
|
887
887
|
"@babel/core": "^7.29.7",
|
|
888
888
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -91,6 +91,8 @@ export interface CalendarDateLexicon {
|
|
|
91
91
|
readonly eras: ReadonlyMap<string, string>;
|
|
92
92
|
readonly yearAliases: ReadonlyMap<string, string>;
|
|
93
93
|
readonly trailers: ReadonlySet<string>;
|
|
94
|
+
readonly yearMarkers: ReadonlySet<string>;
|
|
95
|
+
readonly separators: ReadonlySet<string>;
|
|
94
96
|
readonly weekdays: ReadonlySet<string>;
|
|
95
97
|
readonly vocabulary: ReadonlyArray<string>;
|
|
96
98
|
readonly vocabularySet: ReadonlySet<string>;
|
|
@@ -352,6 +354,8 @@ function buildLexicon(
|
|
|
352
354
|
const yearAliases = new Map<string, string>();
|
|
353
355
|
const skip = new Set<string>();
|
|
354
356
|
const trailers = new Set<string>();
|
|
357
|
+
const yearMarkers = new Set<string>();
|
|
358
|
+
const separators = new Set<string>();
|
|
355
359
|
const weekdays = new Set<string>();
|
|
356
360
|
|
|
357
361
|
const addSkip = (text: string, words = skip) => {
|
|
@@ -439,10 +443,13 @@ function buildLexicon(
|
|
|
439
443
|
for (const options of literalFormats) {
|
|
440
444
|
for (const pd of literalDates) {
|
|
441
445
|
const parts = partsOf(loc, calendar, options, pd);
|
|
442
|
-
for (const part of parts) {
|
|
446
|
+
for (const [k, part] of parts.entries()) {
|
|
443
447
|
if (part.type === 'literal' || partType(part) === 'yearName') {
|
|
444
448
|
addSkip(part.value);
|
|
445
449
|
}
|
|
450
|
+
if (part.type === 'literal' && parts[k - 1]?.type === 'year') {
|
|
451
|
+
addSkip(part.value, yearMarkers);
|
|
452
|
+
}
|
|
446
453
|
}
|
|
447
454
|
if (loc === locale) {
|
|
448
455
|
const tail = parts.at(-1);
|
|
@@ -472,6 +479,13 @@ function buildLexicon(
|
|
|
472
479
|
}
|
|
473
480
|
}
|
|
474
481
|
}
|
|
482
|
+
const hint = displayOptions(locale, calendar, NUMERIC);
|
|
483
|
+
for (const part of partsOf(locale, calendar, hint, literalDates[0])) {
|
|
484
|
+
const separator = norm(part.value).replace(/\s+/g, '');
|
|
485
|
+
if (part.type === 'literal' && separator) {
|
|
486
|
+
separators.add(separator);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
475
489
|
if (LUNISOLAR.has(calendar)) {
|
|
476
490
|
// Year names cycle every 60 years.
|
|
477
491
|
for (let year = 1996; year < 2057; year++) {
|
|
@@ -576,6 +590,8 @@ function buildLexicon(
|
|
|
576
590
|
eras,
|
|
577
591
|
yearAliases,
|
|
578
592
|
trailers,
|
|
593
|
+
yearMarkers,
|
|
594
|
+
separators,
|
|
579
595
|
weekdays,
|
|
580
596
|
vocabulary: [...vocabularySet].sort((a, b) => b.length - a.length),
|
|
581
597
|
vocabularySet,
|
|
@@ -617,13 +633,18 @@ type Token =
|
|
|
617
633
|
| {readonly t: 'monthNum'; readonly v: string; readonly isLeap: boolean}
|
|
618
634
|
| {readonly t: 'era'; readonly code: string; readonly isWeekday?: boolean}
|
|
619
635
|
| {readonly t: 'leap'}
|
|
620
|
-
| {readonly t: 'word'}
|
|
636
|
+
| {readonly t: 'word'; readonly isYearMarker?: boolean}
|
|
621
637
|
| {readonly t: 'sep'; readonly v: string};
|
|
622
638
|
|
|
623
639
|
type Tokens = Token[] | 'incomplete' | 'unreadable';
|
|
624
640
|
|
|
625
|
-
function tokenize(
|
|
641
|
+
function tokenize(
|
|
642
|
+
s: string,
|
|
643
|
+
lex: CalendarDateLexicon,
|
|
644
|
+
isDigitNameRead = true,
|
|
645
|
+
): Tokens {
|
|
626
646
|
const out: Token[] = [];
|
|
647
|
+
let hasDigitName = false;
|
|
627
648
|
let i = 0;
|
|
628
649
|
const end = s.trimEnd().length;
|
|
629
650
|
while (i < end) {
|
|
@@ -638,6 +659,13 @@ function tokenize(s: string, lex: CalendarDateLexicon): Tokens {
|
|
|
638
659
|
i = template.end;
|
|
639
660
|
continue;
|
|
640
661
|
}
|
|
662
|
+
const digitName = isDigitNameRead && /\d/.test(ch) && matchWord(s, i, lex);
|
|
663
|
+
if (digitName) {
|
|
664
|
+
out.push(wordToken(digitName, lex));
|
|
665
|
+
hasDigitName = true;
|
|
666
|
+
i += digitName.length;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
641
669
|
if (/\d/.test(ch)) {
|
|
642
670
|
let j = i;
|
|
643
671
|
while (j < s.length && /\d/.test(s[j])) {
|
|
@@ -684,6 +712,17 @@ function tokenize(s: string, lex: CalendarDateLexicon): Tokens {
|
|
|
684
712
|
}
|
|
685
713
|
return 'unreadable';
|
|
686
714
|
}
|
|
715
|
+
// Latvian "1. adars" is a month, and also day 1 of "adars". It is the day
|
|
716
|
+
// when no other number can be the day. A number before the year word, as in
|
|
717
|
+
// "26. g.", is the year.
|
|
718
|
+
const canBeDay = (token: Token, k: number) => {
|
|
719
|
+
const next = out.slice(k + 1).find(t => t.t !== 'sep');
|
|
720
|
+
const isYear = next?.t === 'word' && next.isYearMarker === true;
|
|
721
|
+
return token.t === 'num' && Number(token.v) <= 31 && !isYear;
|
|
722
|
+
};
|
|
723
|
+
if (hasDigitName && !out.some(canBeDay)) {
|
|
724
|
+
return tokenize(s, lex, false);
|
|
725
|
+
}
|
|
687
726
|
return out;
|
|
688
727
|
}
|
|
689
728
|
|
|
@@ -777,7 +816,7 @@ function wordToken(word: string, lex: CalendarDateLexicon): Token {
|
|
|
777
816
|
if (alias != null) {
|
|
778
817
|
return {t: 'num', v: alias, isAlias: true};
|
|
779
818
|
}
|
|
780
|
-
return {t: 'word'};
|
|
819
|
+
return {t: 'word', isYearMarker: lex.yearMarkers.has(word)};
|
|
781
820
|
}
|
|
782
821
|
|
|
783
822
|
/** The month a typed abbreviation names when it starts only one month name. */
|
|
@@ -913,7 +952,7 @@ export function parseCalendarDate(
|
|
|
913
952
|
if (nums.length < 2) {
|
|
914
953
|
return INCOMPLETE;
|
|
915
954
|
}
|
|
916
|
-
if (nums.length > 3 || !sameSeparators(tokens)) {
|
|
955
|
+
if (nums.length > 3 || !sameSeparators(tokens, lex)) {
|
|
917
956
|
return UNREADABLE;
|
|
918
957
|
}
|
|
919
958
|
let order: ReadonlyArray<DateField> =
|
|
@@ -1022,7 +1061,11 @@ function finish(
|
|
|
1022
1061
|
return {kind: 'value', date, isComplete};
|
|
1023
1062
|
}
|
|
1024
1063
|
|
|
1025
|
-
|
|
1064
|
+
/**
|
|
1065
|
+
* Whether the numbers share one separator. Two separators of the locale's own
|
|
1066
|
+
* numeric pattern also match, such as Hindi Buddhist "1/1`/2443".
|
|
1067
|
+
*/
|
|
1068
|
+
function sameSeparators(tokens: Token[], lex: CalendarDateLexicon): boolean {
|
|
1026
1069
|
let previous: string | null = null;
|
|
1027
1070
|
let seenNumber = false;
|
|
1028
1071
|
let pending: string | null = null;
|
|
@@ -1030,7 +1073,11 @@ function sameSeparators(tokens: Token[]): boolean {
|
|
|
1030
1073
|
if (token.t === 'num') {
|
|
1031
1074
|
if (seenNumber) {
|
|
1032
1075
|
const separator = pending ?? '';
|
|
1033
|
-
if (
|
|
1076
|
+
if (
|
|
1077
|
+
previous != null &&
|
|
1078
|
+
separator !== previous &&
|
|
1079
|
+
!(lex.separators.has(separator) && lex.separators.has(previous))
|
|
1080
|
+
) {
|
|
1034
1081
|
return false;
|
|
1035
1082
|
}
|
|
1036
1083
|
previous = separator;
|
|
@@ -1195,10 +1242,30 @@ const displayOptionCache = new WeakMap<
|
|
|
1195
1242
|
Map<string, Intl.DateTimeFormatOptions>
|
|
1196
1243
|
>();
|
|
1197
1244
|
|
|
1245
|
+
const eraChangeCache = new Map<CalendarSystem, boolean>();
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Whether the era changes between 1900 and 2100. Only then can a year without
|
|
1249
|
+
* its era read as two dates.
|
|
1250
|
+
*/
|
|
1251
|
+
function hasEraChange(calendar: CalendarSystem): boolean {
|
|
1252
|
+
let result = eraChangeCache.get(calendar);
|
|
1253
|
+
if (result == null) {
|
|
1254
|
+
const eraOf = (date: PlainDate) =>
|
|
1255
|
+
calendarEraYear('en', calendar, date).era;
|
|
1256
|
+
result =
|
|
1257
|
+
eraOf({year: 1900, month: 1, day: 1}) !==
|
|
1258
|
+
eraOf({year: 2100, month: 12, day: 31});
|
|
1259
|
+
eraChangeCache.set(calendar, result);
|
|
1260
|
+
}
|
|
1261
|
+
return result;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1198
1264
|
/**
|
|
1199
1265
|
* Adds the era to a format that omits it, so a year never reads as a
|
|
1200
1266
|
* Gregorian year. Keeps the locale's own pattern when the era would reorder
|
|
1201
|
-
* the fields.
|
|
1267
|
+
* the fields. Where the era changes, it uses a narrow era that keeps the
|
|
1268
|
+
* order instead, such as Amharic Japanese "1/1/8 R".
|
|
1202
1269
|
*/
|
|
1203
1270
|
function displayOptions(
|
|
1204
1271
|
locale: Locale,
|
|
@@ -1219,11 +1286,18 @@ function displayOptions(
|
|
|
1219
1286
|
const sample = {year: 2026, month: 11, day: 2};
|
|
1220
1287
|
const parts = partsOf(locale, calendar, options, sample);
|
|
1221
1288
|
result = options;
|
|
1289
|
+
const order = fieldOrder(parts).join();
|
|
1222
1290
|
if (!parts.some(p => p.type === 'era')) {
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1291
|
+
const widths = hasEraChange(calendar)
|
|
1292
|
+
? (['short', 'narrow'] as const)
|
|
1293
|
+
: (['short'] as const);
|
|
1294
|
+
for (const era of widths) {
|
|
1295
|
+
const withEra = {...options, era};
|
|
1296
|
+
const eraParts = partsOf(locale, calendar, withEra, sample);
|
|
1297
|
+
if (fieldOrder(eraParts).join() === order) {
|
|
1298
|
+
result = withEra;
|
|
1299
|
+
break;
|
|
1300
|
+
}
|
|
1227
1301
|
}
|
|
1228
1302
|
}
|
|
1229
1303
|
byLocale.set(key, result);
|
|
@@ -1242,11 +1316,27 @@ function formatInCalendar(
|
|
|
1242
1316
|
relatedYear: true,
|
|
1243
1317
|
});
|
|
1244
1318
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1319
|
+
const display = displayOptions(locale, calendar, options);
|
|
1320
|
+
const text = plainDateFormat(pd, {...display, calendar}, locale);
|
|
1321
|
+
// When every era width reorders the fields, such as the Amharic short-month
|
|
1322
|
+
// date, the text has no era. Text without an era reads in the latest era.
|
|
1323
|
+
// So a date of an earlier era adds the era. It goes at the start or the
|
|
1324
|
+
// end, as in the era format.
|
|
1325
|
+
const eraOf = (date: PlainDate) => calendarEraYear('en', calendar, date).era;
|
|
1326
|
+
if (
|
|
1327
|
+
!hasEraChange(calendar) ||
|
|
1328
|
+
eraOf(pd) === eraOf({year: 9999, month: 12, day: 31}) ||
|
|
1329
|
+
partsOf(locale, calendar, display, pd).some(p => p.type === 'era')
|
|
1330
|
+
) {
|
|
1331
|
+
return text;
|
|
1332
|
+
}
|
|
1333
|
+
const withEra = partsOf(locale, calendar, {...options, era: 'short'}, pd);
|
|
1334
|
+
const at = withEra.findIndex(p => p.type === 'era');
|
|
1335
|
+
const era = withEra
|
|
1336
|
+
.slice(at === 0 ? 0 : at - 1, at === 0 ? 2 : at + 1)
|
|
1337
|
+
.map(p => p.value)
|
|
1338
|
+
.join('');
|
|
1339
|
+
return at === 0 ? era + text : text + era;
|
|
1250
1340
|
}
|
|
1251
1341
|
|
|
1252
1342
|
/**
|