DateTimeFormat.cpp (85517B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Intl.DateTimeFormat implementation. */ 8 9 #include "builtin/intl/DateTimeFormat.h" 10 11 #include "mozilla/Assertions.h" 12 #include "mozilla/intl/Calendar.h" 13 #include "mozilla/intl/DateIntervalFormat.h" 14 #include "mozilla/intl/DateTimeFormat.h" 15 #include "mozilla/intl/DateTimePart.h" 16 #include "mozilla/intl/Locale.h" 17 #include "mozilla/intl/TimeZone.h" 18 #include "mozilla/Span.h" 19 20 #include "jsdate.h" 21 22 #include "builtin/Array.h" 23 #include "builtin/intl/CommonFunctions.h" 24 #include "builtin/intl/FormatBuffer.h" 25 #include "builtin/intl/LanguageTag.h" 26 #include "builtin/intl/LocaleNegotiation.h" 27 #include "builtin/intl/SharedIntlData.h" 28 #include "builtin/temporal/Calendar.h" 29 #include "builtin/temporal/Instant.h" 30 #include "builtin/temporal/PlainDate.h" 31 #include "builtin/temporal/PlainDateTime.h" 32 #include "builtin/temporal/PlainMonthDay.h" 33 #include "builtin/temporal/PlainTime.h" 34 #include "builtin/temporal/PlainYearMonth.h" 35 #include "builtin/temporal/Temporal.h" 36 #include "builtin/temporal/TemporalParser.h" 37 #include "builtin/temporal/TimeZone.h" 38 #include "builtin/temporal/ZonedDateTime.h" 39 #include "gc/GCContext.h" 40 #include "js/Date.h" 41 #include "js/experimental/Intl.h" // JS::AddMozDateTimeFormatConstructor 42 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 43 #include "js/GCAPI.h" 44 #include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_DefineProperties 45 #include "js/PropertySpec.h" 46 #include "js/StableStringChars.h" 47 #include "js/Wrapper.h" 48 #include "vm/DateTime.h" 49 #include "vm/GlobalObject.h" 50 #include "vm/JSContext.h" 51 #include "vm/PlainObject.h" // js::PlainObject 52 #include "vm/Runtime.h" 53 54 #include "vm/GeckoProfiler-inl.h" 55 #include "vm/JSObject-inl.h" 56 #include "vm/NativeObject-inl.h" 57 58 using namespace js; 59 using namespace js::intl; 60 using namespace js::temporal; 61 62 using JS::AutoStableStringChars; 63 using JS::ClippedTime; 64 using JS::TimeClip; 65 66 using js::intl::DateTimeFormatKind; 67 using js::intl::DateTimeFormatOptions; 68 using js::intl::FormatBuffer; 69 using js::intl::INITIAL_CHAR_BUFFER_SIZE; 70 using js::intl::SharedIntlData; 71 72 const JSClassOps DateTimeFormatObject::classOps_ = { 73 nullptr, // addProperty 74 nullptr, // delProperty 75 nullptr, // enumerate 76 nullptr, // newEnumerate 77 nullptr, // resolve 78 nullptr, // mayResolve 79 DateTimeFormatObject::finalize, // finalize 80 nullptr, // call 81 nullptr, // construct 82 nullptr, // trace 83 }; 84 85 const JSClass DateTimeFormatObject::class_ = { 86 "Intl.DateTimeFormat", 87 JSCLASS_HAS_RESERVED_SLOTS(DateTimeFormatObject::SLOT_COUNT) | 88 JSCLASS_HAS_CACHED_PROTO(JSProto_DateTimeFormat) | 89 JSCLASS_FOREGROUND_FINALIZE, 90 &DateTimeFormatObject::classOps_, 91 &DateTimeFormatObject::classSpec_, 92 }; 93 94 const JSClass& DateTimeFormatObject::protoClass_ = PlainObject::class_; 95 96 static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc, 97 Value* vp); 98 99 static bool dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) { 100 CallArgs args = CallArgsFromVp(argc, vp); 101 args.rval().setString(cx->names().DateTimeFormat); 102 return true; 103 } 104 105 static const JSFunctionSpec dateTimeFormat_static_methods[] = { 106 JS_FN("supportedLocalesOf", dateTimeFormat_supportedLocalesOf, 1, 0), 107 JS_FS_END, 108 }; 109 110 static const JSFunctionSpec dateTimeFormat_methods[] = { 111 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 112 0, 0), 113 JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 1, 114 0), 115 JS_SELF_HOSTED_FN("formatRange", "Intl_DateTimeFormat_formatRange", 2, 0), 116 JS_SELF_HOSTED_FN("formatRangeToParts", 117 "Intl_DateTimeFormat_formatRangeToParts", 2, 0), 118 JS_FN("toSource", dateTimeFormat_toSource, 0, 0), 119 JS_FS_END, 120 }; 121 122 static const JSPropertySpec dateTimeFormat_properties[] = { 123 JS_SELF_HOSTED_GET("format", "$Intl_DateTimeFormat_format_get", 0), 124 JS_STRING_SYM_PS(toStringTag, "Intl.DateTimeFormat", JSPROP_READONLY), 125 JS_PS_END, 126 }; 127 128 static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp); 129 130 const ClassSpec DateTimeFormatObject::classSpec_ = { 131 GenericCreateConstructor<DateTimeFormat, 0, gc::AllocKind::FUNCTION>, 132 GenericCreatePrototype<DateTimeFormatObject>, 133 dateTimeFormat_static_methods, 134 nullptr, 135 dateTimeFormat_methods, 136 dateTimeFormat_properties, 137 nullptr, 138 ClassSpec::DontDefineConstructor, 139 }; 140 141 /** 142 * 12.2.1 Intl.DateTimeFormat([ locales [, options]]) 143 * 144 * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b 145 */ 146 static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, 147 HandleString required, HandleString defaults, 148 DateTimeFormatOptions dtfOptions) { 149 AutoJSConstructorProfilerEntry pseudoFrame(cx, "Intl.DateTimeFormat"); 150 151 // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code). 152 153 // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). 154 JSProtoKey protoKey = dtfOptions == DateTimeFormatOptions::Standard 155 ? JSProto_DateTimeFormat 156 : JSProto_Null; 157 RootedObject proto(cx); 158 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) { 159 return false; 160 } 161 162 Rooted<DateTimeFormatObject*> dateTimeFormat(cx); 163 dateTimeFormat = NewObjectWithClassProto<DateTimeFormatObject>(cx, proto); 164 if (!dateTimeFormat) { 165 return false; 166 } 167 168 RootedValue thisValue( 169 cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv()); 170 HandleValue locales = args.get(0); 171 HandleValue options = args.get(1); 172 173 // Step 3. 174 return intl::InitializeDateTimeFormatObject( 175 cx, dateTimeFormat, thisValue, locales, options, required, defaults, 176 UndefinedHandleValue, dtfOptions, args.rval()); 177 } 178 179 static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) { 180 CallArgs args = CallArgsFromVp(argc, vp); 181 182 Handle<PropertyName*> required = cx->names().any; 183 Handle<PropertyName*> defaults = cx->names().date; 184 return DateTimeFormat(cx, args, args.isConstructing(), required, defaults, 185 DateTimeFormatOptions::Standard); 186 } 187 188 static bool MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp) { 189 CallArgs args = CallArgsFromVp(argc, vp); 190 191 // Don't allow to call mozIntl.DateTimeFormat as a function. That way we 192 // don't need to worry how to handle the legacy initialization semantics 193 // when applied on mozIntl.DateTimeFormat. 194 if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat")) { 195 return false; 196 } 197 198 Handle<PropertyName*> required = cx->names().any; 199 Handle<PropertyName*> defaults = cx->names().date; 200 return DateTimeFormat(cx, args, true, required, defaults, 201 DateTimeFormatOptions::EnableMozExtensions); 202 } 203 204 static Handle<PropertyName*> ToRequired(JSContext* cx, 205 DateTimeFormatKind kind) { 206 switch (kind) { 207 case DateTimeFormatKind::All: 208 return cx->names().any; 209 case DateTimeFormatKind::Date: 210 return cx->names().date; 211 case DateTimeFormatKind::Time: 212 return cx->names().time; 213 } 214 MOZ_CRASH("invalid date time format kind"); 215 } 216 217 static Handle<PropertyName*> ToDefaults(JSContext* cx, 218 DateTimeFormatKind kind) { 219 switch (kind) { 220 case DateTimeFormatKind::All: 221 return cx->names().all; 222 case DateTimeFormatKind::Date: 223 return cx->names().date; 224 case DateTimeFormatKind::Time: 225 return cx->names().time; 226 } 227 MOZ_CRASH("invalid date time format kind"); 228 } 229 230 static DateTimeFormatObject* CreateDateTimeFormat( 231 JSContext* cx, Handle<Value> locales, Handle<Value> options, 232 Handle<Value> toLocaleStringTimeZone, DateTimeFormatKind kind) { 233 Rooted<DateTimeFormatObject*> dateTimeFormat( 234 cx, NewBuiltinClassInstance<DateTimeFormatObject>(cx)); 235 if (!dateTimeFormat) { 236 return nullptr; 237 } 238 239 Handle<PropertyName*> required = ToRequired(cx, kind); 240 Handle<PropertyName*> defaults = ToDefaults(cx, kind); 241 242 Rooted<Value> thisValue(cx, ObjectValue(*dateTimeFormat)); 243 Rooted<Value> ignored(cx); 244 if (!InitializeDateTimeFormatObject( 245 cx, dateTimeFormat, thisValue, locales, options, required, defaults, 246 toLocaleStringTimeZone, DateTimeFormatOptions::Standard, &ignored)) { 247 return nullptr; 248 } 249 MOZ_ASSERT(&ignored.toObject() == dateTimeFormat); 250 251 return dateTimeFormat; 252 } 253 254 DateTimeFormatObject* js::intl::CreateDateTimeFormat(JSContext* cx, 255 Handle<Value> locales, 256 Handle<Value> options, 257 DateTimeFormatKind kind) { 258 return CreateDateTimeFormat(cx, locales, options, UndefinedHandleValue, kind); 259 } 260 261 DateTimeFormatObject* js::intl::GetOrCreateDateTimeFormat( 262 JSContext* cx, Handle<Value> locales, Handle<Value> options, 263 DateTimeFormatKind kind) { 264 // Try to use a cached instance when |locales| is either undefined or a 265 // string, and |options| is undefined. 266 if ((locales.isUndefined() || locales.isString()) && options.isUndefined()) { 267 Rooted<JSLinearString*> locale(cx); 268 if (locales.isString()) { 269 locale = locales.toString()->ensureLinear(cx); 270 if (!locale) { 271 return nullptr; 272 } 273 } 274 return cx->global()->globalIntlData().getOrCreateDateTimeFormat(cx, kind, 275 locale); 276 } 277 278 // Create a new Intl.DateTimeFormat instance. 279 return CreateDateTimeFormat(cx, locales, options, UndefinedHandleValue, kind); 280 } 281 282 void js::DateTimeFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) { 283 MOZ_ASSERT(gcx->onMainThread()); 284 285 auto* dateTimeFormat = &obj->as<DateTimeFormatObject>(); 286 mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat(); 287 mozilla::intl::DateIntervalFormat* dif = 288 dateTimeFormat->getDateIntervalFormat(); 289 290 if (df) { 291 intl::RemoveICUCellMemory( 292 gcx, obj, DateTimeFormatObject::UDateFormatEstimatedMemoryUse); 293 294 delete df; 295 } 296 297 if (dif) { 298 intl::RemoveICUCellMemory( 299 gcx, obj, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse); 300 301 delete dif; 302 } 303 } 304 305 bool JS::AddMozDateTimeFormatConstructor(JSContext* cx, 306 JS::Handle<JSObject*> intl) { 307 RootedObject ctor( 308 cx, GlobalObject::createConstructor(cx, MozDateTimeFormat, 309 cx->names().DateTimeFormat, 0)); 310 if (!ctor) { 311 return false; 312 } 313 314 RootedObject proto( 315 cx, GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global())); 316 if (!proto) { 317 return false; 318 } 319 320 if (!LinkConstructorAndPrototype(cx, ctor, proto)) { 321 return false; 322 } 323 324 // 12.3.2 325 if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) { 326 return false; 327 } 328 329 // 12.4.4 and 12.4.5 330 if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) { 331 return false; 332 } 333 334 // 12.4.2 and 12.4.3 335 if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties)) { 336 return false; 337 } 338 339 RootedValue ctorValue(cx, ObjectValue(*ctor)); 340 return DefineDataProperty(cx, intl, cx->names().DateTimeFormat, ctorValue, 0); 341 } 342 343 static bool DefaultCalendar(JSContext* cx, const UniqueChars& locale, 344 MutableHandleValue rval) { 345 auto calendar = mozilla::intl::Calendar::TryCreate(locale.get()); 346 if (calendar.isErr()) { 347 intl::ReportInternalError(cx, calendar.unwrapErr()); 348 return false; 349 } 350 351 auto type = calendar.unwrap()->GetBcp47Type(); 352 if (type.isErr()) { 353 intl::ReportInternalError(cx, type.unwrapErr()); 354 return false; 355 } 356 357 JSString* str = NewStringCopy<CanGC>(cx, type.unwrap()); 358 if (!str) { 359 return false; 360 } 361 362 rval.setString(str); 363 return true; 364 } 365 366 bool js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) { 367 CallArgs args = CallArgsFromVp(argc, vp); 368 MOZ_ASSERT(args.length() == 1); 369 MOZ_ASSERT(args[0].isString()); 370 371 UniqueChars locale = intl::EncodeLocale(cx, args[0].toString()); 372 if (!locale) { 373 return false; 374 } 375 376 RootedObject calendars(cx, NewDenseEmptyArray(cx)); 377 if (!calendars) { 378 return false; 379 } 380 381 // We need the default calendar for the locale as the first result. 382 RootedValue defaultCalendar(cx); 383 if (!DefaultCalendar(cx, locale, &defaultCalendar)) { 384 return false; 385 } 386 387 if (!NewbornArrayPush(cx, calendars, defaultCalendar)) { 388 return false; 389 } 390 391 // Now get the calendars that "would make a difference", i.e., not the 392 // default. 393 auto keywords = 394 mozilla::intl::Calendar::GetBcp47KeywordValuesForLocale(locale.get()); 395 if (keywords.isErr()) { 396 intl::ReportInternalError(cx, keywords.unwrapErr()); 397 return false; 398 } 399 400 for (auto keyword : keywords.unwrap()) { 401 if (keyword.isErr()) { 402 intl::ReportInternalError(cx); 403 return false; 404 } 405 406 JSString* jscalendar = NewStringCopy<CanGC>(cx, keyword.unwrap()); 407 if (!jscalendar) { 408 return false; 409 } 410 if (!NewbornArrayPush(cx, calendars, StringValue(jscalendar))) { 411 return false; 412 } 413 } 414 415 args.rval().setObject(*calendars); 416 return true; 417 } 418 419 bool js::intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp) { 420 CallArgs args = CallArgsFromVp(argc, vp); 421 MOZ_ASSERT(args.length() == 1); 422 MOZ_ASSERT(args[0].isString()); 423 424 UniqueChars locale = intl::EncodeLocale(cx, args[0].toString()); 425 if (!locale) { 426 return false; 427 } 428 429 return DefaultCalendar(cx, locale, args.rval()); 430 } 431 432 enum class HourCycle { 433 // 12 hour cycle, from 0 to 11. 434 H11, 435 436 // 12 hour cycle, from 1 to 12. 437 H12, 438 439 // 24 hour cycle, from 0 to 23. 440 H23, 441 442 // 24 hour cycle, from 1 to 24. 443 H24 444 }; 445 446 static UniqueChars DateTimeFormatLocale( 447 JSContext* cx, HandleObject internals, 448 mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle> hourCycle = 449 mozilla::Nothing()) { 450 // ICU expects calendar, numberingSystem, and hourCycle as Unicode locale 451 // extensions on locale. 452 453 JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx); 454 455 RootedValue value(cx); 456 if (!GetProperty(cx, internals, internals, cx->names().calendar, &value)) { 457 return nullptr; 458 } 459 460 { 461 JSLinearString* calendar = value.toString()->ensureLinear(cx); 462 if (!calendar) { 463 return nullptr; 464 } 465 466 if (!keywords.emplaceBack("ca", calendar)) { 467 return nullptr; 468 } 469 } 470 471 if (!GetProperty(cx, internals, internals, cx->names().numberingSystem, 472 &value)) { 473 return nullptr; 474 } 475 476 { 477 JSLinearString* numberingSystem = value.toString()->ensureLinear(cx); 478 if (!numberingSystem) { 479 return nullptr; 480 } 481 482 if (!keywords.emplaceBack("nu", numberingSystem)) { 483 return nullptr; 484 } 485 } 486 487 if (hourCycle) { 488 JSAtom* hourCycleStr; 489 switch (*hourCycle) { 490 case mozilla::intl::DateTimeFormat::HourCycle::H11: 491 hourCycleStr = cx->names().h11; 492 break; 493 case mozilla::intl::DateTimeFormat::HourCycle::H12: 494 hourCycleStr = cx->names().h12; 495 break; 496 case mozilla::intl::DateTimeFormat::HourCycle::H23: 497 hourCycleStr = cx->names().h23; 498 break; 499 case mozilla::intl::DateTimeFormat::HourCycle::H24: 500 hourCycleStr = cx->names().h24; 501 break; 502 } 503 504 if (!keywords.emplaceBack("hc", hourCycleStr)) { 505 return nullptr; 506 } 507 } 508 509 return intl::FormatLocale(cx, internals, keywords); 510 } 511 512 static bool AssignTextComponent( 513 JSContext* cx, HandleObject internals, Handle<PropertyName*> property, 514 mozilla::Maybe<mozilla::intl::DateTimeFormat::Text>* text) { 515 RootedValue value(cx); 516 if (!GetProperty(cx, internals, internals, property, &value)) { 517 return false; 518 } 519 520 if (value.isString()) { 521 JSLinearString* string = value.toString()->ensureLinear(cx); 522 if (!string) { 523 return false; 524 } 525 if (StringEqualsLiteral(string, "narrow")) { 526 *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Narrow); 527 } else if (StringEqualsLiteral(string, "short")) { 528 *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Short); 529 } else { 530 MOZ_ASSERT(StringEqualsLiteral(string, "long")); 531 *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Long); 532 } 533 } else { 534 MOZ_ASSERT(value.isUndefined()); 535 } 536 537 return true; 538 } 539 540 static bool AssignNumericComponent( 541 JSContext* cx, HandleObject internals, Handle<PropertyName*> property, 542 mozilla::Maybe<mozilla::intl::DateTimeFormat::Numeric>* numeric) { 543 RootedValue value(cx); 544 if (!GetProperty(cx, internals, internals, property, &value)) { 545 return false; 546 } 547 548 if (value.isString()) { 549 JSLinearString* string = value.toString()->ensureLinear(cx); 550 if (!string) { 551 return false; 552 } 553 if (StringEqualsLiteral(string, "numeric")) { 554 *numeric = mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::Numeric); 555 } else { 556 MOZ_ASSERT(StringEqualsLiteral(string, "2-digit")); 557 *numeric = 558 mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::TwoDigit); 559 } 560 } else { 561 MOZ_ASSERT(value.isUndefined()); 562 } 563 564 return true; 565 } 566 567 static bool AssignMonthComponent( 568 JSContext* cx, HandleObject internals, Handle<PropertyName*> property, 569 mozilla::Maybe<mozilla::intl::DateTimeFormat::Month>* month) { 570 RootedValue value(cx); 571 if (!GetProperty(cx, internals, internals, property, &value)) { 572 return false; 573 } 574 575 if (value.isString()) { 576 JSLinearString* string = value.toString()->ensureLinear(cx); 577 if (!string) { 578 return false; 579 } 580 if (StringEqualsLiteral(string, "numeric")) { 581 *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Numeric); 582 } else if (StringEqualsLiteral(string, "2-digit")) { 583 *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::TwoDigit); 584 } else if (StringEqualsLiteral(string, "long")) { 585 *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Long); 586 } else if (StringEqualsLiteral(string, "short")) { 587 *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Short); 588 } else { 589 MOZ_ASSERT(StringEqualsLiteral(string, "narrow")); 590 *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Narrow); 591 } 592 } else { 593 MOZ_ASSERT(value.isUndefined()); 594 } 595 596 return true; 597 } 598 599 static bool AssignTimeZoneNameComponent( 600 JSContext* cx, HandleObject internals, Handle<PropertyName*> property, 601 mozilla::Maybe<mozilla::intl::DateTimeFormat::TimeZoneName>* tzName) { 602 RootedValue value(cx); 603 if (!GetProperty(cx, internals, internals, property, &value)) { 604 return false; 605 } 606 607 if (value.isString()) { 608 JSLinearString* string = value.toString()->ensureLinear(cx); 609 if (!string) { 610 return false; 611 } 612 if (StringEqualsLiteral(string, "long")) { 613 *tzName = 614 mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Long); 615 } else if (StringEqualsLiteral(string, "short")) { 616 *tzName = 617 mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Short); 618 } else if (StringEqualsLiteral(string, "shortOffset")) { 619 *tzName = mozilla::Some( 620 mozilla::intl::DateTimeFormat::TimeZoneName::ShortOffset); 621 } else if (StringEqualsLiteral(string, "longOffset")) { 622 *tzName = mozilla::Some( 623 mozilla::intl::DateTimeFormat::TimeZoneName::LongOffset); 624 } else if (StringEqualsLiteral(string, "shortGeneric")) { 625 *tzName = mozilla::Some( 626 mozilla::intl::DateTimeFormat::TimeZoneName::ShortGeneric); 627 } else { 628 MOZ_ASSERT(StringEqualsLiteral(string, "longGeneric")); 629 *tzName = mozilla::Some( 630 mozilla::intl::DateTimeFormat::TimeZoneName::LongGeneric); 631 } 632 } else { 633 MOZ_ASSERT(value.isUndefined()); 634 } 635 636 return true; 637 } 638 639 static bool AssignHourCycleComponent( 640 JSContext* cx, HandleObject internals, Handle<PropertyName*> property, 641 mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle>* hourCycle) { 642 RootedValue value(cx); 643 if (!GetProperty(cx, internals, internals, property, &value)) { 644 return false; 645 } 646 647 if (value.isString()) { 648 JSLinearString* string = value.toString()->ensureLinear(cx); 649 if (!string) { 650 return false; 651 } 652 if (StringEqualsLiteral(string, "h11")) { 653 *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H11); 654 } else if (StringEqualsLiteral(string, "h12")) { 655 *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H12); 656 } else if (StringEqualsLiteral(string, "h23")) { 657 *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H23); 658 } else { 659 MOZ_ASSERT(StringEqualsLiteral(string, "h24")); 660 *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H24); 661 } 662 } else { 663 MOZ_ASSERT(value.isUndefined()); 664 } 665 666 return true; 667 } 668 669 static bool AssignHour12Component(JSContext* cx, HandleObject internals, 670 mozilla::Maybe<bool>* hour12) { 671 RootedValue value(cx); 672 if (!GetProperty(cx, internals, internals, cx->names().hour12, &value)) { 673 return false; 674 } 675 if (value.isBoolean()) { 676 *hour12 = mozilla::Some(value.toBoolean()); 677 } else { 678 MOZ_ASSERT(value.isUndefined()); 679 } 680 681 return true; 682 } 683 684 static bool AssignDateTimeLength( 685 JSContext* cx, HandleObject internals, Handle<PropertyName*> property, 686 mozilla::Maybe<mozilla::intl::DateTimeFormat::Style>* style) { 687 RootedValue value(cx); 688 if (!GetProperty(cx, internals, internals, property, &value)) { 689 return false; 690 } 691 692 if (value.isString()) { 693 JSLinearString* string = value.toString()->ensureLinear(cx); 694 if (!string) { 695 return false; 696 } 697 if (StringEqualsLiteral(string, "full")) { 698 *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Full); 699 } else if (StringEqualsLiteral(string, "long")) { 700 *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long); 701 } else if (StringEqualsLiteral(string, "medium")) { 702 *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Medium); 703 } else { 704 MOZ_ASSERT(StringEqualsLiteral(string, "short")); 705 *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short); 706 } 707 } else { 708 MOZ_ASSERT(value.isUndefined()); 709 } 710 711 return true; 712 } 713 714 enum class Required { Date, Time, YearMonth, MonthDay, Any }; 715 716 enum class Defaults { Date, Time, YearMonth, MonthDay, ZonedDateTime, All }; 717 718 enum class Inherit { All, Relevant }; 719 720 struct DateTimeFormatArgs { 721 Required required; 722 Defaults defaults; 723 Inherit inherit; 724 }; 725 726 /** 727 * Get the "required" argument passed to CreateDateTimeFormat. 728 */ 729 static bool GetRequired(JSContext* cx, Handle<JSObject*> internals, 730 Required* result) { 731 Rooted<Value> value(cx); 732 if (!GetProperty(cx, internals, internals, cx->names().required, &value)) { 733 return false; 734 } 735 MOZ_ASSERT(value.isString()); 736 737 JSLinearString* string = value.toString()->ensureLinear(cx); 738 if (!string) { 739 return false; 740 } 741 742 if (StringEqualsLiteral(string, "date")) { 743 *result = Required::Date; 744 } else if (StringEqualsLiteral(string, "time")) { 745 *result = Required::Time; 746 } else { 747 MOZ_ASSERT(StringEqualsLiteral(string, "any")); 748 *result = Required::Any; 749 } 750 return true; 751 } 752 753 /** 754 * Get the "defaults" argument passed to CreateDateTimeFormat. 755 */ 756 static bool GetDefaults(JSContext* cx, Handle<JSObject*> internals, 757 Defaults* result) { 758 Rooted<Value> value(cx); 759 if (!GetProperty(cx, internals, internals, cx->names().defaults, &value)) { 760 return false; 761 } 762 MOZ_ASSERT(value.isString()); 763 764 JSLinearString* string = value.toString()->ensureLinear(cx); 765 if (!string) { 766 return false; 767 } 768 769 if (StringEqualsLiteral(string, "date")) { 770 *result = Defaults::Date; 771 } else if (StringEqualsLiteral(string, "time")) { 772 *result = Defaults::Time; 773 } else { 774 MOZ_ASSERT(StringEqualsLiteral(string, "all")); 775 *result = Defaults::All; 776 } 777 return true; 778 } 779 780 /** 781 * Compute the (required, defaults, inherit) arguments passed to 782 * GetDateTimeFormat. 783 */ 784 static bool GetDateTimeFormatArgs(JSContext* cx, Handle<JSObject*> internals, 785 DateTimeValueKind kind, 786 DateTimeFormatArgs* result) { 787 switch (kind) { 788 case DateTimeValueKind::Number: { 789 Required required; 790 if (!GetRequired(cx, internals, &required)) { 791 return false; 792 } 793 Defaults defaults; 794 if (!GetDefaults(cx, internals, &defaults)) { 795 return false; 796 } 797 *result = {required, defaults, Inherit::All}; 798 return true; 799 } 800 case DateTimeValueKind::TemporalDate: 801 *result = {Required::Date, Defaults::Date, Inherit::Relevant}; 802 return true; 803 case DateTimeValueKind::TemporalTime: 804 *result = {Required::Time, Defaults::Time, Inherit::Relevant}; 805 return true; 806 case DateTimeValueKind::TemporalDateTime: 807 *result = {Required::Any, Defaults::All, Inherit::Relevant}; 808 return true; 809 case DateTimeValueKind::TemporalYearMonth: 810 *result = {Required::YearMonth, Defaults::YearMonth, Inherit::Relevant}; 811 return true; 812 case DateTimeValueKind::TemporalMonthDay: 813 *result = {Required::MonthDay, Defaults::MonthDay, Inherit::Relevant}; 814 return true; 815 case DateTimeValueKind::TemporalZonedDateTime: 816 *result = {Required::Any, Defaults::ZonedDateTime, Inherit::All}; 817 return true; 818 case DateTimeValueKind::TemporalInstant: 819 *result = {Required::Any, Defaults::All, Inherit::All}; 820 return true; 821 } 822 MOZ_CRASH("invalid date-time value kind"); 823 } 824 825 enum class DateTimeField { 826 Weekday, 827 Era, 828 Year, 829 Month, 830 Day, 831 DayPeriod, 832 Hour, 833 Minute, 834 Second, 835 FractionalSecondDigits, 836 }; 837 838 /** 839 * GetDateTimeFormat ( formats, matcher, options, required, defaults, inherit ) 840 * 841 * https://tc39.es/proposal-temporal/#sec-getdatetimeformat 842 */ 843 static mozilla::Maybe<mozilla::intl::DateTimeFormat::ComponentsBag> 844 GetDateTimeFormat(const mozilla::intl::DateTimeFormat::ComponentsBag& options, 845 Required required, Defaults defaults, Inherit inherit) { 846 // Steps 1-5. 847 mozilla::EnumSet<DateTimeField> requiredOptions; 848 switch (required) { 849 case Required::Date: 850 requiredOptions = { 851 DateTimeField::Weekday, 852 DateTimeField::Year, 853 DateTimeField::Month, 854 DateTimeField::Day, 855 }; 856 break; 857 case Required::Time: 858 requiredOptions = { 859 DateTimeField::DayPeriod, 860 DateTimeField::Hour, 861 DateTimeField::Minute, 862 DateTimeField::Second, 863 DateTimeField::FractionalSecondDigits, 864 }; 865 break; 866 case Required::YearMonth: 867 requiredOptions = { 868 DateTimeField::Year, 869 DateTimeField::Month, 870 }; 871 break; 872 case Required::MonthDay: 873 requiredOptions = { 874 DateTimeField::Month, 875 DateTimeField::Day, 876 }; 877 break; 878 case Required::Any: 879 requiredOptions = { 880 DateTimeField::Weekday, 881 DateTimeField::Year, 882 DateTimeField::Month, 883 DateTimeField::Day, 884 DateTimeField::DayPeriod, 885 DateTimeField::Hour, 886 DateTimeField::Minute, 887 DateTimeField::Second, 888 DateTimeField::FractionalSecondDigits, 889 }; 890 break; 891 } 892 MOZ_ASSERT(!requiredOptions.contains(DateTimeField::Era), 893 "standalone era not supported"); 894 895 // Steps 6-10. 896 mozilla::EnumSet<DateTimeField> defaultOptions; 897 switch (defaults) { 898 case Defaults::Date: 899 defaultOptions = { 900 DateTimeField::Year, 901 DateTimeField::Month, 902 DateTimeField::Day, 903 }; 904 break; 905 case Defaults::Time: 906 defaultOptions = { 907 DateTimeField::Hour, 908 DateTimeField::Minute, 909 DateTimeField::Second, 910 }; 911 break; 912 case Defaults::YearMonth: 913 defaultOptions = { 914 DateTimeField::Year, 915 DateTimeField::Month, 916 }; 917 break; 918 case Defaults::MonthDay: 919 defaultOptions = { 920 DateTimeField::Month, 921 DateTimeField::Day, 922 }; 923 break; 924 case Defaults::ZonedDateTime: 925 case Defaults::All: 926 defaultOptions = { 927 DateTimeField::Year, DateTimeField::Month, DateTimeField::Day, 928 DateTimeField::Hour, DateTimeField::Minute, DateTimeField::Second, 929 }; 930 break; 931 } 932 MOZ_ASSERT(!defaultOptions.contains(DateTimeField::Weekday)); 933 MOZ_ASSERT(!defaultOptions.contains(DateTimeField::Era)); 934 MOZ_ASSERT(!defaultOptions.contains(DateTimeField::DayPeriod)); 935 MOZ_ASSERT(!defaultOptions.contains(DateTimeField::FractionalSecondDigits)); 936 937 // Steps 11-12. 938 mozilla::intl::DateTimeFormat::ComponentsBag formatOptions; 939 if (inherit == Inherit::All) { 940 // Step 11.a. 941 formatOptions = options; 942 } else { 943 // Step 12.a. (Implicit) 944 945 // Step 12.b. 946 switch (required) { 947 case Required::Date: 948 case Required::YearMonth: 949 case Required::Any: 950 formatOptions.era = options.era; 951 break; 952 case Required::Time: 953 case Required::MonthDay: 954 // |era| option not applicable for these types. 955 break; 956 } 957 958 // Step 12.c. 959 switch (required) { 960 case Required::Time: 961 case Required::Any: 962 formatOptions.hourCycle = options.hourCycle; 963 formatOptions.hour12 = options.hour12; 964 break; 965 case Required::Date: 966 case Required::YearMonth: 967 case Required::MonthDay: 968 // |hourCycle| and |hour12| options not applicable for these types. 969 break; 970 } 971 } 972 973 // Steps 13-14. 974 bool anyPresent = options.weekday || options.year || options.month || 975 options.day || options.dayPeriod || options.hour || 976 options.minute || options.second || 977 options.fractionalSecondDigits; 978 979 // Step 15. 980 bool needDefaults = true; 981 982 // Step 16. (Loop unrolled) 983 if (requiredOptions.contains(DateTimeField::Weekday) && options.weekday) { 984 formatOptions.weekday = options.weekday; 985 needDefaults = false; 986 } 987 if (requiredOptions.contains(DateTimeField::Year) && options.year) { 988 formatOptions.year = options.year; 989 needDefaults = false; 990 } 991 if (requiredOptions.contains(DateTimeField::Month) && options.month) { 992 formatOptions.month = options.month; 993 needDefaults = false; 994 } 995 if (requiredOptions.contains(DateTimeField::Day) && options.day) { 996 formatOptions.day = options.day; 997 needDefaults = false; 998 } 999 if (requiredOptions.contains(DateTimeField::DayPeriod) && options.dayPeriod) { 1000 formatOptions.dayPeriod = options.dayPeriod; 1001 needDefaults = false; 1002 } 1003 if (requiredOptions.contains(DateTimeField::Hour) && options.hour) { 1004 formatOptions.hour = options.hour; 1005 needDefaults = false; 1006 } 1007 if (requiredOptions.contains(DateTimeField::Minute) && options.minute) { 1008 formatOptions.minute = options.minute; 1009 needDefaults = false; 1010 } 1011 if (requiredOptions.contains(DateTimeField::Second) && options.second) { 1012 formatOptions.second = options.second; 1013 needDefaults = false; 1014 } 1015 if (requiredOptions.contains(DateTimeField::FractionalSecondDigits) && 1016 options.fractionalSecondDigits) { 1017 formatOptions.fractionalSecondDigits = options.fractionalSecondDigits; 1018 needDefaults = false; 1019 } 1020 1021 // Step 17. 1022 if (needDefaults) { 1023 // Step 17.a. 1024 if (anyPresent && inherit == Inherit::Relevant) { 1025 return mozilla::Nothing(); 1026 } 1027 1028 // Step 17.b. (Loop unrolled) 1029 auto numericOption = 1030 mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::Numeric); 1031 if (defaultOptions.contains(DateTimeField::Year)) { 1032 formatOptions.year = numericOption; 1033 } 1034 if (defaultOptions.contains(DateTimeField::Month)) { 1035 formatOptions.month = 1036 mozilla::Some(mozilla::intl::DateTimeFormat::Month::Numeric); 1037 } 1038 if (defaultOptions.contains(DateTimeField::Day)) { 1039 formatOptions.day = numericOption; 1040 } 1041 if (defaultOptions.contains(DateTimeField::Hour)) { 1042 formatOptions.hour = numericOption; 1043 } 1044 if (defaultOptions.contains(DateTimeField::Minute)) { 1045 formatOptions.minute = numericOption; 1046 } 1047 if (defaultOptions.contains(DateTimeField::Second)) { 1048 formatOptions.second = numericOption; 1049 } 1050 1051 // Step 17.c. 1052 if (defaults == Defaults::ZonedDateTime && !formatOptions.timeZoneName) { 1053 formatOptions.timeZoneName = 1054 mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Short); 1055 } 1056 } 1057 1058 // Steps 18-20. (Performed in caller). 1059 1060 return mozilla::Some(formatOptions); 1061 } 1062 1063 /** 1064 * AdjustDateTimeStyleFormat ( formats, baseFormat, matcher, allowedOptions ) 1065 * 1066 * https://tc39.es/proposal-temporal/#sec-adjustdatetimestyleformat 1067 */ 1068 static mozilla::Result<mozilla::intl::DateTimeFormat::ComponentsBag, 1069 mozilla::intl::ICUError> 1070 AdjustDateTimeStyleFormat(mozilla::intl::DateTimeFormat* baseFormat, 1071 mozilla::EnumSet<DateTimeField> allowedOptions) { 1072 // Step 1. 1073 mozilla::intl::DateTimeFormat::ComponentsBag formatOptions; 1074 1075 // Step 2. (Loop unrolled) 1076 auto result = baseFormat->ResolveComponents(); 1077 if (result.isErr()) { 1078 return result.propagateErr(); 1079 } 1080 auto options = result.unwrap(); 1081 1082 if (allowedOptions.contains(DateTimeField::Era) && options.era) { 1083 formatOptions.era = options.era; 1084 } 1085 if (allowedOptions.contains(DateTimeField::Weekday) && options.weekday) { 1086 formatOptions.weekday = options.weekday; 1087 } 1088 if (allowedOptions.contains(DateTimeField::Year) && options.year) { 1089 formatOptions.year = options.year; 1090 } 1091 if (allowedOptions.contains(DateTimeField::Month) && options.month) { 1092 formatOptions.month = options.month; 1093 } 1094 if (allowedOptions.contains(DateTimeField::Day) && options.day) { 1095 formatOptions.day = options.day; 1096 } 1097 if (allowedOptions.contains(DateTimeField::DayPeriod) && options.dayPeriod) { 1098 formatOptions.dayPeriod = options.dayPeriod; 1099 } 1100 if (allowedOptions.contains(DateTimeField::Hour) && options.hour) { 1101 formatOptions.hour = options.hour; 1102 formatOptions.hourCycle = options.hourCycle; 1103 } 1104 if (allowedOptions.contains(DateTimeField::Minute) && options.minute) { 1105 formatOptions.minute = options.minute; 1106 } 1107 if (allowedOptions.contains(DateTimeField::Second) && options.second) { 1108 formatOptions.second = options.second; 1109 } 1110 if (allowedOptions.contains(DateTimeField::FractionalSecondDigits) && 1111 options.fractionalSecondDigits) { 1112 formatOptions.fractionalSecondDigits = options.fractionalSecondDigits; 1113 } 1114 1115 // Steps 3-5. (Performed in caller) 1116 1117 return formatOptions; 1118 } 1119 1120 static const char* DateTimeValueKindToString(DateTimeValueKind kind) { 1121 switch (kind) { 1122 case DateTimeValueKind::Number: 1123 return "number"; 1124 case DateTimeValueKind::TemporalDate: 1125 return "Temporal.PlainDate"; 1126 case DateTimeValueKind::TemporalTime: 1127 return "Temporal.PlainTime"; 1128 case DateTimeValueKind::TemporalDateTime: 1129 return "Temporal.PlainDateTime"; 1130 case DateTimeValueKind::TemporalYearMonth: 1131 return "Temporal.PlainYearMonth"; 1132 case DateTimeValueKind::TemporalMonthDay: 1133 return "Temporal.PlainMonthDay"; 1134 case DateTimeValueKind::TemporalZonedDateTime: 1135 return "Temporal.ZonedDateTime"; 1136 case DateTimeValueKind::TemporalInstant: 1137 return "Temporal.Instant"; 1138 } 1139 MOZ_CRASH("invalid date-time value kind"); 1140 } 1141 1142 class TimeZoneOffsetString { 1143 static constexpr std::u16string_view GMT = u"GMT"; 1144 1145 // Time zone offset string format is "±hh:mm". 1146 static constexpr size_t offsetLength = 6; 1147 1148 // ICU custom time zones are in the format "GMT±hh:mm". 1149 char16_t timeZone_[GMT.size() + offsetLength] = {}; 1150 1151 TimeZoneOffsetString() = default; 1152 1153 public: 1154 TimeZoneOffsetString(const TimeZoneOffsetString& other) { *this = other; } 1155 1156 TimeZoneOffsetString& operator=(const TimeZoneOffsetString& other) { 1157 std::copy_n(other.timeZone_, std::size(timeZone_), timeZone_); 1158 return *this; 1159 } 1160 1161 operator mozilla::Span<const char16_t>() const { 1162 return mozilla::Span(timeZone_); 1163 } 1164 1165 /** 1166 * |timeZone| is either a canonical IANA time zone identifier or a normalized 1167 * time zone offset string. 1168 */ 1169 static mozilla::Maybe<TimeZoneOffsetString> from( 1170 const JSLinearString* timeZone) { 1171 MOZ_RELEASE_ASSERT(!timeZone->empty(), "time zone is a non-empty string"); 1172 1173 // If the time zone string starts with either "+" or "-", it is a normalized 1174 // time zone offset string, because (canonical) IANA time zone identifiers 1175 // can't start with "+" or "-". 1176 char16_t timeZoneSign = timeZone->latin1OrTwoByteChar(0); 1177 MOZ_ASSERT(timeZoneSign != 0x2212, 1178 "Minus sign is normalized to Ascii minus"); 1179 if (timeZoneSign != '+' && timeZoneSign != '-') { 1180 return mozilla::Nothing(); 1181 } 1182 1183 // Release assert because we don't want CopyChars to write out-of-bounds. 1184 MOZ_RELEASE_ASSERT(timeZone->length() == offsetLength); 1185 1186 // Self-hosted code has normalized offset strings to the format "±hh:mm". 1187 MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(1))); 1188 MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(2))); 1189 MOZ_ASSERT(timeZone->latin1OrTwoByteChar(3) == ':'); 1190 MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(4))); 1191 MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(5))); 1192 1193 // Self-hosted code has verified the offset is at most ±23:59. 1194 #ifdef DEBUG 1195 auto twoDigit = [&](size_t offset) { 1196 auto c1 = timeZone->latin1OrTwoByteChar(offset); 1197 auto c2 = timeZone->latin1OrTwoByteChar(offset + 1); 1198 return mozilla::AsciiAlphanumericToNumber(c1) * 10 + 1199 mozilla::AsciiAlphanumericToNumber(c2); 1200 }; 1201 1202 int32_t hours = twoDigit(1); 1203 MOZ_ASSERT(0 <= hours && hours <= 23); 1204 1205 int32_t minutes = twoDigit(4); 1206 MOZ_ASSERT(0 <= minutes && minutes <= 59); 1207 #endif 1208 1209 TimeZoneOffsetString result{}; 1210 1211 // Copy the string "GMT" followed by the offset string. 1212 size_t copied = GMT.copy(result.timeZone_, GMT.size()); 1213 CopyChars(result.timeZone_ + copied, *timeZone); 1214 1215 return mozilla::Some(result); 1216 } 1217 }; 1218 1219 /** 1220 * Returns a new mozilla::intl::DateTimeFormat with the locale and date-time 1221 * formatting options of the given DateTimeFormat. 1222 */ 1223 static mozilla::intl::DateTimeFormat* NewDateTimeFormat( 1224 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 1225 DateTimeValueKind kind) { 1226 RootedValue value(cx); 1227 1228 RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat)); 1229 if (!internals) { 1230 return nullptr; 1231 } 1232 1233 UniqueChars locale = DateTimeFormatLocale(cx, internals); 1234 if (!locale) { 1235 return nullptr; 1236 } 1237 1238 if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) { 1239 return nullptr; 1240 } 1241 1242 Rooted<JSLinearString*> timeZoneString(cx, 1243 value.toString()->ensureLinear(cx)); 1244 if (!timeZoneString) { 1245 return nullptr; 1246 } 1247 1248 AutoStableStringChars timeZone(cx); 1249 mozilla::Span<const char16_t> timeZoneChars{}; 1250 1251 auto timeZoneOffset = TimeZoneOffsetString::from(timeZoneString); 1252 if (timeZoneOffset) { 1253 timeZoneChars = *timeZoneOffset; 1254 } else { 1255 if (!timeZone.initTwoByte(cx, timeZoneString)) { 1256 return nullptr; 1257 } 1258 timeZoneChars = timeZone.twoByteRange(); 1259 } 1260 1261 if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) { 1262 return nullptr; 1263 } 1264 bool hasPattern = value.isString(); 1265 1266 if (!GetProperty(cx, internals, internals, cx->names().timeStyle, &value)) { 1267 return nullptr; 1268 } 1269 bool hasStyle = value.isString(); 1270 if (!hasStyle) { 1271 if (!GetProperty(cx, internals, internals, cx->names().dateStyle, &value)) { 1272 return nullptr; 1273 } 1274 hasStyle = value.isString(); 1275 } 1276 1277 mozilla::UniquePtr<mozilla::intl::DateTimeFormat> df = nullptr; 1278 if (hasPattern) { 1279 // This is a DateTimeFormat defined by a pattern option. This is internal 1280 // to Mozilla, and not part of the ECMA-402 API. 1281 if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) { 1282 return nullptr; 1283 } 1284 1285 AutoStableStringChars pattern(cx); 1286 if (!pattern.initTwoByte(cx, value.toString())) { 1287 return nullptr; 1288 } 1289 1290 auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromPattern( 1291 mozilla::MakeStringSpan(locale.get()), pattern.twoByteRange(), 1292 mozilla::Some(timeZoneChars)); 1293 if (dfResult.isErr()) { 1294 intl::ReportInternalError(cx, dfResult.unwrapErr()); 1295 return nullptr; 1296 } 1297 1298 df = dfResult.unwrap(); 1299 } else if (hasStyle) { 1300 // This is a DateTimeFormat defined by a time style or date style. 1301 mozilla::intl::DateTimeFormat::StyleBag style; 1302 if (!AssignDateTimeLength(cx, internals, cx->names().timeStyle, 1303 &style.time)) { 1304 return nullptr; 1305 } 1306 if (!AssignDateTimeLength(cx, internals, cx->names().dateStyle, 1307 &style.date)) { 1308 return nullptr; 1309 } 1310 if (!AssignHourCycleComponent(cx, internals, cx->names().hourCycle, 1311 &style.hourCycle)) { 1312 return nullptr; 1313 } 1314 1315 if (!AssignHour12Component(cx, internals, &style.hour12)) { 1316 return nullptr; 1317 } 1318 1319 switch (kind) { 1320 case DateTimeValueKind::TemporalDate: 1321 case DateTimeValueKind::TemporalYearMonth: 1322 case DateTimeValueKind::TemporalMonthDay: { 1323 if (!style.date) { 1324 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1325 JSMSG_INVALID_FORMAT_OPTIONS, 1326 DateTimeValueKindToString(kind)); 1327 return nullptr; 1328 } 1329 break; 1330 } 1331 1332 case DateTimeValueKind::TemporalTime: { 1333 if (!style.time) { 1334 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1335 JSMSG_INVALID_FORMAT_OPTIONS, 1336 DateTimeValueKindToString(kind)); 1337 return nullptr; 1338 } 1339 break; 1340 } 1341 1342 case DateTimeValueKind::Number: 1343 case DateTimeValueKind::TemporalDateTime: 1344 case DateTimeValueKind::TemporalZonedDateTime: 1345 case DateTimeValueKind::TemporalInstant: 1346 break; 1347 } 1348 1349 SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); 1350 auto* dtpg = sharedIntlData.getDateTimePatternGenerator(cx, locale.get()); 1351 if (!dtpg) { 1352 return nullptr; 1353 } 1354 1355 auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromStyle( 1356 mozilla::MakeStringSpan(locale.get()), style, dtpg, 1357 mozilla::Some(timeZoneChars)); 1358 if (dfResult.isErr()) { 1359 intl::ReportInternalError(cx, dfResult.unwrapErr()); 1360 return nullptr; 1361 } 1362 df = dfResult.unwrap(); 1363 1364 mozilla::EnumSet<DateTimeField> allowedOptions; 1365 switch (kind) { 1366 case DateTimeValueKind::TemporalDate: 1367 allowedOptions = { 1368 DateTimeField::Weekday, DateTimeField::Era, DateTimeField::Year, 1369 DateTimeField::Month, DateTimeField::Day, 1370 }; 1371 break; 1372 case DateTimeValueKind::TemporalTime: 1373 allowedOptions = { 1374 DateTimeField::DayPeriod, 1375 DateTimeField::Hour, 1376 DateTimeField::Minute, 1377 DateTimeField::Second, 1378 DateTimeField::FractionalSecondDigits, 1379 }; 1380 break; 1381 case DateTimeValueKind::TemporalDateTime: 1382 allowedOptions = { 1383 DateTimeField::Weekday, DateTimeField::Era, 1384 DateTimeField::Year, DateTimeField::Month, 1385 DateTimeField::Day, DateTimeField::DayPeriod, 1386 DateTimeField::Hour, DateTimeField::Minute, 1387 DateTimeField::Second, DateTimeField::FractionalSecondDigits, 1388 }; 1389 break; 1390 case DateTimeValueKind::TemporalYearMonth: 1391 allowedOptions = { 1392 DateTimeField::Era, 1393 DateTimeField::Year, 1394 DateTimeField::Month, 1395 }; 1396 break; 1397 case DateTimeValueKind::TemporalMonthDay: 1398 allowedOptions = { 1399 DateTimeField::Month, 1400 DateTimeField::Day, 1401 }; 1402 break; 1403 1404 case DateTimeValueKind::Number: 1405 case DateTimeValueKind::TemporalZonedDateTime: 1406 case DateTimeValueKind::TemporalInstant: 1407 break; 1408 } 1409 1410 if (!allowedOptions.isEmpty()) { 1411 auto adjusted = AdjustDateTimeStyleFormat(df.get(), allowedOptions); 1412 if (adjusted.isErr()) { 1413 intl::ReportInternalError(cx, dfResult.unwrapErr()); 1414 return nullptr; 1415 } 1416 auto bag = adjusted.unwrap(); 1417 1418 auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromComponents( 1419 mozilla::MakeStringSpan(locale.get()), bag, dtpg, 1420 mozilla::Some(timeZoneChars)); 1421 if (dfResult.isErr()) { 1422 intl::ReportInternalError(cx, dfResult.unwrapErr()); 1423 return nullptr; 1424 } 1425 df = dfResult.unwrap(); 1426 } 1427 } else { 1428 // This is a DateTimeFormat defined by a components bag. 1429 mozilla::intl::DateTimeFormat::ComponentsBag bag; 1430 1431 if (!AssignTextComponent(cx, internals, cx->names().era, &bag.era)) { 1432 return nullptr; 1433 } 1434 if (!AssignNumericComponent(cx, internals, cx->names().year, &bag.year)) { 1435 return nullptr; 1436 } 1437 if (!AssignMonthComponent(cx, internals, cx->names().month, &bag.month)) { 1438 return nullptr; 1439 } 1440 if (!AssignNumericComponent(cx, internals, cx->names().day, &bag.day)) { 1441 return nullptr; 1442 } 1443 if (!AssignTextComponent(cx, internals, cx->names().weekday, 1444 &bag.weekday)) { 1445 return nullptr; 1446 } 1447 if (!AssignNumericComponent(cx, internals, cx->names().hour, &bag.hour)) { 1448 return nullptr; 1449 } 1450 if (!AssignNumericComponent(cx, internals, cx->names().minute, 1451 &bag.minute)) { 1452 return nullptr; 1453 } 1454 if (!AssignNumericComponent(cx, internals, cx->names().second, 1455 &bag.second)) { 1456 return nullptr; 1457 } 1458 if (!AssignTimeZoneNameComponent(cx, internals, cx->names().timeZoneName, 1459 &bag.timeZoneName)) { 1460 return nullptr; 1461 } 1462 if (!AssignHourCycleComponent(cx, internals, cx->names().hourCycle, 1463 &bag.hourCycle)) { 1464 return nullptr; 1465 } 1466 if (!AssignTextComponent(cx, internals, cx->names().dayPeriod, 1467 &bag.dayPeriod)) { 1468 return nullptr; 1469 } 1470 if (!AssignHour12Component(cx, internals, &bag.hour12)) { 1471 return nullptr; 1472 } 1473 1474 if (!GetProperty(cx, internals, internals, 1475 cx->names().fractionalSecondDigits, &value)) { 1476 return nullptr; 1477 } 1478 if (value.isInt32()) { 1479 bag.fractionalSecondDigits = mozilla::Some(value.toInt32()); 1480 } else { 1481 MOZ_ASSERT(value.isUndefined()); 1482 } 1483 1484 DateTimeFormatArgs dateTimeFormatArgs; 1485 if (!GetDateTimeFormatArgs(cx, internals, kind, &dateTimeFormatArgs)) { 1486 return nullptr; 1487 } 1488 auto [required, defaults, inherit] = dateTimeFormatArgs; 1489 1490 auto resolvedBag = GetDateTimeFormat(bag, required, defaults, inherit); 1491 if (!resolvedBag) { 1492 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1493 JSMSG_INVALID_FORMAT_OPTIONS, 1494 DateTimeValueKindToString(kind)); 1495 return nullptr; 1496 } 1497 bag = *resolvedBag; 1498 1499 SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref(); 1500 auto* dtpg = sharedIntlData.getDateTimePatternGenerator(cx, locale.get()); 1501 if (!dtpg) { 1502 return nullptr; 1503 } 1504 1505 auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromComponents( 1506 mozilla::MakeStringSpan(locale.get()), bag, dtpg, 1507 mozilla::Some(timeZoneChars)); 1508 if (dfResult.isErr()) { 1509 intl::ReportInternalError(cx, dfResult.unwrapErr()); 1510 return nullptr; 1511 } 1512 df = dfResult.unwrap(); 1513 } 1514 1515 return df.release(); 1516 } 1517 1518 void js::DateTimeFormatObject::maybeClearCache(DateTimeValueKind kind) { 1519 if (getDateTimeValueKind() == kind) { 1520 return; 1521 } 1522 setDateTimeValueKind(kind); 1523 1524 if (auto* df = getDateFormat()) { 1525 intl::RemoveICUCellMemory( 1526 this, DateTimeFormatObject::UDateFormatEstimatedMemoryUse); 1527 delete df; 1528 1529 setDateFormat(nullptr); 1530 } 1531 1532 if (auto* dif = getDateIntervalFormat()) { 1533 intl::RemoveICUCellMemory( 1534 this, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse); 1535 delete dif; 1536 1537 setDateIntervalFormat(nullptr); 1538 } 1539 } 1540 1541 static mozilla::intl::DateTimeFormat* GetOrCreateDateTimeFormat( 1542 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 1543 DateTimeValueKind kind) { 1544 // Clear previously created formatters if their type doesn't match. 1545 dateTimeFormat->maybeClearCache(kind); 1546 1547 // Obtain a cached mozilla::intl::DateTimeFormat object. 1548 mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat(); 1549 if (df) { 1550 return df; 1551 } 1552 1553 df = NewDateTimeFormat(cx, dateTimeFormat, kind); 1554 if (!df) { 1555 return nullptr; 1556 } 1557 dateTimeFormat->setDateFormat(df); 1558 1559 intl::AddICUCellMemory(dateTimeFormat, 1560 DateTimeFormatObject::UDateFormatEstimatedMemoryUse); 1561 return df; 1562 } 1563 1564 template <typename T> 1565 static bool SetResolvedProperty(JSContext* cx, HandleObject resolved, 1566 Handle<PropertyName*> name, 1567 mozilla::Maybe<T> intlProp) { 1568 if (!intlProp) { 1569 return true; 1570 } 1571 JSString* str = NewStringCopyZ<CanGC>( 1572 cx, mozilla::intl::DateTimeFormat::ToString(*intlProp)); 1573 if (!str) { 1574 return false; 1575 } 1576 RootedValue value(cx, StringValue(str)); 1577 return DefineDataProperty(cx, resolved, name, value); 1578 } 1579 1580 bool js::intl_resolveDateTimeFormatComponents(JSContext* cx, unsigned argc, 1581 Value* vp) { 1582 CallArgs args = CallArgsFromVp(argc, vp); 1583 MOZ_ASSERT(args.length() == 3); 1584 MOZ_ASSERT(args[0].isObject()); 1585 MOZ_ASSERT(args[1].isObject()); 1586 MOZ_ASSERT(args[2].isBoolean()); 1587 1588 Rooted<DateTimeFormatObject*> dateTimeFormat(cx); 1589 dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>(); 1590 1591 RootedObject resolved(cx, &args[1].toObject()); 1592 1593 bool includeDateTimeFields = args[2].toBoolean(); 1594 1595 mozilla::intl::DateTimeFormat* df = 1596 GetOrCreateDateTimeFormat(cx, dateTimeFormat, DateTimeValueKind::Number); 1597 if (!df) { 1598 return false; 1599 } 1600 1601 auto result = df->ResolveComponents(); 1602 if (result.isErr()) { 1603 intl::ReportInternalError(cx, result.unwrapErr()); 1604 return false; 1605 } 1606 1607 mozilla::intl::DateTimeFormat::ComponentsBag components = result.unwrap(); 1608 1609 // Map the resolved mozilla::intl::DateTimeFormat::ComponentsBag to the 1610 // options object as returned by DateTimeFormat.prototype.resolvedOptions. 1611 // 1612 // Resolved options must match the ordering as defined in: 1613 // https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.resolvedoptions 1614 1615 if (!SetResolvedProperty(cx, resolved, cx->names().hourCycle, 1616 components.hourCycle)) { 1617 return false; 1618 } 1619 1620 if (components.hour12) { 1621 RootedValue value(cx, BooleanValue(*components.hour12)); 1622 if (!DefineDataProperty(cx, resolved, cx->names().hour12, value)) { 1623 return false; 1624 } 1625 } 1626 1627 if (!includeDateTimeFields) { 1628 args.rval().setUndefined(); 1629 // Do not include date time fields. 1630 return true; 1631 } 1632 1633 if (!SetResolvedProperty(cx, resolved, cx->names().weekday, 1634 components.weekday)) { 1635 return false; 1636 } 1637 if (!SetResolvedProperty(cx, resolved, cx->names().era, components.era)) { 1638 return false; 1639 } 1640 if (!SetResolvedProperty(cx, resolved, cx->names().year, components.year)) { 1641 return false; 1642 } 1643 if (!SetResolvedProperty(cx, resolved, cx->names().month, components.month)) { 1644 return false; 1645 } 1646 if (!SetResolvedProperty(cx, resolved, cx->names().day, components.day)) { 1647 return false; 1648 } 1649 if (!SetResolvedProperty(cx, resolved, cx->names().dayPeriod, 1650 components.dayPeriod)) { 1651 return false; 1652 } 1653 if (!SetResolvedProperty(cx, resolved, cx->names().hour, components.hour)) { 1654 return false; 1655 } 1656 if (!SetResolvedProperty(cx, resolved, cx->names().minute, 1657 components.minute)) { 1658 return false; 1659 } 1660 if (!SetResolvedProperty(cx, resolved, cx->names().second, 1661 components.second)) { 1662 return false; 1663 } 1664 if (!SetResolvedProperty(cx, resolved, cx->names().timeZoneName, 1665 components.timeZoneName)) { 1666 return false; 1667 } 1668 1669 if (components.fractionalSecondDigits) { 1670 RootedValue value(cx, Int32Value(*components.fractionalSecondDigits)); 1671 if (!DefineDataProperty(cx, resolved, cx->names().fractionalSecondDigits, 1672 value)) { 1673 return false; 1674 } 1675 } 1676 1677 args.rval().setUndefined(); 1678 return true; 1679 } 1680 1681 /** 1682 * ToDateTimeFormattable ( value ) 1683 * 1684 * https://tc39.es/proposal-temporal/#sec-todatetimeformattable 1685 */ 1686 static auto ToDateTimeFormattable(const Value& value) { 1687 // Step 1. (Inlined IsTemporalObject) 1688 if (value.isObject()) { 1689 auto* obj = CheckedUnwrapStatic(&value.toObject()); 1690 if (obj) { 1691 if (obj->is<PlainDateObject>()) { 1692 return DateTimeValueKind::TemporalDate; 1693 } 1694 if (obj->is<PlainDateTimeObject>()) { 1695 return DateTimeValueKind::TemporalDateTime; 1696 } 1697 if (obj->is<PlainTimeObject>()) { 1698 return DateTimeValueKind::TemporalTime; 1699 } 1700 if (obj->is<PlainYearMonthObject>()) { 1701 return DateTimeValueKind::TemporalYearMonth; 1702 } 1703 if (obj->is<PlainMonthDayObject>()) { 1704 return DateTimeValueKind::TemporalMonthDay; 1705 } 1706 if (obj->is<ZonedDateTimeObject>()) { 1707 return DateTimeValueKind::TemporalZonedDateTime; 1708 } 1709 if (obj->is<InstantObject>()) { 1710 return DateTimeValueKind::TemporalInstant; 1711 } 1712 return DateTimeValueKind::Number; 1713 } 1714 } 1715 1716 // Step 2. (ToNumber performed in caller) 1717 return DateTimeValueKind::Number; 1718 } 1719 1720 static bool ResolveCalendarAndTimeZone( 1721 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) { 1722 Rooted<JSObject*> internals(cx, intl::GetInternalsObject(cx, dateTimeFormat)); 1723 if (!internals) { 1724 return false; 1725 } 1726 1727 Rooted<Value> calendarValue(cx); 1728 if (!GetProperty(cx, internals, internals, cx->names().calendar, 1729 &calendarValue)) { 1730 return false; 1731 } 1732 Rooted<JSString*> calendarString(cx, calendarValue.toString()); 1733 1734 Rooted<CalendarValue> calendar(cx); 1735 if (!CanonicalizeCalendar(cx, calendarString, &calendar)) { 1736 return false; 1737 } 1738 1739 Rooted<Value> timeZoneValue(cx); 1740 if (!GetProperty(cx, internals, internals, cx->names().timeZone, 1741 &timeZoneValue)) { 1742 return false; 1743 } 1744 Rooted<JSString*> timeZoneString(cx, timeZoneValue.toString()); 1745 1746 Rooted<ParsedTimeZone> parsedTimeZone(cx); 1747 Rooted<TimeZoneValue> timeZone(cx); 1748 if (!ParseTemporalTimeZoneString(cx, timeZoneString, &parsedTimeZone) || 1749 !ToTemporalTimeZone(cx, parsedTimeZone, &timeZone)) { 1750 return false; 1751 } 1752 1753 dateTimeFormat->setCalendar(calendar); 1754 dateTimeFormat->setTimeZone(timeZone); 1755 return true; 1756 } 1757 1758 /** 1759 * HandleDateTimeTemporalDate ( dateTimeFormat, temporalDate ) 1760 * 1761 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldate 1762 */ 1763 static bool HandleDateTimeTemporalDate( 1764 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 1765 Handle<PlainDateObject*> unwrappedTemporalDate, ClippedTime* result) { 1766 auto isoDate = unwrappedTemporalDate->date(); 1767 auto calendarId = unwrappedTemporalDate->calendar().identifier(); 1768 1769 Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar()); 1770 Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone()); 1771 if (!calendar || !timeZone) { 1772 if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) { 1773 return false; 1774 } 1775 calendar.set(dateTimeFormat->getCalendar()); 1776 timeZone.set(dateTimeFormat->getTimeZone()); 1777 } 1778 MOZ_ASSERT(calendar && timeZone); 1779 1780 // Step 1. 1781 if (calendarId != CalendarId::ISO8601 && 1782 calendarId != calendar.identifier()) { 1783 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1784 JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, 1785 CalendarIdentifier(calendarId).data(), 1786 CalendarIdentifier(calendar).data()); 1787 return false; 1788 } 1789 1790 // Step 2. 1791 auto isoDateTime = ISODateTime{isoDate, {12, 0, 0}}; 1792 1793 // Step 3. 1794 EpochNanoseconds epochNs; 1795 if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime, 1796 TemporalDisambiguation::Compatible, &epochNs)) { 1797 return false; 1798 } 1799 1800 // Steps 4-5. (Performed in NewDateTimeFormat) 1801 1802 // Step 6. 1803 int64_t milliseconds = epochNs.floorToMilliseconds(); 1804 *result = JS::TimeClip(double(milliseconds)); 1805 return true; 1806 } 1807 1808 /** 1809 * HandleDateTimeTemporalYearMonth ( dateTimeFormat, temporalYearMonth ) 1810 * 1811 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalyearmonth 1812 */ 1813 static bool HandleDateTimeTemporalYearMonth( 1814 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 1815 Handle<PlainYearMonthObject*> unwrappedTemporalYearMonth, 1816 ClippedTime* result) { 1817 auto isoDate = unwrappedTemporalYearMonth->date(); 1818 auto calendarId = unwrappedTemporalYearMonth->calendar().identifier(); 1819 1820 Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar()); 1821 Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone()); 1822 if (!calendar || !timeZone) { 1823 if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) { 1824 return false; 1825 } 1826 calendar.set(dateTimeFormat->getCalendar()); 1827 timeZone.set(dateTimeFormat->getTimeZone()); 1828 } 1829 MOZ_ASSERT(calendar && timeZone); 1830 1831 // Step 1. 1832 if (calendarId != calendar.identifier()) { 1833 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1834 JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, 1835 CalendarIdentifier(calendarId).data(), 1836 CalendarIdentifier(calendar).data()); 1837 return false; 1838 } 1839 1840 // Step 2. 1841 auto isoDateTime = ISODateTime{isoDate, {12, 0, 0}}; 1842 1843 // Step 3. 1844 EpochNanoseconds epochNs; 1845 if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime, 1846 TemporalDisambiguation::Compatible, &epochNs)) { 1847 return false; 1848 } 1849 1850 // Steps 4-5. (Performed in NewDateTimeFormat) 1851 1852 // Step 6. 1853 int64_t milliseconds = epochNs.floorToMilliseconds(); 1854 *result = JS::TimeClip(double(milliseconds)); 1855 return true; 1856 } 1857 1858 /** 1859 * HandleDateTimeTemporalMonthDay ( dateTimeFormat, temporalMonthDay ) 1860 * 1861 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalmonthday 1862 */ 1863 static bool HandleDateTimeTemporalMonthDay( 1864 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 1865 Handle<PlainMonthDayObject*> unwrappedTemporalMonthDay, 1866 ClippedTime* result) { 1867 auto isoDate = unwrappedTemporalMonthDay->date(); 1868 auto calendarId = unwrappedTemporalMonthDay->calendar().identifier(); 1869 1870 Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar()); 1871 Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone()); 1872 if (!calendar || !timeZone) { 1873 if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) { 1874 return false; 1875 } 1876 calendar.set(dateTimeFormat->getCalendar()); 1877 timeZone.set(dateTimeFormat->getTimeZone()); 1878 } 1879 MOZ_ASSERT(calendar && timeZone); 1880 1881 // Step 1. 1882 if (calendarId != calendar.identifier()) { 1883 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1884 JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, 1885 CalendarIdentifier(calendarId).data(), 1886 CalendarIdentifier(calendar).data()); 1887 return false; 1888 } 1889 1890 // Step 2. 1891 auto isoDateTime = ISODateTime{isoDate, {12, 0, 0}}; 1892 1893 // Step 3. 1894 EpochNanoseconds epochNs; 1895 if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime, 1896 TemporalDisambiguation::Compatible, &epochNs)) { 1897 return false; 1898 } 1899 1900 // Steps 4-5. (Performed in NewDateTimeFormat) 1901 1902 // Step 6. 1903 int64_t milliseconds = epochNs.floorToMilliseconds(); 1904 *result = JS::TimeClip(double(milliseconds)); 1905 return true; 1906 } 1907 1908 /** 1909 * HandleDateTimeTemporalTime ( dateTimeFormat, temporalTime ) 1910 * 1911 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaltime 1912 */ 1913 static bool HandleDateTimeTemporalTime( 1914 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 1915 Handle<PlainTimeObject*> unwrappedTemporalTime, ClippedTime* result) { 1916 auto time = unwrappedTemporalTime->time(); 1917 1918 Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone()); 1919 if (!timeZone) { 1920 if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) { 1921 return false; 1922 } 1923 timeZone.set(dateTimeFormat->getTimeZone()); 1924 } 1925 MOZ_ASSERT(timeZone); 1926 1927 // Steps 1-2. 1928 auto isoDateTime = ISODateTime{{1970, 1, 1}, time}; 1929 1930 // Step 3. 1931 EpochNanoseconds epochNs; 1932 if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime, 1933 TemporalDisambiguation::Compatible, &epochNs)) { 1934 return false; 1935 } 1936 1937 // Steps 4-5. (Performed in NewDateTimeFormat) 1938 1939 // Step 6. 1940 int64_t milliseconds = epochNs.floorToMilliseconds(); 1941 *result = JS::TimeClip(double(milliseconds)); 1942 return true; 1943 } 1944 1945 /** 1946 * HandleDateTimeTemporalDateTime ( dateTimeFormat, dateTime ) 1947 * 1948 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldatetime 1949 */ 1950 static bool HandleDateTimeTemporalDateTime( 1951 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 1952 Handle<PlainDateTimeObject*> unwrappedDateTime, ClippedTime* result) { 1953 auto isoDateTime = unwrappedDateTime->dateTime(); 1954 auto calendarId = unwrappedDateTime->calendar().identifier(); 1955 1956 Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar()); 1957 Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone()); 1958 if (!calendar || !timeZone) { 1959 if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) { 1960 return false; 1961 } 1962 calendar.set(dateTimeFormat->getCalendar()); 1963 timeZone.set(dateTimeFormat->getTimeZone()); 1964 } 1965 MOZ_ASSERT(calendar && timeZone); 1966 1967 // Step 1. 1968 if (calendarId != CalendarId::ISO8601 && 1969 calendarId != calendar.identifier()) { 1970 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1971 JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, 1972 CalendarIdentifier(calendarId).data(), 1973 CalendarIdentifier(calendar).data()); 1974 return false; 1975 } 1976 1977 // Step 2. 1978 EpochNanoseconds epochNs; 1979 if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime, 1980 TemporalDisambiguation::Compatible, &epochNs)) { 1981 return false; 1982 } 1983 1984 // Step 3. (Performed in NewDateTimeFormat) 1985 1986 // Step 4. 1987 int64_t milliseconds = epochNs.floorToMilliseconds(); 1988 *result = JS::TimeClip(double(milliseconds)); 1989 return true; 1990 } 1991 1992 /** 1993 * HandleDateTimeTemporalInstant ( dateTimeFormat, instant ) 1994 * 1995 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalinstant 1996 */ 1997 static bool HandleDateTimeTemporalInstant(InstantObject* unwrappedInstant, 1998 ClippedTime* result) { 1999 // Step 1. (Performed in NewDateTimeFormat) 2000 2001 // Step 2. 2002 auto epochNs = unwrappedInstant->epochNanoseconds(); 2003 int64_t milliseconds = epochNs.floorToMilliseconds(); 2004 *result = JS::TimeClip(double(milliseconds)); 2005 return true; 2006 } 2007 2008 /** 2009 * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] ) 2010 */ 2011 static bool HandleDateTimeTemporalZonedDateTime( 2012 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 2013 Handle<ZonedDateTimeObject*> unwrappedZonedDateTime, ClippedTime* result) { 2014 auto epochNs = unwrappedZonedDateTime->epochNanoseconds(); 2015 auto calendarId = unwrappedZonedDateTime->calendar().identifier(); 2016 2017 Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar()); 2018 Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone()); 2019 if (!calendar || !timeZone) { 2020 if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) { 2021 return false; 2022 } 2023 calendar.set(dateTimeFormat->getCalendar()); 2024 timeZone.set(dateTimeFormat->getTimeZone()); 2025 } 2026 MOZ_ASSERT(calendar && timeZone); 2027 2028 // Step 4. 2029 if (calendarId != CalendarId::ISO8601 && 2030 calendarId != calendar.identifier()) { 2031 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2032 JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, 2033 CalendarIdentifier(calendarId).data(), 2034 CalendarIdentifier(calendar).data()); 2035 return false; 2036 } 2037 2038 // Step 5. 2039 int64_t milliseconds = epochNs.floorToMilliseconds(); 2040 *result = JS::TimeClip(double(milliseconds)); 2041 return true; 2042 } 2043 2044 /** 2045 * HandleDateTimeOthers ( dateTimeFormat, x ) 2046 * 2047 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimeothers 2048 */ 2049 static bool HandleDateTimeOthers(JSContext* cx, const char* method, double x, 2050 ClippedTime* result) { 2051 // Step 1. 2052 auto clipped = JS::TimeClip(x); 2053 2054 // Step 2. 2055 if (!clipped.isValid()) { 2056 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2057 JSMSG_DATE_NOT_FINITE, "DateTimeFormat", method); 2058 return false; 2059 } 2060 2061 // Step 4. (Performed in NewDateTimeFormat) 2062 2063 // Steps 3 and 5. 2064 *result = clipped; 2065 return true; 2066 } 2067 2068 /** 2069 * HandleDateTimeValue ( dateTimeFormat, x ) 2070 * 2071 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevalue 2072 */ 2073 static bool HandleDateTimeValue(JSContext* cx, const char* method, 2074 Handle<DateTimeFormatObject*> dateTimeFormat, 2075 Handle<Value> x, ClippedTime* result) { 2076 MOZ_ASSERT(x.isObject() || x.isNumber()); 2077 2078 // Step 1. 2079 if (x.isObject()) { 2080 Rooted<JSObject*> unwrapped(cx, CheckedUnwrapStatic(&x.toObject())); 2081 if (!unwrapped) { 2082 ReportAccessDenied(cx); 2083 return false; 2084 } 2085 2086 // Step 1.a. 2087 if (unwrapped->is<PlainDateObject>()) { 2088 return HandleDateTimeTemporalDate( 2089 cx, dateTimeFormat, unwrapped.as<PlainDateObject>(), result); 2090 } 2091 2092 // Step 1.b. 2093 if (unwrapped->is<PlainYearMonthObject>()) { 2094 return HandleDateTimeTemporalYearMonth( 2095 cx, dateTimeFormat, unwrapped.as<PlainYearMonthObject>(), result); 2096 } 2097 2098 // Step 1.c. 2099 if (unwrapped->is<PlainMonthDayObject>()) { 2100 return HandleDateTimeTemporalMonthDay( 2101 cx, dateTimeFormat, unwrapped.as<PlainMonthDayObject>(), result); 2102 } 2103 2104 // Step 1.d. 2105 if (unwrapped->is<PlainTimeObject>()) { 2106 return HandleDateTimeTemporalTime( 2107 cx, dateTimeFormat, unwrapped.as<PlainTimeObject>(), result); 2108 } 2109 2110 // Step 1.e. 2111 if (unwrapped->is<PlainDateTimeObject>()) { 2112 return HandleDateTimeTemporalDateTime( 2113 cx, dateTimeFormat, unwrapped.as<PlainDateTimeObject>(), result); 2114 } 2115 2116 // Step 1.f. 2117 if (unwrapped->is<InstantObject>()) { 2118 return HandleDateTimeTemporalInstant(&unwrapped->as<InstantObject>(), 2119 result); 2120 } 2121 2122 // Step 1.g. 2123 MOZ_ASSERT(unwrapped->is<ZonedDateTimeObject>()); 2124 2125 // Step 1.h. 2126 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2127 JSMSG_UNEXPECTED_TYPE, "object", 2128 unwrapped->getClass()->name); 2129 return false; 2130 } 2131 2132 // Step 2. 2133 return HandleDateTimeOthers(cx, method, x.toNumber(), result); 2134 } 2135 2136 static bool intl_FormatDateTime(JSContext* cx, 2137 const mozilla::intl::DateTimeFormat* df, 2138 ClippedTime x, MutableHandleValue result) { 2139 MOZ_ASSERT(x.isValid()); 2140 2141 FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx); 2142 auto dfResult = df->TryFormat(x.toDouble(), buffer); 2143 if (dfResult.isErr()) { 2144 intl::ReportInternalError(cx, dfResult.unwrapErr()); 2145 return false; 2146 } 2147 2148 JSString* str = buffer.toString(cx); 2149 if (!str) { 2150 return false; 2151 } 2152 2153 result.setString(str); 2154 return true; 2155 } 2156 2157 using FieldType = js::ImmutableTenuredPtr<PropertyName*> JSAtomState::*; 2158 2159 static FieldType GetFieldTypeForPartType(mozilla::intl::DateTimePartType type) { 2160 switch (type) { 2161 case mozilla::intl::DateTimePartType::Literal: 2162 return &JSAtomState::literal; 2163 case mozilla::intl::DateTimePartType::Era: 2164 return &JSAtomState::era; 2165 case mozilla::intl::DateTimePartType::Year: 2166 return &JSAtomState::year; 2167 case mozilla::intl::DateTimePartType::YearName: 2168 return &JSAtomState::yearName; 2169 case mozilla::intl::DateTimePartType::RelatedYear: 2170 return &JSAtomState::relatedYear; 2171 case mozilla::intl::DateTimePartType::Month: 2172 return &JSAtomState::month; 2173 case mozilla::intl::DateTimePartType::Day: 2174 return &JSAtomState::day; 2175 case mozilla::intl::DateTimePartType::Hour: 2176 return &JSAtomState::hour; 2177 case mozilla::intl::DateTimePartType::Minute: 2178 return &JSAtomState::minute; 2179 case mozilla::intl::DateTimePartType::Second: 2180 return &JSAtomState::second; 2181 case mozilla::intl::DateTimePartType::Weekday: 2182 return &JSAtomState::weekday; 2183 case mozilla::intl::DateTimePartType::DayPeriod: 2184 return &JSAtomState::dayPeriod; 2185 case mozilla::intl::DateTimePartType::TimeZoneName: 2186 return &JSAtomState::timeZoneName; 2187 case mozilla::intl::DateTimePartType::FractionalSecondDigits: 2188 return &JSAtomState::fractionalSecond; 2189 case mozilla::intl::DateTimePartType::Unknown: 2190 return &JSAtomState::unknown; 2191 } 2192 2193 MOZ_CRASH( 2194 "unenumerated, undocumented format field returned " 2195 "by iterator"); 2196 } 2197 2198 static FieldType GetFieldTypeForPartSource( 2199 mozilla::intl::DateTimePartSource source) { 2200 switch (source) { 2201 case mozilla::intl::DateTimePartSource::Shared: 2202 return &JSAtomState::shared; 2203 case mozilla::intl::DateTimePartSource::StartRange: 2204 return &JSAtomState::startRange; 2205 case mozilla::intl::DateTimePartSource::EndRange: 2206 return &JSAtomState::endRange; 2207 } 2208 2209 MOZ_CRASH( 2210 "unenumerated, undocumented format field returned " 2211 "by iterator"); 2212 } 2213 2214 // A helper function to create an ArrayObject from DateTimePart objects. 2215 // When hasNoSource is true, we don't need to create the ||Source|| property for 2216 // the DateTimePart object. 2217 static bool CreateDateTimePartArray( 2218 JSContext* cx, mozilla::Span<const char16_t> formattedSpan, 2219 bool hasNoSource, const mozilla::intl::DateTimePartVector& parts, 2220 MutableHandleValue result) { 2221 RootedString overallResult(cx, NewStringCopy<CanGC>(cx, formattedSpan)); 2222 if (!overallResult) { 2223 return false; 2224 } 2225 2226 Rooted<ArrayObject*> partsArray( 2227 cx, NewDenseFullyAllocatedArray(cx, parts.length())); 2228 if (!partsArray) { 2229 return false; 2230 } 2231 partsArray->ensureDenseInitializedLength(0, parts.length()); 2232 2233 if (overallResult->length() == 0) { 2234 // An empty string contains no parts, so avoid extra work below. 2235 result.setObject(*partsArray); 2236 return true; 2237 } 2238 2239 RootedObject singlePart(cx); 2240 RootedValue val(cx); 2241 2242 size_t index = 0; 2243 size_t beginIndex = 0; 2244 for (const mozilla::intl::DateTimePart& part : parts) { 2245 singlePart = NewPlainObject(cx); 2246 if (!singlePart) { 2247 return false; 2248 } 2249 2250 FieldType type = GetFieldTypeForPartType(part.mType); 2251 val = StringValue(cx->names().*type); 2252 if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) { 2253 return false; 2254 } 2255 2256 MOZ_ASSERT(part.mEndIndex > beginIndex); 2257 JSLinearString* partStr = NewDependentString(cx, overallResult, beginIndex, 2258 part.mEndIndex - beginIndex); 2259 if (!partStr) { 2260 return false; 2261 } 2262 val = StringValue(partStr); 2263 if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) { 2264 return false; 2265 } 2266 2267 if (!hasNoSource) { 2268 FieldType source = GetFieldTypeForPartSource(part.mSource); 2269 val = StringValue(cx->names().*source); 2270 if (!DefineDataProperty(cx, singlePart, cx->names().source, val)) { 2271 return false; 2272 } 2273 } 2274 2275 beginIndex = part.mEndIndex; 2276 partsArray->initDenseElement(index++, ObjectValue(*singlePart)); 2277 } 2278 2279 MOZ_ASSERT(index == parts.length()); 2280 MOZ_ASSERT(beginIndex == formattedSpan.size()); 2281 result.setObject(*partsArray); 2282 return true; 2283 } 2284 2285 static bool intl_FormatToPartsDateTime(JSContext* cx, 2286 const mozilla::intl::DateTimeFormat* df, 2287 ClippedTime x, bool hasNoSource, 2288 MutableHandleValue result) { 2289 MOZ_ASSERT(x.isValid()); 2290 2291 FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx); 2292 mozilla::intl::DateTimePartVector parts; 2293 auto r = df->TryFormatToParts(x.toDouble(), buffer, parts); 2294 if (r.isErr()) { 2295 intl::ReportInternalError(cx, r.unwrapErr()); 2296 return false; 2297 } 2298 2299 return CreateDateTimePartArray(cx, buffer, hasNoSource, parts, result); 2300 } 2301 2302 bool js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) { 2303 CallArgs args = CallArgsFromVp(argc, vp); 2304 MOZ_ASSERT(args.length() == 3); 2305 MOZ_ASSERT(args[0].isObject()); 2306 MOZ_ASSERT(args[2].isBoolean()); 2307 2308 Rooted<DateTimeFormatObject*> dateTimeFormat(cx); 2309 dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>(); 2310 2311 bool formatToParts = args[2].toBoolean(); 2312 const char* method = formatToParts ? "formatToParts" : "format"; 2313 2314 auto kind = ToDateTimeFormattable(args[1]); 2315 2316 JS::ClippedTime x; 2317 if (!args[1].isUndefined()) { 2318 Rooted<Value> value(cx, args[1]); 2319 if (kind == DateTimeValueKind::Number) { 2320 if (!ToNumber(cx, &value)) { 2321 return false; 2322 } 2323 } 2324 MOZ_ASSERT(value.isNumber() || value.isObject()); 2325 2326 if (!HandleDateTimeValue(cx, method, dateTimeFormat, value, &x)) { 2327 return false; 2328 } 2329 } else { 2330 x = DateNow(cx); 2331 } 2332 MOZ_ASSERT(x.isValid()); 2333 2334 mozilla::intl::DateTimeFormat* df = 2335 GetOrCreateDateTimeFormat(cx, dateTimeFormat, kind); 2336 if (!df) { 2337 return false; 2338 } 2339 2340 // Use the DateTimeFormat to actually format the time stamp. 2341 return formatToParts ? intl_FormatToPartsDateTime( 2342 cx, df, x, /* hasNoSource */ true, args.rval()) 2343 : intl_FormatDateTime(cx, df, x, args.rval()); 2344 } 2345 2346 bool js::intl::FormatDateTime(JSContext* cx, 2347 Handle<DateTimeFormatObject*> dateTimeFormat, 2348 double millis, MutableHandle<Value> result) { 2349 auto x = JS::TimeClip(millis); 2350 MOZ_ASSERT(x.isValid()); 2351 2352 mozilla::intl::DateTimeFormat* df = 2353 GetOrCreateDateTimeFormat(cx, dateTimeFormat, DateTimeValueKind::Number); 2354 if (!df) { 2355 return false; 2356 } 2357 2358 return intl_FormatDateTime(cx, df, x, result); 2359 } 2360 2361 /** 2362 * Returns a new DateIntervalFormat with the locale and date-time formatting 2363 * options of the given DateTimeFormat. 2364 */ 2365 static mozilla::intl::DateIntervalFormat* NewDateIntervalFormat( 2366 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 2367 mozilla::intl::DateTimeFormat& mozDtf) { 2368 RootedValue value(cx); 2369 RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat)); 2370 if (!internals) { 2371 return nullptr; 2372 } 2373 2374 FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> pattern(cx); 2375 auto result = mozDtf.GetPattern(pattern); 2376 if (result.isErr()) { 2377 intl::ReportInternalError(cx, result.unwrapErr()); 2378 return nullptr; 2379 } 2380 2381 // Determine the hour cycle used in the resolved pattern. 2382 mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle> hcPattern = 2383 mozilla::intl::DateTimeFormat::HourCycleFromPattern(pattern); 2384 2385 UniqueChars locale = DateTimeFormatLocale(cx, internals, hcPattern); 2386 if (!locale) { 2387 return nullptr; 2388 } 2389 2390 if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) { 2391 return nullptr; 2392 } 2393 2394 Rooted<JSLinearString*> timeZoneString(cx, 2395 value.toString()->ensureLinear(cx)); 2396 if (!timeZoneString) { 2397 return nullptr; 2398 } 2399 2400 AutoStableStringChars timeZone(cx); 2401 mozilla::Span<const char16_t> timeZoneChars{}; 2402 2403 auto timeZoneOffset = TimeZoneOffsetString::from(timeZoneString); 2404 if (timeZoneOffset) { 2405 timeZoneChars = *timeZoneOffset; 2406 } else { 2407 if (!timeZone.initTwoByte(cx, timeZoneString)) { 2408 return nullptr; 2409 } 2410 timeZoneChars = timeZone.twoByteRange(); 2411 } 2412 2413 FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> skeleton(cx); 2414 auto skelResult = mozDtf.GetOriginalSkeleton(skeleton); 2415 if (skelResult.isErr()) { 2416 intl::ReportInternalError(cx, skelResult.unwrapErr()); 2417 return nullptr; 2418 } 2419 2420 auto dif = mozilla::intl::DateIntervalFormat::TryCreate( 2421 mozilla::MakeStringSpan(locale.get()), skeleton, timeZoneChars); 2422 2423 if (dif.isErr()) { 2424 js::intl::ReportInternalError(cx, dif.unwrapErr()); 2425 return nullptr; 2426 } 2427 2428 return dif.unwrap().release(); 2429 } 2430 2431 static mozilla::intl::DateIntervalFormat* GetOrCreateDateIntervalFormat( 2432 JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat, 2433 mozilla::intl::DateTimeFormat& mozDtf, DateTimeValueKind kind) { 2434 dateTimeFormat->maybeClearCache(kind); 2435 2436 // Obtain a cached DateIntervalFormat object. 2437 mozilla::intl::DateIntervalFormat* dif = 2438 dateTimeFormat->getDateIntervalFormat(); 2439 if (dif) { 2440 return dif; 2441 } 2442 2443 dif = NewDateIntervalFormat(cx, dateTimeFormat, mozDtf); 2444 if (!dif) { 2445 return nullptr; 2446 } 2447 dateTimeFormat->setDateIntervalFormat(dif); 2448 2449 intl::AddICUCellMemory( 2450 dateTimeFormat, 2451 DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse); 2452 return dif; 2453 } 2454 2455 /** 2456 * PartitionDateTimeRangePattern ( dateTimeFormat, x, y ) 2457 */ 2458 static bool PartitionDateTimeRangePattern( 2459 JSContext* cx, const mozilla::intl::DateTimeFormat* df, 2460 const mozilla::intl::DateIntervalFormat* dif, 2461 mozilla::intl::AutoFormattedDateInterval& formatted, ClippedTime x, 2462 ClippedTime y, bool* equal) { 2463 MOZ_ASSERT(x.isValid()); 2464 MOZ_ASSERT(y.isValid()); 2465 2466 auto result = 2467 dif->TryFormatDateTime(x.toDouble(), y.toDouble(), df, formatted, equal); 2468 if (result.isErr()) { 2469 intl::ReportInternalError(cx, result.unwrapErr()); 2470 return false; 2471 } 2472 return true; 2473 } 2474 2475 /** 2476 * FormatDateTimeRange( dateTimeFormat, x, y ) 2477 */ 2478 static bool FormatDateTimeRange(JSContext* cx, 2479 const mozilla::intl::DateTimeFormat* df, 2480 const mozilla::intl::DateIntervalFormat* dif, 2481 ClippedTime x, ClippedTime y, 2482 MutableHandleValue result) { 2483 mozilla::intl::AutoFormattedDateInterval formatted; 2484 if (!formatted.IsValid()) { 2485 intl::ReportInternalError(cx, formatted.GetError()); 2486 return false; 2487 } 2488 2489 bool equal; 2490 if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) { 2491 return false; 2492 } 2493 2494 // PartitionDateTimeRangePattern, step 12. 2495 if (equal) { 2496 return intl_FormatDateTime(cx, df, x, result); 2497 } 2498 2499 auto spanResult = formatted.ToSpan(); 2500 if (spanResult.isErr()) { 2501 intl::ReportInternalError(cx, spanResult.unwrapErr()); 2502 return false; 2503 } 2504 JSString* resultStr = NewStringCopy<CanGC>(cx, spanResult.unwrap()); 2505 if (!resultStr) { 2506 return false; 2507 } 2508 2509 result.setString(resultStr); 2510 return true; 2511 } 2512 2513 /** 2514 * FormatDateTimeRangeToParts ( dateTimeFormat, x, y ) 2515 */ 2516 static bool FormatDateTimeRangeToParts( 2517 JSContext* cx, const mozilla::intl::DateTimeFormat* df, 2518 const mozilla::intl::DateIntervalFormat* dif, ClippedTime x, ClippedTime y, 2519 MutableHandleValue result) { 2520 mozilla::intl::AutoFormattedDateInterval formatted; 2521 if (!formatted.IsValid()) { 2522 intl::ReportInternalError(cx, formatted.GetError()); 2523 return false; 2524 } 2525 2526 bool equal; 2527 if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) { 2528 return false; 2529 } 2530 2531 // PartitionDateTimeRangePattern, step 12. 2532 if (equal) { 2533 return intl_FormatToPartsDateTime(cx, df, x, /* hasNoSource */ false, 2534 result); 2535 } 2536 2537 mozilla::intl::DateTimePartVector parts; 2538 auto r = dif->TryFormattedToParts(formatted, parts); 2539 if (r.isErr()) { 2540 intl::ReportInternalError(cx, r.unwrapErr()); 2541 return false; 2542 } 2543 2544 auto spanResult = formatted.ToSpan(); 2545 if (spanResult.isErr()) { 2546 intl::ReportInternalError(cx, spanResult.unwrapErr()); 2547 return false; 2548 } 2549 return CreateDateTimePartArray(cx, spanResult.unwrap(), 2550 /* hasNoSource */ false, parts, result); 2551 } 2552 2553 bool js::intl_FormatDateTimeRange(JSContext* cx, unsigned argc, Value* vp) { 2554 CallArgs args = CallArgsFromVp(argc, vp); 2555 MOZ_ASSERT(args.length() == 4); 2556 MOZ_ASSERT(args[0].isObject()); 2557 MOZ_ASSERT(!args[1].isUndefined()); 2558 MOZ_ASSERT(!args[2].isUndefined()); 2559 MOZ_ASSERT(args[3].isBoolean()); 2560 2561 Rooted<DateTimeFormatObject*> dateTimeFormat(cx); 2562 dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>(); 2563 2564 bool formatToParts = args[3].toBoolean(); 2565 const char* method = formatToParts ? "formatRangeToParts" : "formatRange"; 2566 2567 Rooted<Value> start(cx, args[1]); 2568 auto startKind = ToDateTimeFormattable(start); 2569 if (startKind == DateTimeValueKind::Number) { 2570 if (!ToNumber(cx, &start)) { 2571 return false; 2572 } 2573 } 2574 MOZ_ASSERT(start.isNumber() || start.isObject()); 2575 2576 Rooted<Value> end(cx, args[2]); 2577 auto endKind = ToDateTimeFormattable(end); 2578 if (endKind == DateTimeValueKind::Number) { 2579 if (!ToNumber(cx, &end)) { 2580 return false; 2581 } 2582 } 2583 MOZ_ASSERT(end.isNumber() || end.isObject()); 2584 2585 if (startKind != endKind) { 2586 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2587 JSMSG_NOT_EXPECTED_TYPE, method, 2588 DateTimeValueKindToString(startKind), 2589 DateTimeValueKindToString(endKind)); 2590 return false; 2591 } 2592 2593 // PartitionDateTimeRangePattern, steps 1-2. 2594 JS::ClippedTime x; 2595 if (!HandleDateTimeValue(cx, method, dateTimeFormat, start, &x)) { 2596 return false; 2597 } 2598 MOZ_ASSERT(x.isValid()); 2599 2600 // PartitionDateTimeRangePattern, steps 3-4. 2601 JS::ClippedTime y; 2602 if (!HandleDateTimeValue(cx, method, dateTimeFormat, end, &y)) { 2603 return false; 2604 } 2605 MOZ_ASSERT(y.isValid()); 2606 2607 mozilla::intl::DateTimeFormat* df = 2608 GetOrCreateDateTimeFormat(cx, dateTimeFormat, startKind); 2609 if (!df) { 2610 return false; 2611 } 2612 2613 mozilla::intl::DateIntervalFormat* dif = 2614 GetOrCreateDateIntervalFormat(cx, dateTimeFormat, *df, startKind); 2615 if (!dif) { 2616 return false; 2617 } 2618 2619 // Use the DateIntervalFormat to actually format the time range. 2620 return formatToParts 2621 ? FormatDateTimeRangeToParts(cx, df, dif, x, y, args.rval()) 2622 : FormatDateTimeRange(cx, df, dif, x, y, args.rval()); 2623 } 2624 2625 /** 2626 * Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] ) 2627 */ 2628 static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc, 2629 Value* vp) { 2630 CallArgs args = CallArgsFromVp(argc, vp); 2631 2632 // Steps 1-3. 2633 auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DateTimeFormat, 2634 args.get(0), args.get(1)); 2635 if (!array) { 2636 return false; 2637 } 2638 args.rval().setObject(*array); 2639 return true; 2640 } 2641 2642 bool js::intl::TemporalObjectToLocaleString( 2643 JSContext* cx, const CallArgs& args, DateTimeFormatKind formatKind, 2644 Handle<Value> toLocaleStringTimeZone) { 2645 MOZ_ASSERT(args.thisv().isObject()); 2646 2647 auto kind = ToDateTimeFormattable(args.thisv()); 2648 MOZ_ASSERT(kind != DateTimeValueKind::Number); 2649 MOZ_ASSERT_IF(kind != DateTimeValueKind::TemporalZonedDateTime, 2650 toLocaleStringTimeZone.isUndefined()); 2651 MOZ_ASSERT_IF(kind == DateTimeValueKind::TemporalZonedDateTime, 2652 toLocaleStringTimeZone.isString()); 2653 2654 HandleValue locales = args.get(0); 2655 HandleValue options = args.get(1); 2656 2657 Rooted<DateTimeFormatObject*> dateTimeFormat(cx); 2658 if (kind != DateTimeValueKind::TemporalZonedDateTime) { 2659 dateTimeFormat = 2660 GetOrCreateDateTimeFormat(cx, locales, options, formatKind); 2661 } else { 2662 // Cache doesn't yet support Temporal.ZonedDateTime. 2663 dateTimeFormat = ::CreateDateTimeFormat(cx, locales, options, 2664 toLocaleStringTimeZone, formatKind); 2665 } 2666 if (!dateTimeFormat) { 2667 return false; 2668 } 2669 2670 JS::ClippedTime x; 2671 if (kind == DateTimeValueKind::TemporalZonedDateTime) { 2672 Rooted<ZonedDateTimeObject*> zonedDateTime( 2673 cx, &args.thisv().toObject().as<ZonedDateTimeObject>()); 2674 if (!HandleDateTimeTemporalZonedDateTime(cx, dateTimeFormat, zonedDateTime, 2675 &x)) { 2676 return false; 2677 } 2678 } else { 2679 if (!HandleDateTimeValue(cx, "toLocaleString", dateTimeFormat, args.thisv(), 2680 &x)) { 2681 return false; 2682 } 2683 } 2684 MOZ_ASSERT(x.isValid()); 2685 2686 auto* df = GetOrCreateDateTimeFormat(cx, dateTimeFormat, kind); 2687 if (!df) { 2688 return false; 2689 } 2690 2691 return intl_FormatDateTime(cx, df, x, args.rval()); 2692 }