@formatjs/intl-datetimeformat 7.2.0 → 7.2.1
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.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formatjs/intl-datetimeformat",
|
|
3
3
|
"description": "Intl.DateTimeFormat polyfill",
|
|
4
|
-
"version": "7.2.
|
|
4
|
+
"version": "7.2.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Long Ho <holevietlong@gmail.com>",
|
|
7
7
|
"type": "module",
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"decimal.js": "^10.6.0",
|
|
20
20
|
"tslib": "^2.8.1",
|
|
21
|
-
"@formatjs/ecma402-abstract": "3.1.
|
|
22
|
-
"@formatjs/intl-localematcher": "0.8.
|
|
21
|
+
"@formatjs/ecma402-abstract": "3.1.1",
|
|
22
|
+
"@formatjs/intl-localematcher": "0.8.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@formatjs/intl-getcanonicallocales": "3.2.
|
|
26
|
-
"@formatjs/intl-locale": "5.2.
|
|
25
|
+
"@formatjs/intl-getcanonicallocales": "3.2.1",
|
|
26
|
+
"@formatjs/intl-locale": "5.2.1"
|
|
27
27
|
},
|
|
28
28
|
"bugs": "https://github.com/formatjs/formatjs/issues",
|
|
29
29
|
"homepage": "https://github.com/formatjs/formatjs#readme",
|
package/polyfill.iife.js
CHANGED
|
@@ -6816,7 +6816,8 @@
|
|
|
6816
6816
|
getInternalSlots: getInternalSlots2,
|
|
6817
6817
|
localeData,
|
|
6818
6818
|
getDefaultTimeZone,
|
|
6819
|
-
tzData
|
|
6819
|
+
tzData,
|
|
6820
|
+
rangeFormatOptions
|
|
6820
6821
|
}) {
|
|
6821
6822
|
x = TimeClip(x);
|
|
6822
6823
|
const internalSlots = getInternalSlots2(dtf);
|
|
@@ -6913,7 +6914,7 @@
|
|
|
6913
6914
|
}
|
|
6914
6915
|
}
|
|
6915
6916
|
if (p === "hour" && hourCycle === "h24") {
|
|
6916
|
-
if (v === 0) {
|
|
6917
|
+
if (v === 0 && !(rangeFormatOptions == null ? void 0 : rangeFormatOptions.isDifferentDate)) {
|
|
6917
6918
|
v = 24;
|
|
6918
6919
|
}
|
|
6919
6920
|
}
|
|
@@ -7040,6 +7041,7 @@
|
|
|
7040
7041
|
let rangePattern;
|
|
7041
7042
|
let dateFieldsPracticallyEqual = true;
|
|
7042
7043
|
let patternContainsLargerDateField = false;
|
|
7044
|
+
let firstDifferingField;
|
|
7043
7045
|
for (const fieldName of TABLE_2_FIELDS) {
|
|
7044
7046
|
if (dateFieldsPracticallyEqual && !patternContainsLargerDateField) {
|
|
7045
7047
|
let rp = fieldName in rangePatterns ? rangePatterns[fieldName] : void 0;
|
|
@@ -7052,6 +7054,7 @@
|
|
|
7052
7054
|
let v2 = tm2.hour;
|
|
7053
7055
|
if (v1 > 11 && v2 < 11 || v1 < 11 && v2 > 11) {
|
|
7054
7056
|
dateFieldsPracticallyEqual = false;
|
|
7057
|
+
firstDifferingField = fieldName;
|
|
7055
7058
|
}
|
|
7056
7059
|
} else if (fieldName === "dayPeriod") {
|
|
7057
7060
|
} else if (fieldName === "fractionalSecondDigits") {
|
|
@@ -7067,12 +7070,14 @@
|
|
|
7067
7070
|
);
|
|
7068
7071
|
if (!SameValue(v1, v2)) {
|
|
7069
7072
|
dateFieldsPracticallyEqual = false;
|
|
7073
|
+
firstDifferingField = fieldName;
|
|
7070
7074
|
}
|
|
7071
7075
|
} else {
|
|
7072
7076
|
let v1 = tm1[fieldName];
|
|
7073
7077
|
let v2 = tm2[fieldName];
|
|
7074
7078
|
if (!SameValue(v1, v2)) {
|
|
7075
7079
|
dateFieldsPracticallyEqual = false;
|
|
7080
|
+
firstDifferingField = fieldName;
|
|
7076
7081
|
}
|
|
7077
7082
|
}
|
|
7078
7083
|
}
|
|
@@ -7090,6 +7095,10 @@
|
|
|
7090
7095
|
}
|
|
7091
7096
|
return result2;
|
|
7092
7097
|
}
|
|
7098
|
+
if (rangePattern === void 0 && firstDifferingField === "ampm") {
|
|
7099
|
+
rangePattern = "hour" in rangePatterns ? rangePatterns["hour"] : void 0;
|
|
7100
|
+
}
|
|
7101
|
+
const datesDiffer = tm1.year !== tm2.year || tm1.month !== tm2.month || tm1.day !== tm2.day;
|
|
7093
7102
|
let result = [];
|
|
7094
7103
|
if (rangePattern === void 0) {
|
|
7095
7104
|
rangePattern = rangePatterns.default;
|
|
@@ -7125,7 +7134,9 @@
|
|
|
7125
7134
|
z = y;
|
|
7126
7135
|
}
|
|
7127
7136
|
const patternParts = PartitionPattern(pattern2);
|
|
7128
|
-
let partResult = FormatDateTimePattern(dtf, patternParts, z, implDetails)
|
|
7137
|
+
let partResult = FormatDateTimePattern(dtf, patternParts, z, __spreadProps(__spreadValues({}, implDetails), {
|
|
7138
|
+
rangeFormatOptions: { isDifferentDate: datesDiffer }
|
|
7139
|
+
}));
|
|
7129
7140
|
for (const r of partResult) {
|
|
7130
7141
|
r.source = source;
|
|
7131
7142
|
}
|
|
@@ -5,10 +5,13 @@ export interface FormatDateTimePatternImplDetails {
|
|
|
5
5
|
getInternalSlots(dtf: Intl.DateTimeFormat | DateTimeFormat): IntlDateTimeFormatInternal;
|
|
6
6
|
localeData: Record<string, DateTimeFormatLocaleInternalData>;
|
|
7
7
|
getDefaultTimeZone(): string;
|
|
8
|
+
rangeFormatOptions?: {
|
|
9
|
+
isDifferentDate?: boolean;
|
|
10
|
+
};
|
|
8
11
|
}
|
|
9
12
|
/**
|
|
10
13
|
* https://tc39.es/ecma402/#sec-partitiondatetimepattern
|
|
11
14
|
* @param dtf
|
|
12
15
|
* @param x
|
|
13
16
|
*/
|
|
14
|
-
export declare function FormatDateTimePattern(dtf: Intl.DateTimeFormat | DateTimeFormat, patternParts: IntlDateTimeFormatPart[], x: Decimal, { getInternalSlots, localeData, getDefaultTimeZone, tzData }: FormatDateTimePatternImplDetails & ToLocalTimeImplDetails): IntlDateTimeFormatPart[];
|
|
17
|
+
export declare function FormatDateTimePattern(dtf: Intl.DateTimeFormat | DateTimeFormat, patternParts: IntlDateTimeFormatPart[], x: Decimal, { getInternalSlots, localeData, getDefaultTimeZone, tzData, rangeFormatOptions }: FormatDateTimePatternImplDetails & ToLocalTimeImplDetails): IntlDateTimeFormatPart[];
|
|
@@ -30,7 +30,7 @@ function offsetToGmtString(gmtFormat, hourFormat, offsetInMs, style) {
|
|
|
30
30
|
* @param dtf
|
|
31
31
|
* @param x
|
|
32
32
|
*/
|
|
33
|
-
export function FormatDateTimePattern(dtf, patternParts, x, { getInternalSlots, localeData, getDefaultTimeZone, tzData }) {
|
|
33
|
+
export function FormatDateTimePattern(dtf, patternParts, x, { getInternalSlots, localeData, getDefaultTimeZone, tzData, rangeFormatOptions }) {
|
|
34
34
|
x = TimeClip(x);
|
|
35
35
|
/** IMPL START */
|
|
36
36
|
const internalSlots = getInternalSlots(dtf);
|
|
@@ -130,8 +130,21 @@ export function FormatDateTimePattern(dtf, patternParts, x, { getInternalSlots,
|
|
|
130
130
|
v = 12;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
// GH #4535: In h24 format, midnight is typically shown as 24:00 (end of day).
|
|
134
|
+
// However, in date ranges where dates differ (e.g., "May 3, 22:00 – May 4, 00:00"),
|
|
135
|
+
// showing "May 4, 24:00" is semantically incorrect because 24:00 of May 4 would
|
|
136
|
+
// actually be May 5, 00:00. In this case, keep midnight as 00:00 for clarity.
|
|
137
|
+
//
|
|
138
|
+
// LDML Spec (UTS #35): "Tuesday 24:00 = Wednesday 00:00" - they represent the same
|
|
139
|
+
// instant. The 'k' symbol (1-24) means 24:00 represents the END of day, not the start.
|
|
140
|
+
// See: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
|
|
141
|
+
//
|
|
142
|
+
// Note: ICU4J's SimpleDateFormat always converts 0→24 for 'k' pattern without this
|
|
143
|
+
// range-aware check. Our fix is more semantically correct for date range formatting.
|
|
144
|
+
// See: https://github.com/unicode-org/icu/blob/main/icu4j/main/core/src/main/java/com/ibm/icu/text/SimpleDateFormat.java
|
|
133
145
|
if (p === "hour" && hourCycle === "h24") {
|
|
134
|
-
if (v === 0) {
|
|
146
|
+
if (v === 0 && !rangeFormatOptions?.isDifferentDate) {
|
|
147
|
+
// Only convert 0 to 24 when NOT in a range with different dates
|
|
135
148
|
v = 24;
|
|
136
149
|
}
|
|
137
150
|
}
|
|
@@ -46,6 +46,8 @@ export function PartitionDateTimeRangePattern(dtf, x, y, implDetails) {
|
|
|
46
46
|
let rangePattern;
|
|
47
47
|
let dateFieldsPracticallyEqual = true;
|
|
48
48
|
let patternContainsLargerDateField = false;
|
|
49
|
+
// Track the first field that differs between the two dates
|
|
50
|
+
let firstDifferingField;
|
|
49
51
|
for (const fieldName of TABLE_2_FIELDS) {
|
|
50
52
|
if (dateFieldsPracticallyEqual && !patternContainsLargerDateField) {
|
|
51
53
|
let rp = fieldName in rangePatterns ? rangePatterns[fieldName] : undefined;
|
|
@@ -58,6 +60,7 @@ export function PartitionDateTimeRangePattern(dtf, x, y, implDetails) {
|
|
|
58
60
|
let v2 = tm2.hour;
|
|
59
61
|
if (v1 > 11 && v2 < 11 || v1 < 11 && v2 > 11) {
|
|
60
62
|
dateFieldsPracticallyEqual = false;
|
|
63
|
+
firstDifferingField = fieldName;
|
|
61
64
|
}
|
|
62
65
|
} else if (fieldName === "dayPeriod") {} else if (fieldName === "fractionalSecondDigits") {
|
|
63
66
|
let fractionalSecondDigits = internalSlots.fractionalSecondDigits;
|
|
@@ -68,12 +71,14 @@ export function PartitionDateTimeRangePattern(dtf, x, y, implDetails) {
|
|
|
68
71
|
let v2 = Math.floor(tm2.millisecond * 10 ** (fractionalSecondDigits - 3));
|
|
69
72
|
if (!SameValue(v1, v2)) {
|
|
70
73
|
dateFieldsPracticallyEqual = false;
|
|
74
|
+
firstDifferingField = fieldName;
|
|
71
75
|
}
|
|
72
76
|
} else {
|
|
73
77
|
let v1 = tm1[fieldName];
|
|
74
78
|
let v2 = tm2[fieldName];
|
|
75
79
|
if (!SameValue(v1, v2)) {
|
|
76
80
|
dateFieldsPracticallyEqual = false;
|
|
81
|
+
firstDifferingField = fieldName;
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
}
|
|
@@ -86,6 +91,25 @@ export function PartitionDateTimeRangePattern(dtf, x, y, implDetails) {
|
|
|
86
91
|
}
|
|
87
92
|
return result;
|
|
88
93
|
}
|
|
94
|
+
// GH #4535: ICU4J-style AM_PM → HOUR fallback.
|
|
95
|
+
// When AM_PM differs but no AM_PM pattern exists, try the HOUR pattern.
|
|
96
|
+
// This handles cases where CLDR provides 'H' (24-hour) patterns but not 'h' (12-hour),
|
|
97
|
+
// or the skeleton uses hour12:true but CLDR only has 24-hour interval patterns.
|
|
98
|
+
//
|
|
99
|
+
// ICU4J DateIntervalFormat.java genIntervalPattern() method (~line 1825):
|
|
100
|
+
// // for 24 hour system, interval patterns in resource file
|
|
101
|
+
// // might not include pattern when am_pm differ,
|
|
102
|
+
// // which should be the same as hour differ.
|
|
103
|
+
// if (field == Calendar.AM_PM) {
|
|
104
|
+
// pattern = fInfo.getIntervalPattern(bestSkeleton, Calendar.HOUR);
|
|
105
|
+
// }
|
|
106
|
+
// See: https://github.com/unicode-org/icu/blob/main/icu4j/main/core/src/main/java/com/ibm/icu/text/DateIntervalFormat.java#L2018
|
|
107
|
+
// LDML Spec: https://unicode.org/reports/tr35/tr35-dates.html#intervalFormats
|
|
108
|
+
if (rangePattern === undefined && firstDifferingField === "ampm") {
|
|
109
|
+
rangePattern = "hour" in rangePatterns ? rangePatterns["hour"] : undefined;
|
|
110
|
+
}
|
|
111
|
+
// GH #4535: Check if dates (year/month/day) differ for h24 midnight handling
|
|
112
|
+
const datesDiffer = tm1.year !== tm2.year || tm1.month !== tm2.month || tm1.day !== tm2.day;
|
|
89
113
|
let result = [];
|
|
90
114
|
if (rangePattern === undefined) {
|
|
91
115
|
rangePattern = rangePatterns.default;
|
|
@@ -161,7 +185,10 @@ export function PartitionDateTimeRangePattern(dtf, x, y, implDetails) {
|
|
|
161
185
|
z = y;
|
|
162
186
|
}
|
|
163
187
|
const patternParts = PartitionPattern(pattern);
|
|
164
|
-
let partResult = FormatDateTimePattern(dtf, patternParts, z,
|
|
188
|
+
let partResult = FormatDateTimePattern(dtf, patternParts, z, {
|
|
189
|
+
...implDetails,
|
|
190
|
+
rangeFormatOptions: { isDifferentDate: datesDiffer }
|
|
191
|
+
});
|
|
165
192
|
for (const r of partResult) {
|
|
166
193
|
r.source = source;
|
|
167
194
|
}
|
package/src/packer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type UnpackedData, type PackedData } from "./types.
|
|
1
|
+
import { type UnpackedData, type PackedData } from "./types.ts";
|
|
2
2
|
import { type UnpackedZoneData } from "@formatjs/ecma402-abstract";
|
|
3
3
|
export declare function pack(data: UnpackedData): PackedData;
|
|
4
4
|
export declare function unpack(data: PackedData): Record<string, UnpackedZoneData[]>;
|