consistent-dates.js (13969B)
1 // |reftest| skip-if(!this.hasOwnProperty("Temporal")||!this.hasOwnProperty("Intl")) 2 3 // Ensure Intl.DateTimeFormat and Temporal return consistent year-month-day values 4 // for reasonable dates. 5 6 const weekdays = [ 7 "Monday", 8 "Tuesday", 9 "Wednesday", 10 "Thursday", 11 "Friday", 12 "Saturday", 13 "Sunday", 14 ]; 15 16 const months = [ 17 "January", 18 "February", 19 "March", 20 "April", 21 "May", 22 "June", 23 "July", 24 "August", 25 "September", 26 "October", 27 "November", 28 "December", 29 ]; 30 31 // Map Hebrew months to their corresponding month code. 32 const hebrewMonths = { 33 "Tishri": "M01", 34 "Heshvan": "M02", 35 "Kislev": "M03", 36 "Tevet": "M04", 37 "Shevat": "M05", 38 "Adar I": "M05L", 39 "Adar II": "M06", 40 "Adar": "M06", 41 "Nisan": "M07", 42 "Iyar": "M08", 43 "Sivan": "M09", 44 "Tamuz": "M10", 45 "Av": "M11", 46 "Elul": "M12", 47 }; 48 49 // Extract date information from a to parts formatted date. 50 function dateFromParts(parts) { 51 let relatedYear = undefined; 52 let year = undefined; 53 let monthCode = ""; 54 let day = 0; 55 let dayOfWeek = -1; 56 57 for (let {type, value} of parts) { 58 switch (type) { 59 case "weekday": 60 dayOfWeek = weekdays.indexOf(value); 61 break; 62 case "year": 63 year = Number(value); 64 break; 65 case "relatedYear": 66 relatedYear = Number(value); 67 break; 68 case "month": { 69 if (months.includes(value)) { 70 monthCode = "M" + String(months.indexOf(value) + 1).padStart(2, "0"); 71 } else if (value in hebrewMonths) { 72 monthCode = hebrewMonths[value]; 73 } else { 74 // Chinese/Dangi leap months end with "bis", from Latin "bis" = "twice". 75 let leapMonth = value.endsWith("bis"); 76 if (leapMonth) { 77 value = value.slice(0, -"bis".length); 78 } 79 monthCode = "M" + value.padStart(2, "0") + (leapMonth ? "L" : ""); 80 } 81 break; 82 } 83 case "day": 84 day = Number(value); 85 break; 86 case "era": 87 case "literal": 88 continue; 89 default: throw new Error("bad part: " + type); 90 } 91 } 92 assertEq(dayOfWeek >= 0, true); 93 assertEq(monthCode.length > 0, true, JSON.stringify(parts)); 94 assertEq(day > 0, true); 95 96 dayOfWeek += 1; 97 98 return { 99 relatedYear, 100 year, 101 monthCode, 102 day, 103 dayOfWeek, 104 }; 105 } 106 107 const tests = { 108 buddhist: [ 109 // Date ranges in 1500..2500 where ICU4C and ICU4X compute different results. 110 // 111 // NOTE: These are dates before the Gregorian change date October 15, 1582. 112 { 113 start: {iso: "1500-01-01", year: 2043, monthCode: "M01", day: 1}, 114 end: {iso: "1582-10-14", year: 2125, monthCode: "M10", day: 14}, 115 }, 116 ], 117 chinese: [ 118 // Date ranges in 1900..2100 where ICU4C and ICU4X compute different results. 119 { 120 start: {iso: "1906-04-23", relatedYear: 1906, monthCode: "M04", day: 1}, 121 end: {iso: "1906-05-22", relatedYear: 1906, monthCode: "M04", day: 30}, 122 }, 123 { 124 start: {iso: "1917-03-23", relatedYear: 1917, monthCode: "M02L", day: 1}, 125 end: {iso: "1917-05-20", relatedYear: 1917, monthCode: "M03", day: 30}, 126 }, 127 { 128 start: {iso: "1922-06-25", relatedYear: 1922, monthCode: "M05L", day: 1}, 129 end: {iso: "1922-08-22", relatedYear: 1922, monthCode: "M06", day: 30}, 130 }, 131 { 132 start: {iso: "1954-02-03", relatedYear: 1954, monthCode: "M01", day: 1}, 133 end: {iso: "1954-03-04", relatedYear: 1954, monthCode: "M01", day: 30}, 134 }, 135 { 136 start: {iso: "1955-02-22", relatedYear: 1955, monthCode: "M02", day: 1}, 137 end: {iso: "1955-03-23", relatedYear: 1955, monthCode: "M02", day: 30}, 138 }, 139 { 140 start: {iso: "1987-07-26", relatedYear: 1987, monthCode: "M06L", day: 1}, 141 end: {iso: "1987-09-22", relatedYear: 1987, monthCode: "M07", day: 30}, 142 }, 143 { 144 start: {iso: "1999-01-17", relatedYear: 1998, monthCode: "M12", day: 1}, 145 end: {iso: "1999-02-15", relatedYear: 1998, monthCode: "M12", day: 30}, 146 }, 147 { 148 start: {iso: "2012-08-17", relatedYear: 2012, monthCode: "M07", day: 1}, 149 end: {iso: "2012-09-15", relatedYear: 2012, monthCode: "M07", day: 30}, 150 }, 151 { 152 start: {iso: "2018-11-07", relatedYear: 2018, monthCode: "M09", day: 30}, 153 end: {iso: "2018-12-06", relatedYear: 2018, monthCode: "M10", day: 29}, 154 }, 155 { 156 start: {iso: "2027-02-06", relatedYear: 2027, monthCode: "M01", day: 1}, 157 end: {iso: "2027-03-07", relatedYear: 2027, monthCode: "M01", day: 30}, 158 }, 159 { 160 start: {iso: "2030-02-02", relatedYear: 2029, monthCode: "M12", day: 30}, 161 end: {iso: "2030-03-03", relatedYear: 2030, monthCode: "M01", day: 29}, 162 }, 163 { 164 start: {iso: "2057-09-28", relatedYear: 2057, monthCode: "M09", day: 1}, 165 end: {iso: "2057-10-27", relatedYear: 2057, monthCode: "M09", day: 30}, 166 }, 167 { 168 start: {iso: "2070-03-12", relatedYear: 2070, monthCode: "M02", day: 1}, 169 end: {iso: "2070-04-10", relatedYear: 2070, monthCode: "M02", day: 30}, 170 }, 171 ], 172 dangi: [ 173 // Date ranges in 1900..2100 where ICU4C and ICU4X compute different results. 174 { 175 start: {iso: "1904-01-17", relatedYear: 1903, monthCode: "M11", day: 30}, 176 end: {iso: "1904-02-15", relatedYear: 1903, monthCode: "M12", day: 29}, 177 }, 178 { 179 start: {iso: "1904-11-07", relatedYear: 1904, monthCode: "M09", day: 30}, 180 end: {iso: "1904-12-06", relatedYear: 1904, monthCode: "M10", day: 29}, 181 }, 182 { 183 start: {iso: "1905-05-04", relatedYear: 1905, monthCode: "M03", day: 30}, 184 end: {iso: "1905-06-02", relatedYear: 1905, monthCode: "M04", day: 29}, 185 }, 186 { 187 start: {iso: "1908-04-30", relatedYear: 1908, monthCode: "M03", day: 30}, 188 end: {iso: "1908-05-29", relatedYear: 1908, monthCode: "M04", day: 29}, 189 }, 190 { 191 start: {iso: "1911-12-20", relatedYear: 1911, monthCode: "M10", day: 30}, 192 end: {iso: "1912-01-18", relatedYear: 1911, monthCode: "M11", day: 29}, 193 }, 194 { 195 start: {iso: "2017-02-26", relatedYear: 2017, monthCode: "M02", day: 1}, 196 end: {iso: "2017-03-27", relatedYear: 2017, monthCode: "M02", day: 30}, 197 }, 198 { 199 start: {iso: "2051-08-06", relatedYear: 2051, monthCode: "M06", day: 30}, 200 end: {iso: "2051-09-04", relatedYear: 2051, monthCode: "M07", day: 29}, 201 }, 202 { 203 start: {iso: "2051-11-03", relatedYear: 2051, monthCode: "M10", day: 1}, 204 end: {iso: "2051-12-02", relatedYear: 2051, monthCode: "M10", day: 30}, 205 }, 206 { 207 start: {iso: "2097-01-13", relatedYear: 2096, monthCode: "M12", day: 1}, 208 end: {iso: "2097-02-11", relatedYear: 2096, monthCode: "M12", day: 30}, 209 }, 210 ], 211 hebrew: [ 212 // Date ranges in 1500..2500 where ICU4C and ICU4X compute different results. 213 // ICU bug report: <https://unicode-org.atlassian.net/browse/ICU-23069>. 214 { 215 start: {iso: "1700-11-12", year: 5461, monthCode: "M03", day: 1}, 216 end: {iso: "1701-12-01", year: 5462, monthCode: "M02", day: 30}, 217 }, 218 { 219 start: {iso: "1798-11-09", year: 5559, monthCode: "M03", day: 1}, 220 end: {iso: "1799-11-28", year: 5560, monthCode: "M02", day: 30}, 221 }, 222 { 223 start: {iso: "2045-11-10", year: 5806, monthCode: "M03", day: 1}, 224 end: {iso: "2046-11-29", year: 5807, monthCode: "M02", day: 30}, 225 }, 226 { 227 start: {iso: "2292-11-11", year: 6053, monthCode: "M03", day: 1}, 228 end: {iso: "2293-11-30", year: 6054, monthCode: "M02", day: 30}, 229 }, 230 ], 231 "islamic-umalqura": [ 232 // TODO: Not yet supported. 233 234 // Date ranges in 2000..2030 where ICU4C and ICU4X compute different results. 235 // { 236 // start: {iso: "2000-02-06", year: 1420, monthCode: "M11", day: 1}, 237 // end: {iso: "2000-03-06", year: 1420, monthCode: "M11", day: 30}, 238 // }, 239 // { 240 // start: {iso: "2000-09-28", year: 1421, monthCode: "M06", day: 30}, 241 // end: {iso: "2000-10-27", year: 1421, monthCode: "M07", day: 29}, 242 // }, 243 // { 244 // start: {iso: "2001-10-17", year: 1422, monthCode: "M07", day: 30}, 245 // end: {iso: "2001-11-15", year: 1422, monthCode: "M08", day: 29}, 246 // }, 247 // { 248 // start: {iso: "2006-06-26", year: 1427, monthCode: "M06", day: 1}, 249 // end: {iso: "2006-07-25", year: 1427, monthCode: "M06", day: 30}, 250 // }, 251 // { 252 // start: {iso: "2024-12-02", year: 1446, monthCode: "M05", day: 30}, 253 // end: {iso: "2024-12-31", year: 1446, monthCode: "M06", day: 29}, 254 // }, 255 // { 256 // start: {iso: "2025-01-30", year: 1446, monthCode: "M08", day: 1}, 257 // end: {iso: "2025-02-28", year: 1446, monthCode: "M08", day: 30}, 258 // }, 259 // { 260 // start: {iso: "2029-08-11", year: 1451, monthCode: "M04", day: 1}, 261 // end: {iso: "2029-09-09", year: 1451, monthCode: "M04", day: 30}, 262 // }, 263 // { 264 // start: {iso: "2029-11-07", year: 1451, monthCode: "M07", day: 1}, 265 // end: {iso: "2029-12-06", year: 1451, monthCode: "M07", day: 30}, 266 // }, 267 ], 268 japanese: [ 269 // Date ranges in 1500..2500 where ICU4C and ICU4X compute different results. 270 // 271 // NOTE: These are dates before the Gregorian change date October 15, 1582. 272 { 273 start: {iso: "1500-01-01", monthCode: "M01", day: 1}, 274 end: {iso: "1582-10-14", monthCode: "M10", day: 14}, 275 }, 276 ], 277 persian: [ 278 // Date ranges in 1500..2500 where ICU4C and ICU4X compute different results. 279 // More info: https://github.com/unicode-org/icu4x/issues/4713 280 { 281 start: {iso: "2124-03-20", year: 1503, monthCode: "M01", day: 1}, 282 end: {iso: "2125-03-20", year: 1503, monthCode: "M12", day: 30}, 283 }, 284 { 285 start: {iso: "2223-03-21", year: 1602, monthCode: "M01", day: 1}, 286 end: {iso: "2224-03-20", year: 1602, monthCode: "M12", day: 30}, 287 }, 288 { 289 start: {iso: "2256-03-20", year: 1635, monthCode: "M01", day: 1}, 290 end: {iso: "2257-03-20", year: 1635, monthCode: "M12", day: 30}, 291 }, 292 { 293 start: {iso: "2289-03-20", year: 1668, monthCode: "M01", day: 1}, 294 end: {iso: "2290-03-20", year: 1668, monthCode: "M12", day: 30}, 295 }, 296 { 297 start: {iso: "2322-03-21", year: 1701, monthCode: "M01", day: 1}, 298 end: {iso: "2323-03-21", year: 1701, monthCode: "M12", day: 30}, 299 }, 300 { 301 start: {iso: "2355-03-21", year: 1734, monthCode: "M01", day: 1}, 302 end: {iso: "2356-03-20", year: 1734, monthCode: "M12", day: 30}, 303 }, 304 { 305 start: {iso: "2388-03-20", year: 1767, monthCode: "M01", day: 1}, 306 end: {iso: "2389-03-20", year: 1767, monthCode: "M12", day: 30}, 307 }, 308 { 309 start: {iso: "2421-03-20", year: 1800, monthCode: "M01", day: 1}, 310 end: {iso: "2422-03-20", year: 1800, monthCode: "M12", day: 30}, 311 }, 312 { 313 start: {iso: "2454-03-20", year: 1833, monthCode: "M01", day: 1}, 314 end: {iso: "2455-03-20", year: 1833, monthCode: "M12", day: 30}, 315 }, 316 { 317 start: {iso: "2487-03-20", year: 1866, monthCode: "M01", day: 1}, 318 end: {iso: "2488-03-19", year: 1866, monthCode: "M12", day: 30}, 319 }, 320 ], 321 roc: [ 322 // Date ranges in 1500..2500 where ICU4C and ICU4X compute different results. 323 // 324 // NOTE: These are dates before the Gregorian change date October 15, 1582. 325 { 326 start: {iso: "1500-01-01", eraYear: 412, monthCode: "M01", day: 1}, 327 end: {iso: "1582-10-14", eraYear: 330, monthCode: "M10", day: 14}, 328 }, 329 ], 330 }; 331 332 for (let [calendar, dates] of Object.entries(tests)) { 333 let dtf = new Intl.DateTimeFormat("en", { 334 timeZone: "UTC", 335 calendar, 336 year: "numeric", 337 month: "numeric", 338 day: "numeric", 339 weekday: "long", 340 }); 341 342 for (let {start, end} of dates) { 343 // Compute from start date. 344 let isoStartDate = Temporal.PlainDate.from(start.iso); 345 let startDate = isoStartDate.withCalendar(calendar); 346 let startDateParts = dtf.formatToParts(startDate); 347 348 // Compute from end date. 349 let isoEndDate = Temporal.PlainDate.from(end.iso); 350 let endDate = isoEndDate.withCalendar(calendar); 351 let endDateParts = dtf.formatToParts(endDate); 352 353 // Compute from ranges. 354 let rangeParts = dtf.formatRangeToParts(startDate, endDate); 355 let startRangeDateParts = rangeParts.filter(({source}) => source !== "endRange"); 356 let endRangeDateParts = rangeParts.filter(({source}) => source !== "startRange"); 357 358 // Entries to check. 359 let entries = [ 360 { 361 date: startDate, 362 parts: startDateParts, 363 expected: start, 364 }, 365 { 366 date: endDate, 367 parts: endDateParts, 368 expected: end, 369 }, 370 { 371 date: startDate, 372 parts: startRangeDateParts, 373 expected: start, 374 }, 375 { 376 date: endDate, 377 parts: endRangeDateParts, 378 expected: end, 379 }, 380 ]; 381 382 for (let {date, parts, expected} of entries) { 383 // Ensure Temporal matches |expected|. 384 if (expected.year !== undefined) { 385 assertEq(date.year, expected.year); 386 } 387 if (expected.eraYear !== undefined) { 388 assertEq(date.eraYear, expected.eraYear); 389 } 390 if (expected.relatedYear !== undefined) { 391 assertEq(date.with({month: 1}).withCalendar("iso8601").year, expected.relatedYear); 392 } 393 assertEq(date.monthCode, expected.monthCode); 394 assertEq(date.day, expected.day); 395 396 // Ensure Intl.DateTimeFormat matches |expected|. 397 let partsDate = dateFromParts(parts); 398 399 if (expected.year !== undefined) { 400 assertEq(partsDate.year, expected.year); 401 } 402 if (expected.eraYear !== undefined) { 403 assertEq(partsDate.year, expected.eraYear); 404 } 405 if (partsDate.relatedYear !== undefined) { 406 // NB: relatedYear isn't used for range formats with chinese/dangi calendars. 407 assertEq(partsDate.relatedYear, expected.relatedYear); 408 } 409 assertEq(partsDate.monthCode, expected.monthCode); 410 assertEq(partsDate.day, expected.day); 411 } 412 } 413 } 414 415 if (typeof reportCompare === "function") 416 reportCompare(true, true);