Duration.cpp (120867B)
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 #include "builtin/temporal/Duration.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/Casting.h" 11 #include "mozilla/CheckedInt.h" 12 #include "mozilla/FloatingPoint.h" 13 #include "mozilla/Maybe.h" 14 15 #include <algorithm> 16 #include <cmath> 17 #include <cstdlib> 18 #include <stdint.h> 19 #include <type_traits> 20 21 #include "jsnum.h" 22 #include "jspubtd.h" 23 #include "NamespaceImports.h" 24 25 #include "builtin/intl/DurationFormat.h" 26 #include "builtin/temporal/Calendar.h" 27 #include "builtin/temporal/CalendarFields.h" 28 #include "builtin/temporal/Instant.h" 29 #include "builtin/temporal/Int96.h" 30 #include "builtin/temporal/PlainDate.h" 31 #include "builtin/temporal/PlainDateTime.h" 32 #include "builtin/temporal/PlainTime.h" 33 #include "builtin/temporal/Temporal.h" 34 #include "builtin/temporal/TemporalParser.h" 35 #include "builtin/temporal/TemporalRoundingMode.h" 36 #include "builtin/temporal/TemporalTypes.h" 37 #include "builtin/temporal/TemporalUnit.h" 38 #include "builtin/temporal/TimeZone.h" 39 #include "builtin/temporal/ZonedDateTime.h" 40 #include "gc/AllocKind.h" 41 #include "gc/Barrier.h" 42 #include "gc/GCEnum.h" 43 #include "js/CallArgs.h" 44 #include "js/CallNonGenericMethod.h" 45 #include "js/Class.h" 46 #include "js/Conversions.h" 47 #include "js/ErrorReport.h" 48 #include "js/friend/ErrorMessages.h" 49 #include "js/Printer.h" 50 #include "js/PropertyDescriptor.h" 51 #include "js/PropertySpec.h" 52 #include "js/RootingAPI.h" 53 #include "js/Value.h" 54 #include "util/StringBuilder.h" 55 #include "vm/BytecodeUtil.h" 56 #include "vm/GlobalObject.h" 57 #include "vm/Int128.h" 58 #include "vm/JSAtomState.h" 59 #include "vm/JSContext.h" 60 #include "vm/JSObject.h" 61 #include "vm/PlainObject.h" 62 #include "vm/StringType.h" 63 64 #include "vm/JSObject-inl.h" 65 #include "vm/NativeObject-inl.h" 66 #include "vm/ObjectOperations-inl.h" 67 68 using namespace js; 69 using namespace js::temporal; 70 71 static inline bool IsDuration(Handle<Value> v) { 72 return v.isObject() && v.toObject().is<DurationObject>(); 73 } 74 75 #ifdef DEBUG 76 static bool IsIntegerOrInfinity(double d) { 77 return IsInteger(d) || std::isinf(d); 78 } 79 80 static bool IsIntegerOrInfinityDuration(const Duration& duration) { 81 const auto& [years, months, weeks, days, hours, minutes, seconds, 82 milliseconds, microseconds, nanoseconds] = duration; 83 84 // Integers exceeding the Number range are represented as infinity. 85 86 return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) && 87 IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) && 88 IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) && 89 IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) && 90 IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds); 91 } 92 93 static bool IsIntegerDuration(const Duration& duration) { 94 const auto& [years, months, weeks, days, hours, minutes, seconds, 95 milliseconds, microseconds, nanoseconds] = duration; 96 97 return IsInteger(years) && IsInteger(months) && IsInteger(weeks) && 98 IsInteger(days) && IsInteger(hours) && IsInteger(minutes) && 99 IsInteger(seconds) && IsInteger(milliseconds) && 100 IsInteger(microseconds) && IsInteger(nanoseconds); 101 } 102 #endif 103 104 /** 105 * DurationSign ( duration ) 106 */ 107 int32_t js::temporal::DurationSign(const Duration& duration) { 108 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration)); 109 110 const auto& [years, months, weeks, days, hours, minutes, seconds, 111 milliseconds, microseconds, nanoseconds] = duration; 112 113 // Step 1. 114 for (auto v : {years, months, weeks, days, hours, minutes, seconds, 115 milliseconds, microseconds, nanoseconds}) { 116 // Step 1.a. 117 if (v < 0) { 118 return -1; 119 } 120 121 // Step 1.b. 122 if (v > 0) { 123 return 1; 124 } 125 } 126 127 // Step 2. 128 return 0; 129 } 130 131 /** 132 * DateDurationSign ( dateDuration ) 133 */ 134 int32_t js::temporal::DateDurationSign(const DateDuration& duration) { 135 const auto& [years, months, weeks, days] = duration; 136 137 // Step 1. 138 for (auto v : {years, months, weeks, days}) { 139 // Step 1.a. 140 if (v < 0) { 141 return -1; 142 } 143 144 // Step 1.b. 145 if (v > 0) { 146 return 1; 147 } 148 } 149 150 // Step 2. 151 return 0; 152 } 153 154 /** 155 * InternalDurationSign ( internalDuration ) 156 */ 157 static int32_t InternalDurationSign(const InternalDuration& duration) { 158 MOZ_ASSERT(IsValidDuration(duration)); 159 160 if (int32_t sign = DateDurationSign(duration.date)) { 161 return sign; 162 } 163 return TimeDurationSign(duration.time); 164 } 165 166 /** 167 * Create a time duration from a nanoseconds amount. 168 */ 169 static TimeDuration TimeDurationFromNanoseconds(const Int96& nanoseconds) { 170 // Split into seconds and nanoseconds. 171 auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second); 172 173 return {{seconds, nanos}}; 174 } 175 176 /** 177 * Create a time duration from a nanoseconds amount. Return Nothing if the value 178 * is too large. 179 */ 180 static mozilla::Maybe<TimeDuration> TimeDurationFromNanoseconds( 181 double nanoseconds) { 182 MOZ_ASSERT(IsInteger(nanoseconds)); 183 184 if (auto int96 = Int96::fromInteger(nanoseconds)) { 185 // The number of time duration seconds must not exceed `2**53 - 1`. 186 constexpr auto limit = 187 Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second); 188 189 if (int96->abs() < limit) { 190 return mozilla::Some(TimeDurationFromNanoseconds(*int96)); 191 } 192 } 193 return mozilla::Nothing(); 194 } 195 196 /** 197 * Create a time duration from a microseconds amount. 198 */ 199 static TimeDuration TimeDurationFromMicroseconds(const Int96& microseconds) { 200 // Split into seconds and microseconds. 201 auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second); 202 203 // Scale microseconds to nanoseconds. 204 int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond)); 205 206 return {{seconds, nanos}}; 207 } 208 209 /** 210 * Create a time duration from a microseconds amount. Return Nothing if the 211 * value is too large. 212 */ 213 static mozilla::Maybe<TimeDuration> TimeDurationFromMicroseconds( 214 double microseconds) { 215 MOZ_ASSERT(IsInteger(microseconds)); 216 217 if (auto int96 = Int96::fromInteger(microseconds)) { 218 // The number of time duration seconds must not exceed `2**53 - 1`. 219 constexpr auto limit = 220 Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second); 221 222 if (int96->abs() < limit) { 223 return mozilla::Some(TimeDurationFromMicroseconds(*int96)); 224 } 225 } 226 return mozilla::Nothing(); 227 } 228 229 /** 230 * Create a time duration from a duration. Return Nothing if any duration 231 * value is too large. 232 */ 233 static mozilla::Maybe<TimeDuration> TimeDurationFromDuration( 234 const Duration& duration) { 235 do { 236 auto nanoseconds = TimeDurationFromNanoseconds(duration.nanoseconds); 237 if (!nanoseconds) { 238 break; 239 } 240 MOZ_ASSERT(IsValidTimeDuration(*nanoseconds)); 241 242 auto microseconds = TimeDurationFromMicroseconds(duration.microseconds); 243 if (!microseconds) { 244 break; 245 } 246 MOZ_ASSERT(IsValidTimeDuration(*microseconds)); 247 248 // Overflows for millis/seconds/minutes/hours/days always result in an 249 // invalid time duration. 250 251 int64_t milliseconds; 252 if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) { 253 break; 254 } 255 256 int64_t seconds; 257 if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) { 258 break; 259 } 260 261 int64_t minutes; 262 if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) { 263 break; 264 } 265 266 int64_t hours; 267 if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) { 268 break; 269 } 270 271 int64_t days; 272 if (!mozilla::NumberEqualsInt64(duration.days, &days)) { 273 break; 274 } 275 276 // Compute the overall amount of milliseconds. 277 mozilla::CheckedInt64 millis = days; 278 millis *= 24; 279 millis += hours; 280 millis *= 60; 281 millis += minutes; 282 millis *= 60; 283 millis += seconds; 284 millis *= 1000; 285 millis += milliseconds; 286 if (!millis.isValid()) { 287 break; 288 } 289 290 auto milli = TimeDuration::fromMilliseconds(millis.value()); 291 if (!IsValidTimeDuration(milli)) { 292 break; 293 } 294 295 // Compute the overall time duration. 296 auto result = milli + *microseconds + *nanoseconds; 297 if (!IsValidTimeDuration(result)) { 298 break; 299 } 300 301 return mozilla::Some(result); 302 } while (false); 303 304 return mozilla::Nothing(); 305 } 306 307 /** 308 * TimeDurationFromComponents ( hours, minutes, seconds, milliseconds, 309 * microseconds, nanoseconds ) 310 */ 311 static TimeDuration TimeDurationFromComponents(double hours, double minutes, 312 double seconds, 313 double milliseconds, 314 double microseconds, 315 double nanoseconds) { 316 MOZ_ASSERT(IsInteger(hours)); 317 MOZ_ASSERT(IsInteger(minutes)); 318 MOZ_ASSERT(IsInteger(seconds)); 319 MOZ_ASSERT(IsInteger(milliseconds)); 320 MOZ_ASSERT(IsInteger(microseconds)); 321 MOZ_ASSERT(IsInteger(nanoseconds)); 322 323 // Steps 1-3. 324 mozilla::CheckedInt64 millis = int64_t(hours); 325 millis *= 60; 326 millis += int64_t(minutes); 327 millis *= 60; 328 millis += int64_t(seconds); 329 millis *= 1000; 330 millis += int64_t(milliseconds); 331 MOZ_ASSERT(millis.isValid()); 332 333 auto timeDuration = TimeDuration::fromMilliseconds(millis.value()); 334 335 // Step 4. 336 auto micros = Int96::fromInteger(microseconds); 337 MOZ_ASSERT(micros); 338 339 timeDuration += TimeDurationFromMicroseconds(*micros); 340 341 // Step 5. 342 auto nanos = Int96::fromInteger(nanoseconds); 343 MOZ_ASSERT(nanos); 344 345 timeDuration += TimeDurationFromNanoseconds(*nanos); 346 347 // Step 6. 348 MOZ_ASSERT(IsValidTimeDuration(timeDuration)); 349 350 // Step 7. 351 return timeDuration; 352 } 353 354 /** 355 * TimeDurationFromComponents ( hours, minutes, seconds, milliseconds, 356 * microseconds, nanoseconds ) 357 */ 358 TimeDuration js::temporal::TimeDurationFromComponents( 359 const Duration& duration) { 360 MOZ_ASSERT(IsValidDuration(duration)); 361 362 return ::TimeDurationFromComponents( 363 duration.hours, duration.minutes, duration.seconds, duration.milliseconds, 364 duration.microseconds, duration.nanoseconds); 365 } 366 367 /** 368 * Add24HourDaysToTimeDuration ( d, days ) 369 */ 370 static bool Add24HourDaysToTimeDuration(JSContext* cx, const TimeDuration& d, 371 int64_t days, TimeDuration* result) { 372 MOZ_ASSERT(IsValidTimeDuration(d)); 373 374 // Step 1. 375 if (days > TimeDuration::max().toDays()) { 376 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 377 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 378 return false; 379 } 380 381 auto timeDurationDays = TimeDuration::fromDays(days); 382 if (!IsValidTimeDuration(timeDurationDays)) { 383 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 384 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 385 return false; 386 } 387 388 // Step 2. 389 auto sum = d + timeDurationDays; 390 if (!IsValidTimeDuration(sum)) { 391 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 392 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 393 return false; 394 } 395 396 // Step 3. 397 *result = sum; 398 return true; 399 } 400 401 /** 402 * ToInternalDurationRecordWith24HourDays ( duration ) 403 */ 404 InternalDuration js::temporal::ToInternalDurationRecordWith24HourDays( 405 const Duration& duration) { 406 MOZ_ASSERT(IsValidDuration(duration)); 407 408 // Step 1. 409 auto timeDuration = TimeDurationFromComponents(duration); 410 411 // Step 2. (Inlined Add24HourDaysToTimeDuration) 412 timeDuration += TimeDuration::fromDays(int64_t(duration.days)); 413 414 // Step 3. 415 auto dateDuration = DateDuration{ 416 int64_t(duration.years), 417 int64_t(duration.months), 418 int64_t(duration.weeks), 419 0, 420 }; 421 422 // Step 4. (Inlined CombineDateAndTimeDuration) 423 return InternalDuration{dateDuration, timeDuration}; 424 } 425 426 /** 427 * ToDateDurationRecordWithoutTime ( duration ) 428 */ 429 DateDuration js::temporal::ToDateDurationRecordWithoutTime( 430 const Duration& duration) { 431 // Step 1. 432 auto internalDuration = ToInternalDurationRecordWith24HourDays(duration); 433 434 // Step 2. 435 int64_t days = internalDuration.time.toDays(); 436 437 // Step 3. 438 auto result = DateDuration{ 439 internalDuration.date.years, 440 internalDuration.date.months, 441 internalDuration.date.weeks, 442 days, 443 }; 444 MOZ_ASSERT(IsValidDuration(result)); 445 446 return result; 447 } 448 449 /** 450 * TemporalDurationFromInternal ( internalDuration, largestUnit ) 451 */ 452 static Duration TemporalDurationFromInternal(const TimeDuration& timeDuration, 453 TemporalUnit largestUnit) { 454 MOZ_ASSERT(IsValidTimeDuration(timeDuration)); 455 MOZ_ASSERT(largestUnit <= TemporalUnit::Second, 456 "fallible fractional seconds units"); 457 458 auto [seconds, nanoseconds] = timeDuration.denormalize(); 459 460 // Step 1. 461 int64_t days = 0; 462 int64_t hours = 0; 463 int64_t minutes = 0; 464 int64_t milliseconds = 0; 465 int64_t microseconds = 0; 466 467 // Steps 2-3. (Not applicable in our implementation.) 468 // 469 // We don't need to convert to positive numbers, because integer division 470 // truncates and the %-operator has modulo semantics. 471 472 // Steps 4-11. 473 switch (largestUnit) { 474 // Step 4. 475 case TemporalUnit::Year: 476 case TemporalUnit::Month: 477 case TemporalUnit::Week: 478 case TemporalUnit::Day: { 479 // Step 4.a. 480 microseconds = nanoseconds / 1000; 481 482 // Step 4.b. 483 nanoseconds = nanoseconds % 1000; 484 485 // Step 4.c. 486 milliseconds = microseconds / 1000; 487 488 // Step 4.d. 489 microseconds = microseconds % 1000; 490 491 // Steps 4.e-f. (Not applicable) 492 MOZ_ASSERT(std::abs(milliseconds) <= 999); 493 494 // Step 4.g. 495 minutes = seconds / 60; 496 497 // Step 4.h. 498 seconds = seconds % 60; 499 500 // Step 4.i. 501 hours = minutes / 60; 502 503 // Step 4.j. 504 minutes = minutes % 60; 505 506 // Step 4.k. 507 days = hours / 24; 508 509 // Step 4.l. 510 hours = hours % 24; 511 512 break; 513 } 514 515 // Step 5. 516 case TemporalUnit::Hour: { 517 // Step 5.a. 518 microseconds = nanoseconds / 1000; 519 520 // Step 5.b. 521 nanoseconds = nanoseconds % 1000; 522 523 // Step 5.c. 524 milliseconds = microseconds / 1000; 525 526 // Step 5.d. 527 microseconds = microseconds % 1000; 528 529 // Steps 5.e-f. (Not applicable) 530 MOZ_ASSERT(std::abs(milliseconds) <= 999); 531 532 // Step 5.g. 533 minutes = seconds / 60; 534 535 // Step 5.h. 536 seconds = seconds % 60; 537 538 // Step 5.i. 539 hours = minutes / 60; 540 541 // Step 5.j. 542 minutes = minutes % 60; 543 544 break; 545 } 546 547 case TemporalUnit::Minute: { 548 // Step 6.a. 549 microseconds = nanoseconds / 1000; 550 551 // Step 6.b. 552 nanoseconds = nanoseconds % 1000; 553 554 // Step 6.c. 555 milliseconds = microseconds / 1000; 556 557 // Step 6.d. 558 microseconds = microseconds % 1000; 559 560 // Steps 6.e-f. (Not applicable) 561 MOZ_ASSERT(std::abs(milliseconds) <= 999); 562 563 // Step 6.g. 564 minutes = seconds / 60; 565 566 // Step 6.h. 567 seconds = seconds % 60; 568 569 break; 570 } 571 572 // Step 7. 573 case TemporalUnit::Second: { 574 // Step 7.a. 575 microseconds = nanoseconds / 1000; 576 577 // Step 7.b. 578 nanoseconds = nanoseconds % 1000; 579 580 // Step 7.c. 581 milliseconds = microseconds / 1000; 582 583 // Step 7.d. 584 microseconds = microseconds % 1000; 585 586 // Steps 7.e-f. (Not applicable) 587 MOZ_ASSERT(std::abs(milliseconds) <= 999); 588 589 break; 590 } 591 592 // Steps 8-11. (Not applicable in our implementation) 593 case TemporalUnit::Millisecond: 594 case TemporalUnit::Microsecond: 595 case TemporalUnit::Nanosecond: 596 case TemporalUnit::Unset: 597 case TemporalUnit::Auto: 598 MOZ_CRASH("Unexpected temporal unit"); 599 } 600 601 // Step 12. 602 auto result = Duration{ 603 0, 604 0, 605 0, 606 double(days), 607 double(hours), 608 double(minutes), 609 double(seconds), 610 double(milliseconds), 611 double(microseconds), 612 double(nanoseconds), 613 }; 614 MOZ_ASSERT(IsValidDuration(result)); 615 return result; 616 } 617 618 /** 619 * TemporalDurationFromInternal ( internalDuration, largestUnit ) 620 */ 621 bool js::temporal::TemporalDurationFromInternal( 622 JSContext* cx, const TimeDuration& timeDuration, TemporalUnit largestUnit, 623 Duration* result) { 624 MOZ_ASSERT(IsValidTimeDuration(timeDuration)); 625 626 auto [seconds, nanoseconds] = timeDuration.denormalize(); 627 628 // Steps 1-3. (Not applicable in our implementation.) 629 // 630 // We don't need to convert to positive numbers, because integer division 631 // truncates and the %-operator has modulo semantics. 632 633 // Steps 4-10. 634 switch (largestUnit) { 635 // Steps 4-7. 636 case TemporalUnit::Year: 637 case TemporalUnit::Month: 638 case TemporalUnit::Week: 639 case TemporalUnit::Day: 640 case TemporalUnit::Hour: 641 case TemporalUnit::Minute: 642 case TemporalUnit::Second: 643 *result = ::TemporalDurationFromInternal(timeDuration, largestUnit); 644 return true; 645 646 // Step 8. 647 case TemporalUnit::Millisecond: { 648 // Valid time durations must be below |limit|. 649 constexpr auto limit = TimeDuration::max().toMilliseconds() + 1; 650 651 // The largest possible milliseconds value whose double representation 652 // doesn't exceed the time duration limit. 653 constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff); 654 655 // Assert |max| is the maximum allowed milliseconds value. 656 static_assert(double(max) < double(limit)); 657 static_assert(double(max + 1) >= double(limit)); 658 659 static_assert((TimeDuration::max().seconds + 1) * 660 ToMilliseconds(TemporalUnit::Second) <= 661 INT64_MAX, 662 "total number duration milliseconds fits into int64"); 663 664 // Step 8.a. 665 int64_t microseconds = nanoseconds / 1000; 666 667 // Step 8.b. 668 nanoseconds = nanoseconds % 1000; 669 670 // Step 8.c. 671 int64_t milliseconds = microseconds / 1000; 672 MOZ_ASSERT(std::abs(milliseconds) <= 999); 673 674 // Step 8.d. 675 microseconds = microseconds % 1000; 676 677 auto millis = 678 (seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds; 679 if (std::abs(millis) > max) { 680 JS_ReportErrorNumberASCII( 681 cx, GetErrorMessage, nullptr, 682 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 683 return false; 684 } 685 686 // Step 11. 687 *result = {0, 688 0, 689 0, 690 0, 691 0, 692 0, 693 0, 694 double(millis), 695 double(microseconds), 696 double(nanoseconds)}; 697 MOZ_ASSERT(IsValidDuration(*result)); 698 return true; 699 } 700 701 // Step 9. 702 case TemporalUnit::Microsecond: { 703 // Valid time durations must be below |limit|. 704 constexpr auto limit = 705 Uint128{TimeDuration::max().toMicroseconds()} + Uint128{1}; 706 707 // The largest possible microseconds value whose double representation 708 // doesn't exceed the time duration limit. 709 constexpr auto max = 710 (Uint128{0x1e8} << 64) + Uint128{0x47ff'ffff'fff7'ffff}; 711 static_assert(max < limit); 712 713 // Assert |max| is the maximum allowed microseconds value. 714 MOZ_ASSERT(double(max) < double(limit)); 715 MOZ_ASSERT(double(max + Uint128{1}) >= double(limit)); 716 717 // Step 9.a. 718 int64_t microseconds = nanoseconds / 1000; 719 MOZ_ASSERT(std::abs(microseconds) <= 999'999); 720 721 // Step 9.b. 722 nanoseconds = nanoseconds % 1000; 723 724 auto micros = 725 (Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) + 726 Int128{microseconds}; 727 if (micros.abs() > max) { 728 JS_ReportErrorNumberASCII( 729 cx, GetErrorMessage, nullptr, 730 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 731 return false; 732 } 733 734 // Step 11. 735 *result = {0, 0, 0, 0, 0, 0, 0, 0, double(micros), double(nanoseconds)}; 736 MOZ_ASSERT(IsValidDuration(*result)); 737 return true; 738 } 739 740 // Step 10. 741 case TemporalUnit::Nanosecond: { 742 // Valid time durations must be below |limit|. 743 constexpr auto limit = 744 Uint128{TimeDuration::max().toNanoseconds()} + Uint128{1}; 745 746 // The largest possible nanoseconds value whose double representation 747 // doesn't exceed the time duration limit. 748 constexpr auto max = 749 (Uint128{0x77359} << 64) + Uint128{0x3fff'ffff'dfff'ffff}; 750 static_assert(max < limit); 751 752 // Assert |max| is the maximum allowed nanoseconds value. 753 MOZ_ASSERT(double(max) < double(limit)); 754 MOZ_ASSERT(double(max + Uint128{1}) >= double(limit)); 755 756 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999); 757 758 auto nanos = 759 (Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) + 760 Int128{nanoseconds}; 761 if (nanos.abs() > max) { 762 JS_ReportErrorNumberASCII( 763 cx, GetErrorMessage, nullptr, 764 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 765 return false; 766 } 767 768 // Step 11. 769 *result = {0, 0, 0, 0, 0, 0, 0, 0, 0, double(nanos)}; 770 MOZ_ASSERT(IsValidDuration(*result)); 771 return true; 772 } 773 774 case TemporalUnit::Unset: 775 case TemporalUnit::Auto: 776 break; 777 } 778 MOZ_CRASH("Unexpected temporal unit"); 779 } 780 781 /** 782 * TemporalDurationFromInternal ( internalDuration, largestUnit ) 783 */ 784 bool js::temporal::TemporalDurationFromInternal( 785 JSContext* cx, const InternalDuration& internalDuration, 786 TemporalUnit largestUnit, Duration* result) { 787 MOZ_ASSERT(IsValidDuration(internalDuration.date)); 788 MOZ_ASSERT(IsValidTimeDuration(internalDuration.time)); 789 790 // Steps 1-11. 791 Duration duration; 792 if (!TemporalDurationFromInternal(cx, internalDuration.time, largestUnit, 793 &duration)) { 794 return false; 795 } 796 MOZ_ASSERT(IsValidDuration(duration)); 797 798 // Step 12. 799 auto days = mozilla::CheckedInt64(internalDuration.date.days) + 800 mozilla::AssertedCast<int64_t>(duration.days); 801 MOZ_ASSERT(days.isValid(), "valid duration days can't overflow"); 802 803 *result = { 804 double(internalDuration.date.years), 805 double(internalDuration.date.months), 806 double(internalDuration.date.weeks), 807 double(days.value()), 808 duration.hours, 809 duration.minutes, 810 duration.seconds, 811 duration.milliseconds, 812 duration.microseconds, 813 duration.nanoseconds, 814 }; 815 return ThrowIfInvalidDuration(cx, *result); 816 } 817 818 /** 819 * TimeDurationFromEpochNanosecondsDifference ( one, two ) 820 */ 821 TimeDuration js::temporal::TimeDurationFromEpochNanosecondsDifference( 822 const EpochNanoseconds& one, const EpochNanoseconds& two) { 823 MOZ_ASSERT(IsValidEpochNanoseconds(one)); 824 MOZ_ASSERT(IsValidEpochNanoseconds(two)); 825 826 // Step 1. 827 auto result = one - two; 828 829 // Step 2. 830 MOZ_ASSERT(IsValidEpochDuration(result)); 831 832 // Step 3. 833 return result.to<TimeDuration>(); 834 } 835 836 #ifdef DEBUG 837 /** 838 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, 839 * milliseconds, microseconds, nanoseconds ) 840 */ 841 bool js::temporal::IsValidDuration(const Duration& duration) { 842 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration)); 843 844 const auto& [years, months, weeks, days, hours, minutes, seconds, 845 milliseconds, microseconds, nanoseconds] = duration; 846 847 // Step 1. 848 int32_t sign = 0; 849 850 // Step 2. 851 for (auto v : {years, months, weeks, days, hours, minutes, seconds, 852 milliseconds, microseconds, nanoseconds}) { 853 // Step 2.a. 854 if (!std::isfinite(v)) { 855 return false; 856 } 857 858 // Step 2.b. 859 if (v < 0) { 860 // Step 2.b.i. 861 if (sign > 0) { 862 return false; 863 } 864 865 // Step 2.b.ii. 866 sign = -1; 867 } 868 869 // Step 2.c. 870 else if (v > 0) { 871 // Step 2.c.i. 872 if (sign < 0) { 873 return false; 874 } 875 876 // Step 2.c.ii. 877 sign = 1; 878 } 879 } 880 881 // Step 3. 882 if (std::abs(years) >= double(int64_t(1) << 32)) { 883 return false; 884 } 885 886 // Step 4. 887 if (std::abs(months) >= double(int64_t(1) << 32)) { 888 return false; 889 } 890 891 // Step 5. 892 if (std::abs(weeks) >= double(int64_t(1) << 32)) { 893 return false; 894 } 895 896 // Steps 6-8. 897 if (!TimeDurationFromDuration(duration)) { 898 return false; 899 } 900 901 // Step 9. 902 return true; 903 } 904 905 /** 906 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, 907 * milliseconds, microseconds, nanoseconds ) 908 */ 909 bool js::temporal::IsValidDuration(const DateDuration& duration) { 910 return IsValidDuration(duration.toDuration()); 911 } 912 913 /** 914 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, 915 * milliseconds, microseconds, nanoseconds ) 916 */ 917 bool js::temporal::IsValidDuration(const InternalDuration& duration) { 918 if (!IsValidTimeDuration(duration.time)) { 919 return false; 920 } 921 922 auto d = duration.date.toDuration(); 923 auto [seconds, nanoseconds] = duration.time.denormalize(); 924 d.seconds = double(seconds); 925 d.nanoseconds = double(nanoseconds); 926 927 return IsValidDuration(d); 928 } 929 #endif 930 931 static bool ThrowInvalidDurationPart(JSContext* cx, double value, 932 const char* name, unsigned errorNumber) { 933 ToCStringBuf cbuf; 934 const char* numStr = NumberToCString(&cbuf, value); 935 936 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name, 937 numStr); 938 return false; 939 } 940 941 /** 942 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds, 943 * milliseconds, microseconds, nanoseconds ) 944 */ 945 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx, 946 const Duration& duration) { 947 MOZ_ASSERT(IsIntegerOrInfinityDuration(duration)); 948 949 const auto& [years, months, weeks, days, hours, minutes, seconds, 950 milliseconds, microseconds, nanoseconds] = duration; 951 952 // Step 1. 953 int32_t sign = DurationSign(duration); 954 955 auto throwIfInvalid = [&](double v, const char* name) { 956 // Step 2.a. 957 if (!std::isfinite(v)) { 958 return ThrowInvalidDurationPart( 959 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE); 960 } 961 962 // Steps 2.b-c. 963 if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) { 964 return ThrowInvalidDurationPart(cx, v, name, 965 JSMSG_TEMPORAL_DURATION_INVALID_SIGN); 966 } 967 968 return true; 969 }; 970 971 auto throwIfTooLarge = [&](double v, const char* name) { 972 if (std::abs(v) >= double(int64_t(1) << 32)) { 973 return ThrowInvalidDurationPart( 974 cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE); 975 } 976 return true; 977 }; 978 979 // Step 2. 980 if (!throwIfInvalid(years, "years")) { 981 return false; 982 } 983 if (!throwIfInvalid(months, "months")) { 984 return false; 985 } 986 if (!throwIfInvalid(weeks, "weeks")) { 987 return false; 988 } 989 if (!throwIfInvalid(days, "days")) { 990 return false; 991 } 992 if (!throwIfInvalid(hours, "hours")) { 993 return false; 994 } 995 if (!throwIfInvalid(minutes, "minutes")) { 996 return false; 997 } 998 if (!throwIfInvalid(seconds, "seconds")) { 999 return false; 1000 } 1001 if (!throwIfInvalid(milliseconds, "milliseconds")) { 1002 return false; 1003 } 1004 if (!throwIfInvalid(microseconds, "microseconds")) { 1005 return false; 1006 } 1007 if (!throwIfInvalid(nanoseconds, "nanoseconds")) { 1008 return false; 1009 } 1010 1011 // Step 3. 1012 if (!throwIfTooLarge(years, "years")) { 1013 return false; 1014 } 1015 1016 // Step 4. 1017 if (!throwIfTooLarge(months, "months")) { 1018 return false; 1019 } 1020 1021 // Step 5. 1022 if (!throwIfTooLarge(weeks, "weeks")) { 1023 return false; 1024 } 1025 1026 // Steps 6-8. 1027 if (!TimeDurationFromDuration(duration)) { 1028 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1029 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 1030 return false; 1031 } 1032 1033 MOZ_ASSERT(IsValidDuration(duration)); 1034 1035 // Step 9. 1036 return true; 1037 } 1038 1039 /** 1040 * DefaultTemporalLargestUnit ( duration ) 1041 */ 1042 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) { 1043 MOZ_ASSERT(IsIntegerDuration(duration)); 1044 1045 // Step 1. 1046 if (duration.years != 0) { 1047 return TemporalUnit::Year; 1048 } 1049 1050 // Step 2. 1051 if (duration.months != 0) { 1052 return TemporalUnit::Month; 1053 } 1054 1055 // Step 3. 1056 if (duration.weeks != 0) { 1057 return TemporalUnit::Week; 1058 } 1059 1060 // Step 4. 1061 if (duration.days != 0) { 1062 return TemporalUnit::Day; 1063 } 1064 1065 // Step 5. 1066 if (duration.hours != 0) { 1067 return TemporalUnit::Hour; 1068 } 1069 1070 // Step 6. 1071 if (duration.minutes != 0) { 1072 return TemporalUnit::Minute; 1073 } 1074 1075 // Step 7. 1076 if (duration.seconds != 0) { 1077 return TemporalUnit::Second; 1078 } 1079 1080 // Step 8. 1081 if (duration.milliseconds != 0) { 1082 return TemporalUnit::Millisecond; 1083 } 1084 1085 // Step 9. 1086 if (duration.microseconds != 0) { 1087 return TemporalUnit::Microsecond; 1088 } 1089 1090 // Step 10. 1091 return TemporalUnit::Nanosecond; 1092 } 1093 1094 /** 1095 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, 1096 * milliseconds, microseconds, nanoseconds [ , newTarget ] ) 1097 */ 1098 static DurationObject* CreateTemporalDuration(JSContext* cx, 1099 const CallArgs& args, 1100 const Duration& duration) { 1101 const auto& [years, months, weeks, days, hours, minutes, seconds, 1102 milliseconds, microseconds, nanoseconds] = duration; 1103 1104 // Step 1. 1105 if (!ThrowIfInvalidDuration(cx, duration)) { 1106 return nullptr; 1107 } 1108 1109 // Steps 2-3. 1110 Rooted<JSObject*> proto(cx); 1111 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) { 1112 return nullptr; 1113 } 1114 1115 auto* object = NewObjectWithClassProto<DurationObject>(cx, proto); 1116 if (!object) { 1117 return nullptr; 1118 } 1119 1120 // Steps 4-13. 1121 // Add zero to convert -0 to +0. 1122 object->initFixedSlot(DurationObject::YEARS_SLOT, 1123 NumberValue(years + (+0.0))); 1124 object->initFixedSlot(DurationObject::MONTHS_SLOT, 1125 NumberValue(months + (+0.0))); 1126 object->initFixedSlot(DurationObject::WEEKS_SLOT, 1127 NumberValue(weeks + (+0.0))); 1128 object->initFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0))); 1129 object->initFixedSlot(DurationObject::HOURS_SLOT, 1130 NumberValue(hours + (+0.0))); 1131 object->initFixedSlot(DurationObject::MINUTES_SLOT, 1132 NumberValue(minutes + (+0.0))); 1133 object->initFixedSlot(DurationObject::SECONDS_SLOT, 1134 NumberValue(seconds + (+0.0))); 1135 object->initFixedSlot(DurationObject::MILLISECONDS_SLOT, 1136 NumberValue(milliseconds + (+0.0))); 1137 object->initFixedSlot(DurationObject::MICROSECONDS_SLOT, 1138 NumberValue(microseconds + (+0.0))); 1139 object->initFixedSlot(DurationObject::NANOSECONDS_SLOT, 1140 NumberValue(nanoseconds + (+0.0))); 1141 1142 // Step 14. 1143 return object; 1144 } 1145 1146 /** 1147 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, 1148 * milliseconds, microseconds, nanoseconds [ , newTarget ] ) 1149 */ 1150 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx, 1151 const Duration& duration) { 1152 const auto& [years, months, weeks, days, hours, minutes, seconds, 1153 milliseconds, microseconds, nanoseconds] = duration; 1154 1155 MOZ_ASSERT(IsInteger(years)); 1156 MOZ_ASSERT(IsInteger(months)); 1157 MOZ_ASSERT(IsInteger(weeks)); 1158 MOZ_ASSERT(IsInteger(days)); 1159 MOZ_ASSERT(IsInteger(hours)); 1160 MOZ_ASSERT(IsInteger(minutes)); 1161 MOZ_ASSERT(IsInteger(seconds)); 1162 MOZ_ASSERT(IsInteger(milliseconds)); 1163 MOZ_ASSERT(IsInteger(microseconds)); 1164 MOZ_ASSERT(IsInteger(nanoseconds)); 1165 1166 // Step 1. 1167 if (!ThrowIfInvalidDuration(cx, duration)) { 1168 return nullptr; 1169 } 1170 1171 // Steps 2-3. 1172 auto* object = NewBuiltinClassInstance<DurationObject>(cx); 1173 if (!object) { 1174 return nullptr; 1175 } 1176 1177 // Steps 4-13. 1178 // Add zero to convert -0 to +0. 1179 object->initFixedSlot(DurationObject::YEARS_SLOT, 1180 NumberValue(years + (+0.0))); 1181 object->initFixedSlot(DurationObject::MONTHS_SLOT, 1182 NumberValue(months + (+0.0))); 1183 object->initFixedSlot(DurationObject::WEEKS_SLOT, 1184 NumberValue(weeks + (+0.0))); 1185 object->initFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0))); 1186 object->initFixedSlot(DurationObject::HOURS_SLOT, 1187 NumberValue(hours + (+0.0))); 1188 object->initFixedSlot(DurationObject::MINUTES_SLOT, 1189 NumberValue(minutes + (+0.0))); 1190 object->initFixedSlot(DurationObject::SECONDS_SLOT, 1191 NumberValue(seconds + (+0.0))); 1192 object->initFixedSlot(DurationObject::MILLISECONDS_SLOT, 1193 NumberValue(milliseconds + (+0.0))); 1194 object->initFixedSlot(DurationObject::MICROSECONDS_SLOT, 1195 NumberValue(microseconds + (+0.0))); 1196 object->initFixedSlot(DurationObject::NANOSECONDS_SLOT, 1197 NumberValue(nanoseconds + (+0.0))); 1198 1199 // Step 14. 1200 return object; 1201 } 1202 1203 /** 1204 * ToIntegerIfIntegral ( argument ) 1205 */ 1206 static bool ToIntegerIfIntegral(JSContext* cx, const char* name, 1207 Handle<Value> argument, double* num) { 1208 // Step 1. 1209 double d; 1210 if (!JS::ToNumber(cx, argument, &d)) { 1211 return false; 1212 } 1213 1214 // Step 2. 1215 if (!js::IsInteger(d)) { 1216 ToCStringBuf cbuf; 1217 const char* numStr = NumberToCString(&cbuf, d); 1218 1219 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1220 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr, 1221 name); 1222 return false; 1223 } 1224 1225 // Step 3. 1226 *num = d; 1227 return true; 1228 } 1229 1230 /** 1231 * ToIntegerIfIntegral ( argument ) 1232 */ 1233 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name, 1234 Handle<Value> argument, double* result) { 1235 // Step 1. 1236 double d; 1237 if (!JS::ToNumber(cx, argument, &d)) { 1238 return false; 1239 } 1240 1241 // Step 2. 1242 if (!js::IsInteger(d)) { 1243 if (auto nameStr = js::QuoteString(cx, name)) { 1244 ToCStringBuf cbuf; 1245 const char* numStr = NumberToCString(&cbuf, d); 1246 1247 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1248 JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr, 1249 nameStr.get()); 1250 } 1251 return false; 1252 } 1253 1254 // Step 3. 1255 *result = d; 1256 return true; 1257 } 1258 1259 /** 1260 * ToTemporalPartialDurationRecord ( temporalDurationLike ) 1261 */ 1262 static bool ToTemporalPartialDurationRecord( 1263 JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) { 1264 // Steps 1-3. (Not applicable in our implementation.) 1265 1266 Rooted<Value> value(cx); 1267 bool any = false; 1268 1269 auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) { 1270 if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name, 1271 &value)) { 1272 return false; 1273 } 1274 1275 if (!value.isUndefined()) { 1276 any = true; 1277 1278 if (!ToIntegerIfIntegral(cx, name, value, num)) { 1279 return false; 1280 } 1281 } 1282 return true; 1283 }; 1284 1285 // Steps 4-23. 1286 if (!getDurationProperty(cx->names().days, &result->days)) { 1287 return false; 1288 } 1289 if (!getDurationProperty(cx->names().hours, &result->hours)) { 1290 return false; 1291 } 1292 if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) { 1293 return false; 1294 } 1295 if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) { 1296 return false; 1297 } 1298 if (!getDurationProperty(cx->names().minutes, &result->minutes)) { 1299 return false; 1300 } 1301 if (!getDurationProperty(cx->names().months, &result->months)) { 1302 return false; 1303 } 1304 if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) { 1305 return false; 1306 } 1307 if (!getDurationProperty(cx->names().seconds, &result->seconds)) { 1308 return false; 1309 } 1310 if (!getDurationProperty(cx->names().weeks, &result->weeks)) { 1311 return false; 1312 } 1313 if (!getDurationProperty(cx->names().years, &result->years)) { 1314 return false; 1315 } 1316 1317 // Step 24. 1318 if (!any) { 1319 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1320 JSMSG_TEMPORAL_DURATION_MISSING_UNIT); 1321 return false; 1322 } 1323 1324 // Step 25. 1325 return true; 1326 } 1327 1328 /** 1329 * ToTemporalDuration ( item ) 1330 */ 1331 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item, 1332 Duration* result) { 1333 // Steps 1 and 3-15. 1334 if (item.isObject()) { 1335 Rooted<JSObject*> itemObj(cx, &item.toObject()); 1336 1337 // Step 1. 1338 if (auto* duration = itemObj->maybeUnwrapIf<DurationObject>()) { 1339 *result = ToDuration(duration); 1340 return true; 1341 } 1342 1343 // Step 3. (Reordered) 1344 Duration duration = {}; 1345 1346 // Steps 4-14. 1347 if (!ToTemporalPartialDurationRecord(cx, itemObj, &duration)) { 1348 return false; 1349 } 1350 1351 // Step 15. 1352 if (!ThrowIfInvalidDuration(cx, duration)) { 1353 return false; 1354 } 1355 1356 *result = duration; 1357 return true; 1358 } 1359 1360 // Step 2.a. 1361 if (!item.isString()) { 1362 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item, 1363 nullptr, "not a string"); 1364 return false; 1365 } 1366 Rooted<JSString*> string(cx, item.toString()); 1367 1368 // Step 2.b. 1369 return ParseTemporalDurationString(cx, string, result); 1370 } 1371 1372 /** 1373 * DateDurationDays ( dateDuration, plainRelativeTo ) 1374 */ 1375 static bool DateDurationDays(JSContext* cx, const DateDuration& duration, 1376 Handle<PlainDate> plainRelativeTo, 1377 int64_t* result) { 1378 MOZ_ASSERT(IsValidDuration(duration)); 1379 1380 auto [years, months, weeks, days] = duration; 1381 1382 // Step 1. 1383 auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks}; 1384 1385 // Step 2. 1386 if (yearsMonthsWeeksDuration == DateDuration{}) { 1387 *result = days; 1388 return true; 1389 } 1390 1391 // Moved from caller. 1392 if (!plainRelativeTo) { 1393 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1394 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, 1395 "relativeTo"); 1396 return false; 1397 } 1398 1399 // Step 3. 1400 ISODate later; 1401 if (!CalendarDateAdd(cx, plainRelativeTo.calendar(), plainRelativeTo, 1402 yearsMonthsWeeksDuration, TemporalOverflow::Constrain, 1403 &later)) { 1404 return false; 1405 } 1406 1407 // Step 4. 1408 int32_t epochDays1 = MakeDay(plainRelativeTo); 1409 MOZ_ASSERT(MinEpochDay <= epochDays1 && epochDays1 <= MaxEpochDay); 1410 1411 // Step 5. 1412 int32_t epochDays2 = MakeDay(later); 1413 MOZ_ASSERT(MinEpochDay <= epochDays2 && epochDays2 <= MaxEpochDay); 1414 1415 // Step 4. 1416 int32_t yearsMonthsWeeksInDay = epochDays2 - epochDays1; 1417 1418 // Step 5. 1419 *result = days + yearsMonthsWeeksInDay; 1420 return true; 1421 } 1422 1423 static bool NumberToStringBuilder(JSContext* cx, double num, 1424 JSStringBuilder& sb) { 1425 MOZ_ASSERT(IsInteger(num)); 1426 MOZ_ASSERT(num >= 0); 1427 MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1428 1429 ToCStringBuf cbuf; 1430 size_t length; 1431 const char* numStr = NumberToCString(&cbuf, num, &length); 1432 1433 return sb.append(numStr, length); 1434 } 1435 1436 static Duration AbsoluteDuration(const Duration& duration) { 1437 return { 1438 std::abs(duration.years), std::abs(duration.months), 1439 std::abs(duration.weeks), std::abs(duration.days), 1440 std::abs(duration.hours), std::abs(duration.minutes), 1441 std::abs(duration.seconds), std::abs(duration.milliseconds), 1442 std::abs(duration.microseconds), std::abs(duration.nanoseconds), 1443 }; 1444 } 1445 1446 /** 1447 * FormatFractionalSeconds ( subSecondNanoseconds, precision ) 1448 */ 1449 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result, 1450 int32_t subSecondNanoseconds, 1451 Precision precision) { 1452 MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000); 1453 MOZ_ASSERT(precision != Precision::Minute()); 1454 1455 // Steps 1-2. 1456 if (precision == Precision::Auto()) { 1457 // Step 1.a. 1458 if (subSecondNanoseconds == 0) { 1459 return true; 1460 } 1461 1462 // Step 3. (Reordered) 1463 if (!result.append('.')) { 1464 return false; 1465 } 1466 1467 // Steps 1.b-c. 1468 int32_t k = 100'000'000; 1469 do { 1470 if (!result.append(char('0' + (subSecondNanoseconds / k)))) { 1471 return false; 1472 } 1473 subSecondNanoseconds %= k; 1474 k /= 10; 1475 } while (subSecondNanoseconds); 1476 } else { 1477 // Step 2.a. 1478 uint8_t p = precision.value(); 1479 if (p == 0) { 1480 return true; 1481 } 1482 1483 // Step 3. (Reordered) 1484 if (!result.append('.')) { 1485 return false; 1486 } 1487 1488 // Steps 2.b-c. 1489 int32_t k = 100'000'000; 1490 for (uint8_t i = 0; i < precision.value(); i++) { 1491 if (!result.append(char('0' + (subSecondNanoseconds / k)))) { 1492 return false; 1493 } 1494 subSecondNanoseconds %= k; 1495 k /= 10; 1496 } 1497 } 1498 1499 return true; 1500 } 1501 1502 /** 1503 * TemporalDurationToString ( duration, precision ) 1504 */ 1505 static JSString* TemporalDurationToString(JSContext* cx, 1506 const Duration& duration, 1507 Precision precision) { 1508 MOZ_ASSERT(IsValidDuration(duration)); 1509 MOZ_ASSERT(precision != Precision::Minute()); 1510 1511 // Fast path for zero durations. 1512 if (duration == Duration{} && 1513 (precision == Precision::Auto() || precision.value() == 0)) { 1514 return NewStringCopyZ<CanGC>(cx, "PT0S"); 1515 } 1516 1517 // Convert to absolute values up front. This is okay to do, because when the 1518 // duration is valid, all components have the same sign. 1519 const auto& [years, months, weeks, days, hours, minutes, seconds, 1520 milliseconds, microseconds, nanoseconds] = 1521 AbsoluteDuration(duration); 1522 1523 // Years to seconds parts are all safe integers for valid durations. 1524 MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1525 MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1526 MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1527 MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1528 MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1529 MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1530 MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT); 1531 1532 // Step 1. 1533 int32_t sign = DurationSign(duration); 1534 1535 // Steps 2 and 7. 1536 JSStringBuilder result(cx); 1537 1538 // Step 14. (Reordered) 1539 if (sign < 0) { 1540 if (!result.append('-')) { 1541 return nullptr; 1542 } 1543 } 1544 1545 // Step 15. (Reordered) 1546 if (!result.append('P')) { 1547 return nullptr; 1548 } 1549 1550 // Step 3. 1551 if (years != 0) { 1552 if (!NumberToStringBuilder(cx, years, result)) { 1553 return nullptr; 1554 } 1555 if (!result.append('Y')) { 1556 return nullptr; 1557 } 1558 } 1559 1560 // Step 4. 1561 if (months != 0) { 1562 if (!NumberToStringBuilder(cx, months, result)) { 1563 return nullptr; 1564 } 1565 if (!result.append('M')) { 1566 return nullptr; 1567 } 1568 } 1569 1570 // Step 5. 1571 if (weeks != 0) { 1572 if (!NumberToStringBuilder(cx, weeks, result)) { 1573 return nullptr; 1574 } 1575 if (!result.append('W')) { 1576 return nullptr; 1577 } 1578 } 1579 1580 // Step 6. 1581 if (days != 0) { 1582 if (!NumberToStringBuilder(cx, days, result)) { 1583 return nullptr; 1584 } 1585 if (!result.append('D')) { 1586 return nullptr; 1587 } 1588 } 1589 1590 // Step 7. (Moved above) 1591 1592 // Steps 10-11. (Reordered) 1593 bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 && 1594 days == 0 && hours == 0 && minutes == 0; 1595 1596 // Step 12. 1597 auto secondsDuration = TimeDurationFromComponents( 1598 0.0, 0.0, seconds, milliseconds, microseconds, nanoseconds); 1599 1600 // Steps 8-9, 13, and 16. 1601 bool hasSecondsPart = (secondsDuration != TimeDuration{}) || 1602 zeroMinutesAndHigher || precision != Precision::Auto(); 1603 if (hours != 0 || minutes != 0 || hasSecondsPart) { 1604 // Step 16. (Reordered) 1605 if (!result.append('T')) { 1606 return nullptr; 1607 } 1608 1609 // Step 8. 1610 if (hours != 0) { 1611 if (!NumberToStringBuilder(cx, hours, result)) { 1612 return nullptr; 1613 } 1614 if (!result.append('H')) { 1615 return nullptr; 1616 } 1617 } 1618 1619 // Step 9. 1620 if (minutes != 0) { 1621 if (!NumberToStringBuilder(cx, minutes, result)) { 1622 return nullptr; 1623 } 1624 if (!result.append('M')) { 1625 return nullptr; 1626 } 1627 } 1628 1629 // Step 13. 1630 if (hasSecondsPart) { 1631 // Step 13.a. 1632 if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) { 1633 return nullptr; 1634 } 1635 1636 // Step 13.b. 1637 if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds, 1638 precision)) { 1639 return nullptr; 1640 } 1641 1642 // Step 13.c. 1643 if (!result.append('S')) { 1644 return nullptr; 1645 } 1646 } 1647 } 1648 1649 // Steps 14-16. (Moved above) 1650 1651 // Step 17. 1652 return result.finishString(); 1653 } 1654 1655 /** 1656 * GetTemporalRelativeToOption ( options ) 1657 */ 1658 static bool GetTemporalRelativeToOption( 1659 JSContext* cx, Handle<JSObject*> options, 1660 MutableHandle<PlainDate> plainRelativeTo, 1661 MutableHandle<ZonedDateTime> zonedRelativeTo) { 1662 // Default initialize both return values. 1663 plainRelativeTo.set(PlainDate{}); 1664 zonedRelativeTo.set(ZonedDateTime{}); 1665 1666 // Step 1. 1667 Rooted<Value> value(cx); 1668 if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) { 1669 return false; 1670 } 1671 1672 // Step 2. 1673 if (value.isUndefined()) { 1674 return true; 1675 } 1676 1677 // Step 3. 1678 auto offsetBehaviour = OffsetBehaviour::Option; 1679 1680 // Step 4. 1681 auto matchBehaviour = MatchBehaviour::MatchExactly; 1682 1683 // Steps 5-6. 1684 EpochNanoseconds epochNanoseconds; 1685 Rooted<TimeZoneValue> timeZone(cx); 1686 Rooted<CalendarValue> calendar(cx); 1687 if (value.isObject()) { 1688 Rooted<JSObject*> obj(cx, &value.toObject()); 1689 1690 // Step 5.a. 1691 if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) { 1692 auto epochNs = zonedDateTime->epochNanoseconds(); 1693 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone()); 1694 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar()); 1695 1696 if (!timeZone.wrap(cx)) { 1697 return false; 1698 } 1699 if (!calendar.wrap(cx)) { 1700 return false; 1701 } 1702 1703 // Step 5.a.i. 1704 zonedRelativeTo.set(ZonedDateTime{epochNs, timeZone, calendar}); 1705 return true; 1706 } 1707 1708 // Step 5.b. 1709 if (auto* plainDate = obj->maybeUnwrapIf<PlainDateObject>()) { 1710 auto date = plainDate->date(); 1711 1712 Rooted<CalendarValue> calendar(cx, plainDate->calendar()); 1713 if (!calendar.wrap(cx)) { 1714 return false; 1715 } 1716 1717 // Step 5.b.i. 1718 plainRelativeTo.set(PlainDate{date, calendar}); 1719 return true; 1720 } 1721 1722 // Step 5.c. 1723 if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) { 1724 auto date = dateTime->date(); 1725 1726 Rooted<CalendarValue> calendar(cx, dateTime->calendar()); 1727 if (!calendar.wrap(cx)) { 1728 return false; 1729 } 1730 1731 // Steps 5.c.i-ii. 1732 plainRelativeTo.set(PlainDate{date, calendar}); 1733 return true; 1734 } 1735 1736 // Step 5.d. 1737 if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) { 1738 return false; 1739 } 1740 1741 // Step 5.e. 1742 Rooted<CalendarFields> fields(cx); 1743 if (!PrepareCalendarFields(cx, calendar, obj, 1744 { 1745 CalendarField::Year, 1746 CalendarField::Month, 1747 CalendarField::MonthCode, 1748 CalendarField::Day, 1749 CalendarField::Hour, 1750 CalendarField::Minute, 1751 CalendarField::Second, 1752 CalendarField::Millisecond, 1753 CalendarField::Microsecond, 1754 CalendarField::Nanosecond, 1755 CalendarField::Offset, 1756 CalendarField::TimeZone, 1757 }, 1758 &fields)) { 1759 return false; 1760 } 1761 1762 // Step 5.f. 1763 ISODateTime dateTime; 1764 if (!InterpretTemporalDateTimeFields( 1765 cx, calendar, fields, TemporalOverflow::Constrain, &dateTime)) { 1766 return false; 1767 } 1768 1769 // Step 5.g. 1770 timeZone = fields.timeZone(); 1771 1772 // Step 5.h. 1773 auto offset = fields.offset(); 1774 1775 // Step 5.j. 1776 if (!fields.has(CalendarField::Offset)) { 1777 offsetBehaviour = OffsetBehaviour::Wall; 1778 } 1779 1780 // Step 7. 1781 if (!timeZone) { 1782 // Steps 7.a-b. 1783 return CreateTemporalDate(cx, dateTime.date, calendar, plainRelativeTo); 1784 } 1785 1786 // Steps 8-9. 1787 int64_t offsetNs = 0; 1788 if (offsetBehaviour == OffsetBehaviour::Option) { 1789 // Step 8.a. 1790 offsetNs = int64_t(offset); 1791 } 1792 1793 // Step 10. 1794 if (!InterpretISODateTimeOffset( 1795 cx, dateTime, offsetBehaviour, offsetNs, timeZone, 1796 TemporalDisambiguation::Compatible, TemporalOffset::Reject, 1797 matchBehaviour, &epochNanoseconds)) { 1798 return false; 1799 } 1800 } else { 1801 // Step 6.a. 1802 if (!value.isString()) { 1803 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value, 1804 nullptr, "not a string"); 1805 return false; 1806 } 1807 Rooted<JSString*> string(cx, value.toString()); 1808 1809 // Step 6.b. 1810 Rooted<ParsedZonedDateTime> parsed(cx); 1811 if (!ParseTemporalRelativeToString(cx, string, &parsed)) { 1812 return false; 1813 } 1814 1815 // Steps 6.c-e. (Not applicable in our implementation.) 1816 1817 // Step 6.f. 1818 if (parsed.timeZoneAnnotation()) { 1819 // Step 6.f.i. 1820 if (!ToTemporalTimeZone(cx, parsed.timeZoneAnnotation(), &timeZone)) { 1821 return false; 1822 } 1823 1824 // Steps 6.f.ii-iii. 1825 if (parsed.timeZone().constructed<UTCTimeZone>()) { 1826 offsetBehaviour = OffsetBehaviour::Exact; 1827 } else if (parsed.timeZone().empty()) { 1828 offsetBehaviour = OffsetBehaviour::Wall; 1829 } 1830 1831 // Step 6.f.iv. 1832 matchBehaviour = MatchBehaviour::MatchMinutes; 1833 1834 // Step 6.f.v. 1835 if (parsed.timeZone().constructed<OffsetTimeZone>()) { 1836 // Steps 6.f.v.1-3. 1837 if (parsed.timeZone().ref<OffsetTimeZone>().hasSubMinutePrecision) { 1838 matchBehaviour = MatchBehaviour::MatchExactly; 1839 } 1840 } 1841 } else { 1842 MOZ_ASSERT(!timeZone); 1843 } 1844 1845 // Steps 6.g-i. 1846 if (parsed.calendar()) { 1847 if (!CanonicalizeCalendar(cx, parsed.calendar(), &calendar)) { 1848 return false; 1849 } 1850 } else { 1851 calendar.set(CalendarValue(CalendarId::ISO8601)); 1852 } 1853 1854 // Step 7. 1855 if (!timeZone) { 1856 // Steps 7.a-b. 1857 return CreateTemporalDate(cx, parsed.dateTime().date, calendar, 1858 plainRelativeTo); 1859 } 1860 1861 // Steps 8-9. 1862 int64_t offsetNs; 1863 if (offsetBehaviour == OffsetBehaviour::Option) { 1864 MOZ_ASSERT(parsed.timeZone().constructed<OffsetTimeZone>()); 1865 1866 // Step 8.a. 1867 offsetNs = parsed.timeZone().ref<OffsetTimeZone>().offset; 1868 } else { 1869 // Step 9. 1870 offsetNs = 0; 1871 } 1872 1873 // Step 10. 1874 if (parsed.isStartOfDay()) { 1875 if (!InterpretISODateTimeOffset( 1876 cx, parsed.dateTime().date, offsetBehaviour, offsetNs, timeZone, 1877 TemporalDisambiguation::Compatible, TemporalOffset::Reject, 1878 matchBehaviour, &epochNanoseconds)) { 1879 return false; 1880 } 1881 } else { 1882 if (!InterpretISODateTimeOffset( 1883 cx, parsed.dateTime(), offsetBehaviour, offsetNs, timeZone, 1884 TemporalDisambiguation::Compatible, TemporalOffset::Reject, 1885 matchBehaviour, &epochNanoseconds)) { 1886 return false; 1887 } 1888 } 1889 } 1890 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); 1891 1892 // Steps 11-12. 1893 zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar}); 1894 return true; 1895 } 1896 1897 /** 1898 * RoundTimeDurationToIncrement ( d, increment, roundingMode ) 1899 */ 1900 static TimeDuration RoundTimeDurationToIncrement( 1901 const TimeDuration& duration, const TemporalUnit unit, Increment increment, 1902 TemporalRoundingMode roundingMode) { 1903 MOZ_ASSERT(IsValidTimeDuration(duration)); 1904 MOZ_ASSERT(unit >= TemporalUnit::Day); 1905 MOZ_ASSERT_IF(unit >= TemporalUnit::Hour, 1906 increment <= MaximumTemporalDurationRoundingIncrement(unit)); 1907 1908 auto divisor = Int128{ToNanoseconds(unit)} * Int128{increment.value()}; 1909 MOZ_ASSERT(divisor > Int128{0}); 1910 MOZ_ASSERT_IF(unit >= TemporalUnit::Hour, 1911 divisor <= Int128{ToNanoseconds(TemporalUnit::Day)}); 1912 1913 auto totalNanoseconds = duration.toNanoseconds(); 1914 auto rounded = 1915 RoundNumberToIncrement(totalNanoseconds, divisor, roundingMode); 1916 return TimeDuration::fromNanoseconds(rounded); 1917 } 1918 1919 /** 1920 * TotalTimeDuration ( timeDuration, unit ) 1921 */ 1922 double js::temporal::TotalTimeDuration(const TimeDuration& duration, 1923 TemporalUnit unit) { 1924 MOZ_ASSERT(IsValidTimeDuration(duration)); 1925 MOZ_ASSERT(unit >= TemporalUnit::Day); 1926 1927 auto numerator = duration.toNanoseconds(); 1928 auto denominator = Int128{ToNanoseconds(unit)}; 1929 return FractionToDouble(numerator, denominator); 1930 } 1931 1932 /** 1933 * RoundTimeDuration ( duration, increment, unit, roundingMode ) 1934 */ 1935 static bool RoundTimeDuration(JSContext* cx, const TimeDuration& duration, 1936 Increment increment, TemporalUnit unit, 1937 TemporalRoundingMode roundingMode, 1938 TimeDuration* result) { 1939 MOZ_ASSERT(IsValidTimeDuration(duration)); 1940 MOZ_ASSERT(increment <= Increment::max()); 1941 MOZ_ASSERT(unit > TemporalUnit::Day); 1942 1943 // Step 1-2. 1944 auto rounded = 1945 RoundTimeDurationToIncrement(duration, unit, increment, roundingMode); 1946 if (!IsValidTimeDuration(rounded)) { 1947 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1948 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 1949 return false; 1950 } 1951 *result = rounded; 1952 return true; 1953 } 1954 1955 /** 1956 * RoundTimeDuration ( duration, increment, unit, roundingMode ) 1957 */ 1958 TimeDuration js::temporal::RoundTimeDuration( 1959 const TimeDuration& duration, Increment increment, TemporalUnit unit, 1960 TemporalRoundingMode roundingMode) { 1961 MOZ_ASSERT(IsValidTimeDuration(duration)); 1962 MOZ_ASSERT(increment <= Increment::max()); 1963 MOZ_ASSERT(unit > TemporalUnit::Day); 1964 1965 auto result = 1966 RoundTimeDurationToIncrement(duration, unit, increment, roundingMode); 1967 MOZ_ASSERT(IsValidTimeDuration(result)); 1968 1969 return result; 1970 } 1971 1972 #ifdef DEBUG 1973 /** 1974 * Return true if the input is within the valid epoch nanoseconds limits with a 1975 * time zone offset applied, i.e. it's smaller than ±(8.64 × 10^21 + nsPerDay). 1976 */ 1977 static bool IsValidLocalNanoseconds(const EpochNanoseconds& epochNanoseconds) { 1978 MOZ_ASSERT(0 <= epochNanoseconds.nanoseconds && 1979 epochNanoseconds.nanoseconds <= 999'999'999); 1980 1981 // Time zone offsets can't exceed 24 hours. 1982 constexpr auto oneDay = EpochDuration::fromDays(1); 1983 1984 // Exclusive limits. 1985 constexpr auto min = EpochNanoseconds::min() - oneDay; 1986 constexpr auto max = EpochNanoseconds::max() + oneDay; 1987 1988 return min < epochNanoseconds && epochNanoseconds < max; 1989 } 1990 #endif 1991 1992 enum class UnsignedRoundingMode { 1993 Zero, 1994 Infinity, 1995 HalfZero, 1996 HalfInfinity, 1997 HalfEven 1998 }; 1999 2000 /** 2001 * GetUnsignedRoundingMode ( roundingMode, sign ) 2002 */ 2003 static UnsignedRoundingMode GetUnsignedRoundingMode( 2004 TemporalRoundingMode roundingMode, bool isNegative) { 2005 switch (roundingMode) { 2006 case TemporalRoundingMode::Ceil: 2007 return isNegative ? UnsignedRoundingMode::Zero 2008 : UnsignedRoundingMode::Infinity; 2009 case TemporalRoundingMode::Floor: 2010 return isNegative ? UnsignedRoundingMode::Infinity 2011 : UnsignedRoundingMode::Zero; 2012 case TemporalRoundingMode::Expand: 2013 return UnsignedRoundingMode::Infinity; 2014 case TemporalRoundingMode::Trunc: 2015 return UnsignedRoundingMode::Zero; 2016 case TemporalRoundingMode::HalfCeil: 2017 return isNegative ? UnsignedRoundingMode::HalfZero 2018 : UnsignedRoundingMode::HalfInfinity; 2019 case TemporalRoundingMode::HalfFloor: 2020 return isNegative ? UnsignedRoundingMode::HalfInfinity 2021 : UnsignedRoundingMode::HalfZero; 2022 case TemporalRoundingMode::HalfExpand: 2023 return UnsignedRoundingMode::HalfInfinity; 2024 case TemporalRoundingMode::HalfTrunc: 2025 return UnsignedRoundingMode::HalfZero; 2026 case TemporalRoundingMode::HalfEven: 2027 return UnsignedRoundingMode::HalfEven; 2028 } 2029 MOZ_CRASH("invalid rounding mode"); 2030 } 2031 2032 struct NudgeWindow { 2033 int64_t r1; 2034 int64_t r2; 2035 EpochNanoseconds startEpochNs; 2036 EpochNanoseconds endEpochNs; 2037 DateDuration startDuration; 2038 DateDuration endDuration; 2039 }; 2040 2041 /** 2042 * ComputeNudgeWindow ( sign, duration, originEpochNs, isoDateTime, timeZone, 2043 * calendar, increment, unit, additionalShift ) 2044 */ 2045 static bool ComputeNudgeWindow(JSContext* cx, const InternalDuration& duration, 2046 const EpochNanoseconds& originEpochNs, 2047 const ISODateTime& isoDateTime, 2048 Handle<TimeZoneValue> timeZone, 2049 Handle<CalendarValue> calendar, 2050 Increment increment, TemporalUnit unit, 2051 bool additionalShift, NudgeWindow* result) { 2052 MOZ_ASSERT(IsValidDuration(duration)); 2053 MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs)); 2054 MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs)); 2055 MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); 2056 MOZ_ASSERT(unit <= TemporalUnit::Day); 2057 2058 int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1; 2059 2060 // Steps 1-4. 2061 int64_t r1; 2062 int64_t r2; 2063 DateDuration startDuration; 2064 DateDuration endDuration; 2065 if (unit == TemporalUnit::Year) { 2066 // Step 1.a. 2067 int64_t years = RoundNumberToIncrement(duration.date.years, increment, 2068 TemporalRoundingMode::Trunc); 2069 2070 // Steps 1.b-c. 2071 if (!additionalShift) { 2072 r1 = years; 2073 } else { 2074 r1 = years + int64_t(increment.value()) * sign; 2075 } 2076 2077 // Step 1.d. 2078 r2 = r1 + int64_t(increment.value()) * sign; 2079 2080 // Step 1.e. 2081 startDuration = {r1}; 2082 2083 // Step 1.f. 2084 endDuration = {r2}; 2085 } else if (unit == TemporalUnit::Month) { 2086 // Step 2.a. 2087 int64_t months = RoundNumberToIncrement(duration.date.months, increment, 2088 TemporalRoundingMode::Trunc); 2089 2090 // Steps 2.b-c. 2091 if (!additionalShift) { 2092 r1 = months; 2093 } else { 2094 r1 = months + int64_t(increment.value()) * sign; 2095 } 2096 2097 // Step 2.d. 2098 r2 = r1 + int64_t(increment.value()) * sign; 2099 2100 // Step 2.e. 2101 startDuration = {duration.date.years, r1}; 2102 2103 // Step 2.f. 2104 endDuration = {duration.date.years, r2}; 2105 } else if (unit == TemporalUnit::Week) { 2106 // Step 3.a. 2107 auto yearsMonths = DateDuration{duration.date.years, duration.date.months}; 2108 2109 // Step 3.b. 2110 ISODate weeksStart; 2111 if (!CalendarDateAdd(cx, calendar, isoDateTime.date, yearsMonths, 2112 TemporalOverflow::Constrain, &weeksStart)) { 2113 return false; 2114 } 2115 MOZ_ASSERT(ISODateWithinLimits(weeksStart)); 2116 2117 // Step 3.c. 2118 ISODate weeksEnd; 2119 if (!BalanceISODate(cx, weeksStart, duration.date.days, &weeksEnd)) { 2120 return false; 2121 } 2122 MOZ_ASSERT(ISODateWithinLimits(weeksEnd)); 2123 2124 // Step 3.d. 2125 DateDuration untilResult; 2126 if (!CalendarDateUntil(cx, calendar, weeksStart, weeksEnd, 2127 TemporalUnit::Week, &untilResult)) { 2128 return false; 2129 } 2130 2131 // Step 3.e. 2132 int64_t weeks = 2133 RoundNumberToIncrement(duration.date.weeks + untilResult.weeks, 2134 increment, TemporalRoundingMode::Trunc); 2135 2136 // Steps 3.f-g. 2137 if (!additionalShift) { 2138 r1 = weeks; 2139 } else { 2140 r1 = weeks + int64_t(increment.value()) * sign; 2141 } 2142 2143 // Step 3.h. 2144 r2 = r1 + int64_t(increment.value()) * sign; 2145 2146 // Step 3.i. 2147 startDuration = {duration.date.years, duration.date.months, r1}; 2148 2149 // Step 3.j. 2150 endDuration = {duration.date.years, duration.date.months, r2}; 2151 } else { 2152 // Step 4.a. 2153 MOZ_ASSERT(unit == TemporalUnit::Day); 2154 2155 // Step 4.b. 2156 int64_t days = RoundNumberToIncrement(duration.date.days, increment, 2157 TemporalRoundingMode::Trunc); 2158 2159 // Steps 4.c-d. 2160 if (!additionalShift) { 2161 r1 = days; 2162 } else { 2163 r1 = days + int64_t(increment.value()) * sign; 2164 } 2165 2166 // Step 4.e. 2167 r2 = r1 + int64_t(increment.value()) * sign; 2168 2169 // Step 4.f. 2170 startDuration = {duration.date.years, duration.date.months, 2171 duration.date.weeks, r1}; 2172 2173 // Step 4.g. 2174 endDuration = {duration.date.years, duration.date.months, 2175 duration.date.weeks, r2}; 2176 } 2177 MOZ_ASSERT(IsValidDuration(startDuration)); 2178 MOZ_ASSERT(IsValidDuration(endDuration)); 2179 2180 // Step 5. 2181 MOZ_ASSERT_IF(sign > 0, r1 >= 0 && r1 < r2); 2182 2183 // Step 6. 2184 MOZ_ASSERT_IF(sign < 0, r1 <= 0 && r1 > r2); 2185 2186 // Steps 7-8. 2187 EpochNanoseconds startEpochNs; 2188 if (r1 == 0) { 2189 // Step 7.a. 2190 startEpochNs = originEpochNs; 2191 } else { 2192 // Step 8.a. 2193 ISODate start; 2194 if (!CalendarDateAdd(cx, calendar, isoDateTime.date, startDuration, 2195 TemporalOverflow::Constrain, &start)) { 2196 return false; 2197 } 2198 2199 // Step 8.b. 2200 auto startDateTime = ISODateTime{start, isoDateTime.time}; 2201 MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime)); 2202 2203 // Steps 8.c-d. 2204 if (!timeZone) { 2205 // Step 8.c. 2206 startEpochNs = GetUTCEpochNanoseconds(startDateTime); 2207 } else { 2208 // Step 8.d. 2209 if (!GetEpochNanosecondsFor(cx, timeZone, startDateTime, 2210 TemporalDisambiguation::Compatible, 2211 &startEpochNs)) { 2212 return false; 2213 } 2214 } 2215 } 2216 2217 // Step 9. 2218 ISODate end; 2219 if (!CalendarDateAdd(cx, calendar, isoDateTime.date, endDuration, 2220 TemporalOverflow::Constrain, &end)) { 2221 return false; 2222 } 2223 2224 // Step 10. 2225 auto endDateTime = ISODateTime{end, isoDateTime.time}; 2226 MOZ_ASSERT(ISODateTimeWithinLimits(endDateTime)); 2227 2228 // Steps 11-12. 2229 EpochNanoseconds endEpochNs; 2230 if (!timeZone) { 2231 // Step 11.a. 2232 endEpochNs = GetUTCEpochNanoseconds(endDateTime); 2233 } else { 2234 // Step 12.a. 2235 if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime, 2236 TemporalDisambiguation::Compatible, 2237 &endEpochNs)) { 2238 return false; 2239 } 2240 } 2241 2242 // Step 13. 2243 *result = {r1, r2, startEpochNs, endEpochNs, startDuration, endDuration}; 2244 return true; 2245 } 2246 2247 struct DurationNudge { 2248 InternalDuration duration; 2249 EpochNanoseconds epochNs; 2250 double total = 0; 2251 bool didExpandCalendarUnit = false; 2252 }; 2253 2254 /** 2255 * NudgeToCalendarUnit ( sign, duration, originEpochNs, destEpochNs, 2256 * isoDateTime, timeZone, calendar, increment, unit, roundingMode ) 2257 */ 2258 static bool NudgeToCalendarUnit( 2259 JSContext* cx, const InternalDuration& duration, 2260 const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, 2261 const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone, 2262 Handle<CalendarValue> calendar, Increment increment, TemporalUnit unit, 2263 TemporalRoundingMode roundingMode, DurationNudge* result) { 2264 MOZ_ASSERT(IsValidDuration(duration)); 2265 MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs)); 2266 MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs)); 2267 MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs)); 2268 MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs)); 2269 MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); 2270 MOZ_ASSERT(unit <= TemporalUnit::Day); 2271 2272 int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1; 2273 2274 // Step 1. 2275 bool didExpandCalendarUnit = false; 2276 2277 // Step 2. 2278 NudgeWindow nudgeWindow; 2279 if (!ComputeNudgeWindow(cx, duration, originEpochNs, isoDateTime, timeZone, 2280 calendar, increment, unit, false, &nudgeWindow)) { 2281 return false; 2282 } 2283 2284 // Steps 3-6. 2285 const auto& startPoint = 2286 sign > 0 ? nudgeWindow.startEpochNs : nudgeWindow.endEpochNs; 2287 const auto& endPoint = 2288 sign > 0 ? nudgeWindow.endEpochNs : nudgeWindow.startEpochNs; 2289 if (!(startPoint <= destEpochNs && destEpochNs <= endPoint)) { 2290 // Steps 5.a.i and 6.a.i. 2291 if (!ComputeNudgeWindow(cx, duration, originEpochNs, isoDateTime, timeZone, 2292 calendar, increment, unit, true, &nudgeWindow)) { 2293 return false; 2294 } 2295 2296 // Steps 5.a.ii and 6.a.ii. (Moved below) 2297 2298 // Steps 5.a.iii and 6.a.iii. 2299 didExpandCalendarUnit = true; 2300 } 2301 2302 // Steps 7-12. 2303 const auto& [r1, r2, startEpochNs, endEpochNs, startDuration, endDuration] = 2304 nudgeWindow; 2305 2306 // Step 13. 2307 MOZ_ASSERT(startEpochNs != endEpochNs); 2308 MOZ_ASSERT_IF(sign > 0, 2309 startEpochNs <= destEpochNs && destEpochNs <= endEpochNs); 2310 MOZ_ASSERT_IF(sign < 0, 2311 endEpochNs <= destEpochNs && destEpochNs <= startEpochNs); 2312 2313 // Step 14. 2314 auto numerator = (destEpochNs - startEpochNs).toNanoseconds(); 2315 auto denominator = (endEpochNs - startEpochNs).toNanoseconds(); 2316 MOZ_ASSERT(denominator != Int128{0}); 2317 MOZ_ASSERT(numerator.abs() <= denominator.abs()); 2318 MOZ_ASSERT_IF(denominator > Int128{0}, numerator >= Int128{0}); 2319 MOZ_ASSERT_IF(denominator < Int128{0}, numerator <= Int128{0}); 2320 2321 // Ensure |numerator| and |denominator| are both non-negative to simplify the 2322 // following computations. 2323 if (denominator < Int128{0}) { 2324 numerator = -numerator; 2325 denominator = -denominator; 2326 } 2327 2328 // Steps 15-17. 2329 // 2330 // |total| must only be computed when called from Duration.prototype.total, 2331 // which always passes "trunc" rounding mode with an increment of one. 2332 double total = mozilla::UnspecifiedNaN<double>(); 2333 if (roundingMode == TemporalRoundingMode::Trunc && 2334 increment == Increment{1}) { 2335 // total = r1 + progress × increment × sign 2336 // = r1 + (numerator / denominator) × increment × sign 2337 // = r1 + (numerator × increment × sign) / denominator 2338 // = (r1 × denominator + numerator × increment × sign) / denominator 2339 // 2340 // Computing `n` can't overflow, because: 2341 // - For years, months, and weeks, `abs(r1) ≤ 2^32`. 2342 // - For days, `abs(r1) < ⌈(2^53) / (24 * 60 * 60)⌉`. 2343 // - `denominator` and `numerator` are below-or-equal `2 × 8.64 × 10^21`. 2344 // - And finally `increment ≤ 10^9`. 2345 auto n = Int128{r1} * denominator + numerator * Int128{sign}; 2346 total = FractionToDouble(n, denominator); 2347 } 2348 2349 // Steps 18-19. 2350 auto unsignedRoundingMode = GetUnsignedRoundingMode(roundingMode, sign < 0); 2351 2352 // Steps 20-21. (Inlined ApplyUnsignedRoundingMode) 2353 // 2354 // clang-format off 2355 // 2356 // ApplyUnsignedRoundingMode, steps 1-16. 2357 // 2358 // `total = r1` iff `progress = 0`. And `progress = 0` iff `numerator = 0`. 2359 // 2360 // d1 = total - r1 2361 // = (r1 × denominator + numerator × increment × sign) / denominator - r1 2362 // = (numerator × increment × sign) / denominator 2363 // 2364 // d2 = r2 - total 2365 // = r1 + increment - (r1 × denominator + numerator × increment × sign) / denominator 2366 // = (increment × denominator - numerator × increment × sign) / denominator 2367 // 2368 // d1 < d2 2369 // ⇔ (numerator × increment × sign) / denominator < (increment × denominator - numerator × increment × sign) / denominator 2370 // ⇔ (numerator × increment × sign) < (increment × denominator - numerator × increment × sign) 2371 // ⇔ (numerator × sign) < (denominator - numerator × sign) 2372 // ⇔ (2 × numerator × sign) < denominator 2373 // 2374 // cardinality = (r1 / (r2 – r1)) modulo 2 2375 // = (r1 / (r1 + increment - r1)) modulo 2 2376 // = (r1 / increment) modulo 2 2377 // 2378 // clang-format on 2379 bool roundedUp; 2380 if (numerator == denominator) { 2381 roundedUp = true; 2382 } else if (numerator == Int128{0}) { 2383 roundedUp = false; 2384 } else if (unsignedRoundingMode == UnsignedRoundingMode::Zero) { 2385 roundedUp = false; 2386 } else if (unsignedRoundingMode == UnsignedRoundingMode::Infinity) { 2387 roundedUp = true; 2388 } else if (numerator + numerator < denominator) { 2389 roundedUp = false; 2390 } else if (numerator + numerator > denominator) { 2391 roundedUp = true; 2392 } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfZero) { 2393 roundedUp = false; 2394 } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfInfinity) { 2395 roundedUp = true; 2396 } else if ((r1 / increment.value()) % 2 == 0) { 2397 roundedUp = false; 2398 } else { 2399 roundedUp = true; 2400 } 2401 2402 // Steps 22-26. 2403 auto resultDuration = roundedUp ? endDuration : startDuration; 2404 auto resultEpochNs = roundedUp ? endEpochNs : startEpochNs; 2405 *result = { 2406 {resultDuration, {}}, 2407 resultEpochNs, 2408 total, 2409 didExpandCalendarUnit || roundedUp, 2410 }; 2411 return true; 2412 } 2413 2414 #ifdef DEBUG 2415 static bool IsValidTimeFromDateTimeDuration(const TimeDuration& timeDuration) { 2416 // Time zone adjustment can't exceed 24 hours. 2417 constexpr auto oneDay = EpochDuration::fromDays(1); 2418 2419 // Time zone adjusted nsMinInstant and nsMaxInstant. 2420 constexpr auto min = EpochNanoseconds::min() - oneDay; 2421 constexpr auto max = EpochNanoseconds::max() + oneDay; 2422 2423 // Maximum duration between two date-time points. 2424 constexpr auto maxDuration = (max - min).to<TimeDuration>(); 2425 static_assert(maxDuration == TimeDuration::fromDays(200'000'002)); 2426 2427 // If |timeDuration| is a duration between two date-times within the valid 2428 // limits, the duration can't exceed the duration between time zone adjusted 2429 // nsMinInstant and nsMaxInstant. 2430 return timeDuration.abs() < maxDuration; 2431 } 2432 #endif 2433 2434 /** 2435 * NudgeToZonedTime ( sign, duration, isoDateTime, timeZone, calendar, 2436 * increment, unit, roundingMode ) 2437 */ 2438 static bool NudgeToZonedTime(JSContext* cx, const InternalDuration& duration, 2439 const ISODateTime& isoDateTime, 2440 Handle<TimeZoneValue> timeZone, 2441 Handle<CalendarValue> calendar, 2442 Increment increment, TemporalUnit unit, 2443 TemporalRoundingMode roundingMode, 2444 DurationNudge* result) { 2445 MOZ_ASSERT(IsValidDuration(duration)); 2446 MOZ_ASSERT(IsValidTimeFromDateTimeDuration(duration.time)); 2447 MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); 2448 MOZ_ASSERT(unit >= TemporalUnit::Hour); 2449 2450 int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1; 2451 2452 // Step 1. 2453 ISODate start; 2454 if (!CalendarDateAdd(cx, calendar, isoDateTime.date, duration.date, 2455 TemporalOverflow::Constrain, &start)) { 2456 return false; 2457 } 2458 2459 // Step 2. 2460 auto startDateTime = ISODateTime{start, isoDateTime.time}; 2461 MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime)); 2462 2463 // Step 3. 2464 auto end = BalanceISODate(start, sign); 2465 2466 // Step 4. 2467 auto endDateTime = ISODateTime{end, isoDateTime.time}; 2468 if (!ISODateTimeWithinLimits(endDateTime)) { 2469 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2470 JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); 2471 return false; 2472 } 2473 2474 // Step 5. 2475 EpochNanoseconds startEpochNs; 2476 if (!GetEpochNanosecondsFor(cx, timeZone, startDateTime, 2477 TemporalDisambiguation::Compatible, 2478 &startEpochNs)) { 2479 return false; 2480 } 2481 2482 // Step 6. 2483 EpochNanoseconds endEpochNs; 2484 if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime, 2485 TemporalDisambiguation::Compatible, 2486 &endEpochNs)) { 2487 return false; 2488 } 2489 2490 // Step 7. 2491 auto daySpan = 2492 TimeDurationFromEpochNanosecondsDifference(endEpochNs, startEpochNs); 2493 MOZ_ASSERT(daySpan.abs() <= TimeDuration::fromDays(2), 2494 "maximum day length for repeated days"); 2495 2496 // Step 8. 2497 MOZ_ASSERT(TimeDurationSign(daySpan) == sign); 2498 2499 // Steps 9-10. 2500 // 2501 // RoundTimeDurationToIncrement is infallible |duration.time| is a valid 2502 // date-time duration. 2503 auto roundedTime = RoundTimeDurationToIncrement(duration.time, unit, 2504 increment, roundingMode); 2505 MOZ_ASSERT(IsValidTimeDuration(roundedTime)); 2506 2507 // Step 11. (Inlined AddTimeDuration) 2508 auto beyondDaySpan = roundedTime - daySpan; 2509 MOZ_ASSERT(IsValidTimeDuration(beyondDaySpan)); 2510 2511 // Steps 12-13. 2512 bool didRoundBeyondDay; 2513 int32_t dayDelta; 2514 EpochNanoseconds nudgedEpochNs; 2515 if (TimeDurationSign(beyondDaySpan) != -sign) { 2516 // Step 12.a. 2517 didRoundBeyondDay = true; 2518 2519 // Step 12.b. 2520 dayDelta = sign; 2521 2522 // Step 12.c. 2523 // 2524 // This call to RoundTimeDurationToIncrement is also infallible. 2525 roundedTime = RoundTimeDurationToIncrement(beyondDaySpan, unit, increment, 2526 roundingMode); 2527 MOZ_ASSERT(IsValidTimeDuration(roundedTime)); 2528 2529 // Step 12.d. (Inlined AddTimeDurationToEpochNanoseconds) 2530 nudgedEpochNs = endEpochNs + roundedTime.to<EpochDuration>(); 2531 } else { 2532 // Step 13.a. 2533 didRoundBeyondDay = false; 2534 2535 // Step 13.b. 2536 dayDelta = 0; 2537 2538 // Step 13.c. (Inlined AddTimeDurationToEpochNanoseconds) 2539 nudgedEpochNs = startEpochNs + roundedTime.to<EpochDuration>(); 2540 } 2541 2542 // Step 14. 2543 auto dateDuration = DateDuration{ 2544 duration.date.years, 2545 duration.date.months, 2546 duration.date.weeks, 2547 duration.date.days + dayDelta, 2548 }; 2549 MOZ_ASSERT(IsValidDuration(dateDuration)); 2550 2551 // Step 15. 2552 MOZ_ASSERT(DateDurationSign(dateDuration) * TimeDurationSign(roundedTime) >= 2553 0); 2554 auto resultDuration = InternalDuration{dateDuration, roundedTime}; 2555 2556 // Step 16. 2557 *result = { 2558 resultDuration, 2559 nudgedEpochNs, 2560 mozilla::UnspecifiedNaN<double>(), 2561 didRoundBeyondDay, 2562 }; 2563 return true; 2564 } 2565 2566 /** 2567 * NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment, 2568 * smallestUnit, roundingMode ) 2569 */ 2570 static DurationNudge NudgeToDayOrTime(const InternalDuration& duration, 2571 const EpochNanoseconds& destEpochNs, 2572 TemporalUnit largestUnit, 2573 Increment increment, 2574 TemporalUnit smallestUnit, 2575 TemporalRoundingMode roundingMode) { 2576 MOZ_ASSERT(IsValidDuration(duration)); 2577 MOZ_ASSERT(IsValidLocalNanoseconds(destEpochNs)); 2578 MOZ_ASSERT(smallestUnit >= TemporalUnit::Day); 2579 2580 // Step 1. (Inlined Add24HourDaysToTimeDuration) 2581 auto timeDuration = 2582 duration.time + TimeDuration::fromDays(duration.date.days); 2583 MOZ_ASSERT(IsValidTimeDuration(timeDuration)); 2584 MOZ_ASSERT(IsValidTimeFromDateTimeDuration(timeDuration)); 2585 2586 // Steps 2-3. 2587 // 2588 // RoundTimeDurationToIncrement is infallible |timeDuration| is a valid 2589 // date-time duration. 2590 auto roundedTime = RoundTimeDurationToIncrement(timeDuration, smallestUnit, 2591 increment, roundingMode); 2592 MOZ_ASSERT(IsValidTimeDuration(roundedTime)); 2593 2594 // Step 4. (Inlined AddTimeDuration) 2595 auto diffTime = roundedTime - timeDuration; 2596 MOZ_ASSERT(IsValidTimeDuration(diffTime)); 2597 2598 // Step 5. 2599 int64_t wholeDays = timeDuration.toDays(); 2600 2601 // Step 6. 2602 int64_t roundedWholeDays = roundedTime.toDays(); 2603 2604 // Step 7. 2605 int64_t dayDelta = roundedWholeDays - wholeDays; 2606 2607 // Step 8. 2608 int32_t dayDeltaSign = dayDelta < 0 ? -1 : dayDelta > 0 ? 1 : 0; 2609 2610 // Step 9. 2611 bool didExpandDays = dayDeltaSign == TimeDurationSign(timeDuration); 2612 2613 // Step 10. (Inlined AddTimeDurationToEpochNanoseconds) 2614 auto nudgedEpochNs = destEpochNs + diffTime.to<EpochDuration>(); 2615 2616 // Step 11. 2617 int64_t days = 0; 2618 2619 // Step 12. 2620 auto remainder = roundedTime; 2621 2622 // Step 13. 2623 if (largestUnit <= TemporalUnit::Day) { 2624 // Step 13.a. 2625 days = roundedWholeDays; 2626 2627 // Step 13.b. 2628 remainder = roundedTime - TimeDuration::fromDays(roundedWholeDays); 2629 MOZ_ASSERT(IsValidTimeDuration(remainder)); 2630 } 2631 2632 // Step 14. 2633 auto dateDuration = DateDuration{ 2634 duration.date.years, 2635 duration.date.months, 2636 duration.date.weeks, 2637 days, 2638 }; 2639 MOZ_ASSERT(IsValidDuration(dateDuration)); 2640 2641 // Step 15. 2642 MOZ_ASSERT(DateDurationSign(dateDuration) * TimeDurationSign(remainder) >= 0); 2643 auto resultDuration = InternalDuration{dateDuration, remainder}; 2644 2645 // Step 16. 2646 return {resultDuration, nudgedEpochNs, mozilla::UnspecifiedNaN<double>(), 2647 didExpandDays}; 2648 } 2649 2650 /** 2651 * BubbleRelativeDuration ( sign, duration, nudgedEpochNs, isoDateTime, 2652 * timeZone, calendar, largestUnit, smallestUnit ) 2653 */ 2654 static bool BubbleRelativeDuration( 2655 JSContext* cx, const InternalDuration& duration, const DurationNudge& nudge, 2656 const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone, 2657 Handle<CalendarValue> calendar, TemporalUnit largestUnit, 2658 TemporalUnit smallestUnit, InternalDuration* result) { 2659 MOZ_ASSERT(IsValidDuration(duration)); 2660 MOZ_ASSERT(IsValidDuration(nudge.duration)); 2661 MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); 2662 MOZ_ASSERT(smallestUnit <= TemporalUnit::Day); 2663 2664 // Step 1. (Modified to use `<=` to return early.) 2665 if (smallestUnit <= largestUnit) { 2666 *result = nudge.duration; 2667 return true; 2668 } 2669 MOZ_ASSERT(smallestUnit != TemporalUnit::Year); 2670 2671 int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1; 2672 2673 // Steps 2-6. 2674 auto dateDuration = nudge.duration.date; 2675 auto timeDuration = nudge.duration.time; 2676 auto unit = smallestUnit; 2677 while (unit > largestUnit) { 2678 using TemporalUnitType = std::underlying_type_t<TemporalUnit>; 2679 2680 static_assert(static_cast<TemporalUnitType>(TemporalUnit::Auto) == 1, 2681 "TemporalUnit::Auto has value one"); 2682 MOZ_ASSERT(unit > TemporalUnit::Auto, "can subtract unit by one"); 2683 2684 // Steps 4, 6.a, and 6.c. 2685 unit = static_cast<TemporalUnit>(static_cast<TemporalUnitType>(unit) - 1); 2686 MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Week); 2687 2688 // Step 6.b. 2689 if (unit != TemporalUnit::Week || largestUnit == TemporalUnit::Week) { 2690 // Steps 6.b.i-iii. 2691 DateDuration endDuration; 2692 if (unit == TemporalUnit::Year) { 2693 // Step 6.b.i.1. 2694 int64_t years = dateDuration.years + sign; 2695 2696 // Step 6.b.i.2. 2697 endDuration = {years}; 2698 } else if (unit == TemporalUnit::Month) { 2699 // Step 6.b.ii.1. 2700 int64_t months = dateDuration.months + sign; 2701 2702 // Step 6.b.ii.2. 2703 endDuration = {dateDuration.years, months}; 2704 } else { 2705 // Step 6.b.iii.1. 2706 MOZ_ASSERT(unit == TemporalUnit::Week); 2707 2708 // Step 6.b.iii.2. 2709 int64_t weeks = dateDuration.weeks + sign; 2710 2711 // Step 6.b.iii.3. 2712 endDuration = {dateDuration.years, dateDuration.months, weeks}; 2713 } 2714 MOZ_ASSERT(IsValidDuration(endDuration)); 2715 2716 // Steps 6.b.iv. 2717 ISODate end; 2718 if (!CalendarDateAdd(cx, calendar, isoDateTime.date, endDuration, 2719 TemporalOverflow::Constrain, &end)) { 2720 return false; 2721 } 2722 2723 // Steps 6.b.v. 2724 auto endDateTime = ISODateTime{end, isoDateTime.time}; 2725 MOZ_ASSERT(ISODateTimeWithinLimits(endDateTime)); 2726 2727 // Steps 6.b.vi-vii. 2728 EpochNanoseconds endEpochNs; 2729 if (!timeZone) { 2730 endEpochNs = GetUTCEpochNanoseconds(endDateTime); 2731 } else { 2732 if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime, 2733 TemporalDisambiguation::Compatible, 2734 &endEpochNs)) { 2735 return false; 2736 } 2737 } 2738 2739 // Step 6.b.viii. 2740 // 2741 // NB: |nudge.epochNs| can be outside the valid epoch nanoseconds limits. 2742 auto beyondEnd = nudge.epochNs - endEpochNs; 2743 2744 // Step 6.b.ix. 2745 int32_t beyondEndSign = beyondEnd < EpochDuration{} ? -1 2746 : beyondEnd > EpochDuration{} ? 1 2747 : 0; 2748 2749 // Steps 6.b.x-xi. 2750 if (beyondEndSign != -sign) { 2751 dateDuration = endDuration; 2752 timeDuration = {}; 2753 } else { 2754 break; 2755 } 2756 } 2757 2758 // Step 6.c. (Moved above) 2759 } 2760 2761 // Step 7. 2762 *result = {dateDuration, timeDuration}; 2763 return true; 2764 } 2765 2766 /** 2767 * RoundRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, 2768 * timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode ) 2769 */ 2770 bool js::temporal::RoundRelativeDuration( 2771 JSContext* cx, const InternalDuration& duration, 2772 const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, 2773 const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone, 2774 Handle<CalendarValue> calendar, TemporalUnit largestUnit, 2775 Increment increment, TemporalUnit smallestUnit, 2776 TemporalRoundingMode roundingMode, InternalDuration* result) { 2777 MOZ_ASSERT(IsValidDuration(duration)); 2778 MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs)); 2779 MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs)); 2780 MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs)); 2781 MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs)); 2782 MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); 2783 MOZ_ASSERT(largestUnit <= smallestUnit); 2784 2785 // Steps 1-3. 2786 bool irregularLengthUnit = (smallestUnit < TemporalUnit::Day) || 2787 (timeZone && smallestUnit == TemporalUnit::Day); 2788 2789 // Step 4. (Not applicable in our implementation.) 2790 2791 // Steps 5-7. 2792 DurationNudge nudge; 2793 if (irregularLengthUnit) { 2794 // Step 5.a. 2795 if (!NudgeToCalendarUnit(cx, duration, originEpochNs, destEpochNs, 2796 isoDateTime, timeZone, calendar, increment, 2797 smallestUnit, roundingMode, &nudge)) { 2798 return false; 2799 } 2800 } else if (timeZone) { 2801 // Step 6.a. 2802 if (!NudgeToZonedTime(cx, duration, isoDateTime, timeZone, calendar, 2803 increment, smallestUnit, roundingMode, &nudge)) { 2804 return false; 2805 } 2806 } else { 2807 // Step 7.a. 2808 nudge = NudgeToDayOrTime(duration, destEpochNs, largestUnit, increment, 2809 smallestUnit, roundingMode); 2810 } 2811 2812 // Step 8. 2813 auto nudgedDuration = nudge.duration; 2814 2815 // Step 9. 2816 if (nudge.didExpandCalendarUnit && smallestUnit != TemporalUnit::Week) { 2817 // Step 9.a. (Inlined LargerOfTwoTemporalUnits) 2818 auto startUnit = std::min(smallestUnit, TemporalUnit::Day); 2819 2820 // Step 9.b. 2821 if (!BubbleRelativeDuration(cx, duration, nudge, isoDateTime, timeZone, 2822 calendar, largestUnit, startUnit, 2823 &nudgedDuration)) { 2824 return false; 2825 } 2826 } 2827 2828 // Step 10. 2829 *result = nudgedDuration; 2830 return true; 2831 } 2832 2833 /** 2834 * TotalRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime, 2835 * timeZone, calendar, unit ) 2836 */ 2837 bool js::temporal::TotalRelativeDuration( 2838 JSContext* cx, const InternalDuration& duration, 2839 const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs, 2840 const ISODateTime& isoDateTime, JS::Handle<TimeZoneValue> timeZone, 2841 JS::Handle<CalendarValue> calendar, TemporalUnit unit, double* result) { 2842 MOZ_ASSERT(IsValidDuration(duration)); 2843 MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs)); 2844 MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs)); 2845 MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs)); 2846 MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs)); 2847 MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); 2848 MOZ_ASSERT(unit <= TemporalUnit::Day); 2849 MOZ_ASSERT_IF(unit == TemporalUnit::Day, timeZone); 2850 2851 // Steps 1.a-b. 2852 DurationNudge nudge; 2853 if (!NudgeToCalendarUnit(cx, duration, originEpochNs, destEpochNs, 2854 isoDateTime, timeZone, calendar, Increment{1}, unit, 2855 TemporalRoundingMode::Trunc, &nudge)) { 2856 return false; 2857 } 2858 2859 // Step 1.c. 2860 *result = nudge.total; 2861 return true; 2862 } 2863 2864 /** 2865 * AddDurations ( operation, duration, other ) 2866 */ 2867 static bool AddDurations(JSContext* cx, TemporalAddDuration operation, 2868 const CallArgs& args) { 2869 auto* durationObj = &args.thisv().toObject().as<DurationObject>(); 2870 auto duration = ToDuration(durationObj); 2871 2872 // Step 1. 2873 Duration other; 2874 if (!ToTemporalDuration(cx, args.get(0), &other)) { 2875 return false; 2876 } 2877 2878 // Step 2. 2879 if (operation == TemporalAddDuration::Subtract) { 2880 other = other.negate(); 2881 } 2882 2883 // Step 3. 2884 auto largestUnit1 = DefaultTemporalLargestUnit(duration); 2885 2886 // Step 4. 2887 auto largestUnit2 = DefaultTemporalLargestUnit(other); 2888 2889 // Step 5. 2890 auto largestUnit = std::min(largestUnit1, largestUnit2); 2891 2892 // Step 6. 2893 if (largestUnit <= TemporalUnit::Week) { 2894 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2895 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, 2896 "relativeTo"); 2897 return false; 2898 } 2899 2900 // Step 7. 2901 auto d1 = ToInternalDurationRecordWith24HourDays(duration).time; 2902 2903 // Step 8. 2904 auto d2 = ToInternalDurationRecordWith24HourDays(other).time; 2905 2906 // Step 9. (Inline AddTimeDuration) 2907 auto timeResult = d1 + d2; 2908 if (!IsValidTimeDuration(timeResult)) { 2909 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2910 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 2911 return false; 2912 } 2913 2914 // Steps 10-11. 2915 Duration resultDuration; 2916 if (!TemporalDurationFromInternal(cx, timeResult, largestUnit, 2917 &resultDuration)) { 2918 return false; 2919 } 2920 MOZ_ASSERT(IsValidDuration(resultDuration)); 2921 2922 auto* obj = CreateTemporalDuration(cx, resultDuration); 2923 if (!obj) { 2924 return false; 2925 } 2926 2927 args.rval().setObject(*obj); 2928 return true; 2929 } 2930 2931 /** 2932 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ , 2933 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ] 2934 * ] ] ] ] ] ] ) 2935 */ 2936 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) { 2937 CallArgs args = CallArgsFromVp(argc, vp); 2938 2939 // Step 1. 2940 if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) { 2941 return false; 2942 } 2943 2944 // Step 2. 2945 double years = 0; 2946 if (args.hasDefined(0) && 2947 !ToIntegerIfIntegral(cx, "years", args[0], &years)) { 2948 return false; 2949 } 2950 2951 // Step 3. 2952 double months = 0; 2953 if (args.hasDefined(1) && 2954 !ToIntegerIfIntegral(cx, "months", args[1], &months)) { 2955 return false; 2956 } 2957 2958 // Step 4. 2959 double weeks = 0; 2960 if (args.hasDefined(2) && 2961 !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) { 2962 return false; 2963 } 2964 2965 // Step 5. 2966 double days = 0; 2967 if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) { 2968 return false; 2969 } 2970 2971 // Step 6. 2972 double hours = 0; 2973 if (args.hasDefined(4) && 2974 !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) { 2975 return false; 2976 } 2977 2978 // Step 7. 2979 double minutes = 0; 2980 if (args.hasDefined(5) && 2981 !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) { 2982 return false; 2983 } 2984 2985 // Step 8. 2986 double seconds = 0; 2987 if (args.hasDefined(6) && 2988 !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) { 2989 return false; 2990 } 2991 2992 // Step 9. 2993 double milliseconds = 0; 2994 if (args.hasDefined(7) && 2995 !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) { 2996 return false; 2997 } 2998 2999 // Step 10. 3000 double microseconds = 0; 3001 if (args.hasDefined(8) && 3002 !ToIntegerIfIntegral(cx, "microseconds", args[8], µseconds)) { 3003 return false; 3004 } 3005 3006 // Step 11. 3007 double nanoseconds = 0; 3008 if (args.hasDefined(9) && 3009 !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) { 3010 return false; 3011 } 3012 3013 // Step 12. 3014 auto* duration = CreateTemporalDuration( 3015 cx, args, 3016 {years, months, weeks, days, hours, minutes, seconds, milliseconds, 3017 microseconds, nanoseconds}); 3018 if (!duration) { 3019 return false; 3020 } 3021 3022 args.rval().setObject(*duration); 3023 return true; 3024 } 3025 3026 /** 3027 * Temporal.Duration.from ( item ) 3028 */ 3029 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) { 3030 CallArgs args = CallArgsFromVp(argc, vp); 3031 3032 // Step 1. 3033 Duration result; 3034 if (!ToTemporalDuration(cx, args.get(0), &result)) { 3035 return false; 3036 } 3037 3038 auto* obj = CreateTemporalDuration(cx, result); 3039 if (!obj) { 3040 return false; 3041 } 3042 3043 args.rval().setObject(*obj); 3044 return true; 3045 } 3046 3047 /** 3048 * Temporal.Duration.compare ( one, two [ , options ] ) 3049 */ 3050 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) { 3051 CallArgs args = CallArgsFromVp(argc, vp); 3052 3053 // Step 1. 3054 Duration one; 3055 if (!ToTemporalDuration(cx, args.get(0), &one)) { 3056 return false; 3057 } 3058 3059 // Step 2. 3060 Duration two; 3061 if (!ToTemporalDuration(cx, args.get(1), &two)) { 3062 return false; 3063 } 3064 3065 // Steps 3-4. 3066 Rooted<PlainDate> plainRelativeTo(cx); 3067 Rooted<ZonedDateTime> zonedRelativeTo(cx); 3068 if (args.hasDefined(2)) { 3069 // Step 3. 3070 Rooted<JSObject*> options( 3071 cx, RequireObjectArg(cx, "options", "compare", args[2])); 3072 if (!options) { 3073 return false; 3074 } 3075 3076 // Step 4. 3077 if (!GetTemporalRelativeToOption(cx, options, &plainRelativeTo, 3078 &zonedRelativeTo)) { 3079 return false; 3080 } 3081 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo); 3082 } 3083 3084 // Step 5. 3085 if (one == two) { 3086 args.rval().setInt32(0); 3087 return true; 3088 } 3089 3090 // Steps 6-9. (Not applicable in our implementation.) 3091 3092 // Step 10. 3093 auto duration1 = ToInternalDurationRecord(one); 3094 3095 // Step 11. 3096 auto duration2 = ToInternalDurationRecord(two); 3097 3098 // Step 12. 3099 if (zonedRelativeTo && 3100 (duration1.date != DateDuration{} || duration2.date != DateDuration{})) { 3101 // Steps 12.a-b. (Not applicable in our implementation.) 3102 3103 // Step 12.c. 3104 EpochNanoseconds after1; 3105 if (!AddZonedDateTime(cx, zonedRelativeTo, duration1, &after1)) { 3106 return false; 3107 } 3108 3109 // Step 12.d. 3110 EpochNanoseconds after2; 3111 if (!AddZonedDateTime(cx, zonedRelativeTo, duration2, &after2)) { 3112 return false; 3113 } 3114 3115 // Steps 12.e-g. 3116 args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0); 3117 return true; 3118 } 3119 3120 // Steps 13.a-b and 14.a. 3121 int64_t days1; 3122 if (!DateDurationDays(cx, duration1.date, plainRelativeTo, &days1)) { 3123 return false; 3124 } 3125 3126 // Steps 13.a, 13.c, and 14.b. 3127 int64_t days2; 3128 if (!DateDurationDays(cx, duration2.date, plainRelativeTo, &days2)) { 3129 return false; 3130 } 3131 3132 // Step 15. 3133 auto timeDuration1 = duration1.time; 3134 if (!Add24HourDaysToTimeDuration(cx, duration1.time, days1, &timeDuration1)) { 3135 return false; 3136 } 3137 3138 // Step 16. 3139 auto timeDuration2 = duration2.time; 3140 if (!Add24HourDaysToTimeDuration(cx, duration2.time, days2, &timeDuration2)) { 3141 return false; 3142 } 3143 3144 // Step 17. 3145 args.rval().setInt32(CompareTimeDuration(timeDuration1, timeDuration2)); 3146 return true; 3147 } 3148 3149 /** 3150 * get Temporal.Duration.prototype.years 3151 */ 3152 static bool Duration_years(JSContext* cx, const CallArgs& args) { 3153 // Step 3. 3154 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3155 args.rval().setNumber(duration->years()); 3156 return true; 3157 } 3158 3159 /** 3160 * get Temporal.Duration.prototype.years 3161 */ 3162 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) { 3163 // Steps 1-2. 3164 CallArgs args = CallArgsFromVp(argc, vp); 3165 return CallNonGenericMethod<IsDuration, Duration_years>(cx, args); 3166 } 3167 3168 /** 3169 * get Temporal.Duration.prototype.months 3170 */ 3171 static bool Duration_months(JSContext* cx, const CallArgs& args) { 3172 // Step 3. 3173 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3174 args.rval().setNumber(duration->months()); 3175 return true; 3176 } 3177 3178 /** 3179 * get Temporal.Duration.prototype.months 3180 */ 3181 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) { 3182 // Steps 1-2. 3183 CallArgs args = CallArgsFromVp(argc, vp); 3184 return CallNonGenericMethod<IsDuration, Duration_months>(cx, args); 3185 } 3186 3187 /** 3188 * get Temporal.Duration.prototype.weeks 3189 */ 3190 static bool Duration_weeks(JSContext* cx, const CallArgs& args) { 3191 // Step 3. 3192 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3193 args.rval().setNumber(duration->weeks()); 3194 return true; 3195 } 3196 3197 /** 3198 * get Temporal.Duration.prototype.weeks 3199 */ 3200 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) { 3201 // Steps 1-2. 3202 CallArgs args = CallArgsFromVp(argc, vp); 3203 return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args); 3204 } 3205 3206 /** 3207 * get Temporal.Duration.prototype.days 3208 */ 3209 static bool Duration_days(JSContext* cx, const CallArgs& args) { 3210 // Step 3. 3211 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3212 args.rval().setNumber(duration->days()); 3213 return true; 3214 } 3215 3216 /** 3217 * get Temporal.Duration.prototype.days 3218 */ 3219 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) { 3220 // Steps 1-2. 3221 CallArgs args = CallArgsFromVp(argc, vp); 3222 return CallNonGenericMethod<IsDuration, Duration_days>(cx, args); 3223 } 3224 3225 /** 3226 * get Temporal.Duration.prototype.hours 3227 */ 3228 static bool Duration_hours(JSContext* cx, const CallArgs& args) { 3229 // Step 3. 3230 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3231 args.rval().setNumber(duration->hours()); 3232 return true; 3233 } 3234 3235 /** 3236 * get Temporal.Duration.prototype.hours 3237 */ 3238 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) { 3239 // Steps 1-2. 3240 CallArgs args = CallArgsFromVp(argc, vp); 3241 return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args); 3242 } 3243 3244 /** 3245 * get Temporal.Duration.prototype.minutes 3246 */ 3247 static bool Duration_minutes(JSContext* cx, const CallArgs& args) { 3248 // Step 3. 3249 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3250 args.rval().setNumber(duration->minutes()); 3251 return true; 3252 } 3253 3254 /** 3255 * get Temporal.Duration.prototype.minutes 3256 */ 3257 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) { 3258 // Steps 1-2. 3259 CallArgs args = CallArgsFromVp(argc, vp); 3260 return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args); 3261 } 3262 3263 /** 3264 * get Temporal.Duration.prototype.seconds 3265 */ 3266 static bool Duration_seconds(JSContext* cx, const CallArgs& args) { 3267 // Step 3. 3268 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3269 args.rval().setNumber(duration->seconds()); 3270 return true; 3271 } 3272 3273 /** 3274 * get Temporal.Duration.prototype.seconds 3275 */ 3276 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) { 3277 // Steps 1-2. 3278 CallArgs args = CallArgsFromVp(argc, vp); 3279 return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args); 3280 } 3281 3282 /** 3283 * get Temporal.Duration.prototype.milliseconds 3284 */ 3285 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) { 3286 // Step 3. 3287 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3288 args.rval().setNumber(duration->milliseconds()); 3289 return true; 3290 } 3291 3292 /** 3293 * get Temporal.Duration.prototype.milliseconds 3294 */ 3295 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) { 3296 // Steps 1-2. 3297 CallArgs args = CallArgsFromVp(argc, vp); 3298 return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args); 3299 } 3300 3301 /** 3302 * get Temporal.Duration.prototype.microseconds 3303 */ 3304 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) { 3305 // Step 3. 3306 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3307 args.rval().setNumber(duration->microseconds()); 3308 return true; 3309 } 3310 3311 /** 3312 * get Temporal.Duration.prototype.microseconds 3313 */ 3314 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) { 3315 // Steps 1-2. 3316 CallArgs args = CallArgsFromVp(argc, vp); 3317 return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args); 3318 } 3319 3320 /** 3321 * get Temporal.Duration.prototype.nanoseconds 3322 */ 3323 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) { 3324 // Step 3. 3325 auto* duration = &args.thisv().toObject().as<DurationObject>(); 3326 args.rval().setNumber(duration->nanoseconds()); 3327 return true; 3328 } 3329 3330 /** 3331 * get Temporal.Duration.prototype.nanoseconds 3332 */ 3333 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) { 3334 // Steps 1-2. 3335 CallArgs args = CallArgsFromVp(argc, vp); 3336 return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args); 3337 } 3338 3339 /** 3340 * get Temporal.Duration.prototype.sign 3341 */ 3342 static bool Duration_sign(JSContext* cx, const CallArgs& args) { 3343 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 3344 3345 // Step 3. 3346 args.rval().setInt32(DurationSign(duration)); 3347 return true; 3348 } 3349 3350 /** 3351 * get Temporal.Duration.prototype.sign 3352 */ 3353 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) { 3354 // Steps 1-2. 3355 CallArgs args = CallArgsFromVp(argc, vp); 3356 return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args); 3357 } 3358 3359 /** 3360 * get Temporal.Duration.prototype.blank 3361 */ 3362 static bool Duration_blank(JSContext* cx, const CallArgs& args) { 3363 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 3364 3365 // Steps 3-4. 3366 args.rval().setBoolean(duration == Duration{}); 3367 return true; 3368 } 3369 3370 /** 3371 * get Temporal.Duration.prototype.blank 3372 */ 3373 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) { 3374 // Steps 1-2. 3375 CallArgs args = CallArgsFromVp(argc, vp); 3376 return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args); 3377 } 3378 3379 /** 3380 * Temporal.Duration.prototype.with ( temporalDurationLike ) 3381 */ 3382 static bool Duration_with(JSContext* cx, const CallArgs& args) { 3383 // Absent values default to the corresponding values of |this| object. 3384 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 3385 3386 // Steps 3-23. 3387 Rooted<JSObject*> temporalDurationLike( 3388 cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0))); 3389 if (!temporalDurationLike) { 3390 return false; 3391 } 3392 if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) { 3393 return false; 3394 } 3395 3396 // Step 24. 3397 auto* result = CreateTemporalDuration(cx, duration); 3398 if (!result) { 3399 return false; 3400 } 3401 3402 args.rval().setObject(*result); 3403 return true; 3404 } 3405 3406 /** 3407 * Temporal.Duration.prototype.with ( temporalDurationLike ) 3408 */ 3409 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) { 3410 // Steps 1-2. 3411 CallArgs args = CallArgsFromVp(argc, vp); 3412 return CallNonGenericMethod<IsDuration, Duration_with>(cx, args); 3413 } 3414 3415 /** 3416 * Temporal.Duration.prototype.negated ( ) 3417 */ 3418 static bool Duration_negated(JSContext* cx, const CallArgs& args) { 3419 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 3420 3421 // Step 3. 3422 auto* result = CreateTemporalDuration(cx, duration.negate()); 3423 if (!result) { 3424 return false; 3425 } 3426 3427 args.rval().setObject(*result); 3428 return true; 3429 } 3430 3431 /** 3432 * Temporal.Duration.prototype.negated ( ) 3433 */ 3434 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) { 3435 // Steps 1-2. 3436 CallArgs args = CallArgsFromVp(argc, vp); 3437 return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args); 3438 } 3439 3440 /** 3441 * Temporal.Duration.prototype.abs ( ) 3442 */ 3443 static bool Duration_abs(JSContext* cx, const CallArgs& args) { 3444 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 3445 3446 // Step 3. 3447 auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration)); 3448 if (!result) { 3449 return false; 3450 } 3451 3452 args.rval().setObject(*result); 3453 return true; 3454 } 3455 3456 /** 3457 * Temporal.Duration.prototype.abs ( ) 3458 */ 3459 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) { 3460 // Steps 1-2. 3461 CallArgs args = CallArgsFromVp(argc, vp); 3462 return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args); 3463 } 3464 3465 /** 3466 * Temporal.Duration.prototype.add ( other ) 3467 */ 3468 static bool Duration_add(JSContext* cx, const CallArgs& args) { 3469 // Step 3. 3470 return AddDurations(cx, TemporalAddDuration::Add, args); 3471 } 3472 3473 /** 3474 * Temporal.Duration.prototype.add ( other ) 3475 */ 3476 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) { 3477 // Steps 1-2. 3478 CallArgs args = CallArgsFromVp(argc, vp); 3479 return CallNonGenericMethod<IsDuration, Duration_add>(cx, args); 3480 } 3481 3482 /** 3483 * Temporal.Duration.prototype.subtract ( other ) 3484 */ 3485 static bool Duration_subtract(JSContext* cx, const CallArgs& args) { 3486 // Step 3. 3487 return AddDurations(cx, TemporalAddDuration::Subtract, args); 3488 } 3489 3490 /** 3491 * Temporal.Duration.prototype.subtract ( other ) 3492 */ 3493 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) { 3494 // Steps 1-2. 3495 CallArgs args = CallArgsFromVp(argc, vp); 3496 return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args); 3497 } 3498 3499 /** 3500 * Temporal.Duration.prototype.round ( roundTo ) 3501 */ 3502 static bool Duration_round(JSContext* cx, const CallArgs& args) { 3503 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 3504 3505 // Step 18. (Reordered) 3506 auto existingLargestUnit = DefaultTemporalLargestUnit(duration); 3507 3508 // Steps 3-26. 3509 auto smallestUnit = TemporalUnit::Unset; 3510 auto largestUnit = TemporalUnit::Unset; 3511 auto roundingMode = TemporalRoundingMode::HalfExpand; 3512 auto roundingIncrement = Increment{1}; 3513 Rooted<PlainDate> plainRelativeTo(cx); 3514 Rooted<ZonedDateTime> zonedRelativeTo(cx); 3515 if (args.get(0).isString()) { 3516 // Step 4. (Not applicable in our implementation.) 3517 3518 // Steps 6-14. (Not applicable) 3519 3520 // Step 15. 3521 Rooted<JSString*> paramString(cx, args[0].toString()); 3522 if (!GetTemporalUnitValuedOption( 3523 cx, paramString, TemporalUnitKey::SmallestUnit, &smallestUnit)) { 3524 return false; 3525 } 3526 3527 // Step 16. 3528 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, 3529 smallestUnit, TemporalUnitGroup::DateTime)) { 3530 return false; 3531 } 3532 3533 // Step 17. (Not applicable) 3534 3535 // Step 18. (Moved above) 3536 3537 // Step 19. 3538 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit); 3539 3540 // Step 20. (Not applicable) 3541 3542 // Step 20.a. (Not applicable) 3543 3544 // Step 20.b. 3545 largestUnit = defaultLargestUnit; 3546 3547 // Steps 21-26. (Not applicable) 3548 } else { 3549 // Steps 3 and 5. 3550 Rooted<JSObject*> options( 3551 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0))); 3552 if (!options) { 3553 return false; 3554 } 3555 3556 // Step 6. 3557 bool smallestUnitPresent = true; 3558 3559 // Step 7. 3560 bool largestUnitPresent = true; 3561 3562 // Steps 8-9. 3563 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::LargestUnit, 3564 &largestUnit)) { 3565 return false; 3566 } 3567 3568 // Steps 10-12. 3569 if (!GetTemporalRelativeToOption(cx, options, &plainRelativeTo, 3570 &zonedRelativeTo)) { 3571 return false; 3572 } 3573 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo); 3574 3575 // Step 13. 3576 if (!GetRoundingIncrementOption(cx, options, &roundingIncrement)) { 3577 return false; 3578 } 3579 3580 // Step 14. 3581 if (!GetRoundingModeOption(cx, options, &roundingMode)) { 3582 return false; 3583 } 3584 3585 // Step 15. 3586 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit, 3587 &smallestUnit)) { 3588 return false; 3589 } 3590 3591 // Step 16. 3592 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, 3593 smallestUnit, TemporalUnitGroup::DateTime)) { 3594 return false; 3595 } 3596 3597 // Step 17. 3598 if (smallestUnit == TemporalUnit::Unset) { 3599 // Step 17.a. 3600 smallestUnitPresent = false; 3601 3602 // Step 17.b. 3603 smallestUnit = TemporalUnit::Nanosecond; 3604 } 3605 3606 // Step 18. (Moved above) 3607 3608 // Step 19. 3609 auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit); 3610 3611 // Steps 20-21. 3612 if (largestUnit == TemporalUnit::Unset) { 3613 // Step 20.a. 3614 largestUnitPresent = false; 3615 3616 // Step 20.b. 3617 largestUnit = defaultLargestUnit; 3618 } else if (largestUnit == TemporalUnit::Auto) { 3619 // Step 21.a 3620 largestUnit = defaultLargestUnit; 3621 } 3622 3623 // Step 22. 3624 if (!smallestUnitPresent && !largestUnitPresent) { 3625 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3626 JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER); 3627 return false; 3628 } 3629 3630 // Step 23. 3631 if (largestUnit > smallestUnit) { 3632 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3633 JSMSG_TEMPORAL_INVALID_UNIT_RANGE); 3634 return false; 3635 } 3636 3637 // Steps 24-25. 3638 if (smallestUnit > TemporalUnit::Day) { 3639 // Step 24. 3640 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit); 3641 3642 // Step 25. 3643 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum, 3644 false)) { 3645 return false; 3646 } 3647 } 3648 3649 // Step 26. 3650 if (roundingIncrement > Increment{1} && largestUnit != smallestUnit && 3651 smallestUnit <= TemporalUnit::Day) { 3652 Int32ToCStringBuf cbuf; 3653 const char* numStr = 3654 Int32ToCString(&cbuf, int32_t(roundingIncrement.value())); 3655 3656 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3657 JSMSG_INVALID_OPTION_VALUE, "roundingIncrement", 3658 numStr); 3659 return false; 3660 } 3661 } 3662 3663 // Step 27. 3664 if (zonedRelativeTo) { 3665 // Step 27.a. 3666 auto internalDuration = ToInternalDurationRecord(duration); 3667 3668 // Steps 27.b-d. (Not applicable in our implementation.) 3669 3670 // Step 27.e. 3671 EpochNanoseconds targetEpochNs; 3672 if (!AddZonedDateTime(cx, zonedRelativeTo, internalDuration, 3673 &targetEpochNs)) { 3674 return false; 3675 } 3676 3677 // Step 27.f. 3678 if (!DifferenceZonedDateTimeWithRounding(cx, zonedRelativeTo, targetEpochNs, 3679 { 3680 smallestUnit, 3681 largestUnit, 3682 roundingMode, 3683 roundingIncrement, 3684 }, 3685 &internalDuration)) { 3686 return false; 3687 } 3688 3689 // Step 27.g. 3690 largestUnit = std::max(largestUnit, TemporalUnit::Hour); 3691 3692 // Step 27.h 3693 Duration result; 3694 if (!TemporalDurationFromInternal(cx, internalDuration, largestUnit, 3695 &result)) { 3696 return false; 3697 } 3698 3699 auto* obj = CreateTemporalDuration(cx, result); 3700 if (!obj) { 3701 return false; 3702 } 3703 3704 args.rval().setObject(*obj); 3705 return true; 3706 } 3707 3708 // Step 28. 3709 if (plainRelativeTo) { 3710 // Step 28.a. 3711 auto internalDuration = ToInternalDurationRecordWith24HourDays(duration); 3712 3713 // Step 28.b. 3714 auto targetTime = AddTime(Time{}, internalDuration.time); 3715 3716 // Step 28.c. 3717 auto calendar = plainRelativeTo.calendar(); 3718 3719 // Step 28.d. 3720 auto dateDuration = DateDuration{ 3721 internalDuration.date.years, 3722 internalDuration.date.months, 3723 internalDuration.date.weeks, 3724 targetTime.days, 3725 }; 3726 MOZ_ASSERT(IsValidDuration(dateDuration)); 3727 3728 // Step 28.e. 3729 ISODate targetDate; 3730 if (!CalendarDateAdd(cx, calendar, plainRelativeTo, dateDuration, 3731 TemporalOverflow::Constrain, &targetDate)) { 3732 return false; 3733 } 3734 3735 // Step 28.f. 3736 auto isoDateTime = ISODateTime{plainRelativeTo, {}}; 3737 3738 // Step 28.g. 3739 auto targetDateTime = ISODateTime{targetDate, targetTime.time}; 3740 3741 // Step 28.h. 3742 if (!DifferencePlainDateTimeWithRounding(cx, isoDateTime, targetDateTime, 3743 calendar, 3744 { 3745 smallestUnit, 3746 largestUnit, 3747 roundingMode, 3748 roundingIncrement, 3749 }, 3750 &internalDuration)) { 3751 return false; 3752 } 3753 3754 // Step 28.i 3755 Duration result; 3756 if (!TemporalDurationFromInternal(cx, internalDuration, largestUnit, 3757 &result)) { 3758 return false; 3759 } 3760 3761 auto* obj = CreateTemporalDuration(cx, result); 3762 if (!obj) { 3763 return false; 3764 } 3765 3766 args.rval().setObject(*obj); 3767 return true; 3768 } 3769 3770 // Step 29. 3771 if (existingLargestUnit < TemporalUnit::Day || 3772 largestUnit < TemporalUnit::Day) { 3773 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3774 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, 3775 "relativeTo"); 3776 return false; 3777 } 3778 3779 // Step 30. 3780 MOZ_ASSERT(smallestUnit >= TemporalUnit::Day); 3781 3782 // Step 31. 3783 auto internalDuration = ToInternalDurationRecordWith24HourDays(duration); 3784 MOZ_ASSERT(internalDuration.date == DateDuration{}); 3785 3786 // Steps 32-33. 3787 if (smallestUnit == TemporalUnit::Day) { 3788 // Steps 32.a-b. 3789 constexpr auto nsPerDay = ToNanoseconds(TemporalUnit::Day); 3790 auto rounded = 3791 RoundNumberToIncrement(internalDuration.time.toNanoseconds(), nsPerDay, 3792 roundingIncrement, roundingMode); 3793 MOZ_ASSERT(Int128{INT64_MIN} <= rounded && rounded <= Int128{INT64_MAX}, 3794 "rounded days fits in int64"); 3795 auto days = static_cast<int64_t>(rounded); 3796 3797 // Step 32.c. (Inlined CreateDateDurationRecord) 3798 if (std::abs(days) > TimeDuration::max().toDays()) { 3799 JS_ReportErrorNumberASCII( 3800 cx, GetErrorMessage, nullptr, 3801 JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); 3802 return false; 3803 } 3804 auto dateDuration = DateDuration{0, 0, 0, days}; 3805 MOZ_ASSERT(IsValidDuration(dateDuration)); 3806 3807 // Step 32.d. 3808 internalDuration = {dateDuration, {}}; 3809 } else { 3810 // Step 33.a. 3811 TimeDuration timeDuration; 3812 if (!RoundTimeDuration(cx, internalDuration.time, roundingIncrement, 3813 smallestUnit, roundingMode, &timeDuration)) { 3814 return false; 3815 } 3816 3817 // Step 33.b. 3818 internalDuration = {{}, timeDuration}; 3819 } 3820 3821 // Step 34. 3822 Duration result; 3823 if (!TemporalDurationFromInternal(cx, internalDuration, largestUnit, 3824 &result)) { 3825 return false; 3826 } 3827 3828 auto* obj = CreateTemporalDuration(cx, result); 3829 if (!obj) { 3830 return false; 3831 } 3832 3833 args.rval().setObject(*obj); 3834 return true; 3835 } 3836 3837 /** 3838 * Temporal.Duration.prototype.round ( options ) 3839 */ 3840 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) { 3841 // Steps 1-2. 3842 CallArgs args = CallArgsFromVp(argc, vp); 3843 return CallNonGenericMethod<IsDuration, Duration_round>(cx, args); 3844 } 3845 3846 /** 3847 * Temporal.Duration.prototype.total ( totalOf ) 3848 */ 3849 static bool Duration_total(JSContext* cx, const CallArgs& args) { 3850 auto* durationObj = &args.thisv().toObject().as<DurationObject>(); 3851 auto duration = ToDuration(durationObj); 3852 3853 // Steps 3-10. 3854 Rooted<PlainDate> plainRelativeTo(cx); 3855 Rooted<ZonedDateTime> zonedRelativeTo(cx); 3856 auto unit = TemporalUnit::Unset; 3857 if (args.get(0).isString()) { 3858 // Step 4. (Not applicable in our implementation.) 3859 3860 // Steps 6-9. (Implicit) 3861 MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo); 3862 3863 // Step 10. 3864 Rooted<JSString*> paramString(cx, args[0].toString()); 3865 if (!GetTemporalUnitValuedOption(cx, paramString, TemporalUnitKey::Unit, 3866 &unit)) { 3867 return false; 3868 } 3869 } else { 3870 // Steps 3 and 5. 3871 Rooted<JSObject*> totalOf( 3872 cx, RequireObjectArg(cx, "totalOf", "total", args.get(0))); 3873 if (!totalOf) { 3874 return false; 3875 } 3876 3877 // Steps 6-9. 3878 if (!GetTemporalRelativeToOption(cx, totalOf, &plainRelativeTo, 3879 &zonedRelativeTo)) { 3880 return false; 3881 } 3882 MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo); 3883 3884 // Step 10. 3885 if (!GetTemporalUnitValuedOption(cx, totalOf, TemporalUnitKey::Unit, 3886 &unit)) { 3887 return false; 3888 } 3889 3890 if (unit == TemporalUnit::Unset) { 3891 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3892 JSMSG_TEMPORAL_MISSING_OPTION, "unit"); 3893 return false; 3894 } 3895 } 3896 3897 // Step 11. 3898 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::Unit, unit, 3899 TemporalUnitGroup::DateTime)) { 3900 return false; 3901 } 3902 3903 // Steps 12-14. 3904 double total; 3905 if (zonedRelativeTo) { 3906 // Step 12.a. 3907 auto internalDuration = ToInternalDurationRecord(duration); 3908 3909 // Steps 12.b-d. (Not applicable in our implementation.) 3910 3911 // Step 12.e. 3912 EpochNanoseconds targetEpochNs; 3913 if (!AddZonedDateTime(cx, zonedRelativeTo, internalDuration, 3914 &targetEpochNs)) { 3915 return false; 3916 } 3917 3918 // Step 12.f. 3919 if (!DifferenceZonedDateTimeWithTotal(cx, zonedRelativeTo, targetEpochNs, 3920 unit, &total)) { 3921 return false; 3922 } 3923 } else if (plainRelativeTo) { 3924 // Step 13.a. 3925 auto internalDuration = ToInternalDurationRecordWith24HourDays(duration); 3926 3927 // Step 13.b. 3928 auto targetTime = AddTime(Time{}, internalDuration.time); 3929 3930 // Step 13.c. 3931 auto calendar = plainRelativeTo.calendar(); 3932 3933 // Step 13.d. 3934 auto dateDuration = DateDuration{ 3935 internalDuration.date.years, 3936 internalDuration.date.months, 3937 internalDuration.date.weeks, 3938 targetTime.days, 3939 }; 3940 MOZ_ASSERT(IsValidDuration(dateDuration)); 3941 3942 // Step 13.e. 3943 ISODate targetDate; 3944 if (!CalendarDateAdd(cx, calendar, plainRelativeTo, dateDuration, 3945 TemporalOverflow::Constrain, &targetDate)) { 3946 return false; 3947 } 3948 3949 // Step 13.f. 3950 auto isoDateTime = ISODateTime{plainRelativeTo, {}}; 3951 3952 // Step 13.g. 3953 auto targetDateTime = ISODateTime{targetDate, targetTime.time}; 3954 3955 // Step 13.h. 3956 if (!DifferencePlainDateTimeWithTotal(cx, isoDateTime, targetDateTime, 3957 calendar, unit, &total)) { 3958 return false; 3959 } 3960 } else { 3961 // Steps 14.a-b. 3962 if (duration.years != 0 || duration.months != 0 || duration.weeks != 0 || 3963 unit < TemporalUnit::Day) { 3964 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 3965 JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, 3966 "relativeTo"); 3967 return false; 3968 } 3969 3970 // Step 14.c. 3971 auto internalDuration = ToInternalDurationRecordWith24HourDays(duration); 3972 3973 // Step 14.d. 3974 total = TotalTimeDuration(internalDuration.time, unit); 3975 } 3976 3977 // Step 15. 3978 args.rval().setNumber(total); 3979 return true; 3980 } 3981 3982 /** 3983 * Temporal.Duration.prototype.total ( totalOf ) 3984 */ 3985 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) { 3986 // Steps 1-2. 3987 CallArgs args = CallArgsFromVp(argc, vp); 3988 return CallNonGenericMethod<IsDuration, Duration_total>(cx, args); 3989 } 3990 3991 /** 3992 * Temporal.Duration.prototype.toString ( [ options ] ) 3993 */ 3994 static bool Duration_toString(JSContext* cx, const CallArgs& args) { 3995 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 3996 3997 SecondsStringPrecision precision = {Precision::Auto(), 3998 TemporalUnit::Nanosecond, Increment{1}}; 3999 auto roundingMode = TemporalRoundingMode::Trunc; 4000 if (args.hasDefined(0)) { 4001 // Step 3. 4002 Rooted<JSObject*> options( 4003 cx, RequireObjectArg(cx, "options", "toString", args[0])); 4004 if (!options) { 4005 return false; 4006 } 4007 4008 // Steps 4-5. 4009 auto digits = Precision::Auto(); 4010 if (!GetTemporalFractionalSecondDigitsOption(cx, options, &digits)) { 4011 return false; 4012 } 4013 4014 // Step 6. 4015 if (!GetRoundingModeOption(cx, options, &roundingMode)) { 4016 return false; 4017 } 4018 4019 // Step 7. 4020 auto smallestUnit = TemporalUnit::Unset; 4021 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit, 4022 &smallestUnit)) { 4023 return false; 4024 } 4025 4026 // Step 8. 4027 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, 4028 smallestUnit, TemporalUnitGroup::Time)) { 4029 return false; 4030 } 4031 4032 // Step 9. 4033 if (smallestUnit == TemporalUnit::Hour || 4034 smallestUnit == TemporalUnit::Minute) { 4035 const char* smallestUnitStr = 4036 smallestUnit == TemporalUnit::Hour ? "hour" : "minute"; 4037 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 4038 JSMSG_TEMPORAL_INVALID_UNIT_OPTION, 4039 smallestUnitStr, "smallestUnit"); 4040 return false; 4041 } 4042 4043 // Step 10. 4044 precision = ToSecondsStringPrecision(smallestUnit, digits); 4045 } 4046 MOZ_ASSERT(precision.unit >= TemporalUnit::Minute); 4047 4048 // Steps 11-17. 4049 auto roundedDuration = duration; 4050 if (precision.unit != TemporalUnit::Nanosecond || 4051 precision.increment != Increment{1}) { 4052 // Step 12. 4053 auto largestUnit = DefaultTemporalLargestUnit(duration); 4054 4055 // Step 13. 4056 auto internalDuration = ToInternalDurationRecord(duration); 4057 4058 // Step 14. 4059 TimeDuration timeDuration; 4060 if (!RoundTimeDuration(cx, internalDuration.time, precision.increment, 4061 precision.unit, roundingMode, &timeDuration)) { 4062 return false; 4063 } 4064 4065 // Step 15. 4066 internalDuration = {internalDuration.date, timeDuration}; 4067 4068 // Step 16. 4069 auto roundedLargestUnit = std::min(largestUnit, TemporalUnit::Second); 4070 4071 // Step 17. 4072 if (!TemporalDurationFromInternal(cx, internalDuration, roundedLargestUnit, 4073 &roundedDuration)) { 4074 return false; 4075 } 4076 MOZ_ASSERT(IsValidDuration(roundedDuration)); 4077 } 4078 4079 // Steps 11.a. and 18. 4080 JSString* str = 4081 TemporalDurationToString(cx, roundedDuration, precision.precision); 4082 if (!str) { 4083 return false; 4084 } 4085 4086 args.rval().setString(str); 4087 return true; 4088 } 4089 4090 /** 4091 * Temporal.Duration.prototype.toString ( [ options ] ) 4092 */ 4093 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) { 4094 // Steps 1-2. 4095 CallArgs args = CallArgsFromVp(argc, vp); 4096 return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args); 4097 } 4098 4099 /** 4100 * Temporal.Duration.prototype.toJSON ( ) 4101 */ 4102 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) { 4103 auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>()); 4104 4105 // Step 3. 4106 JSString* str = TemporalDurationToString(cx, duration, Precision::Auto()); 4107 if (!str) { 4108 return false; 4109 } 4110 4111 args.rval().setString(str); 4112 return true; 4113 } 4114 4115 /** 4116 * Temporal.Duration.prototype.toJSON ( ) 4117 */ 4118 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) { 4119 // Steps 1-2. 4120 CallArgs args = CallArgsFromVp(argc, vp); 4121 return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args); 4122 } 4123 4124 /** 4125 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] ) 4126 */ 4127 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) { 4128 // Steps 3-7. 4129 return TemporalDurationToLocaleString(cx, args); 4130 } 4131 4132 /** 4133 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] ) 4134 */ 4135 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { 4136 // Steps 1-2. 4137 CallArgs args = CallArgsFromVp(argc, vp); 4138 return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args); 4139 } 4140 4141 /** 4142 * Temporal.Duration.prototype.valueOf ( ) 4143 */ 4144 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) { 4145 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, 4146 "Duration", "primitive type"); 4147 return false; 4148 } 4149 4150 const JSClass DurationObject::class_ = { 4151 "Temporal.Duration", 4152 JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) | 4153 JSCLASS_HAS_CACHED_PROTO(JSProto_Duration), 4154 JS_NULL_CLASS_OPS, 4155 &DurationObject::classSpec_, 4156 }; 4157 4158 const JSClass& DurationObject::protoClass_ = PlainObject::class_; 4159 4160 static const JSFunctionSpec Duration_methods[] = { 4161 JS_FN("from", Duration_from, 1, 0), 4162 JS_FN("compare", Duration_compare, 2, 0), 4163 JS_FS_END, 4164 }; 4165 4166 static const JSFunctionSpec Duration_prototype_methods[] = { 4167 JS_FN("with", Duration_with, 1, 0), 4168 JS_FN("negated", Duration_negated, 0, 0), 4169 JS_FN("abs", Duration_abs, 0, 0), 4170 JS_FN("add", Duration_add, 1, 0), 4171 JS_FN("subtract", Duration_subtract, 1, 0), 4172 JS_FN("round", Duration_round, 1, 0), 4173 JS_FN("total", Duration_total, 1, 0), 4174 JS_FN("toString", Duration_toString, 0, 0), 4175 JS_FN("toJSON", Duration_toJSON, 0, 0), 4176 JS_FN("toLocaleString", Duration_toLocaleString, 0, 0), 4177 JS_FN("valueOf", Duration_valueOf, 0, 0), 4178 JS_FS_END, 4179 }; 4180 4181 static const JSPropertySpec Duration_prototype_properties[] = { 4182 JS_PSG("years", Duration_years, 0), 4183 JS_PSG("months", Duration_months, 0), 4184 JS_PSG("weeks", Duration_weeks, 0), 4185 JS_PSG("days", Duration_days, 0), 4186 JS_PSG("hours", Duration_hours, 0), 4187 JS_PSG("minutes", Duration_minutes, 0), 4188 JS_PSG("seconds", Duration_seconds, 0), 4189 JS_PSG("milliseconds", Duration_milliseconds, 0), 4190 JS_PSG("microseconds", Duration_microseconds, 0), 4191 JS_PSG("nanoseconds", Duration_nanoseconds, 0), 4192 JS_PSG("sign", Duration_sign, 0), 4193 JS_PSG("blank", Duration_blank, 0), 4194 JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY), 4195 JS_PS_END, 4196 }; 4197 4198 const ClassSpec DurationObject::classSpec_ = { 4199 GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>, 4200 GenericCreatePrototype<DurationObject>, 4201 Duration_methods, 4202 nullptr, 4203 Duration_prototype_methods, 4204 Duration_prototype_properties, 4205 nullptr, 4206 ClassSpec::DontDefineConstructor, 4207 };