Bug 1874684 - Part 10: Replace BigInt with Int128 in RoundNumberToIncrement. r=mgaudet
[gecko.git] / js / src / builtin / temporal / PlainTime.cpp
blob89bae5928feaf8626f6c14ad7b616c770665545b
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/. */
7 #include "builtin/temporal/PlainTime.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/FloatingPoint.h"
11 #include "mozilla/Maybe.h"
13 #include <algorithm>
14 #include <cmath>
15 #include <cstdlib>
16 #include <type_traits>
17 #include <utility>
19 #include "jsnum.h"
20 #include "jspubtd.h"
21 #include "NamespaceImports.h"
23 #include "builtin/temporal/Duration.h"
24 #include "builtin/temporal/Instant.h"
25 #include "builtin/temporal/PlainDate.h"
26 #include "builtin/temporal/PlainDateTime.h"
27 #include "builtin/temporal/Temporal.h"
28 #include "builtin/temporal/TemporalParser.h"
29 #include "builtin/temporal/TemporalRoundingMode.h"
30 #include "builtin/temporal/TemporalTypes.h"
31 #include "builtin/temporal/TemporalUnit.h"
32 #include "builtin/temporal/TimeZone.h"
33 #include "builtin/temporal/ToString.h"
34 #include "builtin/temporal/ZonedDateTime.h"
35 #include "ds/IdValuePair.h"
36 #include "gc/AllocKind.h"
37 #include "gc/Barrier.h"
38 #include "js/AllocPolicy.h"
39 #include "js/CallArgs.h"
40 #include "js/CallNonGenericMethod.h"
41 #include "js/Class.h"
42 #include "js/ErrorReport.h"
43 #include "js/friend/ErrorMessages.h"
44 #include "js/PropertyDescriptor.h"
45 #include "js/PropertySpec.h"
46 #include "js/RootingAPI.h"
47 #include "js/Value.h"
48 #include "vm/BytecodeUtil.h"
49 #include "vm/GlobalObject.h"
50 #include "vm/JSAtomState.h"
51 #include "vm/JSContext.h"
52 #include "vm/JSObject.h"
53 #include "vm/PlainObject.h"
54 #include "vm/StringType.h"
56 #include "vm/JSObject-inl.h"
57 #include "vm/NativeObject-inl.h"
58 #include "vm/ObjectOperations-inl.h"
60 using namespace js;
61 using namespace js::temporal;
63 static inline bool IsPlainTime(Handle<Value> v) {
64 return v.isObject() && v.toObject().is<PlainTimeObject>();
67 #ifdef DEBUG
68 /**
69 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
71 template <typename T>
72 static bool IsValidTime(T hour, T minute, T second, T millisecond,
73 T microsecond, T nanosecond) {
74 static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
76 // Step 1.
77 MOZ_ASSERT(IsInteger(hour));
78 MOZ_ASSERT(IsInteger(minute));
79 MOZ_ASSERT(IsInteger(second));
80 MOZ_ASSERT(IsInteger(millisecond));
81 MOZ_ASSERT(IsInteger(microsecond));
82 MOZ_ASSERT(IsInteger(nanosecond));
84 // Step 2.
85 if (hour < 0 || hour > 23) {
86 return false;
89 // Step 3.
90 if (minute < 0 || minute > 59) {
91 return false;
94 // Step 4.
95 if (second < 0 || second > 59) {
96 return false;
99 // Step 5.
100 if (millisecond < 0 || millisecond > 999) {
101 return false;
104 // Step 6.
105 if (microsecond < 0 || microsecond > 999) {
106 return false;
109 // Step 7.
110 if (nanosecond < 0 || nanosecond > 999) {
111 return false;
114 // Step 8.
115 return true;
119 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
121 bool js::temporal::IsValidTime(const PlainTime& time) {
122 auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
123 return ::IsValidTime(hour, minute, second, millisecond, microsecond,
124 nanosecond);
128 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
130 bool js::temporal::IsValidTime(double hour, double minute, double second,
131 double millisecond, double microsecond,
132 double nanosecond) {
133 return ::IsValidTime(hour, minute, second, millisecond, microsecond,
134 nanosecond);
136 #endif
138 static void ReportInvalidTimeValue(JSContext* cx, const char* name, int32_t min,
139 int32_t max, double num) {
140 Int32ToCStringBuf minCbuf;
141 const char* minStr = Int32ToCString(&minCbuf, min);
143 Int32ToCStringBuf maxCbuf;
144 const char* maxStr = Int32ToCString(&maxCbuf, max);
146 ToCStringBuf numCbuf;
147 const char* numStr = NumberToCString(&numCbuf, num);
149 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
150 JSMSG_TEMPORAL_PLAIN_TIME_INVALID_VALUE, name,
151 minStr, maxStr, numStr);
154 template <typename T>
155 static inline bool ThrowIfInvalidTimeValue(JSContext* cx, const char* name,
156 int32_t min, int32_t max, T num) {
157 if (min <= num && num <= max) {
158 return true;
160 ReportInvalidTimeValue(cx, name, min, max, num);
161 return false;
165 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
167 template <typename T>
168 static bool ThrowIfInvalidTime(JSContext* cx, T hour, T minute, T second,
169 T millisecond, T microsecond, T nanosecond) {
170 static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
172 // Step 1.
173 MOZ_ASSERT(IsInteger(hour));
174 MOZ_ASSERT(IsInteger(minute));
175 MOZ_ASSERT(IsInteger(second));
176 MOZ_ASSERT(IsInteger(millisecond));
177 MOZ_ASSERT(IsInteger(microsecond));
178 MOZ_ASSERT(IsInteger(nanosecond));
180 // Step 2.
181 if (!ThrowIfInvalidTimeValue(cx, "hour", 0, 23, hour)) {
182 return false;
185 // Step 3.
186 if (!ThrowIfInvalidTimeValue(cx, "minute", 0, 59, minute)) {
187 return false;
190 // Step 4.
191 if (!ThrowIfInvalidTimeValue(cx, "second", 0, 59, second)) {
192 return false;
195 // Step 5.
196 if (!ThrowIfInvalidTimeValue(cx, "millisecond", 0, 999, millisecond)) {
197 return false;
200 // Step 6.
201 if (!ThrowIfInvalidTimeValue(cx, "microsecond", 0, 999, microsecond)) {
202 return false;
205 // Step 7.
206 if (!ThrowIfInvalidTimeValue(cx, "nanosecond", 0, 999, nanosecond)) {
207 return false;
210 // Step 8.
211 return true;
215 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
217 bool js::temporal::ThrowIfInvalidTime(JSContext* cx, const PlainTime& time) {
218 auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
219 return ::ThrowIfInvalidTime(cx, hour, minute, second, millisecond,
220 microsecond, nanosecond);
224 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
226 bool js::temporal::ThrowIfInvalidTime(JSContext* cx, double hour, double minute,
227 double second, double millisecond,
228 double microsecond, double nanosecond) {
229 return ::ThrowIfInvalidTime(cx, hour, minute, second, millisecond,
230 microsecond, nanosecond);
234 * ConstrainTime ( hour, minute, second, millisecond, microsecond, nanosecond )
236 static PlainTime ConstrainTime(double hour, double minute, double second,
237 double millisecond, double microsecond,
238 double nanosecond) {
239 // Step 1.
240 MOZ_ASSERT(IsInteger(hour));
241 MOZ_ASSERT(IsInteger(minute));
242 MOZ_ASSERT(IsInteger(second));
243 MOZ_ASSERT(IsInteger(millisecond));
244 MOZ_ASSERT(IsInteger(microsecond));
245 MOZ_ASSERT(IsInteger(nanosecond));
247 // Steps 2-8.
248 return {
249 int32_t(std::clamp(hour, 0.0, 23.0)),
250 int32_t(std::clamp(minute, 0.0, 59.0)),
251 int32_t(std::clamp(second, 0.0, 59.0)),
252 int32_t(std::clamp(millisecond, 0.0, 999.0)),
253 int32_t(std::clamp(microsecond, 0.0, 999.0)),
254 int32_t(std::clamp(nanosecond, 0.0, 999.0)),
259 * RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond,
260 * overflow )
262 bool js::temporal::RegulateTime(JSContext* cx, const TemporalTimeLike& time,
263 TemporalOverflow overflow, PlainTime* result) {
264 auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
266 // Step 1.
267 MOZ_ASSERT(IsInteger(hour));
268 MOZ_ASSERT(IsInteger(minute));
269 MOZ_ASSERT(IsInteger(second));
270 MOZ_ASSERT(IsInteger(millisecond));
271 MOZ_ASSERT(IsInteger(microsecond));
272 MOZ_ASSERT(IsInteger(nanosecond));
274 // Step 2. (Not applicable in our implementation.)
276 // Step 3.
277 if (overflow == TemporalOverflow::Constrain) {
278 *result = ConstrainTime(hour, minute, second, millisecond, microsecond,
279 nanosecond);
280 return true;
283 // Step 4.a.
284 MOZ_ASSERT(overflow == TemporalOverflow::Reject);
286 // Step 4.b.
287 if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond,
288 nanosecond)) {
289 return false;
292 // Step 4.c.
293 *result = {
294 int32_t(hour), int32_t(minute), int32_t(second),
295 int32_t(millisecond), int32_t(microsecond), int32_t(nanosecond),
297 return true;
301 * CreateTemporalTime ( hour, minute, second, millisecond, microsecond,
302 * nanosecond [ , newTarget ] )
304 static PlainTimeObject* CreateTemporalTime(JSContext* cx, const CallArgs& args,
305 double hour, double minute,
306 double second, double millisecond,
307 double microsecond,
308 double nanosecond) {
309 MOZ_ASSERT(IsInteger(hour));
310 MOZ_ASSERT(IsInteger(minute));
311 MOZ_ASSERT(IsInteger(second));
312 MOZ_ASSERT(IsInteger(millisecond));
313 MOZ_ASSERT(IsInteger(microsecond));
314 MOZ_ASSERT(IsInteger(nanosecond));
316 // Step 1.
317 if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond,
318 nanosecond)) {
319 return nullptr;
322 // Steps 2-3.
323 Rooted<JSObject*> proto(cx);
324 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainTime,
325 &proto)) {
326 return nullptr;
329 auto* object = NewObjectWithClassProto<PlainTimeObject>(cx, proto);
330 if (!object) {
331 return nullptr;
334 // Step 4.
335 object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
337 // Step 5.
338 object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT, Int32Value(minute));
340 // Step 6.
341 object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT, Int32Value(second));
343 // Step 7.
344 object->setFixedSlot(PlainTimeObject::ISO_MILLISECOND_SLOT,
345 Int32Value(millisecond));
347 // Step 8.
348 object->setFixedSlot(PlainTimeObject::ISO_MICROSECOND_SLOT,
349 Int32Value(microsecond));
351 // Step 9.
352 object->setFixedSlot(PlainTimeObject::ISO_NANOSECOND_SLOT,
353 Int32Value(nanosecond));
355 // Step 10.
356 return object;
360 * CreateTemporalTime ( hour, minute, second, millisecond, microsecond,
361 * nanosecond [ , newTarget ] )
363 PlainTimeObject* js::temporal::CreateTemporalTime(JSContext* cx,
364 const PlainTime& time) {
365 auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
367 // Step 1.
368 if (!ThrowIfInvalidTime(cx, time)) {
369 return nullptr;
372 // Steps 2-3.
373 auto* object = NewBuiltinClassInstance<PlainTimeObject>(cx);
374 if (!object) {
375 return nullptr;
378 // Step 4.
379 object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
381 // Step 5.
382 object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT, Int32Value(minute));
384 // Step 6.
385 object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT, Int32Value(second));
387 // Step 7.
388 object->setFixedSlot(PlainTimeObject::ISO_MILLISECOND_SLOT,
389 Int32Value(millisecond));
391 // Step 8.
392 object->setFixedSlot(PlainTimeObject::ISO_MICROSECOND_SLOT,
393 Int32Value(microsecond));
395 // Step 9.
396 object->setFixedSlot(PlainTimeObject::ISO_NANOSECOND_SLOT,
397 Int32Value(nanosecond));
399 // Step 10.
400 return object;
404 * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
406 template <typename IntT>
407 static BalancedTime BalanceTime(IntT hour, IntT minute, IntT second,
408 IntT millisecond, IntT microsecond,
409 IntT nanosecond) {
410 // Combined floor'ed division and modulo operation.
411 auto divmod = [](IntT dividend, int32_t divisor, int32_t* remainder) {
412 MOZ_ASSERT(divisor > 0);
414 IntT quotient = dividend / divisor;
415 *remainder = dividend % divisor;
417 // The remainder is negative, add the divisor and simulate a floor instead
418 // of trunc division.
419 if (*remainder < 0) {
420 *remainder += divisor;
421 quotient -= 1;
424 return quotient;
427 PlainTime time = {};
429 // Steps 1-2.
430 microsecond += divmod(nanosecond, 1000, &time.nanosecond);
432 // Steps 3-4.
433 millisecond += divmod(microsecond, 1000, &time.microsecond);
435 // Steps 5-6.
436 second += divmod(millisecond, 1000, &time.millisecond);
438 // Steps 7-8.
439 minute += divmod(second, 60, &time.second);
441 // Steps 9-10.
442 hour += divmod(minute, 60, &time.minute);
444 // Steps 11-12.
445 int32_t days = divmod(hour, 24, &time.hour);
447 // Step 13.
448 MOZ_ASSERT(IsValidTime(time));
449 return {days, time};
453 * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
455 static BalancedTime BalanceTime(int32_t hour, int32_t minute, int32_t second,
456 int32_t millisecond, int32_t microsecond,
457 int32_t nanosecond) {
458 MOZ_ASSERT(-24 < hour && hour < 2 * 24);
459 MOZ_ASSERT(-60 < minute && minute < 2 * 60);
460 MOZ_ASSERT(-60 < second && second < 2 * 60);
461 MOZ_ASSERT(-1000 < millisecond && millisecond < 2 * 1000);
462 MOZ_ASSERT(-1000 < microsecond && microsecond < 2 * 1000);
463 MOZ_ASSERT(-1000 < nanosecond && nanosecond < 2 * 1000);
465 return BalanceTime<int32_t>(hour, minute, second, millisecond, microsecond,
466 nanosecond);
470 * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
472 BalancedTime js::temporal::BalanceTime(const PlainTime& time,
473 int64_t nanoseconds) {
474 MOZ_ASSERT(IsValidTime(time));
475 MOZ_ASSERT(std::abs(nanoseconds) <= 2 * ToNanoseconds(TemporalUnit::Day));
477 return ::BalanceTime<int64_t>(time.hour, time.minute, time.second,
478 time.millisecond, time.microsecond,
479 time.nanosecond + nanoseconds);
483 * DifferenceTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 )
485 NormalizedTimeDuration js::temporal::DifferenceTime(const PlainTime& time1,
486 const PlainTime& time2) {
487 MOZ_ASSERT(IsValidTime(time1));
488 MOZ_ASSERT(IsValidTime(time2));
490 // Step 1.
491 int32_t hours = time2.hour - time1.hour;
493 // Step 2.
494 int32_t minutes = time2.minute - time1.minute;
496 // Step 3.
497 int32_t seconds = time2.second - time1.second;
499 // Step 4.
500 int32_t milliseconds = time2.millisecond - time1.millisecond;
502 // Step 5.
503 int32_t microseconds = time2.microsecond - time1.microsecond;
505 // Step 6.
506 int32_t nanoseconds = time2.nanosecond - time1.nanosecond;
508 // Step 7.
509 auto result = NormalizeTimeDuration(hours, minutes, seconds, milliseconds,
510 microseconds, nanoseconds);
512 // Step 8.
513 MOZ_ASSERT(result.abs().toTotalNanoseconds() <
514 Int128{ToNanoseconds(TemporalUnit::Day)});
516 // Step 9.
517 return result;
521 * ToTemporalTime ( item [ , overflow ] )
523 static bool ToTemporalTime(JSContext* cx, Handle<Value> item,
524 TemporalOverflow overflow, PlainTime* result) {
525 // Steps 1-2. (Not applicable in our implementation.)
527 // Steps 3-4.
528 if (item.isObject()) {
529 // Step 3.
530 Rooted<JSObject*> itemObj(cx, &item.toObject());
532 // Step 3.a.
533 if (auto* time = itemObj->maybeUnwrapIf<PlainTimeObject>()) {
534 *result = ToPlainTime(time);
535 return true;
538 // Step 3.b.
539 if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
540 auto epochInstant = ToInstant(zonedDateTime);
541 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
543 if (!timeZone.wrap(cx)) {
544 return false;
547 // Steps 3.b.i-iii.
548 PlainDateTime dateTime;
549 if (!GetPlainDateTimeFor(cx, timeZone, epochInstant, &dateTime)) {
550 return false;
553 // Step 3.b.iv.
554 *result = dateTime.time;
555 return true;
558 // Step 3.c.
559 if (auto* dateTime = itemObj->maybeUnwrapIf<PlainDateTimeObject>()) {
560 *result = ToPlainTime(dateTime);
561 return true;
564 // Step 3.d.
565 TemporalTimeLike timeResult;
566 if (!ToTemporalTimeRecord(cx, itemObj, &timeResult)) {
567 return false;
570 // Step 3.e.
571 if (!RegulateTime(cx, timeResult, overflow, result)) {
572 return false;
574 } else {
575 // Step 4.
577 // Step 4.a.
578 if (!item.isString()) {
579 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
580 nullptr, "not a string");
581 return false;
583 Rooted<JSString*> string(cx, item.toString());
585 // Step 4.b.
586 if (!ParseTemporalTimeString(cx, string, result)) {
587 return false;
590 // Step 4.c.
591 MOZ_ASSERT(IsValidTime(*result));
594 // Step 5.
595 return true;
599 * ToTemporalTime ( item [ , overflow ] )
601 static PlainTimeObject* ToTemporalTime(JSContext* cx, Handle<Value> item,
602 TemporalOverflow overflow) {
603 PlainTime time;
604 if (!ToTemporalTime(cx, item, overflow, &time)) {
605 return nullptr;
607 MOZ_ASSERT(IsValidTime(time));
609 return CreateTemporalTime(cx, time);
613 * ToTemporalTime ( item [ , overflow ] )
615 bool js::temporal::ToTemporalTime(JSContext* cx, Handle<Value> item,
616 PlainTime* result) {
617 return ToTemporalTime(cx, item, TemporalOverflow::Constrain, result);
621 * CompareTemporalTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2,
622 * ns2 )
624 int32_t js::temporal::CompareTemporalTime(const PlainTime& one,
625 const PlainTime& two) {
626 // Steps 1-2.
627 if (int32_t diff = one.hour - two.hour) {
628 return diff < 0 ? -1 : 1;
631 // Steps 3-4.
632 if (int32_t diff = one.minute - two.minute) {
633 return diff < 0 ? -1 : 1;
636 // Steps 5-6.
637 if (int32_t diff = one.second - two.second) {
638 return diff < 0 ? -1 : 1;
641 // Steps 7-8.
642 if (int32_t diff = one.millisecond - two.millisecond) {
643 return diff < 0 ? -1 : 1;
646 // Steps 9-10.
647 if (int32_t diff = one.microsecond - two.microsecond) {
648 return diff < 0 ? -1 : 1;
651 // Steps 11-12.
652 if (int32_t diff = one.nanosecond - two.nanosecond) {
653 return diff < 0 ? -1 : 1;
656 // Step 13.
657 return 0;
661 * ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
663 static bool ToTemporalTimeRecord(JSContext* cx,
664 Handle<JSObject*> temporalTimeLike,
665 TemporalTimeLike* result) {
666 // Steps 1 and 3-4. (Not applicable in our implementation.)
668 // Step 2. (Inlined call to PrepareTemporalFields.)
669 // PrepareTemporalFields, step 1. (Not applicable in our implementation.)
671 // PrepareTemporalFields, step 2.
672 bool any = false;
674 // PrepareTemporalFields, steps 3-4. (Loop unrolled)
675 Rooted<Value> value(cx);
676 auto getTimeProperty = [&](Handle<PropertyName*> property, const char* name,
677 double* num) {
678 // Step 4.a.
679 if (!GetProperty(cx, temporalTimeLike, temporalTimeLike, property,
680 &value)) {
681 return false;
684 // Step 4.b.
685 if (!value.isUndefined()) {
686 // Step 4.b.i.
687 any = true;
689 // Step 4.b.ii.2.
690 if (!ToIntegerWithTruncation(cx, value, name, num)) {
691 return false;
694 return true;
697 if (!getTimeProperty(cx->names().hour, "hour", &result->hour)) {
698 return false;
700 if (!getTimeProperty(cx->names().microsecond, "microsecond",
701 &result->microsecond)) {
702 return false;
704 if (!getTimeProperty(cx->names().millisecond, "millisecond",
705 &result->millisecond)) {
706 return false;
708 if (!getTimeProperty(cx->names().minute, "minute", &result->minute)) {
709 return false;
711 if (!getTimeProperty(cx->names().nanosecond, "nanosecond",
712 &result->nanosecond)) {
713 return false;
715 if (!getTimeProperty(cx->names().second, "second", &result->second)) {
716 return false;
719 // PrepareTemporalFields, step 5.
720 if (!any) {
721 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
722 JSMSG_TEMPORAL_PLAIN_TIME_MISSING_UNIT);
723 return false;
726 // Steps 5-16. (Performed implicitly in our implementation.)
728 // Step 17.
729 return true;
733 * ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
735 bool js::temporal::ToTemporalTimeRecord(JSContext* cx,
736 Handle<JSObject*> temporalTimeLike,
737 TemporalTimeLike* result) {
738 // Step 3.a. (Set all fields to zero.)
739 *result = {};
741 // Steps 1-2 and 4-17.
742 return ::ToTemporalTimeRecord(cx, temporalTimeLike, result);
745 static int64_t TimeToNanos(const PlainTime& time) {
746 // No overflow possible because the input is a valid time.
747 MOZ_ASSERT(IsValidTime(time));
749 int64_t hour = time.hour;
750 int64_t minute = time.minute;
751 int64_t second = time.second;
752 int64_t millisecond = time.millisecond;
753 int64_t microsecond = time.microsecond;
754 int64_t nanosecond = time.nanosecond;
756 int64_t millis = ((hour * 60 + minute) * 60 + second) * 1000 + millisecond;
757 return (millis * 1000 + microsecond) * 1000 + nanosecond;
761 * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
762 * increment, unit, roundingMode [ , dayLengthNs ] )
764 RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
765 TemporalUnit unit,
766 TemporalRoundingMode roundingMode) {
767 MOZ_ASSERT(IsValidTime(time));
768 MOZ_ASSERT(unit >= TemporalUnit::Day);
769 MOZ_ASSERT_IF(unit > TemporalUnit::Day,
770 increment <= MaximumTemporalDurationRoundingIncrement(unit));
771 MOZ_ASSERT_IF(unit == TemporalUnit::Day, increment == Increment{1});
773 int32_t days = 0;
774 auto [hour, minute, second, millisecond, microsecond, nanosecond] = time;
776 // Take the same approach as used in RoundDuration() to perform exact
777 // mathematical operations without possible loss of precision.
779 // Steps 1-8.
780 PlainTime quantity;
781 int32_t* result;
782 switch (unit) {
783 case TemporalUnit::Day:
784 quantity = time;
785 result = &days;
786 break;
787 case TemporalUnit::Hour:
788 quantity = time;
789 result = &hour;
790 minute = 0;
791 second = 0;
792 millisecond = 0;
793 microsecond = 0;
794 nanosecond = 0;
795 break;
796 case TemporalUnit::Minute:
797 quantity = {0, minute, second, millisecond, microsecond, nanosecond};
798 result = &minute;
799 second = 0;
800 millisecond = 0;
801 microsecond = 0;
802 nanosecond = 0;
803 break;
804 case TemporalUnit::Second:
805 quantity = {0, 0, second, millisecond, microsecond, nanosecond};
806 result = &second;
807 millisecond = 0;
808 microsecond = 0;
809 nanosecond = 0;
810 break;
811 case TemporalUnit::Millisecond:
812 quantity = {0, 0, 0, millisecond, microsecond, nanosecond};
813 result = &millisecond;
814 microsecond = 0;
815 nanosecond = 0;
816 break;
817 case TemporalUnit::Microsecond:
818 quantity = {0, 0, 0, 0, microsecond, nanosecond};
819 result = &microsecond;
820 nanosecond = 0;
821 break;
822 case TemporalUnit::Nanosecond:
823 quantity = {0, 0, 0, 0, 0, nanosecond};
824 result = &nanosecond;
825 break;
827 case TemporalUnit::Auto:
828 case TemporalUnit::Year:
829 case TemporalUnit::Month:
830 case TemporalUnit::Week:
831 MOZ_CRASH("unexpected temporal unit");
834 // Step 9.
835 int64_t nanos = TimeToNanos(quantity);
836 MOZ_ASSERT(0 <= nanos && nanos < ToNanoseconds(TemporalUnit::Day));
838 auto r = RoundNumberToIncrement(nanos, ToNanoseconds(unit), increment,
839 roundingMode);
840 MOZ_ASSERT(r == Int128{int32_t(r)},
841 "can't overflow when inputs are all in range");
843 *result = int32_t(r);
845 // Step 10.
846 if (unit == TemporalUnit::Day) {
847 return {int64_t(days), {0, 0, 0, 0, 0, 0}};
850 // Steps 11-17.
851 auto balanced =
852 ::BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond);
853 return {int64_t(balanced.days), balanced.time};
857 * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
858 * increment, unit, roundingMode [ , dayLengthNs ] )
860 RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
861 TemporalUnit unit,
862 TemporalRoundingMode roundingMode,
863 const InstantSpan& dayLengthNs) {
864 MOZ_ASSERT(IsValidTime(time));
865 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
866 MOZ_ASSERT(dayLengthNs > (InstantSpan{}));
868 if (unit != TemporalUnit::Day) {
869 return RoundTime(time, increment, unit, roundingMode);
872 // Step 1. (Not applicable)
874 // Step 2.
875 int64_t quantity = TimeToNanos(time);
876 MOZ_ASSERT(0 <= quantity && quantity < ToNanoseconds(TemporalUnit::Day));
878 // Steps 3-8. (Not applicable)
880 // Step 9.
882 // When the divisor is too large, the expression `quantity / divisor` is a
883 // value near zero. Substitute |divisor| with an equivalent expression.
884 // Choose |86'400'000'000'000| which will give a similar result because
885 // |quantity| is guaranteed to be lower than |86'400'000'000'000|.
886 int64_t divisor = int64_t(std::min(dayLengthNs.toTotalNanoseconds(),
887 Int128{ToNanoseconds(TemporalUnit::Day)}));
888 MOZ_ASSERT(divisor > 0);
889 MOZ_ASSERT(increment == Increment{1}, "Rounding increment for 'day' is 1");
891 auto result =
892 RoundNumberToIncrement(quantity, divisor, increment, roundingMode);
893 MOZ_ASSERT(result == Int128{int64_t(result)});
895 // Step 10.
896 return {int64_t(result), {0, 0, 0, 0, 0, 0}};
900 * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, norm )
902 AddedTime js::temporal::AddTime(const PlainTime& time,
903 const NormalizedTimeDuration& duration) {
904 MOZ_ASSERT(IsValidTime(time));
905 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
907 auto [seconds, nanoseconds] = duration;
908 if (seconds < 0 && nanoseconds > 0) {
909 seconds += 1;
910 nanoseconds -= 1'000'000'000;
912 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
914 // Step 1.
915 int64_t second = time.second + seconds;
917 // Step 2.
918 int32_t nanosecond = time.nanosecond + nanoseconds;
920 // Step 3.
921 auto balanced =
922 ::BalanceTime<int64_t>(time.hour, time.minute, second, time.millisecond,
923 time.microsecond, nanosecond);
924 return {balanced.days, balanced.time};
928 * DifferenceTemporalPlainTime ( operation, temporalTime, other, options )
930 static bool DifferenceTemporalPlainTime(JSContext* cx,
931 TemporalDifference operation,
932 const CallArgs& args) {
933 auto temporalTime =
934 ToPlainTime(&args.thisv().toObject().as<PlainTimeObject>());
936 // Step 1. (Not applicable in our implementation.)
938 // Step 2.
939 PlainTime other;
940 if (!ToTemporalTime(cx, args.get(0), &other)) {
941 return false;
944 // Steps 3-4.
945 DifferenceSettings settings;
946 if (args.hasDefined(1)) {
947 Rooted<JSObject*> options(
948 cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
949 if (!options) {
950 return false;
953 // Step 3.
954 Rooted<PlainObject*> resolvedOptions(cx,
955 SnapshotOwnProperties(cx, options));
956 if (!resolvedOptions) {
957 return false;
960 // Step 4.
961 if (!GetDifferenceSettings(
962 cx, operation, resolvedOptions, TemporalUnitGroup::Time,
963 TemporalUnit::Nanosecond, TemporalUnit::Hour, &settings)) {
964 return false;
966 } else {
967 // Steps 3-4.
968 settings = {
969 TemporalUnit::Nanosecond,
970 TemporalUnit::Hour,
971 TemporalRoundingMode::Trunc,
972 Increment{1},
976 // Step 5.
977 auto diff = DifferenceTime(temporalTime, other);
979 // Step 6.
980 if (settings.smallestUnit != TemporalUnit::Nanosecond ||
981 settings.roundingIncrement != Increment{1}) {
982 // Steps 6.a-b.
983 diff = RoundDuration(diff, settings.roundingIncrement,
984 settings.smallestUnit, settings.roundingMode);
987 // Step 7.
988 auto balancedDuration = BalanceTimeDuration(diff, settings.largestUnit);
990 // Step 8.
991 auto duration = balancedDuration.toDuration();
992 if (operation == TemporalDifference::Since) {
993 duration = duration.negate();
996 auto* result = CreateTemporalDuration(cx, duration);
997 if (!result) {
998 return false;
1001 args.rval().setObject(*result);
1002 return true;
1005 enum class PlainTimeDuration { Add, Subtract };
1008 * AddDurationToOrSubtractDurationFromPlainTime ( operation, temporalTime,
1009 * temporalDurationLike )
1011 static bool AddDurationToOrSubtractDurationFromPlainTime(
1012 JSContext* cx, PlainTimeDuration operation, const CallArgs& args) {
1013 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1014 auto time = ToPlainTime(temporalTime);
1016 // Step 1. (Not applicable in our implementation.)
1018 // Step 2.
1019 Duration duration;
1020 if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
1021 return false;
1024 // Step 3.
1025 if (operation == PlainTimeDuration::Subtract) {
1026 duration = duration.negate();
1028 auto timeDuration = NormalizeTimeDuration(duration);
1030 // Step 4.
1031 auto result = AddTime(time, timeDuration);
1032 MOZ_ASSERT(IsValidTime(result.time));
1034 // Step 5.
1035 auto* obj = CreateTemporalTime(cx, result.time);
1036 if (!obj) {
1037 return false;
1040 args.rval().setObject(*obj);
1041 return true;
1045 * Temporal.PlainTime ( [ hour [ , minute [ , second [ , millisecond [ ,
1046 * microsecond [ , nanosecond ] ] ] ] ] ] )
1048 static bool PlainTimeConstructor(JSContext* cx, unsigned argc, Value* vp) {
1049 CallArgs args = CallArgsFromVp(argc, vp);
1051 // Step 1.
1052 if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainTime")) {
1053 return false;
1056 // Step 2.
1057 double hour = 0;
1058 if (args.hasDefined(0)) {
1059 if (!ToIntegerWithTruncation(cx, args[0], "hour", &hour)) {
1060 return false;
1064 // Step 3.
1065 double minute = 0;
1066 if (args.hasDefined(1)) {
1067 if (!ToIntegerWithTruncation(cx, args[1], "minute", &minute)) {
1068 return false;
1072 // Step 4.
1073 double second = 0;
1074 if (args.hasDefined(2)) {
1075 if (!ToIntegerWithTruncation(cx, args[2], "second", &second)) {
1076 return false;
1080 // Step 5.
1081 double millisecond = 0;
1082 if (args.hasDefined(3)) {
1083 if (!ToIntegerWithTruncation(cx, args[3], "millisecond", &millisecond)) {
1084 return false;
1088 // Step 6.
1089 double microsecond = 0;
1090 if (args.hasDefined(4)) {
1091 if (!ToIntegerWithTruncation(cx, args[4], "microsecond", &microsecond)) {
1092 return false;
1096 // Step 7.
1097 double nanosecond = 0;
1098 if (args.hasDefined(5)) {
1099 if (!ToIntegerWithTruncation(cx, args[5], "nanosecond", &nanosecond)) {
1100 return false;
1104 // Step 8.
1105 auto* temporalTime = CreateTemporalTime(cx, args, hour, minute, second,
1106 millisecond, microsecond, nanosecond);
1107 if (!temporalTime) {
1108 return false;
1111 args.rval().setObject(*temporalTime);
1112 return true;
1116 * Temporal.PlainTime.from ( item [ , options ] )
1118 static bool PlainTime_from(JSContext* cx, unsigned argc, Value* vp) {
1119 CallArgs args = CallArgsFromVp(argc, vp);
1121 // Step 1. (Not applicable)
1123 auto overflow = TemporalOverflow::Constrain;
1124 if (args.hasDefined(1)) {
1125 // Step 2.
1126 Rooted<JSObject*> options(cx,
1127 RequireObjectArg(cx, "options", "from", args[1]));
1128 if (!options) {
1129 return false;
1132 // Step 3.
1133 if (!ToTemporalOverflow(cx, options, &overflow)) {
1134 return false;
1138 // Steps 4-5.
1139 auto result = ToTemporalTime(cx, args.get(0), overflow);
1140 if (!result) {
1141 return false;
1144 args.rval().setObject(*result);
1145 return true;
1149 * Temporal.PlainTime.compare ( one, two )
1151 static bool PlainTime_compare(JSContext* cx, unsigned argc, Value* vp) {
1152 CallArgs args = CallArgsFromVp(argc, vp);
1154 // Step 1.
1155 PlainTime one;
1156 if (!ToTemporalTime(cx, args.get(0), &one)) {
1157 return false;
1160 // Step 2.
1161 PlainTime two;
1162 if (!ToTemporalTime(cx, args.get(1), &two)) {
1163 return false;
1166 // Step 3.
1167 args.rval().setInt32(CompareTemporalTime(one, two));
1168 return true;
1172 * get Temporal.PlainTime.prototype.hour
1174 static bool PlainTime_hour(JSContext* cx, const CallArgs& args) {
1175 // Step 3.
1176 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1177 args.rval().setInt32(temporalTime->isoHour());
1178 return true;
1182 * get Temporal.PlainTime.prototype.hour
1184 static bool PlainTime_hour(JSContext* cx, unsigned argc, Value* vp) {
1185 // Steps 1-2.
1186 CallArgs args = CallArgsFromVp(argc, vp);
1187 return CallNonGenericMethod<IsPlainTime, PlainTime_hour>(cx, args);
1191 * get Temporal.PlainTime.prototype.minute
1193 static bool PlainTime_minute(JSContext* cx, const CallArgs& args) {
1194 // Step 3.
1195 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1196 args.rval().setInt32(temporalTime->isoMinute());
1197 return true;
1201 * get Temporal.PlainTime.prototype.minute
1203 static bool PlainTime_minute(JSContext* cx, unsigned argc, Value* vp) {
1204 // Steps 1-2.
1205 CallArgs args = CallArgsFromVp(argc, vp);
1206 return CallNonGenericMethod<IsPlainTime, PlainTime_minute>(cx, args);
1210 * get Temporal.PlainTime.prototype.second
1212 static bool PlainTime_second(JSContext* cx, const CallArgs& args) {
1213 // Step 3.
1214 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1215 args.rval().setInt32(temporalTime->isoSecond());
1216 return true;
1220 * get Temporal.PlainTime.prototype.second
1222 static bool PlainTime_second(JSContext* cx, unsigned argc, Value* vp) {
1223 // Steps 1-2.
1224 CallArgs args = CallArgsFromVp(argc, vp);
1225 return CallNonGenericMethod<IsPlainTime, PlainTime_second>(cx, args);
1229 * get Temporal.PlainTime.prototype.millisecond
1231 static bool PlainTime_millisecond(JSContext* cx, const CallArgs& args) {
1232 // Step 3.
1233 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1234 args.rval().setInt32(temporalTime->isoMillisecond());
1235 return true;
1239 * get Temporal.PlainTime.prototype.millisecond
1241 static bool PlainTime_millisecond(JSContext* cx, unsigned argc, Value* vp) {
1242 // Steps 1-2.
1243 CallArgs args = CallArgsFromVp(argc, vp);
1244 return CallNonGenericMethod<IsPlainTime, PlainTime_millisecond>(cx, args);
1248 * get Temporal.PlainTime.prototype.microsecond
1250 static bool PlainTime_microsecond(JSContext* cx, const CallArgs& args) {
1251 // Step 3.
1252 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1253 args.rval().setInt32(temporalTime->isoMicrosecond());
1254 return true;
1258 * get Temporal.PlainTime.prototype.microsecond
1260 static bool PlainTime_microsecond(JSContext* cx, unsigned argc, Value* vp) {
1261 // Steps 1-2.
1262 CallArgs args = CallArgsFromVp(argc, vp);
1263 return CallNonGenericMethod<IsPlainTime, PlainTime_microsecond>(cx, args);
1267 * get Temporal.PlainTime.prototype.nanosecond
1269 static bool PlainTime_nanosecond(JSContext* cx, const CallArgs& args) {
1270 // Step 3.
1271 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1272 args.rval().setInt32(temporalTime->isoNanosecond());
1273 return true;
1277 * get Temporal.PlainTime.prototype.nanosecond
1279 static bool PlainTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) {
1280 // Steps 1-2.
1281 CallArgs args = CallArgsFromVp(argc, vp);
1282 return CallNonGenericMethod<IsPlainTime, PlainTime_nanosecond>(cx, args);
1286 * Temporal.PlainTime.prototype.add ( temporalDurationLike )
1288 static bool PlainTime_add(JSContext* cx, const CallArgs& args) {
1289 // Step 3.
1290 return AddDurationToOrSubtractDurationFromPlainTime(
1291 cx, PlainTimeDuration::Add, args);
1295 * Temporal.PlainTime.prototype.add ( temporalDurationLike )
1297 static bool PlainTime_add(JSContext* cx, unsigned argc, Value* vp) {
1298 // Steps 1-2.
1299 CallArgs args = CallArgsFromVp(argc, vp);
1300 return CallNonGenericMethod<IsPlainTime, PlainTime_add>(cx, args);
1304 * Temporal.PlainTime.prototype.subtract ( temporalDurationLike )
1306 static bool PlainTime_subtract(JSContext* cx, const CallArgs& args) {
1307 // Step 3.
1308 return AddDurationToOrSubtractDurationFromPlainTime(
1309 cx, PlainTimeDuration::Subtract, args);
1313 * Temporal.PlainTime.prototype.subtract ( temporalDurationLike )
1315 static bool PlainTime_subtract(JSContext* cx, unsigned argc, Value* vp) {
1316 // Steps 1-2.
1317 CallArgs args = CallArgsFromVp(argc, vp);
1318 return CallNonGenericMethod<IsPlainTime, PlainTime_subtract>(cx, args);
1322 * Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] )
1324 static bool PlainTime_with(JSContext* cx, const CallArgs& args) {
1325 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1326 auto time = ToPlainTime(temporalTime);
1328 // Step 3.
1329 Rooted<JSObject*> temporalTimeLike(
1330 cx, RequireObjectArg(cx, "temporalTimeLike", "with", args.get(0)));
1331 if (!temporalTimeLike) {
1332 return false;
1335 // Step 4.
1336 if (!RejectTemporalLikeObject(cx, temporalTimeLike)) {
1337 return false;
1340 auto overflow = TemporalOverflow::Constrain;
1341 if (args.hasDefined(1)) {
1342 // Step 5.
1343 Rooted<JSObject*> options(cx,
1344 RequireObjectArg(cx, "options", "with", args[1]));
1345 if (!options) {
1346 return false;
1349 // Step 6.
1350 if (!ToTemporalOverflow(cx, options, &overflow)) {
1351 return false;
1355 // Steps 7-19.
1356 TemporalTimeLike partialTime = {
1357 double(time.hour), double(time.minute),
1358 double(time.second), double(time.millisecond),
1359 double(time.microsecond), double(time.nanosecond),
1361 if (!::ToTemporalTimeRecord(cx, temporalTimeLike, &partialTime)) {
1362 return false;
1365 // Step 20.
1366 PlainTime result;
1367 if (!RegulateTime(cx, partialTime, overflow, &result)) {
1368 return false;
1371 // Step 21.
1372 auto* obj = CreateTemporalTime(cx, result);
1373 if (!obj) {
1374 return false;
1377 args.rval().setObject(*obj);
1378 return true;
1382 * Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] )
1384 static bool PlainTime_with(JSContext* cx, unsigned argc, Value* vp) {
1385 // Steps 1-2.
1386 CallArgs args = CallArgsFromVp(argc, vp);
1387 return CallNonGenericMethod<IsPlainTime, PlainTime_with>(cx, args);
1391 * Temporal.PlainTime.prototype.until ( other [ , options ] )
1393 static bool PlainTime_until(JSContext* cx, const CallArgs& args) {
1394 // Step 3.
1395 return DifferenceTemporalPlainTime(cx, TemporalDifference::Until, args);
1399 * Temporal.PlainTime.prototype.until ( other [ , options ] )
1401 static bool PlainTime_until(JSContext* cx, unsigned argc, Value* vp) {
1402 // Steps 1-2.
1403 CallArgs args = CallArgsFromVp(argc, vp);
1404 return CallNonGenericMethod<IsPlainTime, PlainTime_until>(cx, args);
1408 * Temporal.PlainTime.prototype.since ( other [ , options ] )
1410 static bool PlainTime_since(JSContext* cx, const CallArgs& args) {
1411 // Step 3.
1412 return DifferenceTemporalPlainTime(cx, TemporalDifference::Since, args);
1416 * Temporal.PlainTime.prototype.since ( other [ , options ] )
1418 static bool PlainTime_since(JSContext* cx, unsigned argc, Value* vp) {
1419 // Steps 1-2.
1420 CallArgs args = CallArgsFromVp(argc, vp);
1421 return CallNonGenericMethod<IsPlainTime, PlainTime_since>(cx, args);
1425 * Temporal.PlainTime.prototype.round ( roundTo )
1427 static bool PlainTime_round(JSContext* cx, const CallArgs& args) {
1428 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1429 auto time = ToPlainTime(temporalTime);
1431 // Steps 3-12.
1432 auto smallestUnit = TemporalUnit::Auto;
1433 auto roundingMode = TemporalRoundingMode::HalfExpand;
1434 auto roundingIncrement = Increment{1};
1435 if (args.get(0).isString()) {
1436 // Step 4. (Not applicable in our implementation.)
1438 // Step 9.
1439 Rooted<JSString*> paramString(cx, args[0].toString());
1440 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
1441 TemporalUnitGroup::Time, &smallestUnit)) {
1442 return false;
1445 // Steps 6-8 and 10-12. (Implicit)
1446 } else {
1447 // Steps 3 and 5.
1448 Rooted<JSObject*> options(
1449 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
1450 if (!options) {
1451 return false;
1454 // Steps 6-7.
1455 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
1456 return false;
1459 // Step 8.
1460 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
1461 return false;
1464 // Step 9.
1465 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
1466 TemporalUnitGroup::Time, &smallestUnit)) {
1467 return false;
1470 if (smallestUnit == TemporalUnit::Auto) {
1471 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1472 JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
1473 return false;
1476 // Steps 10-11.
1477 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
1479 // Step 12.
1480 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
1481 false)) {
1482 return false;
1486 // Step 13.
1487 auto result = RoundTime(time, roundingIncrement, smallestUnit, roundingMode);
1489 // Step 14.
1490 auto* obj = CreateTemporalTime(cx, result.time);
1491 if (!obj) {
1492 return false;
1495 args.rval().setObject(*obj);
1496 return true;
1500 * Temporal.PlainTime.prototype.round ( roundTo )
1502 static bool PlainTime_round(JSContext* cx, unsigned argc, Value* vp) {
1503 // Steps 1-2.
1504 CallArgs args = CallArgsFromVp(argc, vp);
1505 return CallNonGenericMethod<IsPlainTime, PlainTime_round>(cx, args);
1509 * Temporal.PlainTime.prototype.equals ( other )
1511 static bool PlainTime_equals(JSContext* cx, const CallArgs& args) {
1512 auto temporalTime =
1513 ToPlainTime(&args.thisv().toObject().as<PlainTimeObject>());
1515 // Step 3.
1516 PlainTime other;
1517 if (!ToTemporalTime(cx, args.get(0), &other)) {
1518 return false;
1521 // Steps 4-10.
1522 args.rval().setBoolean(temporalTime == other);
1523 return true;
1527 * Temporal.PlainTime.prototype.equals ( other )
1529 static bool PlainTime_equals(JSContext* cx, unsigned argc, Value* vp) {
1530 // Steps 1-2.
1531 CallArgs args = CallArgsFromVp(argc, vp);
1532 return CallNonGenericMethod<IsPlainTime, PlainTime_equals>(cx, args);
1536 * Temporal.PlainTime.prototype.toPlainDateTime ( temporalDate )
1538 static bool PlainTime_toPlainDateTime(JSContext* cx, const CallArgs& args) {
1539 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1540 auto time = ToPlainTime(temporalTime);
1542 // Step 3.
1543 Rooted<PlainDateWithCalendar> plainDate(cx);
1544 if (!ToTemporalDate(cx, args.get(0), &plainDate)) {
1545 return false;
1547 auto date = plainDate.date();
1548 auto calendar = plainDate.calendar();
1550 // Step 4.
1551 auto* result = CreateTemporalDateTime(cx, {date, time}, calendar);
1552 if (!result) {
1553 return false;
1556 args.rval().setObject(*result);
1557 return true;
1561 * Temporal.PlainTime.prototype.toPlainDateTime ( temporalDate )
1563 static bool PlainTime_toPlainDateTime(JSContext* cx, unsigned argc, Value* vp) {
1564 // Steps 1-2.
1565 CallArgs args = CallArgsFromVp(argc, vp);
1566 return CallNonGenericMethod<IsPlainTime, PlainTime_toPlainDateTime>(cx, args);
1570 * Temporal.PlainTime.prototype.toZonedDateTime ( item )
1572 * |item| is an options object with `plainDate` and `timeZone` properties.
1574 static bool PlainTime_toZonedDateTime(JSContext* cx, const CallArgs& args) {
1575 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1576 auto time = ToPlainTime(temporalTime);
1578 // Step 3.
1579 Rooted<JSObject*> itemObj(
1580 cx, RequireObjectArg(cx, "item", "toZonedDateTime", args.get(0)));
1581 if (!itemObj) {
1582 return false;
1585 // Step 4.
1586 Rooted<Value> temporalDateLike(cx);
1587 if (!GetProperty(cx, itemObj, args[0], cx->names().plainDate,
1588 &temporalDateLike)) {
1589 return false;
1592 // Step 5.
1593 if (temporalDateLike.isUndefined()) {
1594 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1595 JSMSG_TEMPORAL_MISSING_PROPERTY, "plainDate");
1596 return false;
1599 // Step 6.
1600 Rooted<PlainDateWithCalendar> plainDate(cx);
1601 if (!ToTemporalDate(cx, temporalDateLike, &plainDate)) {
1602 return false;
1604 auto date = plainDate.date();
1605 auto calendar = plainDate.calendar();
1607 // Step 7.
1608 Rooted<Value> temporalTimeZoneLike(cx);
1609 if (!GetProperty(cx, itemObj, itemObj, cx->names().timeZone,
1610 &temporalTimeZoneLike)) {
1611 return false;
1614 // Step 8.
1615 if (temporalTimeZoneLike.isUndefined()) {
1616 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1617 JSMSG_TEMPORAL_MISSING_PROPERTY, "timeZone");
1618 return false;
1621 // Step 9.
1622 Rooted<TimeZoneValue> timeZone(cx);
1623 if (!ToTemporalTimeZone(cx, temporalTimeZoneLike, &timeZone)) {
1624 return false;
1627 // Step 10.
1628 Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
1629 if (!CreateTemporalDateTime(cx, {date, time}, calendar, &temporalDateTime)) {
1630 return false;
1633 // Steps 11-12.
1634 Instant instant;
1635 if (!GetInstantFor(cx, timeZone, temporalDateTime,
1636 TemporalDisambiguation::Compatible, &instant)) {
1637 return false;
1640 // Step 13.
1641 auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
1642 if (!result) {
1643 return false;
1646 args.rval().setObject(*result);
1647 return true;
1651 * Temporal.PlainTime.prototype.toZonedDateTime ( item )
1653 static bool PlainTime_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) {
1654 // Steps 1-2.
1655 CallArgs args = CallArgsFromVp(argc, vp);
1656 return CallNonGenericMethod<IsPlainTime, PlainTime_toZonedDateTime>(cx, args);
1660 * Temporal.PlainTime.prototype.getISOFields ( )
1662 static bool PlainTime_getISOFields(JSContext* cx, const CallArgs& args) {
1663 Rooted<PlainTimeObject*> temporalTime(
1664 cx, &args.thisv().toObject().as<PlainTimeObject>());
1665 auto time = ToPlainTime(temporalTime);
1667 // Step 3.
1668 Rooted<IdValueVector> fields(cx, IdValueVector(cx));
1670 // Step 4.
1671 if (!fields.emplaceBack(NameToId(cx->names().isoHour),
1672 Int32Value(time.hour))) {
1673 return false;
1676 // Step 5.
1677 if (!fields.emplaceBack(NameToId(cx->names().isoMicrosecond),
1678 Int32Value(time.microsecond))) {
1679 return false;
1682 // Step 6.
1683 if (!fields.emplaceBack(NameToId(cx->names().isoMillisecond),
1684 Int32Value(time.millisecond))) {
1685 return false;
1688 // Step 7.
1689 if (!fields.emplaceBack(NameToId(cx->names().isoMinute),
1690 Int32Value(time.minute))) {
1691 return false;
1694 // Step 8.
1695 if (!fields.emplaceBack(NameToId(cx->names().isoNanosecond),
1696 Int32Value(time.nanosecond))) {
1697 return false;
1700 // Step 9.
1701 if (!fields.emplaceBack(NameToId(cx->names().isoSecond),
1702 Int32Value(time.second))) {
1703 return false;
1706 // Step 10.
1707 auto* obj = NewPlainObjectWithUniqueNames(cx, fields);
1708 if (!obj) {
1709 return false;
1712 args.rval().setObject(*obj);
1713 return true;
1717 * Temporal.PlainTime.prototype.getISOFields ( )
1719 static bool PlainTime_getISOFields(JSContext* cx, unsigned argc, Value* vp) {
1720 // Steps 1-2.
1721 CallArgs args = CallArgsFromVp(argc, vp);
1722 return CallNonGenericMethod<IsPlainTime, PlainTime_getISOFields>(cx, args);
1726 * Temporal.PlainTime.prototype.toString ( [ options ] )
1728 static bool PlainTime_toString(JSContext* cx, const CallArgs& args) {
1729 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1730 auto time = ToPlainTime(temporalTime);
1732 SecondsStringPrecision precision = {Precision::Auto(),
1733 TemporalUnit::Nanosecond, Increment{1}};
1734 auto roundingMode = TemporalRoundingMode::Trunc;
1735 if (args.hasDefined(0)) {
1736 // Step 3.
1737 Rooted<JSObject*> options(
1738 cx, RequireObjectArg(cx, "options", "toString", args[0]));
1739 if (!options) {
1740 return false;
1743 // Steps 4-5.
1744 auto digits = Precision::Auto();
1745 if (!ToFractionalSecondDigits(cx, options, &digits)) {
1746 return false;
1749 // Step 6.
1750 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
1751 return false;
1754 // Step 7.
1755 auto smallestUnit = TemporalUnit::Auto;
1756 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
1757 TemporalUnitGroup::Time, &smallestUnit)) {
1758 return false;
1761 // Step 8.
1762 if (smallestUnit == TemporalUnit::Hour) {
1763 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1764 JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
1765 "smallestUnit");
1766 return false;
1769 // Step 9.
1770 precision = ToSecondsStringPrecision(smallestUnit, digits);
1773 // Step 10.
1774 auto roundedTime =
1775 RoundTime(time, precision.increment, precision.unit, roundingMode);
1777 // Step 11.
1778 JSString* str =
1779 TemporalTimeToString(cx, roundedTime.time, precision.precision);
1780 if (!str) {
1781 return false;
1784 args.rval().setString(str);
1785 return true;
1789 * Temporal.PlainTime.prototype.toString ( [ options ] )
1791 static bool PlainTime_toString(JSContext* cx, unsigned argc, Value* vp) {
1792 // Steps 1-2.
1793 CallArgs args = CallArgsFromVp(argc, vp);
1794 return CallNonGenericMethod<IsPlainTime, PlainTime_toString>(cx, args);
1798 * Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] )
1800 static bool PlainTime_toLocaleString(JSContext* cx, const CallArgs& args) {
1801 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1802 auto time = ToPlainTime(temporalTime);
1804 // Step 3.
1805 JSString* str = TemporalTimeToString(cx, time, Precision::Auto());
1806 if (!str) {
1807 return false;
1810 args.rval().setString(str);
1811 return true;
1815 * Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] )
1817 static bool PlainTime_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
1818 // Steps 1-2.
1819 CallArgs args = CallArgsFromVp(argc, vp);
1820 return CallNonGenericMethod<IsPlainTime, PlainTime_toLocaleString>(cx, args);
1824 * Temporal.PlainTime.prototype.toJSON ( )
1826 static bool PlainTime_toJSON(JSContext* cx, const CallArgs& args) {
1827 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1828 auto time = ToPlainTime(temporalTime);
1830 // Step 3.
1831 JSString* str = TemporalTimeToString(cx, time, Precision::Auto());
1832 if (!str) {
1833 return false;
1836 args.rval().setString(str);
1837 return true;
1841 * Temporal.PlainTime.prototype.toJSON ( )
1843 static bool PlainTime_toJSON(JSContext* cx, unsigned argc, Value* vp) {
1844 // Steps 1-2.
1845 CallArgs args = CallArgsFromVp(argc, vp);
1846 return CallNonGenericMethod<IsPlainTime, PlainTime_toJSON>(cx, args);
1850 * Temporal.PlainTime.prototype.valueOf ( )
1852 static bool PlainTime_valueOf(JSContext* cx, unsigned argc, Value* vp) {
1853 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
1854 "PlainTime", "primitive type");
1855 return false;
1858 const JSClass PlainTimeObject::class_ = {
1859 "Temporal.PlainTime",
1860 JSCLASS_HAS_RESERVED_SLOTS(PlainTimeObject::SLOT_COUNT) |
1861 JSCLASS_HAS_CACHED_PROTO(JSProto_PlainTime),
1862 JS_NULL_CLASS_OPS,
1863 &PlainTimeObject::classSpec_,
1866 const JSClass& PlainTimeObject::protoClass_ = PlainObject::class_;
1868 static const JSFunctionSpec PlainTime_methods[] = {
1869 JS_FN("from", PlainTime_from, 1, 0),
1870 JS_FN("compare", PlainTime_compare, 2, 0),
1871 JS_FS_END,
1874 static const JSFunctionSpec PlainTime_prototype_methods[] = {
1875 JS_FN("add", PlainTime_add, 1, 0),
1876 JS_FN("subtract", PlainTime_subtract, 1, 0),
1877 JS_FN("with", PlainTime_with, 1, 0),
1878 JS_FN("until", PlainTime_until, 1, 0),
1879 JS_FN("since", PlainTime_since, 1, 0),
1880 JS_FN("round", PlainTime_round, 1, 0),
1881 JS_FN("equals", PlainTime_equals, 1, 0),
1882 JS_FN("toPlainDateTime", PlainTime_toPlainDateTime, 1, 0),
1883 JS_FN("toZonedDateTime", PlainTime_toZonedDateTime, 1, 0),
1884 JS_FN("getISOFields", PlainTime_getISOFields, 0, 0),
1885 JS_FN("toString", PlainTime_toString, 0, 0),
1886 JS_FN("toLocaleString", PlainTime_toLocaleString, 0, 0),
1887 JS_FN("toJSON", PlainTime_toJSON, 0, 0),
1888 JS_FN("valueOf", PlainTime_valueOf, 0, 0),
1889 JS_FS_END,
1892 static const JSPropertySpec PlainTime_prototype_properties[] = {
1893 JS_PSG("hour", PlainTime_hour, 0),
1894 JS_PSG("minute", PlainTime_minute, 0),
1895 JS_PSG("second", PlainTime_second, 0),
1896 JS_PSG("millisecond", PlainTime_millisecond, 0),
1897 JS_PSG("microsecond", PlainTime_microsecond, 0),
1898 JS_PSG("nanosecond", PlainTime_nanosecond, 0),
1899 JS_STRING_SYM_PS(toStringTag, "Temporal.PlainTime", JSPROP_READONLY),
1900 JS_PS_END,
1903 const ClassSpec PlainTimeObject::classSpec_ = {
1904 GenericCreateConstructor<PlainTimeConstructor, 0, gc::AllocKind::FUNCTION>,
1905 GenericCreatePrototype<PlainTimeObject>,
1906 PlainTime_methods,
1907 nullptr,
1908 PlainTime_prototype_methods,
1909 PlainTime_prototype_properties,
1910 nullptr,
1911 ClassSpec::DontDefineConstructor,