DurationFormat.cpp (55201B)
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.DurationFormat implementation. */ 8 9 #include "builtin/intl/DurationFormat.h" 10 11 #include "mozilla/Assertions.h" 12 #include "mozilla/intl/DateTimeFormat.h" 13 #include "mozilla/intl/ListFormat.h" 14 #include "mozilla/intl/NumberFormat.h" 15 #include "mozilla/Span.h" 16 17 #include <array> 18 #include <charconv> 19 20 #include "jspubtd.h" 21 #include "NamespaceImports.h" 22 23 #include "builtin/intl/CommonFunctions.h" 24 #include "builtin/intl/FormatBuffer.h" 25 #include "builtin/intl/LanguageTag.h" 26 #include "builtin/intl/ListFormat.h" 27 #include "builtin/intl/LocaleNegotiation.h" 28 #include "builtin/intl/NumberFormat.h" 29 #include "builtin/temporal/Duration.h" 30 #include "gc/AllocKind.h" 31 #include "gc/GCContext.h" 32 #include "js/CallArgs.h" 33 #include "js/PropertyDescriptor.h" 34 #include "js/PropertySpec.h" 35 #include "js/RootingAPI.h" 36 #include "js/TypeDecls.h" 37 #include "js/Value.h" 38 #include "vm/GlobalObject.h" 39 #include "vm/JSContext.h" 40 #include "vm/PlainObject.h" 41 #include "vm/SelfHosting.h" 42 #include "vm/WellKnownAtom.h" 43 44 #include "vm/JSObject-inl.h" 45 #include "vm/NativeObject-inl.h" 46 47 using namespace js; 48 using namespace js::intl; 49 50 static constexpr auto durationUnits = std::array{ 51 temporal::TemporalUnit::Year, temporal::TemporalUnit::Month, 52 temporal::TemporalUnit::Week, temporal::TemporalUnit::Day, 53 temporal::TemporalUnit::Hour, temporal::TemporalUnit::Minute, 54 temporal::TemporalUnit::Second, temporal::TemporalUnit::Millisecond, 55 temporal::TemporalUnit::Microsecond, temporal::TemporalUnit::Nanosecond, 56 }; 57 58 const JSClass DurationFormatObject::class_ = { 59 "Intl.DurationFormat", 60 JSCLASS_HAS_RESERVED_SLOTS(DurationFormatObject::SLOT_COUNT) | 61 JSCLASS_HAS_CACHED_PROTO(JSProto_DurationFormat) | 62 JSCLASS_FOREGROUND_FINALIZE, 63 &DurationFormatObject::classOps_, 64 &DurationFormatObject::classSpec_, 65 }; 66 67 const JSClass& DurationFormatObject::protoClass_ = PlainObject::class_; 68 69 static bool durationFormat_format(JSContext* cx, unsigned argc, Value* vp); 70 static bool durationFormat_formatToParts(JSContext* cx, unsigned argc, 71 Value* vp); 72 static bool durationFormat_supportedLocalesOf(JSContext* cx, unsigned argc, 73 Value* vp); 74 75 static bool durationFormat_toSource(JSContext* cx, unsigned argc, Value* vp) { 76 CallArgs args = CallArgsFromVp(argc, vp); 77 args.rval().setString(cx->names().DurationFormat); 78 return true; 79 } 80 81 static const JSFunctionSpec durationFormat_static_methods[] = { 82 JS_FN("supportedLocalesOf", durationFormat_supportedLocalesOf, 1, 0), 83 JS_FS_END, 84 }; 85 86 static const JSFunctionSpec durationFormat_methods[] = { 87 JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DurationFormat_resolvedOptions", 88 0, 0), 89 JS_FN("format", durationFormat_format, 1, 0), 90 JS_FN("formatToParts", durationFormat_formatToParts, 1, 0), 91 JS_FN("toSource", durationFormat_toSource, 0, 0), 92 JS_FS_END, 93 }; 94 95 static const JSPropertySpec durationFormat_properties[] = { 96 JS_STRING_SYM_PS(toStringTag, "Intl.DurationFormat", JSPROP_READONLY), 97 JS_PS_END, 98 }; 99 100 static bool DurationFormat(JSContext* cx, unsigned argc, Value* vp); 101 102 const JSClassOps DurationFormatObject::classOps_ = { 103 nullptr, // addProperty 104 nullptr, // delProperty 105 nullptr, // enumerate 106 nullptr, // newEnumerate 107 nullptr, // resolve 108 nullptr, // mayResolve 109 DurationFormatObject::finalize, // finalize 110 nullptr, // call 111 nullptr, // construct 112 nullptr, // trace 113 }; 114 115 const ClassSpec DurationFormatObject::classSpec_ = { 116 GenericCreateConstructor<DurationFormat, 0, gc::AllocKind::FUNCTION>, 117 GenericCreatePrototype<DurationFormatObject>, 118 durationFormat_static_methods, 119 nullptr, 120 durationFormat_methods, 121 durationFormat_properties, 122 nullptr, 123 ClassSpec::DontDefineConstructor, 124 }; 125 126 void js::DurationFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) { 127 MOZ_ASSERT(gcx->onMainThread()); 128 129 auto* durationFormat = &obj->as<DurationFormatObject>(); 130 131 for (auto unit : durationUnits) { 132 if (auto* nf = durationFormat->getNumberFormat(unit)) { 133 RemoveICUCellMemory(gcx, obj, NumberFormatObject::EstimatedMemoryUse); 134 delete nf; 135 } 136 } 137 138 if (auto* lf = durationFormat->getListFormat()) { 139 RemoveICUCellMemory(gcx, obj, ListFormatObject::EstimatedMemoryUse); 140 delete lf; 141 } 142 143 if (auto* options = durationFormat->getOptions()) { 144 gcx->delete_(obj, options, MemoryUse::IntlOptions); 145 } 146 } 147 148 /** 149 * Intl.DurationFormat ( [ locales [ , options ] ] ) 150 */ 151 static bool DurationFormat(JSContext* cx, unsigned argc, Value* vp) { 152 CallArgs args = CallArgsFromVp(argc, vp); 153 154 // Step 1. 155 if (!ThrowIfNotConstructing(cx, args, "Intl.DurationFormat")) { 156 return false; 157 } 158 159 // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). 160 RootedObject proto(cx); 161 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_DurationFormat, 162 &proto)) { 163 return false; 164 } 165 166 Rooted<DurationFormatObject*> durationFormat( 167 cx, NewObjectWithClassProto<DurationFormatObject>(cx, proto)); 168 if (!durationFormat) { 169 return false; 170 } 171 172 HandleValue locales = args.get(0); 173 HandleValue options = args.get(1); 174 175 // Steps 3-28. 176 if (!InitializeObject(cx, durationFormat, 177 cx->names().InitializeDurationFormat, locales, 178 options)) { 179 return false; 180 } 181 182 args.rval().setObject(*durationFormat); 183 return true; 184 } 185 186 /** 187 * Returns the time separator string for the given locale and numbering system. 188 */ 189 static JSString* GetTimeSeparator( 190 JSContext* cx, Handle<DurationFormatObject*> durationFormat) { 191 if (auto* separator = durationFormat->getTimeSeparator()) { 192 return separator; 193 } 194 195 Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat)); 196 if (!internals) { 197 return nullptr; 198 } 199 200 Rooted<Value> value(cx); 201 202 if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { 203 return nullptr; 204 } 205 206 UniqueChars locale = EncodeLocale(cx, value.toString()); 207 if (!locale) { 208 return nullptr; 209 } 210 211 if (!GetProperty(cx, internals, internals, cx->names().numberingSystem, 212 &value)) { 213 return nullptr; 214 } 215 216 UniqueChars numberingSystem = EncodeAscii(cx, value.toString()); 217 if (!numberingSystem) { 218 return nullptr; 219 } 220 221 FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> separator(cx); 222 auto result = mozilla::intl::DateTimeFormat::GetTimeSeparator( 223 mozilla::MakeStringSpan(locale.get()), 224 mozilla::MakeStringSpan(numberingSystem.get()), separator); 225 if (result.isErr()) { 226 ReportInternalError(cx, result.unwrapErr()); 227 return nullptr; 228 } 229 230 auto* string = separator.toString(cx); 231 if (!string) { 232 return nullptr; 233 } 234 235 durationFormat->setTimeSeparator(string); 236 return string; 237 } 238 239 struct DurationValue { 240 // The seconds part in a `temporal::TimeDuration` can't exceed 241 // 9'007'199'254'740'991 and the nanoseconds part can't exceed 999'999'999. 242 // This means the string representation needs at most 27 characters. 243 static constexpr size_t MaximumDecimalStringLength = 244 /* sign */ 1 + 245 /* seconds part */ 16 + 246 /* decimal dot */ 1 + 247 /* nanoseconds part */ 9; 248 249 // Next power of two after `MaximumDecimalStringLength`. 250 static constexpr size_t DecimalStringCapacity = 32; 251 252 double number = 0; 253 char decimal[DecimalStringCapacity] = {}; 254 255 explicit DurationValue() = default; 256 explicit DurationValue(double number) : number(number) {} 257 258 bool isNegative() const { 259 return mozilla::IsNegative(number) || decimal[0] == '-'; 260 } 261 262 auto abs() const { 263 // Return unchanged if not negative. 264 if (!isNegative()) { 265 return *this; 266 } 267 268 // Call |std::abs| for non-decimal values. 269 if (!isDecimal()) { 270 return DurationValue{std::abs(number)}; 271 } 272 273 // Copy decimal strings without the leading '-' sign character. 274 auto result = DurationValue{}; 275 std::copy(std::next(decimal), std::end(decimal), result.decimal); 276 return result; 277 } 278 279 // |number| is active by default unless |decimal| is used. 280 bool isDecimal() const { return decimal[0] != '\0'; } 281 282 // Return true if this value represents either +0 or -0. 283 bool isZero() const { return number == 0 && !isDecimal(); } 284 285 operator std::string_view() const { 286 MOZ_ASSERT(isDecimal()); 287 return {decimal}; 288 } 289 }; 290 291 /** 292 * Return the |unit| value from |duration|. 293 */ 294 static auto ToDurationValue(const temporal::Duration& duration, 295 temporal::TemporalUnit unit) { 296 using namespace temporal; 297 298 switch (unit) { 299 case TemporalUnit::Year: 300 return DurationValue{duration.years}; 301 case TemporalUnit::Month: 302 return DurationValue{duration.months}; 303 case TemporalUnit::Week: 304 return DurationValue{duration.weeks}; 305 case TemporalUnit::Day: 306 return DurationValue{duration.days}; 307 case TemporalUnit::Hour: 308 return DurationValue{duration.hours}; 309 case TemporalUnit::Minute: 310 return DurationValue{duration.minutes}; 311 case TemporalUnit::Second: 312 return DurationValue{duration.seconds}; 313 case TemporalUnit::Millisecond: 314 return DurationValue{duration.milliseconds}; 315 case TemporalUnit::Microsecond: 316 return DurationValue{duration.microseconds}; 317 case TemporalUnit::Nanosecond: 318 return DurationValue{duration.nanoseconds}; 319 case TemporalUnit::Unset: 320 case TemporalUnit::Auto: 321 break; 322 } 323 MOZ_CRASH("invalid temporal unit"); 324 } 325 326 /** 327 * Return the "display" property name for |unit|. 328 */ 329 static PropertyName* DurationDisplayName(temporal::TemporalUnit unit, 330 JSContext* cx) { 331 using namespace temporal; 332 333 switch (unit) { 334 case TemporalUnit::Year: 335 return cx->names().yearsDisplay; 336 case TemporalUnit::Month: 337 return cx->names().monthsDisplay; 338 case TemporalUnit::Week: 339 return cx->names().weeksDisplay; 340 case TemporalUnit::Day: 341 return cx->names().daysDisplay; 342 case TemporalUnit::Hour: 343 return cx->names().hoursDisplay; 344 case TemporalUnit::Minute: 345 return cx->names().minutesDisplay; 346 case TemporalUnit::Second: 347 return cx->names().secondsDisplay; 348 case TemporalUnit::Millisecond: 349 return cx->names().millisecondsDisplay; 350 case TemporalUnit::Microsecond: 351 return cx->names().microsecondsDisplay; 352 case TemporalUnit::Nanosecond: 353 return cx->names().nanosecondsDisplay; 354 case TemporalUnit::Unset: 355 case TemporalUnit::Auto: 356 break; 357 } 358 MOZ_CRASH("invalid temporal unit"); 359 } 360 361 /** 362 * Convert |value|, which must be a string, to a |DurationDisplay|. 363 */ 364 static bool ToDurationDisplay(JSContext* cx, const Value& value, 365 DurationDisplay* result) { 366 MOZ_ASSERT(value.isString()); 367 368 auto* linear = value.toString()->ensureLinear(cx); 369 if (!linear) { 370 return false; 371 } 372 373 if (StringEqualsAscii(linear, "auto")) { 374 *result = DurationDisplay::Auto; 375 } else { 376 MOZ_ASSERT(StringEqualsAscii(linear, "always")); 377 *result = DurationDisplay::Always; 378 } 379 return true; 380 } 381 382 /** 383 * Return the "style" property name for |unit|. 384 */ 385 static PropertyName* DurationStyleName(temporal::TemporalUnit unit, 386 JSContext* cx) { 387 using namespace temporal; 388 389 switch (unit) { 390 case TemporalUnit::Year: 391 return cx->names().yearsStyle; 392 case TemporalUnit::Month: 393 return cx->names().monthsStyle; 394 case TemporalUnit::Week: 395 return cx->names().weeksStyle; 396 case TemporalUnit::Day: 397 return cx->names().daysStyle; 398 case TemporalUnit::Hour: 399 return cx->names().hoursStyle; 400 case TemporalUnit::Minute: 401 return cx->names().minutesStyle; 402 case TemporalUnit::Second: 403 return cx->names().secondsStyle; 404 case TemporalUnit::Millisecond: 405 return cx->names().millisecondsStyle; 406 case TemporalUnit::Microsecond: 407 return cx->names().microsecondsStyle; 408 case TemporalUnit::Nanosecond: 409 return cx->names().nanosecondsStyle; 410 case TemporalUnit::Unset: 411 case TemporalUnit::Auto: 412 break; 413 } 414 MOZ_CRASH("invalid temporal unit"); 415 } 416 417 /** 418 * Convert |value|, which must be a string, to a |DurationStyle|. 419 */ 420 static bool ToDurationStyle(JSContext* cx, const Value& value, 421 DurationStyle* result) { 422 MOZ_ASSERT(value.isString()); 423 424 auto* linear = value.toString()->ensureLinear(cx); 425 if (!linear) { 426 return false; 427 } 428 429 if (StringEqualsAscii(linear, "long")) { 430 *result = DurationStyle::Long; 431 } else if (StringEqualsAscii(linear, "short")) { 432 *result = DurationStyle::Short; 433 } else if (StringEqualsAscii(linear, "narrow")) { 434 *result = DurationStyle::Narrow; 435 } else if (StringEqualsAscii(linear, "numeric")) { 436 *result = DurationStyle::Numeric; 437 } else { 438 MOZ_ASSERT(StringEqualsAscii(linear, "2-digit")); 439 *result = DurationStyle::TwoDigit; 440 } 441 return true; 442 } 443 444 /** 445 * Return the fractional digits setting from |durationFormat|. 446 */ 447 static std::pair<uint32_t, uint32_t> GetFractionalDigits( 448 const DurationFormatObject* durationFormat) { 449 auto* options = durationFormat->getOptions(); 450 MOZ_ASSERT(options, "unexpected unresolved duration format options"); 451 452 int8_t digits = options->fractionalDigits; 453 MOZ_ASSERT(digits <= 9); 454 455 if (digits < 0) { 456 return {0U, 9U}; 457 } 458 return {uint32_t(digits), uint32_t(digits)}; 459 } 460 461 static DurationUnitOptions GetUnitOptions(const DurationFormatOptions& options, 462 temporal::TemporalUnit unit) { 463 using namespace temporal; 464 465 switch (unit) { 466 #define GET_UNIT_OPTIONS(name) \ 467 DurationUnitOptions { options.name##Display, options.name##Style } 468 469 case TemporalUnit::Year: 470 return GET_UNIT_OPTIONS(years); 471 case TemporalUnit::Month: 472 return GET_UNIT_OPTIONS(months); 473 case TemporalUnit::Week: 474 return GET_UNIT_OPTIONS(weeks); 475 case TemporalUnit::Day: 476 return GET_UNIT_OPTIONS(days); 477 case TemporalUnit::Hour: 478 return GET_UNIT_OPTIONS(hours); 479 case TemporalUnit::Minute: 480 return GET_UNIT_OPTIONS(minutes); 481 case TemporalUnit::Second: 482 return GET_UNIT_OPTIONS(seconds); 483 case TemporalUnit::Millisecond: 484 return GET_UNIT_OPTIONS(milliseconds); 485 case TemporalUnit::Microsecond: 486 return GET_UNIT_OPTIONS(microseconds); 487 case TemporalUnit::Nanosecond: 488 return GET_UNIT_OPTIONS(nanoseconds); 489 case TemporalUnit::Unset: 490 case TemporalUnit::Auto: 491 break; 492 493 #undef GET_UNIT_OPTIONS 494 } 495 MOZ_CRASH("invalid duration unit"); 496 } 497 498 static void SetUnitOptions(DurationFormatOptions& options, 499 temporal::TemporalUnit unit, 500 const DurationUnitOptions& unitOptions) { 501 using namespace temporal; 502 503 switch (unit) { 504 #define SET_UNIT_OPTIONS(name) \ 505 do { \ 506 options.name##Display = unitOptions.display_; \ 507 options.name##Style = unitOptions.style_; \ 508 } while (0) 509 510 case TemporalUnit::Year: 511 SET_UNIT_OPTIONS(years); 512 return; 513 case TemporalUnit::Month: 514 SET_UNIT_OPTIONS(months); 515 return; 516 case TemporalUnit::Week: 517 SET_UNIT_OPTIONS(weeks); 518 return; 519 case TemporalUnit::Day: 520 SET_UNIT_OPTIONS(days); 521 return; 522 case TemporalUnit::Hour: 523 SET_UNIT_OPTIONS(hours); 524 return; 525 case TemporalUnit::Minute: 526 SET_UNIT_OPTIONS(minutes); 527 return; 528 case TemporalUnit::Second: 529 SET_UNIT_OPTIONS(seconds); 530 return; 531 case TemporalUnit::Millisecond: 532 SET_UNIT_OPTIONS(milliseconds); 533 return; 534 case TemporalUnit::Microsecond: 535 SET_UNIT_OPTIONS(microseconds); 536 return; 537 case TemporalUnit::Nanosecond: 538 SET_UNIT_OPTIONS(nanoseconds); 539 return; 540 case TemporalUnit::Unset: 541 case TemporalUnit::Auto: 542 break; 543 544 #undef SET_UNIT_OPTIONS 545 } 546 MOZ_CRASH("invalid duration unit"); 547 } 548 549 static DurationFormatOptions* NewDurationFormatOptions( 550 JSContext* cx, Handle<DurationFormatObject*> durationFormat) { 551 Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat)); 552 if (!internals) { 553 return nullptr; 554 } 555 556 auto options = cx->make_unique<DurationFormatOptions>(); 557 if (!options) { 558 return nullptr; 559 } 560 561 Rooted<Value> value(cx); 562 for (temporal::TemporalUnit unit : durationUnits) { 563 DurationDisplay display; 564 if (!GetProperty(cx, internals, internals, DurationDisplayName(unit, cx), 565 &value)) { 566 return nullptr; 567 } 568 if (!ToDurationDisplay(cx, value, &display)) { 569 return nullptr; 570 } 571 572 DurationStyle style; 573 if (!GetProperty(cx, internals, internals, DurationStyleName(unit, cx), 574 &value)) { 575 return nullptr; 576 } 577 if (!ToDurationStyle(cx, value, &style)) { 578 return nullptr; 579 } 580 581 SetUnitOptions(*options, unit, 582 DurationUnitOptions{static_cast<uint8_t>(display), 583 static_cast<uint8_t>(style)}); 584 } 585 586 if (!GetProperty(cx, internals, internals, cx->names().fractionalDigits, 587 &value)) { 588 return nullptr; 589 } 590 if (value.isUndefined()) { 591 options->fractionalDigits = -1; 592 } else { 593 options->fractionalDigits = value.toInt32(); 594 } 595 596 return options.release(); 597 } 598 599 static DurationFormatOptions* GetOrCreateDurationFormatOptions( 600 JSContext* cx, Handle<DurationFormatObject*> durationFormat) { 601 auto* options = durationFormat->getOptions(); 602 if (options) { 603 return options; 604 } 605 606 options = NewDurationFormatOptions(cx, durationFormat); 607 if (!options) { 608 return nullptr; 609 } 610 durationFormat->setOptions(options); 611 612 AddCellMemory(durationFormat, sizeof(DurationFormatOptions), 613 MemoryUse::IntlOptions); 614 return options; 615 } 616 617 /** 618 * Return the locale for `mozilla::intl::NumberFormat` objects. 619 */ 620 static UniqueChars NewDurationNumberFormatLocale( 621 JSContext* cx, Handle<DurationFormatObject*> durationFormat) { 622 // ICU expects numberingSystem as a Unicode locale extensions on locale. 623 624 Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat)); 625 if (!internals) { 626 return nullptr; 627 } 628 629 JS::RootedVector<UnicodeExtensionKeyword> keywords(cx); 630 631 Rooted<Value> value(cx); 632 if (!GetProperty(cx, internals, internals, cx->names().numberingSystem, 633 &value)) { 634 return nullptr; 635 } 636 637 { 638 auto* numberingSystem = value.toString()->ensureLinear(cx); 639 if (!numberingSystem) { 640 return nullptr; 641 } 642 643 if (!keywords.emplaceBack("nu", numberingSystem)) { 644 return nullptr; 645 } 646 } 647 648 return FormatLocale(cx, internals, keywords); 649 } 650 651 /** 652 * Create a `mozilla::intl::NumberFormat` instance based on |internals.locale| 653 * and |options|. 654 */ 655 static mozilla::intl::NumberFormat* NewDurationNumberFormat( 656 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 657 const mozilla::intl::NumberFormatOptions& options) { 658 auto locale = NewDurationNumberFormatLocale(cx, durationFormat); 659 if (!locale) { 660 return nullptr; 661 } 662 663 auto result = mozilla::intl::NumberFormat::TryCreate(locale.get(), options); 664 if (result.isErr()) { 665 ReportInternalError(cx, result.unwrapErr()); 666 return nullptr; 667 } 668 return result.unwrap().release(); 669 } 670 671 /** 672 * Return the singular name for |unit|. 673 */ 674 static std::string_view UnitName(temporal::TemporalUnit unit) { 675 using namespace temporal; 676 677 switch (unit) { 678 case TemporalUnit::Year: 679 return "year"; 680 case TemporalUnit::Month: 681 return "month"; 682 case TemporalUnit::Week: 683 return "week"; 684 case TemporalUnit::Day: 685 return "day"; 686 case TemporalUnit::Hour: 687 return "hour"; 688 case TemporalUnit::Minute: 689 return "minute"; 690 case TemporalUnit::Second: 691 return "second"; 692 case TemporalUnit::Millisecond: 693 return "millisecond"; 694 case TemporalUnit::Microsecond: 695 return "microsecond"; 696 case TemporalUnit::Nanosecond: 697 return "nanosecond"; 698 case TemporalUnit::Unset: 699 case TemporalUnit::Auto: 700 break; 701 } 702 MOZ_CRASH("invalid temporal unit"); 703 } 704 705 /** 706 * Return the singular name for |unit|. 707 */ 708 static auto PartUnitName(temporal::TemporalUnit unit) { 709 using namespace temporal; 710 711 switch (unit) { 712 case TemporalUnit::Year: 713 return &JSAtomState::year; 714 case TemporalUnit::Month: 715 return &JSAtomState::month; 716 case TemporalUnit::Week: 717 return &JSAtomState::week; 718 case TemporalUnit::Day: 719 return &JSAtomState::day; 720 case TemporalUnit::Hour: 721 return &JSAtomState::hour; 722 case TemporalUnit::Minute: 723 return &JSAtomState::minute; 724 case TemporalUnit::Second: 725 return &JSAtomState::second; 726 case TemporalUnit::Millisecond: 727 return &JSAtomState::millisecond; 728 case TemporalUnit::Microsecond: 729 return &JSAtomState::microsecond; 730 case TemporalUnit::Nanosecond: 731 return &JSAtomState::nanosecond; 732 case TemporalUnit::Unset: 733 case TemporalUnit::Auto: 734 break; 735 } 736 MOZ_CRASH("invalid temporal unit"); 737 } 738 739 /** 740 * Convert a duration-style to the corresponding NumberFormat unit-display. 741 */ 742 static auto UnitDisplay(DurationStyle style) { 743 using UnitDisplay = mozilla::intl::NumberFormatOptions::UnitDisplay; 744 745 switch (style) { 746 case DurationStyle::Long: 747 return UnitDisplay::Long; 748 case DurationStyle::Short: 749 return UnitDisplay::Short; 750 case DurationStyle::Narrow: 751 return UnitDisplay::Narrow; 752 case DurationStyle::Numeric: 753 case DurationStyle::TwoDigit: 754 // Both numeric styles are invalid inputs for this function. 755 break; 756 } 757 MOZ_CRASH("invalid duration style"); 758 } 759 760 /** 761 * ComputeFractionalDigits ( durationFormat, duration ) 762 * 763 * Return the fractional seconds from |duration| as an exact value. This is 764 * either an integer Number value when the fractional part is zero, or a 765 * decimal string when the fractional part is non-zero. 766 */ 767 static auto ComputeFractionalDigits(const temporal::Duration& duration, 768 temporal::TemporalUnit unit) { 769 using namespace temporal; 770 771 MOZ_ASSERT(IsValidDuration(duration)); 772 MOZ_ASSERT(TemporalUnit::Second <= unit && unit <= TemporalUnit::Microsecond); 773 774 // Directly return the duration amount when no sub-seconds are present, i.e. 775 // the fractional part is zero. 776 TimeDuration timeDuration; 777 int32_t exponent; 778 switch (unit) { 779 case TemporalUnit::Second: { 780 if (duration.milliseconds == 0 && duration.microseconds == 0 && 781 duration.nanoseconds == 0) { 782 return DurationValue{duration.seconds}; 783 } 784 timeDuration = TimeDurationFromComponents({ 785 0, 786 0, 787 0, 788 0, 789 0, 790 0, 791 duration.seconds, 792 duration.milliseconds, 793 duration.microseconds, 794 duration.nanoseconds, 795 }); 796 exponent = 100'000'000; 797 break; 798 } 799 800 case TemporalUnit::Millisecond: { 801 if (duration.microseconds == 0 && duration.nanoseconds == 0) { 802 return DurationValue{duration.milliseconds}; 803 } 804 timeDuration = TimeDurationFromComponents({ 805 0, 806 0, 807 0, 808 0, 809 0, 810 0, 811 0, 812 duration.milliseconds, 813 duration.microseconds, 814 duration.nanoseconds, 815 }); 816 exponent = 100'000; 817 break; 818 } 819 820 case TemporalUnit::Microsecond: { 821 if (duration.nanoseconds == 0) { 822 return DurationValue{duration.microseconds}; 823 } 824 timeDuration = TimeDurationFromComponents({ 825 0, 826 0, 827 0, 828 0, 829 0, 830 0, 831 0, 832 0, 833 duration.microseconds, 834 duration.nanoseconds, 835 }); 836 exponent = 100; 837 break; 838 } 839 840 default: 841 MOZ_CRASH("bad temporal unit"); 842 } 843 844 // Return the result as a decimal string when the fractional part is non-zero. 845 846 DurationValue result{}; 847 848 char* chars = result.decimal; 849 850 // Leading '-' sign when the duration is negative. 851 if (timeDuration < TimeDuration{}) { 852 *chars++ = '-'; 853 timeDuration = timeDuration.abs(); 854 } 855 856 // Next the string representation of the seconds value. 857 auto res = 858 std::to_chars(chars, std::end(result.decimal), timeDuration.seconds); 859 MOZ_ASSERT(res.ec == std::errc()); 860 861 // Set |chars| to one past the last character written by `std::to_chars`. 862 chars = res.ptr; 863 864 // Finish with string representation of the nanoseconds value, without any 865 // trailing zeros. 866 int32_t nanos = timeDuration.nanoseconds; 867 for (int32_t k = 100'000'000; k != 0 && nanos != 0; k /= 10) { 868 // Add decimal separator add the correct position based on |exponent|. 869 if (k == exponent) { 870 *chars++ = '.'; 871 } 872 873 *chars++ = char('0' + (nanos / k)); 874 nanos %= k; 875 } 876 877 MOZ_ASSERT((chars - result.decimal) <= 878 ptrdiff_t(DurationValue::MaximumDecimalStringLength), 879 "unexpected decimal string length"); 880 881 return result; 882 } 883 884 /** 885 * FormatNumericHours ( durationFormat, hoursValue, signDisplayed ) 886 * 887 * FormatNumericMinutes ( durationFormat, minutesValue, hoursDisplayed, 888 * signDisplayed ) 889 * 890 * FormatNumericSeconds ( durationFormat, secondsValue, minutesDisplayed, 891 * signDisplayed ) 892 */ 893 static mozilla::intl::NumberFormat* NewNumericFormatter( 894 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 895 temporal::TemporalUnit unit) { 896 // FormatNumericHours, step 1. (Not applicable in our implementation.) 897 // FormatNumericMinutes, steps 1-2. (Not applicable in our implementation.) 898 // FormatNumericSeconds, steps 1-2. (Not applicable in our implementation.) 899 900 // FormatNumericHours, step 2. 901 // FormatNumericMinutes, step 3. 902 // FormatNumericSeconds, step 3. 903 auto* dfOptions = durationFormat->getOptions(); 904 MOZ_ASSERT(dfOptions, "unexpected unresolved duration format options"); 905 906 auto style = GetUnitOptions(*dfOptions, unit).style(); 907 908 // FormatNumericHours, step 3. 909 // FormatNumericMinutes, step 4. 910 // FormatNumericSeconds, step 4. 911 MOZ_ASSERT(style == DurationStyle::Numeric || 912 style == DurationStyle::TwoDigit); 913 914 // FormatNumericHours, step 4. 915 // FormatNumericMinutes, step 5. 916 // FormatNumericSeconds, step 5. 917 mozilla::intl::NumberFormatOptions options{}; 918 919 // FormatNumericHours, steps 5-6. (Not applicable in our implementation.) 920 // FormatNumericMinutes, steps 6-7. (Not applicable in our implementation.) 921 // FormatNumericSeconds, steps 6-7. (Not applicable in our implementation.) 922 923 // FormatNumericHours, step 7. 924 // FormatNumericMinutes, step 8. 925 // FormatNumericSeconds, step 8. 926 if (style == DurationStyle::TwoDigit) { 927 options.mMinIntegerDigits = mozilla::Some(2); 928 } 929 930 // FormatNumericHours, step 8. (Not applicable in our implementation.) 931 // FormatNumericMinutes, step 9. (Not applicable in our implementation.) 932 // FormatNumericSeconds, step 9. (Not applicable in our implementation.) 933 934 // FormatNumericHours, step 9. 935 // FormatNumericMinutes, step 10. 936 // FormatNumericSeconds, step 10. 937 options.mGrouping = mozilla::intl::NumberFormatOptions::Grouping::Never; 938 939 // FormatNumericSeconds, steps 11-14. 940 if (unit == temporal::TemporalUnit::Second) { 941 // FormatNumericSeconds, step 11. 942 auto fractionalDigits = GetFractionalDigits(durationFormat); 943 944 // FormatNumericSeconds, steps 12-13. 945 options.mFractionDigits = mozilla::Some(fractionalDigits); 946 947 // FormatNumericSeconds, step 14. 948 options.mRoundingMode = 949 mozilla::intl::NumberFormatOptions::RoundingMode::Trunc; 950 } 951 952 // FormatNumericHours, step 10. 953 // FormatNumericMinutes, step 11. 954 // FormatNumericSeconds, step 15. 955 return NewDurationNumberFormat(cx, durationFormat, options); 956 } 957 958 static mozilla::intl::NumberFormat* GetOrCreateNumericFormatter( 959 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 960 temporal::TemporalUnit unit) { 961 // Obtain a cached mozilla::intl::NumberFormat object. 962 auto* nf = durationFormat->getNumberFormat(unit); 963 if (nf) { 964 return nf; 965 } 966 967 nf = NewNumericFormatter(cx, durationFormat, unit); 968 if (!nf) { 969 return nullptr; 970 } 971 durationFormat->setNumberFormat(unit, nf); 972 973 AddICUCellMemory(durationFormat, NumberFormatObject::EstimatedMemoryUse); 974 return nf; 975 } 976 977 /** 978 * NextUnitFractional ( durationFormat, unit ) 979 */ 980 static bool NextUnitFractional(const DurationFormatObject* durationFormat, 981 temporal::TemporalUnit unit) { 982 using namespace temporal; 983 984 // Steps 1-3. 985 if (TemporalUnit::Second <= unit && unit <= TemporalUnit::Microsecond) { 986 auto* options = durationFormat->getOptions(); 987 MOZ_ASSERT(options, "unexpected unresolved duration format options"); 988 989 using TemporalUnitType = std::underlying_type_t<TemporalUnit>; 990 991 auto nextUnit = 992 static_cast<TemporalUnit>(static_cast<TemporalUnitType>(unit) + 1); 993 auto nextStyle = GetUnitOptions(*options, nextUnit).style(); 994 return nextStyle == DurationStyle::Numeric; 995 } 996 997 // Step 4. 998 return false; 999 } 1000 1001 /** 1002 * PartitionDurationFormatPattern ( durationFormat, duration ) 1003 */ 1004 static mozilla::intl::NumberFormat* NewNumberFormat( 1005 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 1006 temporal::TemporalUnit unit, DurationStyle style) { 1007 // Step 4.h.i. 1008 mozilla::intl::NumberFormatOptions options{}; 1009 1010 // Step 4.h.ii. 1011 if (NextUnitFractional(durationFormat, unit)) { 1012 // Steps 4.h.ii.2-4. 1013 auto fractionalDigits = GetFractionalDigits(durationFormat); 1014 options.mFractionDigits = mozilla::Some(fractionalDigits); 1015 1016 // Step 4.h.ii.5. 1017 options.mRoundingMode = 1018 mozilla::intl::NumberFormatOptions::RoundingMode::Trunc; 1019 } 1020 1021 // Steps 4.h.iii.4-6. 1022 options.mUnit = mozilla::Some(std::pair{UnitName(unit), UnitDisplay(style)}); 1023 1024 // Step 4.h.iii.7. 1025 return NewDurationNumberFormat(cx, durationFormat, options); 1026 } 1027 1028 static mozilla::intl::NumberFormat* GetOrCreateNumberFormat( 1029 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 1030 temporal::TemporalUnit unit, DurationStyle style) { 1031 // Obtain a cached mozilla::intl::NumberFormat object. 1032 auto* nf = durationFormat->getNumberFormat(unit); 1033 if (nf) { 1034 return nf; 1035 } 1036 1037 nf = NewNumberFormat(cx, durationFormat, unit, style); 1038 if (!nf) { 1039 return nullptr; 1040 } 1041 durationFormat->setNumberFormat(unit, nf); 1042 1043 AddICUCellMemory(durationFormat, NumberFormatObject::EstimatedMemoryUse); 1044 return nf; 1045 } 1046 1047 static JSLinearString* FormatDurationValueToString( 1048 JSContext* cx, mozilla::intl::NumberFormat* nf, 1049 const DurationValue& value) { 1050 if (value.isDecimal()) { 1051 return FormatNumber(cx, nf, std::string_view{value}); 1052 } 1053 return FormatNumber(cx, nf, value.number); 1054 } 1055 1056 static ArrayObject* FormatDurationValueToParts(JSContext* cx, 1057 mozilla::intl::NumberFormat* nf, 1058 const DurationValue& value, 1059 temporal::TemporalUnit unit) { 1060 if (value.isDecimal()) { 1061 return FormatNumberToParts(cx, nf, std::string_view{value}, 1062 PartUnitName(unit)); 1063 } 1064 return FormatNumberToParts(cx, nf, value.number, PartUnitName(unit)); 1065 } 1066 1067 static bool FormatDurationValue(JSContext* cx, mozilla::intl::NumberFormat* nf, 1068 temporal::TemporalUnit unit, 1069 const DurationValue& value, bool formatToParts, 1070 MutableHandle<Value> result) { 1071 if (!formatToParts) { 1072 auto* str = FormatDurationValueToString(cx, nf, value); 1073 if (!str) { 1074 return false; 1075 } 1076 result.setString(str); 1077 } else { 1078 auto* parts = FormatDurationValueToParts(cx, nf, value, unit); 1079 if (!parts) { 1080 return false; 1081 } 1082 result.setObject(*parts); 1083 } 1084 return true; 1085 } 1086 1087 /** 1088 * FormatNumericHours ( durationFormat, hoursValue, signDisplayed ) 1089 * 1090 * FormatNumericMinutes ( durationFormat, minutesValue, hoursDisplayed, 1091 * signDisplayed ) 1092 * 1093 * FormatNumericSeconds ( durationFormat, secondsValue, minutesDisplayed, 1094 * signDisplayed ) 1095 */ 1096 static bool FormatNumericHoursOrMinutesOrSeconds( 1097 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 1098 temporal::TemporalUnit unit, const DurationValue& value, bool formatToParts, 1099 MutableHandle<Value> result) { 1100 MOZ_ASSERT(temporal::TemporalUnit::Hour <= unit && 1101 unit <= temporal::TemporalUnit::Second); 1102 1103 // FormatNumericHours, steps 1-10. 1104 // FormatNumericMinutes, steps 1-11. 1105 // FormatNumericSeconds, steps 1-15. 1106 auto* nf = GetOrCreateNumericFormatter(cx, durationFormat, unit); 1107 if (!nf) { 1108 return false; 1109 } 1110 1111 // FormatNumericHours, steps 11-13. 1112 // FormatNumericMinutes, steps 12-14. 1113 // FormatNumericSeconds, steps 16-18. 1114 return FormatDurationValue(cx, nf, unit, value, formatToParts, result); 1115 } 1116 1117 static PlainObject* NewLiteralPart(JSContext* cx, JSString* value) { 1118 Rooted<IdValueVector> properties(cx, cx); 1119 if (!properties.emplaceBack(NameToId(cx->names().type), 1120 StringValue(cx->names().literal))) { 1121 return nullptr; 1122 } 1123 if (!properties.emplaceBack(NameToId(cx->names().value), 1124 StringValue(value))) { 1125 return nullptr; 1126 } 1127 1128 return NewPlainObjectWithUniqueNames(cx, properties); 1129 } 1130 1131 /** 1132 * FormatNumericUnits ( durationFormat, duration, firstNumericUnit, 1133 * signDisplayed ) 1134 */ 1135 static bool FormatNumericUnits(JSContext* cx, 1136 Handle<DurationFormatObject*> durationFormat, 1137 const temporal::Duration& duration, 1138 temporal::TemporalUnit firstNumericUnit, 1139 bool signDisplayed, bool formatToParts, 1140 MutableHandle<Value> result) { 1141 using namespace temporal; 1142 1143 auto* options = durationFormat->getOptions(); 1144 MOZ_ASSERT(options, "unexpected unresolved duration format options"); 1145 1146 Rooted<Value> formattedValue(cx); 1147 1148 // Step 1. 1149 MOZ_ASSERT(TemporalUnit::Hour <= firstNumericUnit && 1150 firstNumericUnit <= TemporalUnit::Second); 1151 1152 // Step 2. 1153 using FormattedNumericUnitsVector = JS::GCVector<Value, 3>; 1154 Rooted<FormattedNumericUnitsVector> numericPartsList(cx, cx); 1155 if (!numericPartsList.reserve(3)) { 1156 return false; 1157 } 1158 1159 // Step 3. 1160 auto hoursValue = DurationValue{duration.hours}; 1161 1162 // Step 4. 1163 auto hoursDisplay = GetUnitOptions(*options, TemporalUnit::Hour).display(); 1164 1165 // Step 5. 1166 auto minutesValue = DurationValue{duration.minutes}; 1167 1168 // Step 6. 1169 auto minutesDisplay = 1170 GetUnitOptions(*options, TemporalUnit::Minute).display(); 1171 1172 // Step 7-8. 1173 auto secondsValue = ComputeFractionalDigits(duration, TemporalUnit::Second); 1174 1175 // Step 9. 1176 auto secondsDisplay = 1177 GetUnitOptions(*options, TemporalUnit::Second).display(); 1178 1179 // Step 10. 1180 bool hoursFormatted = false; 1181 1182 // Step 11. 1183 if (firstNumericUnit == TemporalUnit::Hour) { 1184 // Step 11.a. 1185 hoursFormatted = 1186 !hoursValue.isZero() || hoursDisplay == DurationDisplay::Always; 1187 } 1188 1189 // Steps 12-13. 1190 bool secondsFormatted = 1191 !secondsValue.isZero() || secondsDisplay == DurationDisplay::Always; 1192 1193 // Step 14. 1194 bool minutesFormatted = false; 1195 1196 // Step 15. 1197 if (firstNumericUnit == TemporalUnit::Hour || 1198 firstNumericUnit == TemporalUnit::Minute) { 1199 // Steps 15.a-b. 1200 minutesFormatted = (hoursFormatted && secondsFormatted) || 1201 !minutesValue.isZero() || 1202 minutesDisplay == DurationDisplay::Always; 1203 } 1204 1205 // Return early when no units are displayed. 1206 if (!hoursFormatted && !minutesFormatted && !secondsFormatted) { 1207 return true; 1208 } 1209 1210 // Step 16. 1211 if (hoursFormatted) { 1212 // Step 16.a. 1213 if (signDisplayed) { 1214 if (hoursValue.isZero() && temporal::DurationSign(duration) < 0) { 1215 hoursValue = DurationValue{-0.0}; 1216 } 1217 } else { 1218 // Use the absolute value to avoid changing number-format sign display. 1219 hoursValue = hoursValue.abs(); 1220 } 1221 1222 // Step 16.b. 1223 if (!FormatNumericHoursOrMinutesOrSeconds(cx, durationFormat, 1224 TemporalUnit::Hour, hoursValue, 1225 formatToParts, &formattedValue)) { 1226 return false; 1227 } 1228 1229 // Step 16.c. 1230 numericPartsList.infallibleAppend(formattedValue); 1231 1232 // Step 16.d. 1233 signDisplayed = false; 1234 } 1235 1236 // Step 17. 1237 if (minutesFormatted) { 1238 // Step 17.a. 1239 if (signDisplayed) { 1240 if (minutesValue.isZero() && temporal::DurationSign(duration) < 0) { 1241 minutesValue = DurationValue{-0.0}; 1242 } 1243 } else { 1244 // Use the absolute value to avoid changing number-format sign display. 1245 minutesValue = minutesValue.abs(); 1246 } 1247 1248 // Step 17.b. 1249 if (!FormatNumericHoursOrMinutesOrSeconds( 1250 cx, durationFormat, TemporalUnit::Minute, minutesValue, 1251 formatToParts, &formattedValue)) { 1252 return false; 1253 } 1254 1255 // Step 17.c. 1256 numericPartsList.infallibleAppend(formattedValue); 1257 1258 // Step 17.d. 1259 signDisplayed = false; 1260 } 1261 1262 // Step 18. 1263 if (secondsFormatted) { 1264 // Step 18.a. 1265 if (!signDisplayed) { 1266 // Use the absolute value to avoid changing number-format sign display. 1267 secondsValue = secondsValue.abs(); 1268 } 1269 if (!FormatNumericHoursOrMinutesOrSeconds( 1270 cx, durationFormat, TemporalUnit::Second, secondsValue, 1271 formatToParts, &formattedValue)) { 1272 return false; 1273 } 1274 1275 // Step 18.b. 1276 numericPartsList.infallibleAppend(formattedValue); 1277 } 1278 1279 MOZ_ASSERT(numericPartsList.length() > 0); 1280 1281 // Step 19. 1282 if (numericPartsList.length() <= 1) { 1283 result.set(numericPartsList[0]); 1284 return true; 1285 } 1286 1287 Rooted<JSString*> timeSeparator(cx, GetTimeSeparator(cx, durationFormat)); 1288 if (!timeSeparator) { 1289 return false; 1290 } 1291 1292 // Combine the individual parts into a single result. 1293 if (!formatToParts) { 1294 // Perform string concatenation when not formatting to parts. 1295 1296 Rooted<JSString*> string(cx, numericPartsList[0].toString()); 1297 Rooted<JSString*> nextString(cx); 1298 for (size_t i = 1; i < numericPartsList.length(); i++) { 1299 // Add the time separator between all elements. 1300 string = ConcatStrings<CanGC>(cx, string, timeSeparator); 1301 if (!string) { 1302 return false; 1303 } 1304 1305 // Concatenate the formatted parts. 1306 nextString = numericPartsList[i].toString(); 1307 string = ConcatStrings<CanGC>(cx, string, nextString); 1308 if (!string) { 1309 return false; 1310 } 1311 } 1312 1313 result.setString(string); 1314 } else { 1315 // Append all formatted parts into a new array when formatting to parts. 1316 1317 // First compute the final length of the result array. 1318 size_t length = 0; 1319 for (size_t i = 0; i < numericPartsList.length(); i++) { 1320 length += numericPartsList[i].toObject().as<ArrayObject>().length(); 1321 } 1322 1323 // Account for the time separator parts. 1324 length += numericPartsList.length() - 1; 1325 1326 Rooted<ArrayObject*> array(cx, NewDenseFullyAllocatedArray(cx, length)); 1327 if (!array) { 1328 return false; 1329 } 1330 array->ensureDenseInitializedLength(0, length); 1331 1332 size_t index = 0; 1333 for (size_t i = 0; i < numericPartsList.length(); i++) { 1334 // Add the time separator between all elements. 1335 if (i > 0) { 1336 auto* timeSeparatorPart = NewLiteralPart(cx, timeSeparator); 1337 if (!timeSeparatorPart) { 1338 return false; 1339 } 1340 array->initDenseElement(index++, ObjectValue(*timeSeparatorPart)); 1341 } 1342 1343 auto* part = &numericPartsList[i].toObject().as<ArrayObject>(); 1344 MOZ_ASSERT(IsPackedArray(part)); 1345 1346 // Append the formatted parts from |part|. 1347 for (size_t j = 0; j < part->length(); j++) { 1348 array->initDenseElement(index++, part->getDenseElement(j)); 1349 } 1350 } 1351 MOZ_ASSERT(index == length); 1352 1353 result.setObject(*array); 1354 } 1355 return true; 1356 } 1357 1358 static mozilla::intl::ListFormat* NewDurationListFormat( 1359 JSContext* cx, Handle<DurationFormatObject*> durationFormat) { 1360 Rooted<JSObject*> internals(cx, GetInternalsObject(cx, durationFormat)); 1361 if (!internals) { 1362 return nullptr; 1363 } 1364 1365 Rooted<Value> value(cx); 1366 if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { 1367 return nullptr; 1368 } 1369 1370 UniqueChars locale = EncodeLocale(cx, value.toString()); 1371 if (!locale) { 1372 return nullptr; 1373 } 1374 1375 mozilla::intl::ListFormat::Options options; 1376 options.mType = mozilla::intl::ListFormat::Type::Unit; 1377 1378 if (!GetProperty(cx, internals, internals, cx->names().style, &value)) { 1379 return nullptr; 1380 } 1381 { 1382 auto* linear = value.toString()->ensureLinear(cx); 1383 if (!linear) { 1384 return nullptr; 1385 } 1386 1387 using ListFormatStyle = mozilla::intl::ListFormat::Style; 1388 if (StringEqualsLiteral(linear, "long")) { 1389 options.mStyle = ListFormatStyle::Long; 1390 } else if (StringEqualsLiteral(linear, "short")) { 1391 options.mStyle = ListFormatStyle::Short; 1392 } else if (StringEqualsLiteral(linear, "narrow")) { 1393 options.mStyle = ListFormatStyle::Narrow; 1394 } else { 1395 MOZ_ASSERT(StringEqualsLiteral(linear, "digital")); 1396 options.mStyle = ListFormatStyle::Short; 1397 } 1398 } 1399 1400 auto result = mozilla::intl::ListFormat::TryCreate( 1401 mozilla::MakeStringSpan(locale.get()), options); 1402 if (result.isErr()) { 1403 ReportInternalError(cx, result.unwrapErr()); 1404 return nullptr; 1405 } 1406 return result.unwrap().release(); 1407 } 1408 1409 static mozilla::intl::ListFormat* GetOrCreateListFormat( 1410 JSContext* cx, Handle<DurationFormatObject*> durationFormat) { 1411 // Obtain a cached mozilla::intl::ListFormat object. 1412 auto* lf = durationFormat->getListFormat(); 1413 if (lf) { 1414 return lf; 1415 } 1416 1417 lf = NewDurationListFormat(cx, durationFormat); 1418 if (!lf) { 1419 return nullptr; 1420 } 1421 durationFormat->setListFormat(lf); 1422 1423 AddICUCellMemory(durationFormat, ListFormatObject::EstimatedMemoryUse); 1424 return lf; 1425 } 1426 1427 // Stack space must be large enough to hold all ten duration values. 1428 static constexpr size_t FormattedDurationValueVectorCapacity = 10; 1429 1430 using FormattedDurationValueVector = 1431 JS::GCVector<JS::Value, FormattedDurationValueVectorCapacity>; 1432 1433 /** 1434 * ListFormatParts ( durationFormat, partitionedPartsList ) 1435 */ 1436 static bool ListFormatParts( 1437 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 1438 Handle<FormattedDurationValueVector> partitionedPartsList, 1439 bool formatToParts, MutableHandle<Value> result) { 1440 // Steps 1-6. 1441 auto* lf = GetOrCreateListFormat(cx, durationFormat); 1442 if (!lf) { 1443 return false; 1444 } 1445 1446 // <https://unicode.org/reports/tr35/tr35-general.html#ListPatterns> requires 1447 // that the list patterns are sorted, for example "{1} and {0}" isn't a valid 1448 // pattern, because "{1}" appears before "{0}". This requirement also means 1449 // all entries appear in order in the formatted result. 1450 1451 // Step 7. 1452 Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx); 1453 mozilla::intl::ListFormat::StringList stringList{}; 1454 1455 // Step 8. 1456 Rooted<JSString*> string(cx); 1457 Rooted<JSString*> nextString(cx); 1458 Rooted<ArrayObject*> parts(cx); 1459 Rooted<NativeObject*> part(cx); 1460 Rooted<Value> value(cx); 1461 for (size_t i = 0; i < partitionedPartsList.length(); i++) { 1462 if (!formatToParts) { 1463 string = partitionedPartsList[i].toString(); 1464 } else { 1465 parts = &partitionedPartsList[i].toObject().as<ArrayObject>(); 1466 MOZ_ASSERT(IsPackedArray(parts)); 1467 1468 // Combine the individual number-formatted parts into a single string. 1469 string = cx->emptyString(); 1470 for (size_t j = 0; j < parts->length(); j++) { 1471 part = &parts->getDenseElement(j).toObject().as<NativeObject>(); 1472 MOZ_ASSERT(part->containsPure(cx->names().type) && 1473 part->containsPure(cx->names().value), 1474 "part is a number-formatted element"); 1475 1476 if (!GetProperty(cx, part, part, cx->names().value, &value)) { 1477 return false; 1478 } 1479 MOZ_ASSERT(value.isString()); 1480 1481 nextString = value.toString(); 1482 string = ConcatStrings<CanGC>(cx, string, nextString); 1483 if (!string) { 1484 return false; 1485 } 1486 } 1487 } 1488 1489 auto* linear = string->ensureLinear(cx); 1490 if (!linear) { 1491 return false; 1492 } 1493 1494 size_t linearLength = linear->length(); 1495 1496 auto chars = cx->make_pod_array<char16_t>(linearLength); 1497 if (!chars) { 1498 return false; 1499 } 1500 CopyChars(chars.get(), *linear); 1501 1502 if (!strings.append(std::move(chars))) { 1503 return false; 1504 } 1505 1506 if (!stringList.emplaceBack(strings[i].get(), linearLength)) { 1507 return false; 1508 } 1509 } 1510 1511 FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx); 1512 mozilla::intl::ListFormat::PartVector partVector{}; 1513 1514 // Step 9. 1515 auto formatResult = formatToParts 1516 ? lf->FormatToParts(stringList, buffer, partVector) 1517 : lf->Format(stringList, buffer); 1518 if (formatResult.isErr()) { 1519 ReportInternalError(cx, formatResult.unwrapErr()); 1520 return false; 1521 } 1522 1523 Rooted<JSLinearString*> overallResult(cx, buffer.toString(cx)); 1524 if (!overallResult) { 1525 return false; 1526 } 1527 1528 // Directly return the string result when not formatting to parts. 1529 if (!formatToParts) { 1530 result.setString(overallResult); 1531 return true; 1532 } 1533 1534 // Step 10. 1535 size_t partitionedPartsIndex = 0; 1536 1537 // Step 11. (Not applicable in our implementation.) 1538 1539 // Compute the final length of the result array. 1540 size_t flattenedLength = 0; 1541 for (size_t i = 0; i < partitionedPartsList.length(); i++) { 1542 auto* parts = &partitionedPartsList[i].toObject().as<ArrayObject>(); 1543 flattenedLength += parts->length(); 1544 } 1545 for (const auto& part : partVector) { 1546 if (part.first == mozilla::intl::ListFormat::PartType::Literal) { 1547 flattenedLength += 1; 1548 } 1549 } 1550 1551 // Step 12. 1552 Rooted<ArrayObject*> flattenedPartsList( 1553 cx, NewDenseFullyAllocatedArray(cx, flattenedLength)); 1554 if (!flattenedPartsList) { 1555 return false; 1556 } 1557 flattenedPartsList->ensureDenseInitializedLength(0, flattenedLength); 1558 1559 // Step 13. 1560 size_t flattenedPartsIndex = 0; 1561 size_t partBeginIndex = 0; 1562 for (const auto& part : partVector) { 1563 // Steps 13.a-b. 1564 if (part.first == mozilla::intl::ListFormat::PartType::Element) { 1565 // Step 13.a.i. 1566 MOZ_ASSERT(partitionedPartsIndex < partitionedPartsList.length(), 1567 "partitionedPartsIndex is an index into result"); 1568 1569 // Step 13.a.ii. 1570 auto* parts = &partitionedPartsList[partitionedPartsIndex] 1571 .toObject() 1572 .as<ArrayObject>(); 1573 MOZ_ASSERT(IsPackedArray(parts)); 1574 1575 // Step 13.a.iii. 1576 // 1577 // Replace the "element" parts with the number-formatted result. 1578 for (size_t i = 0; i < parts->length(); i++) { 1579 flattenedPartsList->initDenseElement(flattenedPartsIndex++, 1580 parts->getDenseElement(i)); 1581 } 1582 1583 // Step 13.a.iv. 1584 partitionedPartsIndex += 1; 1585 } else { 1586 // Step 13.b.i. 1587 // 1588 // Append "literal" parts as-is. 1589 MOZ_ASSERT(part.first == mozilla::intl::ListFormat::PartType::Literal); 1590 1591 // Step 13.b.ii. 1592 MOZ_ASSERT(part.second >= partBeginIndex); 1593 auto* partStr = NewDependentString(cx, overallResult, partBeginIndex, 1594 part.second - partBeginIndex); 1595 if (!partStr) { 1596 return false; 1597 } 1598 1599 auto* literalPart = NewLiteralPart(cx, partStr); 1600 if (!literalPart) { 1601 return false; 1602 } 1603 1604 flattenedPartsList->initDenseElement(flattenedPartsIndex++, 1605 ObjectValue(*literalPart)); 1606 } 1607 1608 partBeginIndex = part.second; 1609 } 1610 1611 MOZ_ASSERT(partitionedPartsIndex == partitionedPartsList.length(), 1612 "all number-formatted parts handled"); 1613 MOZ_ASSERT(flattenedPartsIndex == flattenedLength, 1614 "flattened array length miscomputed"); 1615 1616 // Step 14. 1617 result.setObject(*flattenedPartsList); 1618 return true; 1619 } 1620 1621 /** 1622 * PartitionDurationFormatPattern ( durationFormat, duration ) 1623 */ 1624 static bool PartitionDurationFormatPattern( 1625 JSContext* cx, Handle<DurationFormatObject*> durationFormat, 1626 Handle<Value> durationLike, bool formatToParts, 1627 MutableHandle<Value> result) { 1628 using namespace temporal; 1629 1630 Duration duration; 1631 if (!ToTemporalDuration(cx, durationLike, &duration)) { 1632 return false; 1633 } 1634 1635 // Normalize -0 to +0 by adding zero. 1636 duration.years += +0.0; 1637 duration.months += +0.0; 1638 duration.weeks += +0.0; 1639 duration.days += +0.0; 1640 duration.hours += +0.0; 1641 duration.minutes += +0.0; 1642 duration.seconds += +0.0; 1643 duration.milliseconds += +0.0; 1644 duration.microseconds += +0.0; 1645 duration.nanoseconds += +0.0; 1646 1647 static_assert(durationUnits.size() == FormattedDurationValueVectorCapacity, 1648 "inline stack capacity large enough for all duration units"); 1649 1650 auto* options = GetOrCreateDurationFormatOptions(cx, durationFormat); 1651 if (!options) { 1652 return false; 1653 } 1654 1655 Rooted<Value> formattedValue(cx); 1656 1657 // Step 1. 1658 Rooted<FormattedDurationValueVector> formattedValues(cx, cx); 1659 if (!formattedValues.reserve(FormattedDurationValueVectorCapacity)) { 1660 return false; 1661 } 1662 1663 // Step 2. 1664 bool signDisplayed = true; 1665 1666 // Step 3. 1667 bool numericUnitFound = false; 1668 1669 // Step 4. 1670 for (auto unit : durationUnits) { 1671 if (numericUnitFound) { 1672 break; 1673 } 1674 1675 // Step 4.a. (Moved below) 1676 1677 // Step 4.b. 1678 auto unitOptions = GetUnitOptions(*options, unit); 1679 1680 // Step 4.c. 1681 auto style = unitOptions.style(); 1682 1683 // Step 4.d. 1684 auto display = unitOptions.display(); 1685 1686 // Steps 4.e-f. (Not applicable in our implementation.) 1687 1688 // Steps 4.g-h. 1689 if (style == DurationStyle::Numeric || style == DurationStyle::TwoDigit) { 1690 // Step 4.g.i. 1691 if (!FormatNumericUnits(cx, durationFormat, duration, unit, signDisplayed, 1692 formatToParts, &formattedValue)) { 1693 return false; 1694 } 1695 1696 // Step 4.g.ii. 1697 if (!formattedValue.isUndefined()) { 1698 formattedValues.infallibleAppend(formattedValue); 1699 } 1700 1701 // Step 4.g.iii. 1702 numericUnitFound = true; 1703 } else { 1704 // Step 4.a. 1705 auto value = ToDurationValue(duration, unit); 1706 1707 // Step 4.h.i. (Performed in NewNumberFormat) 1708 1709 // Step 4.h.ii. 1710 if (NextUnitFractional(durationFormat, unit)) { 1711 // Step 4.h.ii.1. 1712 value = ComputeFractionalDigits(duration, unit); 1713 1714 // Steps 4.h.ii.2-5. (Performed in NewNumberFormat) 1715 1716 // Step 4.h.ii.6. 1717 numericUnitFound = true; 1718 } 1719 1720 // Step 4.h.iii. (Condition inverted to reduce indentation.) 1721 if (display == DurationDisplay::Auto && value.isZero()) { 1722 continue; 1723 } 1724 1725 // Steps 4.h.iii.2-3. 1726 if (signDisplayed) { 1727 // Step 4.h.iii.2.a. 1728 signDisplayed = false; 1729 1730 // Step 4.h.iii.2.b. 1731 if (value.isZero() && temporal::DurationSign(duration) < 0) { 1732 value = DurationValue{-0.0}; 1733 } 1734 } else { 1735 // Use the absolute value to avoid changing number-format sign display. 1736 value = value.abs(); 1737 } 1738 1739 // Steps 4.h.iii.1, 4.h.iii.4-7. 1740 auto* nf = GetOrCreateNumberFormat(cx, durationFormat, unit, style); 1741 if (!nf) { 1742 return false; 1743 } 1744 1745 // Steps 4.h.iii.8-10. 1746 if (!FormatDurationValue(cx, nf, unit, value, formatToParts, 1747 &formattedValue)) { 1748 return false; 1749 } 1750 1751 // Step 4.h.iii.11. 1752 formattedValues.infallibleAppend(formattedValue); 1753 } 1754 } 1755 1756 // Step 5. 1757 return ListFormatParts(cx, durationFormat, formattedValues, formatToParts, 1758 result); 1759 } 1760 1761 static bool IsDurationFormat(HandleValue v) { 1762 return v.isObject() && v.toObject().is<DurationFormatObject>(); 1763 } 1764 1765 /** 1766 * Intl.DurationFormat.prototype.format ( durationLike ) 1767 */ 1768 static bool durationFormat_format(JSContext* cx, const JS::CallArgs& args) { 1769 Rooted<DurationFormatObject*> durationFormat( 1770 cx, &args.thisv().toObject().as<DurationFormatObject>()); 1771 return PartitionDurationFormatPattern( 1772 cx, durationFormat, args.get(0), /* formatToParts= */ false, args.rval()); 1773 } 1774 1775 /** 1776 * Intl.DurationFormat.prototype.format ( durationLike ) 1777 */ 1778 static bool durationFormat_format(JSContext* cx, unsigned argc, Value* vp) { 1779 CallArgs args = CallArgsFromVp(argc, vp); 1780 return CallNonGenericMethod<IsDurationFormat, durationFormat_format>(cx, 1781 args); 1782 } 1783 1784 /** 1785 * Intl.DurationFormat.prototype.formatToParts ( durationLike ) 1786 */ 1787 static bool durationFormat_formatToParts(JSContext* cx, 1788 const JS::CallArgs& args) { 1789 Rooted<DurationFormatObject*> durationFormat( 1790 cx, &args.thisv().toObject().as<DurationFormatObject>()); 1791 return PartitionDurationFormatPattern(cx, durationFormat, args.get(0), 1792 /* formatToParts= */ true, args.rval()); 1793 } 1794 1795 /** 1796 * Intl.DurationFormat.prototype.formatToParts ( durationLike ) 1797 */ 1798 static bool durationFormat_formatToParts(JSContext* cx, unsigned argc, 1799 Value* vp) { 1800 CallArgs args = CallArgsFromVp(argc, vp); 1801 return CallNonGenericMethod<IsDurationFormat, durationFormat_formatToParts>( 1802 cx, args); 1803 } 1804 1805 /** 1806 * Intl.DurationFormat.supportedLocalesOf ( locales [ , options ] ) 1807 */ 1808 static bool durationFormat_supportedLocalesOf(JSContext* cx, unsigned argc, 1809 Value* vp) { 1810 CallArgs args = CallArgsFromVp(argc, vp); 1811 1812 // Steps 1-3. 1813 auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DurationFormat, 1814 args.get(0), args.get(1)); 1815 if (!array) { 1816 return false; 1817 } 1818 args.rval().setObject(*array); 1819 return true; 1820 } 1821 1822 bool js::TemporalDurationToLocaleString(JSContext* cx, 1823 const JS::CallArgs& args) { 1824 MOZ_ASSERT(args.thisv().isObject()); 1825 MOZ_ASSERT(args.thisv().toObject().is<temporal::DurationObject>()); 1826 1827 Rooted<DurationFormatObject*> durationFormat( 1828 cx, NewBuiltinClassInstance<DurationFormatObject>(cx)); 1829 if (!durationFormat) { 1830 return false; 1831 } 1832 1833 if (!intl::InitializeObject(cx, durationFormat, 1834 cx->names().InitializeDurationFormat, args.get(0), 1835 args.get(1))) { 1836 return false; 1837 } 1838 1839 return PartitionDurationFormatPattern(cx, durationFormat, args.thisv(), 1840 /* formatToParts= */ false, 1841 args.rval()); 1842 }