Bug 1874684 - Part 21: Rename SecondsAndNanoseconds::toTotalNanoseconds. r=dminor
[gecko.git] / js / src / builtin / temporal / PlainTime.cpp
blob092b033aa0c0fdd56dbcfdb538e810b5ab8dbf3f
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 const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
123 time;
124 return ::IsValidTime(hour, minute, second, millisecond, microsecond,
125 nanosecond);
129 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
131 bool js::temporal::IsValidTime(double hour, double minute, double second,
132 double millisecond, double microsecond,
133 double nanosecond) {
134 return ::IsValidTime(hour, minute, second, millisecond, microsecond,
135 nanosecond);
137 #endif
139 static void ReportInvalidTimeValue(JSContext* cx, const char* name, int32_t min,
140 int32_t max, double num) {
141 Int32ToCStringBuf minCbuf;
142 const char* minStr = Int32ToCString(&minCbuf, min);
144 Int32ToCStringBuf maxCbuf;
145 const char* maxStr = Int32ToCString(&maxCbuf, max);
147 ToCStringBuf numCbuf;
148 const char* numStr = NumberToCString(&numCbuf, num);
150 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
151 JSMSG_TEMPORAL_PLAIN_TIME_INVALID_VALUE, name,
152 minStr, maxStr, numStr);
155 template <typename T>
156 static inline bool ThrowIfInvalidTimeValue(JSContext* cx, const char* name,
157 int32_t min, int32_t max, T num) {
158 if (min <= num && num <= max) {
159 return true;
161 ReportInvalidTimeValue(cx, name, min, max, num);
162 return false;
166 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
168 template <typename T>
169 static bool ThrowIfInvalidTime(JSContext* cx, T hour, T minute, T second,
170 T millisecond, T microsecond, T nanosecond) {
171 static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
173 // Step 1.
174 MOZ_ASSERT(IsInteger(hour));
175 MOZ_ASSERT(IsInteger(minute));
176 MOZ_ASSERT(IsInteger(second));
177 MOZ_ASSERT(IsInteger(millisecond));
178 MOZ_ASSERT(IsInteger(microsecond));
179 MOZ_ASSERT(IsInteger(nanosecond));
181 // Step 2.
182 if (!ThrowIfInvalidTimeValue(cx, "hour", 0, 23, hour)) {
183 return false;
186 // Step 3.
187 if (!ThrowIfInvalidTimeValue(cx, "minute", 0, 59, minute)) {
188 return false;
191 // Step 4.
192 if (!ThrowIfInvalidTimeValue(cx, "second", 0, 59, second)) {
193 return false;
196 // Step 5.
197 if (!ThrowIfInvalidTimeValue(cx, "millisecond", 0, 999, millisecond)) {
198 return false;
201 // Step 6.
202 if (!ThrowIfInvalidTimeValue(cx, "microsecond", 0, 999, microsecond)) {
203 return false;
206 // Step 7.
207 if (!ThrowIfInvalidTimeValue(cx, "nanosecond", 0, 999, nanosecond)) {
208 return false;
211 // Step 8.
212 return true;
216 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
218 bool js::temporal::ThrowIfInvalidTime(JSContext* cx, const PlainTime& time) {
219 const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
220 time;
221 return ::ThrowIfInvalidTime(cx, hour, minute, second, millisecond,
222 microsecond, nanosecond);
226 * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
228 bool js::temporal::ThrowIfInvalidTime(JSContext* cx, double hour, double minute,
229 double second, double millisecond,
230 double microsecond, double nanosecond) {
231 return ::ThrowIfInvalidTime(cx, hour, minute, second, millisecond,
232 microsecond, nanosecond);
236 * ConstrainTime ( hour, minute, second, millisecond, microsecond, nanosecond )
238 static PlainTime ConstrainTime(double hour, double minute, double second,
239 double millisecond, double microsecond,
240 double nanosecond) {
241 // Step 1.
242 MOZ_ASSERT(IsInteger(hour));
243 MOZ_ASSERT(IsInteger(minute));
244 MOZ_ASSERT(IsInteger(second));
245 MOZ_ASSERT(IsInteger(millisecond));
246 MOZ_ASSERT(IsInteger(microsecond));
247 MOZ_ASSERT(IsInteger(nanosecond));
249 // Steps 2-8.
250 return {
251 int32_t(std::clamp(hour, 0.0, 23.0)),
252 int32_t(std::clamp(minute, 0.0, 59.0)),
253 int32_t(std::clamp(second, 0.0, 59.0)),
254 int32_t(std::clamp(millisecond, 0.0, 999.0)),
255 int32_t(std::clamp(microsecond, 0.0, 999.0)),
256 int32_t(std::clamp(nanosecond, 0.0, 999.0)),
261 * RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond,
262 * overflow )
264 bool js::temporal::RegulateTime(JSContext* cx, const TemporalTimeLike& time,
265 TemporalOverflow overflow, PlainTime* result) {
266 const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
267 time;
269 // Step 1.
270 MOZ_ASSERT(IsInteger(hour));
271 MOZ_ASSERT(IsInteger(minute));
272 MOZ_ASSERT(IsInteger(second));
273 MOZ_ASSERT(IsInteger(millisecond));
274 MOZ_ASSERT(IsInteger(microsecond));
275 MOZ_ASSERT(IsInteger(nanosecond));
277 // Step 2. (Not applicable in our implementation.)
279 // Step 3.
280 if (overflow == TemporalOverflow::Constrain) {
281 *result = ConstrainTime(hour, minute, second, millisecond, microsecond,
282 nanosecond);
283 return true;
286 // Step 4.a.
287 MOZ_ASSERT(overflow == TemporalOverflow::Reject);
289 // Step 4.b.
290 if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond,
291 nanosecond)) {
292 return false;
295 // Step 4.c.
296 *result = {
297 int32_t(hour), int32_t(minute), int32_t(second),
298 int32_t(millisecond), int32_t(microsecond), int32_t(nanosecond),
300 return true;
304 * CreateTemporalTime ( hour, minute, second, millisecond, microsecond,
305 * nanosecond [ , newTarget ] )
307 static PlainTimeObject* CreateTemporalTime(JSContext* cx, const CallArgs& args,
308 double hour, double minute,
309 double second, double millisecond,
310 double microsecond,
311 double nanosecond) {
312 MOZ_ASSERT(IsInteger(hour));
313 MOZ_ASSERT(IsInteger(minute));
314 MOZ_ASSERT(IsInteger(second));
315 MOZ_ASSERT(IsInteger(millisecond));
316 MOZ_ASSERT(IsInteger(microsecond));
317 MOZ_ASSERT(IsInteger(nanosecond));
319 // Step 1.
320 if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond,
321 nanosecond)) {
322 return nullptr;
325 // Steps 2-3.
326 Rooted<JSObject*> proto(cx);
327 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainTime,
328 &proto)) {
329 return nullptr;
332 auto* object = NewObjectWithClassProto<PlainTimeObject>(cx, proto);
333 if (!object) {
334 return nullptr;
337 // Step 4.
338 object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT,
339 Int32Value(int32_t(hour)));
341 // Step 5.
342 object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT,
343 Int32Value(int32_t(minute)));
345 // Step 6.
346 object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT,
347 Int32Value(int32_t(second)));
349 // Step 7.
350 object->setFixedSlot(PlainTimeObject::ISO_MILLISECOND_SLOT,
351 Int32Value(int32_t(millisecond)));
353 // Step 8.
354 object->setFixedSlot(PlainTimeObject::ISO_MICROSECOND_SLOT,
355 Int32Value(int32_t(microsecond)));
357 // Step 9.
358 object->setFixedSlot(PlainTimeObject::ISO_NANOSECOND_SLOT,
359 Int32Value(int32_t(nanosecond)));
361 // Step 10.
362 return object;
366 * CreateTemporalTime ( hour, minute, second, millisecond, microsecond,
367 * nanosecond [ , newTarget ] )
369 PlainTimeObject* js::temporal::CreateTemporalTime(JSContext* cx,
370 const PlainTime& time) {
371 const auto& [hour, minute, second, millisecond, microsecond, nanosecond] =
372 time;
374 // Step 1.
375 if (!ThrowIfInvalidTime(cx, time)) {
376 return nullptr;
379 // Steps 2-3.
380 auto* object = NewBuiltinClassInstance<PlainTimeObject>(cx);
381 if (!object) {
382 return nullptr;
385 // Step 4.
386 object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
388 // Step 5.
389 object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT, Int32Value(minute));
391 // Step 6.
392 object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT, Int32Value(second));
394 // Step 7.
395 object->setFixedSlot(PlainTimeObject::ISO_MILLISECOND_SLOT,
396 Int32Value(millisecond));
398 // Step 8.
399 object->setFixedSlot(PlainTimeObject::ISO_MICROSECOND_SLOT,
400 Int32Value(microsecond));
402 // Step 9.
403 object->setFixedSlot(PlainTimeObject::ISO_NANOSECOND_SLOT,
404 Int32Value(nanosecond));
406 // Step 10.
407 return object;
411 * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
413 template <typename IntT>
414 static BalancedTime BalanceTime(IntT hour, IntT minute, IntT second,
415 IntT millisecond, IntT microsecond,
416 IntT nanosecond) {
417 // Combined floor'ed division and modulo operation.
418 auto divmod = [](IntT dividend, int32_t divisor, int32_t* remainder) {
419 MOZ_ASSERT(divisor > 0);
421 IntT quotient = dividend / divisor;
422 *remainder = dividend % divisor;
424 // The remainder is negative, add the divisor and simulate a floor instead
425 // of trunc division.
426 if (*remainder < 0) {
427 *remainder += divisor;
428 quotient -= 1;
431 return quotient;
434 PlainTime time = {};
436 // Steps 1-2.
437 microsecond += divmod(nanosecond, 1000, &time.nanosecond);
439 // Steps 3-4.
440 millisecond += divmod(microsecond, 1000, &time.microsecond);
442 // Steps 5-6.
443 second += divmod(millisecond, 1000, &time.millisecond);
445 // Steps 7-8.
446 minute += divmod(second, 60, &time.second);
448 // Steps 9-10.
449 hour += divmod(minute, 60, &time.minute);
451 // Steps 11-12.
452 int32_t days = divmod(hour, 24, &time.hour);
454 // Step 13.
455 MOZ_ASSERT(IsValidTime(time));
456 return {days, time};
460 * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
462 static BalancedTime BalanceTime(int32_t hour, int32_t minute, int32_t second,
463 int32_t millisecond, int32_t microsecond,
464 int32_t nanosecond) {
465 MOZ_ASSERT(-24 < hour && hour < 2 * 24);
466 MOZ_ASSERT(-60 < minute && minute < 2 * 60);
467 MOZ_ASSERT(-60 < second && second < 2 * 60);
468 MOZ_ASSERT(-1000 < millisecond && millisecond < 2 * 1000);
469 MOZ_ASSERT(-1000 < microsecond && microsecond < 2 * 1000);
470 MOZ_ASSERT(-1000 < nanosecond && nanosecond < 2 * 1000);
472 return BalanceTime<int32_t>(hour, minute, second, millisecond, microsecond,
473 nanosecond);
477 * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
479 BalancedTime js::temporal::BalanceTime(const PlainTime& time,
480 int64_t nanoseconds) {
481 MOZ_ASSERT(IsValidTime(time));
482 MOZ_ASSERT(std::abs(nanoseconds) <= 2 * ToNanoseconds(TemporalUnit::Day));
484 return ::BalanceTime<int64_t>(time.hour, time.minute, time.second,
485 time.millisecond, time.microsecond,
486 time.nanosecond + nanoseconds);
490 * DifferenceTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 )
492 NormalizedTimeDuration js::temporal::DifferenceTime(const PlainTime& time1,
493 const PlainTime& time2) {
494 MOZ_ASSERT(IsValidTime(time1));
495 MOZ_ASSERT(IsValidTime(time2));
497 // Step 1.
498 int32_t hours = time2.hour - time1.hour;
500 // Step 2.
501 int32_t minutes = time2.minute - time1.minute;
503 // Step 3.
504 int32_t seconds = time2.second - time1.second;
506 // Step 4.
507 int32_t milliseconds = time2.millisecond - time1.millisecond;
509 // Step 5.
510 int32_t microseconds = time2.microsecond - time1.microsecond;
512 // Step 6.
513 int32_t nanoseconds = time2.nanosecond - time1.nanosecond;
515 // Step 7.
516 auto result = NormalizeTimeDuration(hours, minutes, seconds, milliseconds,
517 microseconds, nanoseconds);
519 // Step 8.
520 MOZ_ASSERT(result.abs().toNanoseconds() <
521 Int128{ToNanoseconds(TemporalUnit::Day)});
523 // Step 9.
524 return result;
528 * ToTemporalTime ( item [ , overflow ] )
530 static bool ToTemporalTime(JSContext* cx, Handle<Value> item,
531 TemporalOverflow overflow, PlainTime* result) {
532 // Steps 1-2. (Not applicable in our implementation.)
534 // Steps 3-4.
535 if (item.isObject()) {
536 // Step 3.
537 Rooted<JSObject*> itemObj(cx, &item.toObject());
539 // Step 3.a.
540 if (auto* time = itemObj->maybeUnwrapIf<PlainTimeObject>()) {
541 *result = ToPlainTime(time);
542 return true;
545 // Step 3.b.
546 if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
547 auto epochInstant = ToInstant(zonedDateTime);
548 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
550 if (!timeZone.wrap(cx)) {
551 return false;
554 // Steps 3.b.i-iii.
555 PlainDateTime dateTime;
556 if (!GetPlainDateTimeFor(cx, timeZone, epochInstant, &dateTime)) {
557 return false;
560 // Step 3.b.iv.
561 *result = dateTime.time;
562 return true;
565 // Step 3.c.
566 if (auto* dateTime = itemObj->maybeUnwrapIf<PlainDateTimeObject>()) {
567 *result = ToPlainTime(dateTime);
568 return true;
571 // Step 3.d.
572 TemporalTimeLike timeResult;
573 if (!ToTemporalTimeRecord(cx, itemObj, &timeResult)) {
574 return false;
577 // Step 3.e.
578 if (!RegulateTime(cx, timeResult, overflow, result)) {
579 return false;
581 } else {
582 // Step 4.
584 // Step 4.a.
585 if (!item.isString()) {
586 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
587 nullptr, "not a string");
588 return false;
590 Rooted<JSString*> string(cx, item.toString());
592 // Step 4.b.
593 if (!ParseTemporalTimeString(cx, string, result)) {
594 return false;
597 // Step 4.c.
598 MOZ_ASSERT(IsValidTime(*result));
601 // Step 5.
602 return true;
606 * ToTemporalTime ( item [ , overflow ] )
608 static PlainTimeObject* ToTemporalTime(JSContext* cx, Handle<Value> item,
609 TemporalOverflow overflow) {
610 PlainTime time;
611 if (!ToTemporalTime(cx, item, overflow, &time)) {
612 return nullptr;
614 MOZ_ASSERT(IsValidTime(time));
616 return CreateTemporalTime(cx, time);
620 * ToTemporalTime ( item [ , overflow ] )
622 bool js::temporal::ToTemporalTime(JSContext* cx, Handle<Value> item,
623 PlainTime* result) {
624 return ToTemporalTime(cx, item, TemporalOverflow::Constrain, result);
628 * CompareTemporalTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2,
629 * ns2 )
631 int32_t js::temporal::CompareTemporalTime(const PlainTime& one,
632 const PlainTime& two) {
633 // Steps 1-2.
634 if (int32_t diff = one.hour - two.hour) {
635 return diff < 0 ? -1 : 1;
638 // Steps 3-4.
639 if (int32_t diff = one.minute - two.minute) {
640 return diff < 0 ? -1 : 1;
643 // Steps 5-6.
644 if (int32_t diff = one.second - two.second) {
645 return diff < 0 ? -1 : 1;
648 // Steps 7-8.
649 if (int32_t diff = one.millisecond - two.millisecond) {
650 return diff < 0 ? -1 : 1;
653 // Steps 9-10.
654 if (int32_t diff = one.microsecond - two.microsecond) {
655 return diff < 0 ? -1 : 1;
658 // Steps 11-12.
659 if (int32_t diff = one.nanosecond - two.nanosecond) {
660 return diff < 0 ? -1 : 1;
663 // Step 13.
664 return 0;
668 * ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
670 static bool ToTemporalTimeRecord(JSContext* cx,
671 Handle<JSObject*> temporalTimeLike,
672 TemporalTimeLike* result) {
673 // Steps 1 and 3-4. (Not applicable in our implementation.)
675 // Step 2. (Inlined call to PrepareTemporalFields.)
676 // PrepareTemporalFields, step 1. (Not applicable in our implementation.)
678 // PrepareTemporalFields, step 2.
679 bool any = false;
681 // PrepareTemporalFields, steps 3-4. (Loop unrolled)
682 Rooted<Value> value(cx);
683 auto getTimeProperty = [&](Handle<PropertyName*> property, const char* name,
684 double* num) {
685 // Step 4.a.
686 if (!GetProperty(cx, temporalTimeLike, temporalTimeLike, property,
687 &value)) {
688 return false;
691 // Step 4.b.
692 if (!value.isUndefined()) {
693 // Step 4.b.i.
694 any = true;
696 // Step 4.b.ii.2.
697 if (!ToIntegerWithTruncation(cx, value, name, num)) {
698 return false;
701 return true;
704 if (!getTimeProperty(cx->names().hour, "hour", &result->hour)) {
705 return false;
707 if (!getTimeProperty(cx->names().microsecond, "microsecond",
708 &result->microsecond)) {
709 return false;
711 if (!getTimeProperty(cx->names().millisecond, "millisecond",
712 &result->millisecond)) {
713 return false;
715 if (!getTimeProperty(cx->names().minute, "minute", &result->minute)) {
716 return false;
718 if (!getTimeProperty(cx->names().nanosecond, "nanosecond",
719 &result->nanosecond)) {
720 return false;
722 if (!getTimeProperty(cx->names().second, "second", &result->second)) {
723 return false;
726 // PrepareTemporalFields, step 5.
727 if (!any) {
728 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
729 JSMSG_TEMPORAL_PLAIN_TIME_MISSING_UNIT);
730 return false;
733 // Steps 5-16. (Performed implicitly in our implementation.)
735 // Step 17.
736 return true;
740 * ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
742 bool js::temporal::ToTemporalTimeRecord(JSContext* cx,
743 Handle<JSObject*> temporalTimeLike,
744 TemporalTimeLike* result) {
745 // Step 3.a. (Set all fields to zero.)
746 *result = {};
748 // Steps 1-2 and 4-17.
749 return ::ToTemporalTimeRecord(cx, temporalTimeLike, result);
752 static int64_t TimeToNanos(const PlainTime& time) {
753 // No overflow possible because the input is a valid time.
754 MOZ_ASSERT(IsValidTime(time));
756 int64_t hour = time.hour;
757 int64_t minute = time.minute;
758 int64_t second = time.second;
759 int64_t millisecond = time.millisecond;
760 int64_t microsecond = time.microsecond;
761 int64_t nanosecond = time.nanosecond;
763 int64_t millis = ((hour * 60 + minute) * 60 + second) * 1000 + millisecond;
764 return (millis * 1000 + microsecond) * 1000 + nanosecond;
768 * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
769 * increment, unit, roundingMode [ , dayLengthNs ] )
771 RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
772 TemporalUnit unit,
773 TemporalRoundingMode roundingMode) {
774 MOZ_ASSERT(IsValidTime(time));
775 MOZ_ASSERT(unit >= TemporalUnit::Day);
776 MOZ_ASSERT_IF(unit > TemporalUnit::Day,
777 increment <= MaximumTemporalDurationRoundingIncrement(unit));
778 MOZ_ASSERT_IF(unit == TemporalUnit::Day, increment == Increment{1});
780 int32_t days = 0;
781 auto [hour, minute, second, millisecond, microsecond, nanosecond] = time;
783 // Take the same approach as used in RoundDuration() to perform exact
784 // mathematical operations without possible loss of precision.
786 // Steps 1-8.
787 PlainTime quantity;
788 int32_t* result;
789 switch (unit) {
790 case TemporalUnit::Day:
791 quantity = time;
792 result = &days;
793 break;
794 case TemporalUnit::Hour:
795 quantity = time;
796 result = &hour;
797 minute = 0;
798 second = 0;
799 millisecond = 0;
800 microsecond = 0;
801 nanosecond = 0;
802 break;
803 case TemporalUnit::Minute:
804 quantity = {0, minute, second, millisecond, microsecond, nanosecond};
805 result = &minute;
806 second = 0;
807 millisecond = 0;
808 microsecond = 0;
809 nanosecond = 0;
810 break;
811 case TemporalUnit::Second:
812 quantity = {0, 0, second, millisecond, microsecond, nanosecond};
813 result = &second;
814 millisecond = 0;
815 microsecond = 0;
816 nanosecond = 0;
817 break;
818 case TemporalUnit::Millisecond:
819 quantity = {0, 0, 0, millisecond, microsecond, nanosecond};
820 result = &millisecond;
821 microsecond = 0;
822 nanosecond = 0;
823 break;
824 case TemporalUnit::Microsecond:
825 quantity = {0, 0, 0, 0, microsecond, nanosecond};
826 result = &microsecond;
827 nanosecond = 0;
828 break;
829 case TemporalUnit::Nanosecond:
830 quantity = {0, 0, 0, 0, 0, nanosecond};
831 result = &nanosecond;
832 break;
834 case TemporalUnit::Auto:
835 case TemporalUnit::Year:
836 case TemporalUnit::Month:
837 case TemporalUnit::Week:
838 MOZ_CRASH("unexpected temporal unit");
841 // Step 9.
842 int64_t nanos = TimeToNanos(quantity);
843 MOZ_ASSERT(0 <= nanos && nanos < ToNanoseconds(TemporalUnit::Day));
845 auto r = RoundNumberToIncrement(nanos, ToNanoseconds(unit), increment,
846 roundingMode);
847 MOZ_ASSERT(r == Int128{int32_t(r)},
848 "can't overflow when inputs are all in range");
850 *result = int32_t(r);
852 // Step 10.
853 if (unit == TemporalUnit::Day) {
854 return {int64_t(days), {0, 0, 0, 0, 0, 0}};
857 // Steps 11-17.
858 auto balanced =
859 ::BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond);
860 return {int64_t(balanced.days), balanced.time};
864 * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
865 * increment, unit, roundingMode [ , dayLengthNs ] )
867 RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
868 TemporalUnit unit,
869 TemporalRoundingMode roundingMode,
870 const InstantSpan& dayLengthNs) {
871 MOZ_ASSERT(IsValidTime(time));
872 MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
873 MOZ_ASSERT(dayLengthNs > (InstantSpan{}));
875 if (unit != TemporalUnit::Day) {
876 return RoundTime(time, increment, unit, roundingMode);
879 // Step 1. (Not applicable)
881 // Step 2.
882 int64_t quantity = TimeToNanos(time);
883 MOZ_ASSERT(0 <= quantity && quantity < ToNanoseconds(TemporalUnit::Day));
885 // Steps 3-8. (Not applicable)
887 // Step 9.
889 // When the divisor is too large, the expression `quantity / divisor` is a
890 // value near zero. Substitute |divisor| with an equivalent expression.
891 // Choose |86'400'000'000'000| which will give a similar result because
892 // |quantity| is guaranteed to be lower than |86'400'000'000'000|.
893 int64_t divisor = int64_t(std::min(dayLengthNs.toNanoseconds(),
894 Int128{ToNanoseconds(TemporalUnit::Day)}));
895 MOZ_ASSERT(divisor > 0);
896 MOZ_ASSERT(increment == Increment{1}, "Rounding increment for 'day' is 1");
898 auto result =
899 RoundNumberToIncrement(quantity, divisor, increment, roundingMode);
900 MOZ_ASSERT(result == Int128{int64_t(result)});
902 // Step 10.
903 return {int64_t(result), {0, 0, 0, 0, 0, 0}};
907 * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, norm )
909 AddedTime js::temporal::AddTime(const PlainTime& time,
910 const NormalizedTimeDuration& duration) {
911 MOZ_ASSERT(IsValidTime(time));
912 MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
914 auto [seconds, nanoseconds] = duration;
915 if (seconds < 0 && nanoseconds > 0) {
916 seconds += 1;
917 nanoseconds -= 1'000'000'000;
919 MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
921 // Step 1.
922 int64_t second = time.second + seconds;
924 // Step 2.
925 int32_t nanosecond = time.nanosecond + nanoseconds;
927 // Step 3.
928 auto balanced =
929 ::BalanceTime<int64_t>(time.hour, time.minute, second, time.millisecond,
930 time.microsecond, nanosecond);
931 return {balanced.days, balanced.time};
935 * DifferenceTemporalPlainTime ( operation, temporalTime, other, options )
937 static bool DifferenceTemporalPlainTime(JSContext* cx,
938 TemporalDifference operation,
939 const CallArgs& args) {
940 auto temporalTime =
941 ToPlainTime(&args.thisv().toObject().as<PlainTimeObject>());
943 // Step 1. (Not applicable in our implementation.)
945 // Step 2.
946 PlainTime other;
947 if (!ToTemporalTime(cx, args.get(0), &other)) {
948 return false;
951 // Steps 3-4.
952 DifferenceSettings settings;
953 if (args.hasDefined(1)) {
954 Rooted<JSObject*> options(
955 cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
956 if (!options) {
957 return false;
960 // Step 3.
961 Rooted<PlainObject*> resolvedOptions(cx,
962 SnapshotOwnProperties(cx, options));
963 if (!resolvedOptions) {
964 return false;
967 // Step 4.
968 if (!GetDifferenceSettings(
969 cx, operation, resolvedOptions, TemporalUnitGroup::Time,
970 TemporalUnit::Nanosecond, TemporalUnit::Hour, &settings)) {
971 return false;
973 } else {
974 // Steps 3-4.
975 settings = {
976 TemporalUnit::Nanosecond,
977 TemporalUnit::Hour,
978 TemporalRoundingMode::Trunc,
979 Increment{1},
983 // Step 5.
984 auto diff = DifferenceTime(temporalTime, other);
986 // Step 6.
987 if (settings.smallestUnit != TemporalUnit::Nanosecond ||
988 settings.roundingIncrement != Increment{1}) {
989 // Steps 6.a-b.
990 diff = RoundDuration(diff, settings.roundingIncrement,
991 settings.smallestUnit, settings.roundingMode);
994 // Step 7.
995 auto balancedDuration = BalanceTimeDuration(diff, settings.largestUnit);
997 // Step 8.
998 auto duration = balancedDuration.toDuration();
999 if (operation == TemporalDifference::Since) {
1000 duration = duration.negate();
1003 auto* result = CreateTemporalDuration(cx, duration);
1004 if (!result) {
1005 return false;
1008 args.rval().setObject(*result);
1009 return true;
1012 enum class PlainTimeDuration { Add, Subtract };
1015 * AddDurationToOrSubtractDurationFromPlainTime ( operation, temporalTime,
1016 * temporalDurationLike )
1018 static bool AddDurationToOrSubtractDurationFromPlainTime(
1019 JSContext* cx, PlainTimeDuration operation, const CallArgs& args) {
1020 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1021 auto time = ToPlainTime(temporalTime);
1023 // Step 1. (Not applicable in our implementation.)
1025 // Step 2.
1026 Duration duration;
1027 if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
1028 return false;
1031 // Step 3.
1032 if (operation == PlainTimeDuration::Subtract) {
1033 duration = duration.negate();
1035 auto timeDuration = NormalizeTimeDuration(duration);
1037 // Step 4.
1038 auto result = AddTime(time, timeDuration);
1039 MOZ_ASSERT(IsValidTime(result.time));
1041 // Step 5.
1042 auto* obj = CreateTemporalTime(cx, result.time);
1043 if (!obj) {
1044 return false;
1047 args.rval().setObject(*obj);
1048 return true;
1052 * Temporal.PlainTime ( [ hour [ , minute [ , second [ , millisecond [ ,
1053 * microsecond [ , nanosecond ] ] ] ] ] ] )
1055 static bool PlainTimeConstructor(JSContext* cx, unsigned argc, Value* vp) {
1056 CallArgs args = CallArgsFromVp(argc, vp);
1058 // Step 1.
1059 if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainTime")) {
1060 return false;
1063 // Step 2.
1064 double hour = 0;
1065 if (args.hasDefined(0)) {
1066 if (!ToIntegerWithTruncation(cx, args[0], "hour", &hour)) {
1067 return false;
1071 // Step 3.
1072 double minute = 0;
1073 if (args.hasDefined(1)) {
1074 if (!ToIntegerWithTruncation(cx, args[1], "minute", &minute)) {
1075 return false;
1079 // Step 4.
1080 double second = 0;
1081 if (args.hasDefined(2)) {
1082 if (!ToIntegerWithTruncation(cx, args[2], "second", &second)) {
1083 return false;
1087 // Step 5.
1088 double millisecond = 0;
1089 if (args.hasDefined(3)) {
1090 if (!ToIntegerWithTruncation(cx, args[3], "millisecond", &millisecond)) {
1091 return false;
1095 // Step 6.
1096 double microsecond = 0;
1097 if (args.hasDefined(4)) {
1098 if (!ToIntegerWithTruncation(cx, args[4], "microsecond", &microsecond)) {
1099 return false;
1103 // Step 7.
1104 double nanosecond = 0;
1105 if (args.hasDefined(5)) {
1106 if (!ToIntegerWithTruncation(cx, args[5], "nanosecond", &nanosecond)) {
1107 return false;
1111 // Step 8.
1112 auto* temporalTime = CreateTemporalTime(cx, args, hour, minute, second,
1113 millisecond, microsecond, nanosecond);
1114 if (!temporalTime) {
1115 return false;
1118 args.rval().setObject(*temporalTime);
1119 return true;
1123 * Temporal.PlainTime.from ( item [ , options ] )
1125 static bool PlainTime_from(JSContext* cx, unsigned argc, Value* vp) {
1126 CallArgs args = CallArgsFromVp(argc, vp);
1128 // Step 1. (Not applicable)
1130 auto overflow = TemporalOverflow::Constrain;
1131 if (args.hasDefined(1)) {
1132 // Step 2.
1133 Rooted<JSObject*> options(cx,
1134 RequireObjectArg(cx, "options", "from", args[1]));
1135 if (!options) {
1136 return false;
1139 // Step 3.
1140 if (!ToTemporalOverflow(cx, options, &overflow)) {
1141 return false;
1145 // Steps 4-5.
1146 auto* result = ToTemporalTime(cx, args.get(0), overflow);
1147 if (!result) {
1148 return false;
1151 args.rval().setObject(*result);
1152 return true;
1156 * Temporal.PlainTime.compare ( one, two )
1158 static bool PlainTime_compare(JSContext* cx, unsigned argc, Value* vp) {
1159 CallArgs args = CallArgsFromVp(argc, vp);
1161 // Step 1.
1162 PlainTime one;
1163 if (!ToTemporalTime(cx, args.get(0), &one)) {
1164 return false;
1167 // Step 2.
1168 PlainTime two;
1169 if (!ToTemporalTime(cx, args.get(1), &two)) {
1170 return false;
1173 // Step 3.
1174 args.rval().setInt32(CompareTemporalTime(one, two));
1175 return true;
1179 * get Temporal.PlainTime.prototype.hour
1181 static bool PlainTime_hour(JSContext* cx, const CallArgs& args) {
1182 // Step 3.
1183 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1184 args.rval().setInt32(temporalTime->isoHour());
1185 return true;
1189 * get Temporal.PlainTime.prototype.hour
1191 static bool PlainTime_hour(JSContext* cx, unsigned argc, Value* vp) {
1192 // Steps 1-2.
1193 CallArgs args = CallArgsFromVp(argc, vp);
1194 return CallNonGenericMethod<IsPlainTime, PlainTime_hour>(cx, args);
1198 * get Temporal.PlainTime.prototype.minute
1200 static bool PlainTime_minute(JSContext* cx, const CallArgs& args) {
1201 // Step 3.
1202 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1203 args.rval().setInt32(temporalTime->isoMinute());
1204 return true;
1208 * get Temporal.PlainTime.prototype.minute
1210 static bool PlainTime_minute(JSContext* cx, unsigned argc, Value* vp) {
1211 // Steps 1-2.
1212 CallArgs args = CallArgsFromVp(argc, vp);
1213 return CallNonGenericMethod<IsPlainTime, PlainTime_minute>(cx, args);
1217 * get Temporal.PlainTime.prototype.second
1219 static bool PlainTime_second(JSContext* cx, const CallArgs& args) {
1220 // Step 3.
1221 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1222 args.rval().setInt32(temporalTime->isoSecond());
1223 return true;
1227 * get Temporal.PlainTime.prototype.second
1229 static bool PlainTime_second(JSContext* cx, unsigned argc, Value* vp) {
1230 // Steps 1-2.
1231 CallArgs args = CallArgsFromVp(argc, vp);
1232 return CallNonGenericMethod<IsPlainTime, PlainTime_second>(cx, args);
1236 * get Temporal.PlainTime.prototype.millisecond
1238 static bool PlainTime_millisecond(JSContext* cx, const CallArgs& args) {
1239 // Step 3.
1240 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1241 args.rval().setInt32(temporalTime->isoMillisecond());
1242 return true;
1246 * get Temporal.PlainTime.prototype.millisecond
1248 static bool PlainTime_millisecond(JSContext* cx, unsigned argc, Value* vp) {
1249 // Steps 1-2.
1250 CallArgs args = CallArgsFromVp(argc, vp);
1251 return CallNonGenericMethod<IsPlainTime, PlainTime_millisecond>(cx, args);
1255 * get Temporal.PlainTime.prototype.microsecond
1257 static bool PlainTime_microsecond(JSContext* cx, const CallArgs& args) {
1258 // Step 3.
1259 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1260 args.rval().setInt32(temporalTime->isoMicrosecond());
1261 return true;
1265 * get Temporal.PlainTime.prototype.microsecond
1267 static bool PlainTime_microsecond(JSContext* cx, unsigned argc, Value* vp) {
1268 // Steps 1-2.
1269 CallArgs args = CallArgsFromVp(argc, vp);
1270 return CallNonGenericMethod<IsPlainTime, PlainTime_microsecond>(cx, args);
1274 * get Temporal.PlainTime.prototype.nanosecond
1276 static bool PlainTime_nanosecond(JSContext* cx, const CallArgs& args) {
1277 // Step 3.
1278 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1279 args.rval().setInt32(temporalTime->isoNanosecond());
1280 return true;
1284 * get Temporal.PlainTime.prototype.nanosecond
1286 static bool PlainTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) {
1287 // Steps 1-2.
1288 CallArgs args = CallArgsFromVp(argc, vp);
1289 return CallNonGenericMethod<IsPlainTime, PlainTime_nanosecond>(cx, args);
1293 * Temporal.PlainTime.prototype.add ( temporalDurationLike )
1295 static bool PlainTime_add(JSContext* cx, const CallArgs& args) {
1296 // Step 3.
1297 return AddDurationToOrSubtractDurationFromPlainTime(
1298 cx, PlainTimeDuration::Add, args);
1302 * Temporal.PlainTime.prototype.add ( temporalDurationLike )
1304 static bool PlainTime_add(JSContext* cx, unsigned argc, Value* vp) {
1305 // Steps 1-2.
1306 CallArgs args = CallArgsFromVp(argc, vp);
1307 return CallNonGenericMethod<IsPlainTime, PlainTime_add>(cx, args);
1311 * Temporal.PlainTime.prototype.subtract ( temporalDurationLike )
1313 static bool PlainTime_subtract(JSContext* cx, const CallArgs& args) {
1314 // Step 3.
1315 return AddDurationToOrSubtractDurationFromPlainTime(
1316 cx, PlainTimeDuration::Subtract, args);
1320 * Temporal.PlainTime.prototype.subtract ( temporalDurationLike )
1322 static bool PlainTime_subtract(JSContext* cx, unsigned argc, Value* vp) {
1323 // Steps 1-2.
1324 CallArgs args = CallArgsFromVp(argc, vp);
1325 return CallNonGenericMethod<IsPlainTime, PlainTime_subtract>(cx, args);
1329 * Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] )
1331 static bool PlainTime_with(JSContext* cx, const CallArgs& args) {
1332 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1333 auto time = ToPlainTime(temporalTime);
1335 // Step 3.
1336 Rooted<JSObject*> temporalTimeLike(
1337 cx, RequireObjectArg(cx, "temporalTimeLike", "with", args.get(0)));
1338 if (!temporalTimeLike) {
1339 return false;
1342 // Step 4.
1343 if (!RejectTemporalLikeObject(cx, temporalTimeLike)) {
1344 return false;
1347 auto overflow = TemporalOverflow::Constrain;
1348 if (args.hasDefined(1)) {
1349 // Step 5.
1350 Rooted<JSObject*> options(cx,
1351 RequireObjectArg(cx, "options", "with", args[1]));
1352 if (!options) {
1353 return false;
1356 // Step 6.
1357 if (!ToTemporalOverflow(cx, options, &overflow)) {
1358 return false;
1362 // Steps 7-19.
1363 TemporalTimeLike partialTime = {
1364 double(time.hour), double(time.minute),
1365 double(time.second), double(time.millisecond),
1366 double(time.microsecond), double(time.nanosecond),
1368 if (!::ToTemporalTimeRecord(cx, temporalTimeLike, &partialTime)) {
1369 return false;
1372 // Step 20.
1373 PlainTime result;
1374 if (!RegulateTime(cx, partialTime, overflow, &result)) {
1375 return false;
1378 // Step 21.
1379 auto* obj = CreateTemporalTime(cx, result);
1380 if (!obj) {
1381 return false;
1384 args.rval().setObject(*obj);
1385 return true;
1389 * Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] )
1391 static bool PlainTime_with(JSContext* cx, unsigned argc, Value* vp) {
1392 // Steps 1-2.
1393 CallArgs args = CallArgsFromVp(argc, vp);
1394 return CallNonGenericMethod<IsPlainTime, PlainTime_with>(cx, args);
1398 * Temporal.PlainTime.prototype.until ( other [ , options ] )
1400 static bool PlainTime_until(JSContext* cx, const CallArgs& args) {
1401 // Step 3.
1402 return DifferenceTemporalPlainTime(cx, TemporalDifference::Until, args);
1406 * Temporal.PlainTime.prototype.until ( other [ , options ] )
1408 static bool PlainTime_until(JSContext* cx, unsigned argc, Value* vp) {
1409 // Steps 1-2.
1410 CallArgs args = CallArgsFromVp(argc, vp);
1411 return CallNonGenericMethod<IsPlainTime, PlainTime_until>(cx, args);
1415 * Temporal.PlainTime.prototype.since ( other [ , options ] )
1417 static bool PlainTime_since(JSContext* cx, const CallArgs& args) {
1418 // Step 3.
1419 return DifferenceTemporalPlainTime(cx, TemporalDifference::Since, args);
1423 * Temporal.PlainTime.prototype.since ( other [ , options ] )
1425 static bool PlainTime_since(JSContext* cx, unsigned argc, Value* vp) {
1426 // Steps 1-2.
1427 CallArgs args = CallArgsFromVp(argc, vp);
1428 return CallNonGenericMethod<IsPlainTime, PlainTime_since>(cx, args);
1432 * Temporal.PlainTime.prototype.round ( roundTo )
1434 static bool PlainTime_round(JSContext* cx, const CallArgs& args) {
1435 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1436 auto time = ToPlainTime(temporalTime);
1438 // Steps 3-12.
1439 auto smallestUnit = TemporalUnit::Auto;
1440 auto roundingMode = TemporalRoundingMode::HalfExpand;
1441 auto roundingIncrement = Increment{1};
1442 if (args.get(0).isString()) {
1443 // Step 4. (Not applicable in our implementation.)
1445 // Step 9.
1446 Rooted<JSString*> paramString(cx, args[0].toString());
1447 if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
1448 TemporalUnitGroup::Time, &smallestUnit)) {
1449 return false;
1452 // Steps 6-8 and 10-12. (Implicit)
1453 } else {
1454 // Steps 3 and 5.
1455 Rooted<JSObject*> options(
1456 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
1457 if (!options) {
1458 return false;
1461 // Steps 6-7.
1462 if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
1463 return false;
1466 // Step 8.
1467 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
1468 return false;
1471 // Step 9.
1472 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
1473 TemporalUnitGroup::Time, &smallestUnit)) {
1474 return false;
1477 if (smallestUnit == TemporalUnit::Auto) {
1478 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1479 JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
1480 return false;
1483 // Steps 10-11.
1484 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
1486 // Step 12.
1487 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
1488 false)) {
1489 return false;
1493 // Step 13.
1494 auto result = RoundTime(time, roundingIncrement, smallestUnit, roundingMode);
1496 // Step 14.
1497 auto* obj = CreateTemporalTime(cx, result.time);
1498 if (!obj) {
1499 return false;
1502 args.rval().setObject(*obj);
1503 return true;
1507 * Temporal.PlainTime.prototype.round ( roundTo )
1509 static bool PlainTime_round(JSContext* cx, unsigned argc, Value* vp) {
1510 // Steps 1-2.
1511 CallArgs args = CallArgsFromVp(argc, vp);
1512 return CallNonGenericMethod<IsPlainTime, PlainTime_round>(cx, args);
1516 * Temporal.PlainTime.prototype.equals ( other )
1518 static bool PlainTime_equals(JSContext* cx, const CallArgs& args) {
1519 auto temporalTime =
1520 ToPlainTime(&args.thisv().toObject().as<PlainTimeObject>());
1522 // Step 3.
1523 PlainTime other;
1524 if (!ToTemporalTime(cx, args.get(0), &other)) {
1525 return false;
1528 // Steps 4-10.
1529 args.rval().setBoolean(temporalTime == other);
1530 return true;
1534 * Temporal.PlainTime.prototype.equals ( other )
1536 static bool PlainTime_equals(JSContext* cx, unsigned argc, Value* vp) {
1537 // Steps 1-2.
1538 CallArgs args = CallArgsFromVp(argc, vp);
1539 return CallNonGenericMethod<IsPlainTime, PlainTime_equals>(cx, args);
1543 * Temporal.PlainTime.prototype.toPlainDateTime ( temporalDate )
1545 static bool PlainTime_toPlainDateTime(JSContext* cx, const CallArgs& args) {
1546 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1547 auto time = ToPlainTime(temporalTime);
1549 // Step 3.
1550 Rooted<PlainDateWithCalendar> plainDate(cx);
1551 if (!ToTemporalDate(cx, args.get(0), &plainDate)) {
1552 return false;
1554 auto date = plainDate.date();
1555 auto calendar = plainDate.calendar();
1557 // Step 4.
1558 auto* result = CreateTemporalDateTime(cx, {date, time}, calendar);
1559 if (!result) {
1560 return false;
1563 args.rval().setObject(*result);
1564 return true;
1568 * Temporal.PlainTime.prototype.toPlainDateTime ( temporalDate )
1570 static bool PlainTime_toPlainDateTime(JSContext* cx, unsigned argc, Value* vp) {
1571 // Steps 1-2.
1572 CallArgs args = CallArgsFromVp(argc, vp);
1573 return CallNonGenericMethod<IsPlainTime, PlainTime_toPlainDateTime>(cx, args);
1577 * Temporal.PlainTime.prototype.toZonedDateTime ( item )
1579 * |item| is an options object with `plainDate` and `timeZone` properties.
1581 static bool PlainTime_toZonedDateTime(JSContext* cx, const CallArgs& args) {
1582 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1583 auto time = ToPlainTime(temporalTime);
1585 // Step 3.
1586 Rooted<JSObject*> itemObj(
1587 cx, RequireObjectArg(cx, "item", "toZonedDateTime", args.get(0)));
1588 if (!itemObj) {
1589 return false;
1592 // Step 4.
1593 Rooted<Value> temporalDateLike(cx);
1594 if (!GetProperty(cx, itemObj, args[0], cx->names().plainDate,
1595 &temporalDateLike)) {
1596 return false;
1599 // Step 5.
1600 if (temporalDateLike.isUndefined()) {
1601 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1602 JSMSG_TEMPORAL_MISSING_PROPERTY, "plainDate");
1603 return false;
1606 // Step 6.
1607 Rooted<PlainDateWithCalendar> plainDate(cx);
1608 if (!ToTemporalDate(cx, temporalDateLike, &plainDate)) {
1609 return false;
1611 auto date = plainDate.date();
1612 auto calendar = plainDate.calendar();
1614 // Step 7.
1615 Rooted<Value> temporalTimeZoneLike(cx);
1616 if (!GetProperty(cx, itemObj, itemObj, cx->names().timeZone,
1617 &temporalTimeZoneLike)) {
1618 return false;
1621 // Step 8.
1622 if (temporalTimeZoneLike.isUndefined()) {
1623 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1624 JSMSG_TEMPORAL_MISSING_PROPERTY, "timeZone");
1625 return false;
1628 // Step 9.
1629 Rooted<TimeZoneValue> timeZone(cx);
1630 if (!ToTemporalTimeZone(cx, temporalTimeZoneLike, &timeZone)) {
1631 return false;
1634 // Step 10.
1635 Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
1636 if (!CreateTemporalDateTime(cx, {date, time}, calendar, &temporalDateTime)) {
1637 return false;
1640 // Steps 11-12.
1641 Instant instant;
1642 if (!GetInstantFor(cx, timeZone, temporalDateTime,
1643 TemporalDisambiguation::Compatible, &instant)) {
1644 return false;
1647 // Step 13.
1648 auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
1649 if (!result) {
1650 return false;
1653 args.rval().setObject(*result);
1654 return true;
1658 * Temporal.PlainTime.prototype.toZonedDateTime ( item )
1660 static bool PlainTime_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) {
1661 // Steps 1-2.
1662 CallArgs args = CallArgsFromVp(argc, vp);
1663 return CallNonGenericMethod<IsPlainTime, PlainTime_toZonedDateTime>(cx, args);
1667 * Temporal.PlainTime.prototype.getISOFields ( )
1669 static bool PlainTime_getISOFields(JSContext* cx, const CallArgs& args) {
1670 Rooted<PlainTimeObject*> temporalTime(
1671 cx, &args.thisv().toObject().as<PlainTimeObject>());
1672 auto time = ToPlainTime(temporalTime);
1674 // Step 3.
1675 Rooted<IdValueVector> fields(cx, IdValueVector(cx));
1677 // Step 4.
1678 if (!fields.emplaceBack(NameToId(cx->names().isoHour),
1679 Int32Value(time.hour))) {
1680 return false;
1683 // Step 5.
1684 if (!fields.emplaceBack(NameToId(cx->names().isoMicrosecond),
1685 Int32Value(time.microsecond))) {
1686 return false;
1689 // Step 6.
1690 if (!fields.emplaceBack(NameToId(cx->names().isoMillisecond),
1691 Int32Value(time.millisecond))) {
1692 return false;
1695 // Step 7.
1696 if (!fields.emplaceBack(NameToId(cx->names().isoMinute),
1697 Int32Value(time.minute))) {
1698 return false;
1701 // Step 8.
1702 if (!fields.emplaceBack(NameToId(cx->names().isoNanosecond),
1703 Int32Value(time.nanosecond))) {
1704 return false;
1707 // Step 9.
1708 if (!fields.emplaceBack(NameToId(cx->names().isoSecond),
1709 Int32Value(time.second))) {
1710 return false;
1713 // Step 10.
1714 auto* obj = NewPlainObjectWithUniqueNames(cx, fields);
1715 if (!obj) {
1716 return false;
1719 args.rval().setObject(*obj);
1720 return true;
1724 * Temporal.PlainTime.prototype.getISOFields ( )
1726 static bool PlainTime_getISOFields(JSContext* cx, unsigned argc, Value* vp) {
1727 // Steps 1-2.
1728 CallArgs args = CallArgsFromVp(argc, vp);
1729 return CallNonGenericMethod<IsPlainTime, PlainTime_getISOFields>(cx, args);
1733 * Temporal.PlainTime.prototype.toString ( [ options ] )
1735 static bool PlainTime_toString(JSContext* cx, const CallArgs& args) {
1736 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1737 auto time = ToPlainTime(temporalTime);
1739 SecondsStringPrecision precision = {Precision::Auto(),
1740 TemporalUnit::Nanosecond, Increment{1}};
1741 auto roundingMode = TemporalRoundingMode::Trunc;
1742 if (args.hasDefined(0)) {
1743 // Step 3.
1744 Rooted<JSObject*> options(
1745 cx, RequireObjectArg(cx, "options", "toString", args[0]));
1746 if (!options) {
1747 return false;
1750 // Steps 4-5.
1751 auto digits = Precision::Auto();
1752 if (!ToFractionalSecondDigits(cx, options, &digits)) {
1753 return false;
1756 // Step 6.
1757 if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
1758 return false;
1761 // Step 7.
1762 auto smallestUnit = TemporalUnit::Auto;
1763 if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
1764 TemporalUnitGroup::Time, &smallestUnit)) {
1765 return false;
1768 // Step 8.
1769 if (smallestUnit == TemporalUnit::Hour) {
1770 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1771 JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
1772 "smallestUnit");
1773 return false;
1776 // Step 9.
1777 precision = ToSecondsStringPrecision(smallestUnit, digits);
1780 // Step 10.
1781 auto roundedTime =
1782 RoundTime(time, precision.increment, precision.unit, roundingMode);
1784 // Step 11.
1785 JSString* str =
1786 TemporalTimeToString(cx, roundedTime.time, precision.precision);
1787 if (!str) {
1788 return false;
1791 args.rval().setString(str);
1792 return true;
1796 * Temporal.PlainTime.prototype.toString ( [ options ] )
1798 static bool PlainTime_toString(JSContext* cx, unsigned argc, Value* vp) {
1799 // Steps 1-2.
1800 CallArgs args = CallArgsFromVp(argc, vp);
1801 return CallNonGenericMethod<IsPlainTime, PlainTime_toString>(cx, args);
1805 * Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] )
1807 static bool PlainTime_toLocaleString(JSContext* cx, const CallArgs& args) {
1808 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1809 auto time = ToPlainTime(temporalTime);
1811 // Step 3.
1812 JSString* str = TemporalTimeToString(cx, time, Precision::Auto());
1813 if (!str) {
1814 return false;
1817 args.rval().setString(str);
1818 return true;
1822 * Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] )
1824 static bool PlainTime_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
1825 // Steps 1-2.
1826 CallArgs args = CallArgsFromVp(argc, vp);
1827 return CallNonGenericMethod<IsPlainTime, PlainTime_toLocaleString>(cx, args);
1831 * Temporal.PlainTime.prototype.toJSON ( )
1833 static bool PlainTime_toJSON(JSContext* cx, const CallArgs& args) {
1834 auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
1835 auto time = ToPlainTime(temporalTime);
1837 // Step 3.
1838 JSString* str = TemporalTimeToString(cx, time, Precision::Auto());
1839 if (!str) {
1840 return false;
1843 args.rval().setString(str);
1844 return true;
1848 * Temporal.PlainTime.prototype.toJSON ( )
1850 static bool PlainTime_toJSON(JSContext* cx, unsigned argc, Value* vp) {
1851 // Steps 1-2.
1852 CallArgs args = CallArgsFromVp(argc, vp);
1853 return CallNonGenericMethod<IsPlainTime, PlainTime_toJSON>(cx, args);
1857 * Temporal.PlainTime.prototype.valueOf ( )
1859 static bool PlainTime_valueOf(JSContext* cx, unsigned argc, Value* vp) {
1860 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
1861 "PlainTime", "primitive type");
1862 return false;
1865 const JSClass PlainTimeObject::class_ = {
1866 "Temporal.PlainTime",
1867 JSCLASS_HAS_RESERVED_SLOTS(PlainTimeObject::SLOT_COUNT) |
1868 JSCLASS_HAS_CACHED_PROTO(JSProto_PlainTime),
1869 JS_NULL_CLASS_OPS,
1870 &PlainTimeObject::classSpec_,
1873 const JSClass& PlainTimeObject::protoClass_ = PlainObject::class_;
1875 static const JSFunctionSpec PlainTime_methods[] = {
1876 JS_FN("from", PlainTime_from, 1, 0),
1877 JS_FN("compare", PlainTime_compare, 2, 0),
1878 JS_FS_END,
1881 static const JSFunctionSpec PlainTime_prototype_methods[] = {
1882 JS_FN("add", PlainTime_add, 1, 0),
1883 JS_FN("subtract", PlainTime_subtract, 1, 0),
1884 JS_FN("with", PlainTime_with, 1, 0),
1885 JS_FN("until", PlainTime_until, 1, 0),
1886 JS_FN("since", PlainTime_since, 1, 0),
1887 JS_FN("round", PlainTime_round, 1, 0),
1888 JS_FN("equals", PlainTime_equals, 1, 0),
1889 JS_FN("toPlainDateTime", PlainTime_toPlainDateTime, 1, 0),
1890 JS_FN("toZonedDateTime", PlainTime_toZonedDateTime, 1, 0),
1891 JS_FN("getISOFields", PlainTime_getISOFields, 0, 0),
1892 JS_FN("toString", PlainTime_toString, 0, 0),
1893 JS_FN("toLocaleString", PlainTime_toLocaleString, 0, 0),
1894 JS_FN("toJSON", PlainTime_toJSON, 0, 0),
1895 JS_FN("valueOf", PlainTime_valueOf, 0, 0),
1896 JS_FS_END,
1899 static const JSPropertySpec PlainTime_prototype_properties[] = {
1900 JS_PSG("hour", PlainTime_hour, 0),
1901 JS_PSG("minute", PlainTime_minute, 0),
1902 JS_PSG("second", PlainTime_second, 0),
1903 JS_PSG("millisecond", PlainTime_millisecond, 0),
1904 JS_PSG("microsecond", PlainTime_microsecond, 0),
1905 JS_PSG("nanosecond", PlainTime_nanosecond, 0),
1906 JS_STRING_SYM_PS(toStringTag, "Temporal.PlainTime", JSPROP_READONLY),
1907 JS_PS_END,
1910 const ClassSpec PlainTimeObject::classSpec_ = {
1911 GenericCreateConstructor<PlainTimeConstructor, 0, gc::AllocKind::FUNCTION>,
1912 GenericCreatePrototype<PlainTimeObject>,
1913 PlainTime_methods,
1914 nullptr,
1915 PlainTime_prototype_methods,
1916 PlainTime_prototype_properties,
1917 nullptr,
1918 ClassSpec::DontDefineConstructor,