Bug 1874684 - Part 33: Defer allocation of options object for CalendarDateFromFields...
[gecko.git] / js / src / builtin / temporal / Calendar.cpp
blob49b00ad9526872445c0cf329a25753181ac09107
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/Calendar.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/EnumSet.h"
12 #include "mozilla/FloatingPoint.h"
13 #include "mozilla/Likely.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/Range.h"
16 #include "mozilla/TextUtils.h"
18 #include <algorithm>
19 #include <array>
20 #include <cmath>
21 #include <cstring>
22 #include <iterator>
23 #include <stddef.h>
24 #include <stdint.h>
25 #include <utility>
27 #include "jsfriendapi.h"
28 #include "jsnum.h"
29 #include "jspubtd.h"
30 #include "jstypes.h"
31 #include "NamespaceImports.h"
33 #include "builtin/Array.h"
34 #include "builtin/String.h"
35 #include "builtin/temporal/Duration.h"
36 #include "builtin/temporal/PlainDate.h"
37 #include "builtin/temporal/PlainDateTime.h"
38 #include "builtin/temporal/PlainMonthDay.h"
39 #include "builtin/temporal/PlainTime.h"
40 #include "builtin/temporal/PlainYearMonth.h"
41 #include "builtin/temporal/Temporal.h"
42 #include "builtin/temporal/TemporalFields.h"
43 #include "builtin/temporal/TemporalParser.h"
44 #include "builtin/temporal/TemporalTypes.h"
45 #include "builtin/temporal/TemporalUnit.h"
46 #include "builtin/temporal/Wrapped.h"
47 #include "builtin/temporal/ZonedDateTime.h"
48 #include "gc/AllocKind.h"
49 #include "gc/Barrier.h"
50 #include "gc/GCEnum.h"
51 #include "gc/Tracer.h"
52 #include "js/AllocPolicy.h"
53 #include "js/CallArgs.h"
54 #include "js/CallNonGenericMethod.h"
55 #include "js/Class.h"
56 #include "js/Conversions.h"
57 #include "js/ErrorReport.h"
58 #include "js/ForOfIterator.h"
59 #include "js/friend/ErrorMessages.h"
60 #include "js/GCAPI.h"
61 #include "js/GCHashTable.h"
62 #include "js/GCVector.h"
63 #include "js/Id.h"
64 #include "js/Printer.h"
65 #include "js/PropertyDescriptor.h"
66 #include "js/PropertySpec.h"
67 #include "js/RootingAPI.h"
68 #include "js/TracingAPI.h"
69 #include "js/Value.h"
70 #include "util/Text.h"
71 #include "vm/ArrayObject.h"
72 #include "vm/BytecodeUtil.h"
73 #include "vm/Compartment.h"
74 #include "vm/GlobalObject.h"
75 #include "vm/Interpreter.h"
76 #include "vm/JSAtomState.h"
77 #include "vm/JSContext.h"
78 #include "vm/JSObject.h"
79 #include "vm/PlainObject.h"
80 #include "vm/PropertyInfo.h"
81 #include "vm/PropertyKey.h"
82 #include "vm/Realm.h"
83 #include "vm/Shape.h"
84 #include "vm/Stack.h"
85 #include "vm/StringType.h"
87 #include "vm/Compartment-inl.h"
88 #include "vm/JSAtomUtils-inl.h"
89 #include "vm/JSObject-inl.h"
90 #include "vm/NativeObject-inl.h"
91 #include "vm/ObjectOperations-inl.h"
93 using namespace js;
94 using namespace js::temporal;
96 static inline bool IsCalendar(Handle<Value> v) {
97 return v.isObject() && v.toObject().is<CalendarObject>();
100 void js::temporal::CalendarValue::trace(JSTracer* trc) {
101 TraceRoot(trc, &value_, "CalendarValue::value");
104 void js::temporal::CalendarRecord::trace(JSTracer* trc) {
105 receiver_.trace(trc);
106 TraceNullableRoot(trc, &dateAdd_, "CalendarRecord::dateAdd");
107 TraceNullableRoot(trc, &dateFromFields_, "CalendarRecord::dateFromFields");
108 TraceNullableRoot(trc, &dateUntil_, "CalendarRecord::dateUntil");
109 TraceNullableRoot(trc, &day_, "CalendarRecord::day");
110 TraceNullableRoot(trc, &fields_, "CalendarRecord::fields");
111 TraceNullableRoot(trc, &mergeFields_, "CalendarRecord::mergeFields");
112 TraceNullableRoot(trc, &monthDayFromFields_,
113 "CalendarRecord::monthDayFromFields");
114 TraceNullableRoot(trc, &yearMonthFromFields_,
115 "CalendarRecord::yearMonthFromFields");
118 bool js::temporal::WrapCalendarValue(JSContext* cx,
119 MutableHandle<JS::Value> calendar) {
120 MOZ_ASSERT(calendar.isString() || calendar.isObject());
121 return cx->compartment()->wrap(cx, calendar);
125 * IteratorToListOfType ( iteratorRecord, elementTypes )
127 * With `elementTypes = « String »`.
129 * This implementation accepts an iterable instead of an iterator record.
131 static bool IterableToListOfStrings(JSContext* cx, Handle<Value> items,
132 MutableHandle<CalendarFieldNames> list) {
133 JS::ForOfIterator iterator(cx);
134 if (!iterator.init(items)) {
135 return false;
138 // Step 1. (Not applicable in our implementation.)
140 // Step 2.
141 Rooted<Value> nextValue(cx);
142 Rooted<PropertyKey> value(cx);
143 while (true) {
144 // Step 2.a.
145 bool done;
146 if (!iterator.next(&nextValue, &done)) {
147 return false;
150 // Step 2.b.
151 if (done) {
152 return true;
155 // Step 2.d. (Reordered)
156 if (nextValue.isString()) {
157 if (!PrimitiveValueToId<CanGC>(cx, nextValue, &value)) {
158 return false;
160 if (!list.append(value)) {
161 return false;
163 continue;
166 // Step 2.c.1.
167 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
168 nullptr, "not a string");
170 // Step 2.c.2.
171 iterator.closeThrow();
172 return false;
177 * IsISOLeapYear ( year )
179 static constexpr bool IsISOLeapYear(int32_t year) {
180 // Steps 1-5.
181 return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
185 * IsISOLeapYear ( year )
187 static bool IsISOLeapYear(double year) {
188 // Step 1.
189 MOZ_ASSERT(IsInteger(year));
191 // Steps 2-5.
192 return std::fmod(year, 4) == 0 &&
193 (std::fmod(year, 100) != 0 || std::fmod(year, 400) == 0);
197 * ISODaysInYear ( year )
199 int32_t js::temporal::ISODaysInYear(int32_t year) {
200 // Steps 1-3.
201 return IsISOLeapYear(year) ? 366 : 365;
205 * ISODaysInMonth ( year, month )
207 static constexpr int32_t ISODaysInMonth(int32_t year, int32_t month) {
208 MOZ_ASSERT(1 <= month && month <= 12);
210 constexpr uint8_t daysInMonth[2][13] = {
211 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
212 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
214 // Steps 1-4.
215 return daysInMonth[IsISOLeapYear(year)][month];
219 * ISODaysInMonth ( year, month )
221 int32_t js::temporal::ISODaysInMonth(int32_t year, int32_t month) {
222 return ::ISODaysInMonth(year, month);
226 * ISODaysInMonth ( year, month )
228 int32_t js::temporal::ISODaysInMonth(double year, int32_t month) {
229 MOZ_ASSERT(1 <= month && month <= 12);
231 static constexpr uint8_t daysInMonth[2][13] = {
232 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
233 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
235 // Steps 1-4.
236 return daysInMonth[IsISOLeapYear(year)][month];
240 * 21.4.1.6 Week Day
242 * Compute the week day from |day| without first expanding |day| into a full
243 * date through |MakeDate(day, 0)|:
245 * WeekDay(MakeDate(day, 0))
246 * = WeekDay(day × msPerDay + 0)
247 * = WeekDay(day × msPerDay)
248 * = 𝔽(ℝ(Day(day × msPerDay) + 4𝔽) modulo 7)
249 * = 𝔽(ℝ(𝔽(floor(ℝ((day × msPerDay) / msPerDay))) + 4𝔽) modulo 7)
250 * = 𝔽(ℝ(𝔽(floor(ℝ(day))) + 4𝔽) modulo 7)
251 * = 𝔽(ℝ(𝔽(day) + 4𝔽) modulo 7)
253 static int32_t WeekDay(int32_t day) {
254 int32_t result = (day + 4) % 7;
255 if (result < 0) {
256 result += 7;
258 return result;
262 * ToISODayOfWeek ( year, month, day )
264 static int32_t ToISODayOfWeek(const PlainDate& date) {
265 MOZ_ASSERT(ISODateTimeWithinLimits(date));
267 // Steps 1-3. (Not applicable in our implementation.)
269 // TODO: Check if ES MakeDate + WeekDay is efficient enough.
271 // https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Methods_in_computer_code
273 // Step 4.
274 int32_t day = MakeDay(date);
276 // Step 5.
277 int32_t weekday = WeekDay(day);
278 return weekday != 0 ? weekday : 7;
281 static constexpr auto FirstDayOfMonth(int32_t year) {
282 // The following array contains the day of year for the first day of each
283 // month, where index 0 is January, and day 0 is January 1.
284 std::array<int32_t, 13> days = {};
285 for (int32_t month = 1; month <= 12; ++month) {
286 days[month] = days[month - 1] + ::ISODaysInMonth(year, month);
288 return days;
292 * ToISODayOfYear ( year, month, day )
294 static int32_t ToISODayOfYear(int32_t year, int32_t month, int32_t day) {
295 MOZ_ASSERT(1 <= month && month <= 12);
297 // First day of month arrays for non-leap and leap years.
298 constexpr decltype(FirstDayOfMonth(0)) firstDayOfMonth[2] = {
299 FirstDayOfMonth(1), FirstDayOfMonth(0)};
301 // Steps 1-3. (Not applicable in our implementation.)
303 // Steps 4-5.
305 // Instead of first computing the date and then using DayWithinYear to map the
306 // date to the day within the year, directly lookup the first day of the month
307 // and then add the additional days.
308 return firstDayOfMonth[IsISOLeapYear(year)][month - 1] + day;
312 * ToISODayOfYear ( year, month, day )
314 int32_t js::temporal::ToISODayOfYear(const PlainDate& date) {
315 MOZ_ASSERT(ISODateTimeWithinLimits(date));
317 // Steps 1-5.
318 const auto& [year, month, day] = date;
319 return ::ToISODayOfYear(year, month, day);
322 static int32_t FloorDiv(int32_t dividend, int32_t divisor) {
323 MOZ_ASSERT(divisor > 0);
325 int32_t quotient = dividend / divisor;
326 int32_t remainder = dividend % divisor;
327 if (remainder < 0) {
328 quotient -= 1;
330 return quotient;
334 * 21.4.1.3 Year Number, DayFromYear
336 static int32_t DayFromYear(int32_t year) {
337 return 365 * (year - 1970) + FloorDiv(year - 1969, 4) -
338 FloorDiv(year - 1901, 100) + FloorDiv(year - 1601, 400);
342 * 21.4.1.11 MakeTime ( hour, min, sec, ms )
344 static int64_t MakeTime(const PlainTime& time) {
345 MOZ_ASSERT(IsValidTime(time));
347 // Step 1 (Not applicable).
349 // Step 2.
350 int64_t h = time.hour;
352 // Step 3.
353 int64_t m = time.minute;
355 // Step 4.
356 int64_t s = time.second;
358 // Step 5.
359 int64_t milli = time.millisecond;
361 // Steps 6-7.
362 return h * ToMilliseconds(TemporalUnit::Hour) +
363 m * ToMilliseconds(TemporalUnit::Minute) +
364 s * ToMilliseconds(TemporalUnit::Second) + milli;
368 * 21.4.1.12 MakeDay ( year, month, date )
370 int32_t js::temporal::MakeDay(const PlainDate& date) {
371 MOZ_ASSERT(ISODateTimeWithinLimits(date));
373 return DayFromYear(date.year) + ToISODayOfYear(date) - 1;
377 * 21.4.1.13 MakeDate ( day, time )
379 int64_t js::temporal::MakeDate(const PlainDateTime& dateTime) {
380 MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
382 // Step 1 (Not applicable).
384 // Steps 2-3.
385 int64_t tv = MakeDay(dateTime.date) * ToMilliseconds(TemporalUnit::Day) +
386 MakeTime(dateTime.time);
388 // Step 4.
389 return tv;
393 * 21.4.1.12 MakeDay ( year, month, date )
395 static int32_t MakeDay(int32_t year, int32_t month, int32_t day) {
396 MOZ_ASSERT(1 <= month && month <= 12);
398 // FIXME: spec issue - what should happen for invalid years/days?
399 return DayFromYear(year) + ::ToISODayOfYear(year, month, day) - 1;
403 * 21.4.1.13 MakeDate ( day, time )
405 int64_t js::temporal::MakeDate(int32_t year, int32_t month, int32_t day) {
406 // NOTE: This version accepts values outside the valid date-time limits.
407 MOZ_ASSERT(1 <= month && month <= 12);
409 // Step 1 (Not applicable).
411 // Steps 2-3.
412 int64_t tv = ::MakeDay(year, month, day) * ToMilliseconds(TemporalUnit::Day);
414 // Step 4.
415 return tv;
418 struct YearWeek final {
419 int32_t year = 0;
420 int32_t week = 0;
424 * ToISOWeekOfYear ( year, month, day )
426 static YearWeek ToISOWeekOfYear(const PlainDate& date) {
427 MOZ_ASSERT(ISODateTimeWithinLimits(date));
429 const auto& [year, month, day] = date;
431 // TODO: https://en.wikipedia.org/wiki/Week#The_ISO_week_date_system
432 // TODO: https://en.wikipedia.org/wiki/ISO_week_date#Algorithms
434 // Steps 1-3. (Not applicable in our implementation.)
436 // Steps 4-5.
437 int32_t doy = ToISODayOfYear(date);
438 int32_t dow = ToISODayOfWeek(date);
440 int32_t woy = (10 + doy - dow) / 7;
441 MOZ_ASSERT(0 <= woy && woy <= 53);
443 // An ISO year has 53 weeks if the year starts on a Thursday or if it's a
444 // leap year which starts on a Wednesday.
445 auto isLongYear = [](int32_t year) {
446 int32_t startOfYear = ToISODayOfWeek({year, 1, 1});
447 return startOfYear == 4 || (startOfYear == 3 && IsISOLeapYear(year));
450 // Part of last year's last week, which is either week 52 or week 53.
451 if (woy == 0) {
452 return {year - 1, 52 + int32_t(isLongYear(year - 1))};
455 // Part of next year's first week if the current year isn't a long year.
456 if (woy == 53 && !isLongYear(year)) {
457 return {year + 1, 1};
460 return {year, woy};
464 * ISOMonthCode ( month )
466 static JSString* ISOMonthCode(JSContext* cx, int32_t month) {
467 MOZ_ASSERT(1 <= month && month <= 12);
469 // Steps 1-2.
470 char monthCode[3] = {'M', char('0' + (month / 10)), char('0' + (month % 10))};
471 return NewStringCopyN<CanGC>(cx, monthCode, std::size(monthCode));
474 template <typename T, typename... Ts>
475 static bool ToPlainDate(JSObject* temporalDateLike, PlainDate* result) {
476 if (auto* obj = temporalDateLike->maybeUnwrapIf<T>()) {
477 *result = ToPlainDate(obj);
478 return true;
480 if constexpr (sizeof...(Ts) > 0) {
481 return ToPlainDate<Ts...>(temporalDateLike, result);
483 return false;
486 template <typename... Ts>
487 static bool ToPlainDate(JSContext* cx, Handle<Value> temporalDateLike,
488 PlainDate* result) {
489 if (temporalDateLike.isObject()) {
490 if (ToPlainDate<Ts...>(&temporalDateLike.toObject(), result)) {
491 return true;
495 return ToTemporalDate(cx, temporalDateLike, result);
498 #ifdef DEBUG
499 template <typename CharT>
500 static bool StringIsAsciiLowerCase(mozilla::Range<CharT> str) {
501 return std::all_of(str.begin().get(), str.end().get(), [](CharT ch) {
502 return mozilla::IsAscii(ch) && !mozilla::IsAsciiUppercaseAlpha(ch);
506 static bool StringIsAsciiLowerCase(JSLinearString* str) {
507 JS::AutoCheckCannotGC nogc;
508 return str->hasLatin1Chars()
509 ? StringIsAsciiLowerCase(str->latin1Range(nogc))
510 : StringIsAsciiLowerCase(str->twoByteRange(nogc));
512 #endif
514 static bool IsISO8601Calendar(JSLinearString* id) {
515 return StringEqualsLiteral(id, "iso8601");
518 #ifdef DEBUG
519 static bool IsISO8601Calendar(CalendarObject* calendar) {
520 return IsISO8601Calendar(calendar->identifier());
522 #endif
524 static bool IsISO8601Calendar(JSContext* cx, JSString* id, bool* result) {
525 JSLinearString* linear = id->ensureLinear(cx);
526 if (!linear) {
527 return false;
529 *result = IsISO8601Calendar(linear);
530 return true;
534 * IsBuiltinCalendar ( id )
536 static bool IsBuiltinCalendar(JSLinearString* id) {
537 // Callers must convert to lower case.
538 MOZ_ASSERT(StringIsAsciiLowerCase(id));
540 // Steps 1-3.
541 return StringEqualsLiteral(id, "iso8601");
544 static JSLinearString* ThrowIfNotBuiltinCalendar(JSContext* cx,
545 Handle<JSLinearString*> id) {
546 if (!StringIsAscii(id)) {
547 if (auto chars = QuoteString(cx, id)) {
548 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
549 JSMSG_TEMPORAL_CALENDAR_INVALID_ID, chars.get());
551 return nullptr;
554 JSString* lower = StringToLowerCase(cx, id);
555 if (!lower) {
556 return nullptr;
559 JSLinearString* linear = lower->ensureLinear(cx);
560 if (!linear) {
561 return nullptr;
564 if (!IsBuiltinCalendar(linear)) {
565 if (auto chars = QuoteString(cx, id)) {
566 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
567 JSMSG_TEMPORAL_CALENDAR_INVALID_ID, chars.get());
569 return nullptr;
572 return linear;
575 bool js::temporal::ToBuiltinCalendar(JSContext* cx, Handle<JSString*> id,
576 MutableHandle<CalendarValue> result) {
577 Rooted<JSLinearString*> linear(cx, id->ensureLinear(cx));
578 if (!linear) {
579 return false;
582 auto* identifier = ThrowIfNotBuiltinCalendar(cx, linear);
583 if (!identifier) {
584 return false;
587 result.set(CalendarValue(identifier));
588 return true;
592 * CreateTemporalCalendar ( identifier [ , newTarget ] )
594 static CalendarObject* CreateTemporalCalendar(
595 JSContext* cx, const CallArgs& args, Handle<JSLinearString*> identifier) {
596 // Step 1.
597 MOZ_ASSERT(IsBuiltinCalendar(identifier));
599 // Steps 2-3.
600 Rooted<JSObject*> proto(cx);
601 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Calendar, &proto)) {
602 return nullptr;
605 auto* obj = NewObjectWithClassProto<CalendarObject>(cx, proto);
606 if (!obj) {
607 return nullptr;
610 // Step 4.
611 obj->setFixedSlot(CalendarObject::IDENTIFIER_SLOT, StringValue(identifier));
613 // Step 5.
614 return obj;
618 * CreateTemporalCalendar ( identifier [ , newTarget ] )
620 static CalendarObject* CreateTemporalCalendar(
621 JSContext* cx, Handle<JSLinearString*> identifier) {
622 // Step 1.
623 MOZ_ASSERT(IsBuiltinCalendar(identifier));
625 // Steps 2-3.
626 auto* obj = NewBuiltinClassInstance<CalendarObject>(cx);
627 if (!obj) {
628 return nullptr;
631 // Step 4.
632 obj->setFixedSlot(CalendarObject::IDENTIFIER_SLOT, StringValue(identifier));
634 // Step 5.
635 return obj;
639 * ObjectImplementsTemporalCalendarProtocol ( object )
641 static bool ObjectImplementsTemporalCalendarProtocol(JSContext* cx,
642 Handle<JSObject*> object,
643 bool* result) {
644 // Step 1. (Not applicable in our implementation.)
645 MOZ_ASSERT(!object->canUnwrapAs<CalendarObject>(),
646 "Calendar objects handled in the caller");
648 // Step 2.
649 for (auto key : {
650 &JSAtomState::dateAdd, &JSAtomState::dateFromFields,
651 &JSAtomState::dateUntil, &JSAtomState::day,
652 &JSAtomState::dayOfWeek, &JSAtomState::dayOfYear,
653 &JSAtomState::daysInMonth, &JSAtomState::daysInWeek,
654 &JSAtomState::daysInYear, &JSAtomState::fields,
655 &JSAtomState::id, &JSAtomState::inLeapYear,
656 &JSAtomState::mergeFields, &JSAtomState::month,
657 &JSAtomState::monthCode, &JSAtomState::monthDayFromFields,
658 &JSAtomState::monthsInYear, &JSAtomState::weekOfYear,
659 &JSAtomState::year, &JSAtomState::yearMonthFromFields,
660 &JSAtomState::yearOfWeek,
661 }) {
662 // Step 2.a.
663 bool has;
664 if (!HasProperty(cx, object, cx->names().*key, &has)) {
665 return false;
667 if (!has) {
668 *result = false;
669 return true;
673 // Step 3.
674 *result = true;
675 return true;
678 template <typename T, typename... Ts>
679 static bool ToTemporalCalendar(JSContext* cx, Handle<JSObject*> object,
680 MutableHandle<CalendarValue> result) {
681 if (auto* unwrapped = object->maybeUnwrapIf<T>()) {
682 result.set(unwrapped->calendar());
683 return result.wrap(cx);
686 if constexpr (sizeof...(Ts) > 0) {
687 return ToTemporalCalendar<Ts...>(cx, object, result);
690 result.set(CalendarValue());
691 return true;
695 * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )
697 bool js::temporal::ToTemporalCalendar(JSContext* cx,
698 Handle<Value> temporalCalendarLike,
699 MutableHandle<CalendarValue> result) {
700 // Step 1. (Not applicable)
702 // Step 2.
703 Rooted<Value> calendarLike(cx, temporalCalendarLike);
704 if (calendarLike.isObject()) {
705 Rooted<JSObject*> obj(cx, &calendarLike.toObject());
707 // Step 2.b. (Partial)
708 if (obj->canUnwrapAs<CalendarObject>()) {
709 result.set(CalendarValue(obj));
710 return true;
713 // Step 2.a.
714 Rooted<CalendarValue> calendar(cx);
715 if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject,
716 PlainMonthDayObject, PlainYearMonthObject,
717 ZonedDateTimeObject>(cx, obj, &calendar)) {
718 return false;
720 if (calendar) {
721 result.set(calendar);
722 return true;
725 // Step 2.b.
726 bool implementsCalendarProtocol;
727 if (!ObjectImplementsTemporalCalendarProtocol(
728 cx, obj, &implementsCalendarProtocol)) {
729 return false;
731 if (!implementsCalendarProtocol) {
732 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
733 JSMSG_TEMPORAL_INVALID_OBJECT,
734 "Temporal.Calendar", obj->getClass()->name);
735 return false;
738 // Step 2.c.
739 result.set(CalendarValue(obj));
740 return true;
743 // Step 3.
744 if (!calendarLike.isString()) {
745 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
746 calendarLike, nullptr, "not a string");
747 return false;
749 Rooted<JSString*> str(cx, calendarLike.toString());
751 // Step 4.
752 Rooted<JSLinearString*> identifier(cx, ParseTemporalCalendarString(cx, str));
753 if (!identifier) {
754 return false;
757 // Step 5.
758 identifier = ThrowIfNotBuiltinCalendar(cx, identifier);
759 if (!identifier) {
760 return false;
763 // Step 6.
764 result.set(CalendarValue(identifier));
765 return true;
769 * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )
771 * When called with `default = "iso8601"`.
773 bool js::temporal::ToTemporalCalendarWithISODefault(
774 JSContext* cx, Handle<Value> temporalCalendarLike,
775 MutableHandle<CalendarValue> result) {
776 // Step 1.
777 if (temporalCalendarLike.isUndefined()) {
778 result.set(CalendarValue(cx->names().iso8601));
779 return true;
782 // Steps 2-6.
783 return ToTemporalCalendar(cx, temporalCalendarLike, result);
787 * GetTemporalCalendarSlotValueWithISODefault ( item )
789 bool js::temporal::GetTemporalCalendarWithISODefault(
790 JSContext* cx, Handle<JSObject*> item,
791 MutableHandle<CalendarValue> result) {
792 // Step 1.
793 Rooted<CalendarValue> calendar(cx);
794 if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject,
795 PlainMonthDayObject, PlainYearMonthObject,
796 ZonedDateTimeObject>(cx, item, &calendar)) {
797 return false;
799 if (calendar) {
800 result.set(calendar);
801 return true;
804 // Step 2.
805 Rooted<Value> calendarValue(cx);
806 if (!GetProperty(cx, item, item, cx->names().calendar, &calendarValue)) {
807 return false;
810 // Step 3.
811 return ToTemporalCalendarWithISODefault(cx, calendarValue, result);
815 * ToTemporalCalendarIdentifier ( calendarSlotValue )
817 JSString* js::temporal::ToTemporalCalendarIdentifier(
818 JSContext* cx, Handle<CalendarValue> calendar) {
819 // Step 1.
820 if (calendar.isString()) {
821 // Step 1.a.
822 MOZ_ASSERT(IsBuiltinCalendar(calendar.toString()));
824 // Step 1.b.
825 return calendar.toString();
828 // Step 2.
829 Rooted<JSObject*> calendarObj(cx, calendar.toObject());
830 Rooted<Value> identifier(cx);
831 if (!GetProperty(cx, calendarObj, calendarObj, cx->names().id, &identifier)) {
832 return nullptr;
835 // Step 3.
836 if (!identifier.isString()) {
837 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, identifier,
838 nullptr, "not a string");
839 return nullptr;
842 // Step 4.
843 return identifier.toString();
847 * ToTemporalCalendarObject ( calendarSlotValue )
849 JSObject* js::temporal::ToTemporalCalendarObject(
850 JSContext* cx, Handle<CalendarValue> calendar) {
851 // Step 1.
852 if (calendar.isObject()) {
853 return calendar.toObject();
856 // Step 2.
857 Rooted<JSLinearString*> calendarId(cx, calendar.toString());
858 return CreateTemporalCalendar(cx, calendarId);
861 static bool Calendar_dateAdd(JSContext* cx, unsigned argc, Value* vp);
862 static bool Calendar_dateFromFields(JSContext* cx, unsigned argc, Value* vp);
863 static bool Calendar_dateUntil(JSContext* cx, unsigned argc, Value* vp);
864 static bool Calendar_day(JSContext* cx, unsigned argc, Value* vp);
865 static bool Calendar_fields(JSContext* cx, unsigned argc, Value* vp);
866 static bool Calendar_mergeFields(JSContext* cx, unsigned argc, Value* vp);
867 static bool Calendar_monthDayFromFields(JSContext* cx, unsigned argc,
868 Value* vp);
869 static bool Calendar_yearMonthFromFields(JSContext* cx, unsigned argc,
870 Value* vp);
873 * CalendarMethodsRecordLookup ( calendarRec, methodName )
875 static bool CalendarMethodsRecordLookup(JSContext* cx,
876 MutableHandle<CalendarRecord> calendar,
877 CalendarMethod methodName) {
878 // Step 1. (Not applicable in our implementation.)
880 // Steps 2-10.
881 Rooted<JSObject*> object(cx, calendar.receiver().toObject());
883 auto lookup = [&](Handle<PropertyName*> name, JSNative native,
884 MutableHandle<JSObject*> result) {
885 auto* method = GetMethod(cx, object, name);
886 if (!method) {
887 return false;
890 // As an optimization we only store the method if the receiver is either
891 // a custom calendar object or if the method isn't the default, built-in
892 // calender method.
893 if (!object->is<CalendarObject>() || !IsNativeFunction(method, native)) {
894 result.set(method);
896 return true;
899 switch (methodName) {
900 // Steps 2 and 10.
901 case CalendarMethod::DateAdd:
902 return lookup(cx->names().dateAdd, Calendar_dateAdd, calendar.dateAdd());
904 // Steps 3 and 10.
905 case CalendarMethod::DateFromFields:
906 return lookup(cx->names().dateFromFields, Calendar_dateFromFields,
907 calendar.dateFromFields());
909 // Steps 4 and 10.
910 case CalendarMethod::DateUntil:
911 return lookup(cx->names().dateUntil, Calendar_dateUntil,
912 calendar.dateUntil());
914 // Steps 5 and 10.
915 case CalendarMethod::Day:
916 return lookup(cx->names().day, Calendar_day, calendar.day());
918 // Steps 6 and 10.
919 case CalendarMethod::Fields:
920 return lookup(cx->names().fields, Calendar_fields, calendar.fields());
922 // Steps 7 and 10.
923 case CalendarMethod::MergeFields:
924 return lookup(cx->names().mergeFields, Calendar_mergeFields,
925 calendar.mergeFields());
927 // Steps 8 and 10.
928 case CalendarMethod::MonthDayFromFields:
929 return lookup(cx->names().monthDayFromFields, Calendar_monthDayFromFields,
930 calendar.monthDayFromFields());
932 // Steps 9 and 10.
933 case CalendarMethod::YearMonthFromFields:
934 return lookup(cx->names().yearMonthFromFields,
935 Calendar_yearMonthFromFields,
936 calendar.yearMonthFromFields());
939 MOZ_CRASH("invalid calendar method");
943 * CreateCalendarMethodsRecord ( calendar, methods )
945 bool js::temporal::CreateCalendarMethodsRecord(
946 JSContext* cx, Handle<CalendarValue> calendar,
947 mozilla::EnumSet<CalendarMethod> methods,
948 MutableHandle<CalendarRecord> result) {
949 MOZ_ASSERT(!methods.isEmpty());
951 // Step 1.
952 result.set(CalendarRecord{calendar});
954 #ifdef DEBUG
955 // Remember the set of looked-up methods for assertions.
956 result.get().lookedUp() += methods;
957 #endif
959 // Built-in calendars don't perform observable lookups.
960 if (calendar.isString()) {
961 return true;
964 // Step 2.
965 for (auto method : methods) {
966 if (!CalendarMethodsRecordLookup(cx, result, method)) {
967 return false;
971 // Step 3.
972 return true;
975 static bool ToCalendarField(JSContext* cx, JSLinearString* linear,
976 CalendarField* result) {
977 if (StringEqualsLiteral(linear, "year")) {
978 *result = CalendarField::Year;
979 return true;
981 if (StringEqualsLiteral(linear, "month")) {
982 *result = CalendarField::Month;
983 return true;
985 if (StringEqualsLiteral(linear, "monthCode")) {
986 *result = CalendarField::MonthCode;
987 return true;
989 if (StringEqualsLiteral(linear, "day")) {
990 *result = CalendarField::Day;
991 return true;
993 if (auto chars = QuoteString(cx, linear, '"')) {
994 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
995 JSMSG_TEMPORAL_CALENDAR_INVALID_FIELD,
996 chars.get());
998 return false;
1001 static PropertyName* ToPropertyName(JSContext* cx, CalendarField field) {
1002 switch (field) {
1003 case CalendarField::Year:
1004 return cx->names().year;
1005 case CalendarField::Month:
1006 return cx->names().month;
1007 case CalendarField::MonthCode:
1008 return cx->names().monthCode;
1009 case CalendarField::Day:
1010 return cx->names().day;
1012 MOZ_CRASH("invalid calendar field name");
1015 #ifdef DEBUG
1016 static const char* ToCString(CalendarField field) {
1017 switch (field) {
1018 case CalendarField::Year:
1019 return "year";
1020 case CalendarField::Month:
1021 return "month";
1022 case CalendarField::MonthCode:
1023 return "monthCode";
1024 case CalendarField::Day:
1025 return "day";
1027 MOZ_CRASH("invalid calendar field name");
1029 #endif
1032 * Temporal.Calendar.prototype.fields ( fields )
1034 static bool BuiltinCalendarFields(
1035 JSContext* cx, std::initializer_list<CalendarField> fieldNames,
1036 CalendarFieldNames& result) {
1037 MOZ_ASSERT(result.empty());
1039 // Steps 1-5. (Not applicable.)
1041 // Reserve space for the append operation.
1042 if (!result.reserve(fieldNames.size())) {
1043 return false;
1046 // Steps 6-7.
1047 for (auto fieldName : fieldNames) {
1048 auto* name = ToPropertyName(cx, fieldName);
1050 // Steps 7.a and 7.b.i-iv. (Not applicable)
1052 // Step 7.b.v.
1053 result.infallibleAppend(NameToId(name));
1056 // Step 8. (Not applicable)
1057 return true;
1060 #ifdef DEBUG
1061 static bool IsSorted(std::initializer_list<CalendarField> fieldNames) {
1062 return std::is_sorted(fieldNames.begin(), fieldNames.end(),
1063 [](auto x, auto y) {
1064 auto* a = ToCString(x);
1065 auto* b = ToCString(y);
1066 return std::strcmp(a, b) < 0;
1069 #endif
1072 * CalendarFields ( calendarRec, fieldNames )
1074 bool js::temporal::CalendarFields(
1075 JSContext* cx, Handle<CalendarRecord> calendar,
1076 std::initializer_list<CalendarField> fieldNames,
1077 MutableHandle<CalendarFieldNames> result) {
1078 MOZ_ASSERT(
1079 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::Fields));
1081 // Step 1.
1082 MOZ_ASSERT(IsSorted(fieldNames));
1083 MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
1084 fieldNames.end());
1086 // Step 2.
1087 auto fields = calendar.fields();
1088 if (!fields) {
1089 bool arrayIterationSane;
1090 if (calendar.receiver().isString()) {
1091 // "String" calendars don't perform observable array iteration.
1092 arrayIterationSane = true;
1093 } else {
1094 // "Object" calendars need to ensure array iteration is still sane.
1095 if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
1096 return false;
1100 if (arrayIterationSane) {
1101 // Steps 2.a-b.
1102 return BuiltinCalendarFields(cx, fieldNames, result.get());
1104 // Steps 2.c-e. (Not applicable in our implementation.)
1108 // Step 3. (Inlined call to CalendarMethodsRecordCall.)
1110 auto* array = NewDenseFullyAllocatedArray(cx, fieldNames.size());
1111 if (!array) {
1112 return false;
1114 array->setDenseInitializedLength(fieldNames.size());
1116 for (size_t i = 0; i < fieldNames.size(); i++) {
1117 auto* name = ToPropertyName(cx, fieldNames.begin()[i]);
1118 array->initDenseElement(i, StringValue(name));
1121 Rooted<Value> fieldsFn(cx, ObjectValue(*fields));
1122 auto thisv = calendar.receiver().toValue();
1123 Rooted<Value> fieldsArray(cx, ObjectValue(*array));
1124 if (!Call(cx, fieldsFn, thisv, fieldsArray, &fieldsArray)) {
1125 return false;
1128 // Steps 4-5.
1129 if (!IterableToListOfStrings(cx, fieldsArray, result)) {
1130 return false;
1133 // The spec sorts the field names in PrepareTemporalFields. Sorting is only
1134 // needed for user-defined calendars, so our implementation performs this step
1135 // here instead of in PrepareTemporalFields.
1136 return SortTemporalFieldNames(cx, result.get());
1139 static bool RequireIntegralNumber(JSContext* cx, Handle<Value> value,
1140 Handle<PropertyName*> name,
1141 MutableHandle<Value> result) {
1142 if (MOZ_LIKELY(value.isInt32())) {
1143 result.set(value);
1144 return true;
1147 if (value.isDouble()) {
1148 double d = value.toDouble();
1149 if (js::IsInteger(d)) {
1150 result.setNumber(d);
1151 return true;
1154 if (auto str = QuoteString(cx, name)) {
1155 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1156 JSMSG_TEMPORAL_INVALID_INTEGER, str.get());
1158 return false;
1161 if (auto str = QuoteString(cx, name)) {
1162 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1163 JSMSG_UNEXPECTED_TYPE, str.get(), "not a number");
1165 return false;
1168 static bool RequireIntegralPositiveNumber(JSContext* cx, Handle<Value> value,
1169 Handle<PropertyName*> name,
1170 MutableHandle<Value> result) {
1171 if (!RequireIntegralNumber(cx, value, name, result)) {
1172 return false;
1175 if (result.toNumber() <= 0) {
1176 if (auto str = QuoteString(cx, name)) {
1177 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1178 JSMSG_TEMPORAL_INVALID_NUMBER, str.get());
1180 return false;
1182 return true;
1185 static bool RequireString(JSContext* cx, Handle<Value> value,
1186 Handle<PropertyName*> name,
1187 MutableHandle<Value> result) {
1188 if (MOZ_LIKELY(value.isString())) {
1189 result.set(value);
1190 return true;
1193 if (auto str = QuoteString(cx, name)) {
1194 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1195 JSMSG_UNEXPECTED_TYPE, str.get(), "not a string");
1197 return false;
1200 static bool RequireBoolean(JSContext* cx, Handle<Value> value,
1201 Handle<PropertyName*> name,
1202 MutableHandle<Value> result) {
1203 if (MOZ_LIKELY(value.isBoolean())) {
1204 result.set(value);
1205 return true;
1208 if (auto str = QuoteString(cx, name)) {
1209 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1210 JSMSG_UNEXPECTED_TYPE, str.get(),
1211 "not a boolean");
1213 return false;
1216 using BuiltinCalendarMethod = bool (*)(JSContext* cx, const PlainDate&,
1217 MutableHandle<Value>);
1219 using CalendarConversion = bool (*)(JSContext*, Handle<Value>,
1220 Handle<PropertyName*>,
1221 MutableHandle<Value>);
1223 template <BuiltinCalendarMethod builtin, CalendarConversion conversion>
1224 static bool CallCalendarMethod(JSContext* cx, Handle<PropertyName*> name,
1225 JSNative native, Handle<CalendarValue> calendar,
1226 Handle<JSObject*> dateLike,
1227 const PlainDate& date,
1228 MutableHandle<Value> result) {
1229 // Step 1.
1230 if (calendar.isString()) {
1231 return builtin(cx, date, result);
1234 // Step 2.
1235 Rooted<JSObject*> calendarObj(cx, calendar.toObject());
1236 JSObject* fn = GetMethod(cx, calendarObj, name);
1237 if (!fn) {
1238 return false;
1241 // Fast-path for the default implementation.
1242 if (calendarObj->is<CalendarObject>() && IsNativeFunction(fn, native)) {
1243 return builtin(cx, date, result);
1246 Rooted<JS::Value> fnVal(cx, ObjectValue(*fn));
1247 Rooted<JS::Value> dateLikeValue(cx, ObjectValue(*dateLike));
1248 if (!Call(cx, fnVal, calendarObj, dateLikeValue, result)) {
1249 return false;
1252 // Steps 3-5.
1253 return conversion(cx, result, name, result);
1257 * Temporal.Calendar.prototype.year ( temporalDateLike )
1259 static bool BuiltinCalendarYear(JSContext* cx, const PlainDate& date,
1260 MutableHandle<Value> result) {
1261 // Steps 1-4. (Not applicable.)
1263 // Steps 5-6.
1264 result.setInt32(date.year);
1265 return true;
1268 static bool Calendar_year(JSContext* cx, unsigned argc, Value* vp);
1271 * CalendarYear ( calendar, dateLike )
1273 static bool CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
1274 Handle<JSObject*> dateLike, const PlainDate& date,
1275 MutableHandle<Value> result) {
1276 // Steps 1-5.
1277 return CallCalendarMethod<BuiltinCalendarYear, RequireIntegralNumber>(
1278 cx, cx->names().year, Calendar_year, calendar, dateLike, date, result);
1282 * CalendarYear ( calendar, dateLike )
1284 bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
1285 Handle<PlainDateObject*> dateLike,
1286 MutableHandle<Value> result) {
1287 return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result);
1291 * CalendarYear ( calendar, dateLike )
1293 bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
1294 Handle<PlainDateTimeObject*> dateLike,
1295 MutableHandle<Value> result) {
1296 return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result);
1300 * CalendarYear ( calendar, dateLike )
1302 bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
1303 Handle<PlainYearMonthObject*> dateLike,
1304 MutableHandle<Value> result) {
1305 return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result);
1309 * CalendarYear ( calendar, dateLike )
1311 bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
1312 const PlainDateTime& dateTime,
1313 MutableHandle<Value> result) {
1314 Rooted<PlainDateTimeObject*> dateLike(
1315 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1316 if (!dateLike) {
1317 return false;
1320 return ::CalendarYear(cx, calendar, dateLike, dateTime.date, result);
1324 * Temporal.Calendar.prototype.month ( temporalDateLike )
1326 static bool BuiltinCalendarMonth(JSContext* cx, const PlainDate& date,
1327 MutableHandle<Value> result) {
1328 // Steps 1-5. (Not applicable.)
1330 // Steps 6-7.
1331 result.setInt32(date.month);
1332 return true;
1335 static bool Calendar_month(JSContext* cx, unsigned argc, Value* vp);
1338 * CalendarMonth ( calendar, dateLike )
1340 static bool CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
1341 Handle<JSObject*> dateLike, const PlainDate& date,
1342 MutableHandle<Value> result) {
1343 // Steps 1-6.
1344 return CallCalendarMethod<BuiltinCalendarMonth,
1345 RequireIntegralPositiveNumber>(
1346 cx, cx->names().month, Calendar_month, calendar, dateLike, date, result);
1350 * CalendarMonth ( calendar, dateLike )
1352 bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
1353 Handle<PlainDateObject*> dateLike,
1354 MutableHandle<Value> result) {
1355 return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result);
1359 * CalendarMonth ( calendar, dateLike )
1361 bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
1362 Handle<PlainDateTimeObject*> dateLike,
1363 MutableHandle<Value> result) {
1364 return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result);
1368 * CalendarMonth ( calendar, dateLike )
1370 bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
1371 Handle<PlainYearMonthObject*> dateLike,
1372 MutableHandle<Value> result) {
1373 return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result);
1377 * CalendarMonth ( calendar, dateLike )
1379 bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
1380 const PlainDateTime& dateTime,
1381 MutableHandle<Value> result) {
1382 Rooted<PlainDateTimeObject*> dateLike(
1383 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1384 if (!dateLike) {
1385 return false;
1388 return ::CalendarMonth(cx, calendar, dateLike, dateTime.date, result);
1392 * Temporal.Calendar.prototype.monthCode ( temporalDateLike )
1394 static bool BuiltinCalendarMonthCode(JSContext* cx, const PlainDate& date,
1395 MutableHandle<Value> result) {
1396 // Steps 1-4. (Not applicable.)
1398 // Steps 5-6.
1399 JSString* str = ISOMonthCode(cx, date.month);
1400 if (!str) {
1401 return false;
1404 result.setString(str);
1405 return true;
1408 static bool Calendar_monthCode(JSContext* cx, unsigned argc, Value* vp);
1411 * CalendarMonthCode ( calendar, dateLike )
1413 static bool CalendarMonthCode(JSContext* cx, Handle<CalendarValue> calendar,
1414 Handle<JSObject*> dateLike, const PlainDate& date,
1415 MutableHandle<Value> result) {
1416 // Steps 1-4.
1417 return CallCalendarMethod<BuiltinCalendarMonthCode, RequireString>(
1418 cx, cx->names().monthCode, Calendar_monthCode, calendar, dateLike, date,
1419 result);
1423 * CalendarMonthCode ( calendar, dateLike )
1425 bool js::temporal::CalendarMonthCode(JSContext* cx,
1426 Handle<CalendarValue> calendar,
1427 Handle<PlainDateObject*> dateLike,
1428 MutableHandle<Value> result) {
1429 return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
1430 result);
1434 * CalendarMonthCode ( calendar, dateLike )
1436 bool js::temporal::CalendarMonthCode(JSContext* cx,
1437 Handle<CalendarValue> calendar,
1438 Handle<PlainDateTimeObject*> dateLike,
1439 MutableHandle<Value> result) {
1440 return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
1441 result);
1445 * CalendarMonthCode ( calendar, dateLike )
1447 bool js::temporal::CalendarMonthCode(JSContext* cx,
1448 Handle<CalendarValue> calendar,
1449 Handle<PlainMonthDayObject*> dateLike,
1450 MutableHandle<Value> result) {
1451 return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
1452 result);
1456 * CalendarMonthCode ( calendar, dateLike )
1458 bool js::temporal::CalendarMonthCode(JSContext* cx,
1459 Handle<CalendarValue> calendar,
1460 Handle<PlainYearMonthObject*> dateLike,
1461 MutableHandle<Value> result) {
1462 return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
1463 result);
1467 * CalendarMonthCode ( calendar, dateLike )
1469 bool js::temporal::CalendarMonthCode(JSContext* cx,
1470 Handle<CalendarValue> calendar,
1471 const PlainDateTime& dateTime,
1472 MutableHandle<Value> result) {
1473 Rooted<PlainDateTimeObject*> dateLike(
1474 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1475 if (!dateLike) {
1476 return false;
1479 return ::CalendarMonthCode(cx, calendar, dateLike, dateTime.date, result);
1483 * Temporal.Calendar.prototype.day ( temporalDateLike )
1485 static bool BuiltinCalendarDay(const PlainDate& date,
1486 MutableHandle<Value> result) {
1487 // Steps 1-4. (Not applicable.)
1489 // Steps 5-6.
1490 result.setInt32(date.day);
1491 return true;
1495 * CalendarDay ( calendarRec, dateLike )
1497 static bool CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar,
1498 Handle<JSObject*> dateLike, const PlainDate& date,
1499 MutableHandle<Value> result) {
1500 MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::Day));
1502 // Step 2. (Reordered)
1503 auto day = calendar.day();
1504 if (!day) {
1505 return BuiltinCalendarDay(date, result);
1508 // Step 1. (Inlined call to CalendarMethodsRecordCall.)
1509 Rooted<Value> fn(cx, ObjectValue(*day));
1510 auto thisv = calendar.receiver().toValue();
1511 Rooted<JS::Value> dateLikeValue(cx, ObjectValue(*dateLike));
1512 if (!Call(cx, fn, thisv, dateLikeValue, result)) {
1513 return false;
1516 // Steps 3-6.
1517 return RequireIntegralPositiveNumber(cx, result, cx->names().day, result);
1521 * CalendarDay ( calendarRec, dateLike )
1523 bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar,
1524 Handle<PlainDateObject*> dateLike,
1525 MutableHandle<Value> result) {
1526 Rooted<CalendarRecord> calendarRec(cx);
1527 if (!CreateCalendarMethodsRecord(cx, calendar,
1529 CalendarMethod::Day,
1531 &calendarRec)) {
1532 return false;
1535 return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result);
1539 * CalendarDay ( calendarRec, dateLike )
1541 bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar,
1542 Handle<PlainDateTimeObject*> dateLike,
1543 MutableHandle<Value> result) {
1544 Rooted<CalendarRecord> calendarRec(cx);
1545 if (!CreateCalendarMethodsRecord(cx, calendar,
1547 CalendarMethod::Day,
1549 &calendarRec)) {
1550 return false;
1553 return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result);
1557 * CalendarDay ( calendarRec, dateLike )
1559 bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar,
1560 Handle<PlainMonthDayObject*> dateLike,
1561 MutableHandle<Value> result) {
1562 Rooted<CalendarRecord> calendarRec(cx);
1563 if (!CreateCalendarMethodsRecord(cx, calendar,
1565 CalendarMethod::Day,
1567 &calendarRec)) {
1568 return false;
1571 return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result);
1575 * CalendarDay ( calendarRec, dateLike )
1577 bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar,
1578 const PlainDate& date,
1579 MutableHandle<Value> result) {
1580 Rooted<PlainDateObject*> dateLike(
1581 cx, CreateTemporalDate(cx, date, calendar.receiver()));
1582 if (!dateLike) {
1583 return false;
1586 return ::CalendarDay(cx, calendar, dateLike, date, result);
1590 * CalendarDay ( calendarRec, dateLike )
1592 bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar,
1593 const PlainDateTime& dateTime,
1594 MutableHandle<Value> result) {
1595 Rooted<PlainDateTimeObject*> dateLike(
1596 cx, CreateTemporalDateTime(cx, dateTime, calendar.receiver()));
1597 if (!dateLike) {
1598 return false;
1601 return ::CalendarDay(cx, calendar, dateLike, dateTime.date, result);
1605 * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike )
1607 static bool BuiltinCalendarDayOfWeek(JSContext* cx, const PlainDate& date,
1608 MutableHandle<Value> result) {
1609 // Steps 1-4. (Not applicable.)
1611 // Steps 5-9.
1612 result.setInt32(ToISODayOfWeek(date));
1613 return true;
1616 static bool Calendar_dayOfWeek(JSContext* cx, unsigned argc, Value* vp);
1619 * CalendarDayOfWeek ( calendar, dateLike )
1621 static bool CalendarDayOfWeek(JSContext* cx, Handle<CalendarValue> calendar,
1622 Handle<JSObject*> dateLike, const PlainDate& date,
1623 MutableHandle<Value> result) {
1624 // Steps 1-6.
1625 return CallCalendarMethod<BuiltinCalendarDayOfWeek,
1626 RequireIntegralPositiveNumber>(
1627 cx, cx->names().dayOfWeek, Calendar_dayOfWeek, calendar, dateLike, date,
1628 result);
1632 * CalendarDayOfWeek ( calendar, dateLike )
1634 bool js::temporal::CalendarDayOfWeek(JSContext* cx,
1635 Handle<CalendarValue> calendar,
1636 Handle<PlainDateObject*> dateLike,
1637 MutableHandle<Value> result) {
1638 return CalendarDayOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
1639 result);
1643 * CalendarDayOfWeek ( calendar, dateLike )
1645 bool js::temporal::CalendarDayOfWeek(JSContext* cx,
1646 Handle<CalendarValue> calendar,
1647 Handle<PlainDateTimeObject*> dateLike,
1648 MutableHandle<Value> result) {
1649 return CalendarDayOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
1650 result);
1654 * CalendarDayOfWeek ( calendar, dateLike )
1656 bool js::temporal::CalendarDayOfWeek(JSContext* cx,
1657 Handle<CalendarValue> calendar,
1658 const PlainDateTime& dateTime,
1659 MutableHandle<Value> result) {
1660 Rooted<PlainDateTimeObject*> dateLike(
1661 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1662 if (!dateLike) {
1663 return false;
1666 return ::CalendarDayOfWeek(cx, calendar, dateLike, dateTime.date, result);
1670 * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )
1672 static bool BuiltinCalendarDayOfYear(JSContext* cx, const PlainDate& date,
1673 MutableHandle<Value> result) {
1674 // Steps 1-4. (Not applicable.)
1676 // Steps 5-7.
1677 result.setInt32(ToISODayOfYear(date));
1678 return true;
1681 static bool Calendar_dayOfYear(JSContext* cx, unsigned argc, Value* vp);
1684 * CalendarDayOfYear ( calendar, dateLike )
1686 static bool CalendarDayOfYear(JSContext* cx, Handle<CalendarValue> calendar,
1687 Handle<JSObject*> dateLike, const PlainDate& date,
1688 MutableHandle<Value> result) {
1689 // Steps 1-6.
1690 return CallCalendarMethod<BuiltinCalendarDayOfYear,
1691 RequireIntegralPositiveNumber>(
1692 cx, cx->names().dayOfYear, Calendar_dayOfYear, calendar, dateLike, date,
1693 result);
1697 * CalendarDayOfYear ( calendar, dateLike )
1699 bool js::temporal::CalendarDayOfYear(JSContext* cx,
1700 Handle<CalendarValue> calendar,
1701 Handle<PlainDateObject*> dateLike,
1702 MutableHandle<Value> result) {
1703 return CalendarDayOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
1704 result);
1708 * CalendarDayOfYear ( calendar, dateLike )
1710 bool js::temporal::CalendarDayOfYear(JSContext* cx,
1711 Handle<CalendarValue> calendar,
1712 Handle<PlainDateTimeObject*> dateLike,
1713 MutableHandle<Value> result) {
1714 return CalendarDayOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
1715 result);
1719 * CalendarDayOfYear ( calendar, dateLike )
1721 bool js::temporal::CalendarDayOfYear(JSContext* cx,
1722 Handle<CalendarValue> calendar,
1723 const PlainDateTime& dateTime,
1724 MutableHandle<Value> result) {
1725 Rooted<PlainDateTimeObject*> dateLike(
1726 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1727 if (!dateLike) {
1728 return false;
1731 return ::CalendarDayOfYear(cx, calendar, dateLike, dateTime.date, result);
1735 * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )
1737 static bool BuiltinCalendarWeekOfYear(JSContext* cx, const PlainDate& date,
1738 MutableHandle<Value> result) {
1739 // Steps 1-4. (Not applicable.)
1741 // Steps 5-6.
1742 result.setInt32(ToISOWeekOfYear(date).week);
1743 return true;
1746 static bool Calendar_weekOfYear(JSContext* cx, unsigned argc, Value* vp);
1749 * CalendarWeekOfYear ( calendar, dateLike )
1751 static bool CalendarWeekOfYear(JSContext* cx, Handle<CalendarValue> calendar,
1752 Handle<JSObject*> dateLike,
1753 const PlainDate& date,
1754 MutableHandle<Value> result) {
1755 // Steps 1-6.
1756 return CallCalendarMethod<BuiltinCalendarWeekOfYear,
1757 RequireIntegralPositiveNumber>(
1758 cx, cx->names().weekOfYear, Calendar_weekOfYear, calendar, dateLike, date,
1759 result);
1763 * CalendarWeekOfYear ( calendar, dateLike )
1765 bool js::temporal::CalendarWeekOfYear(JSContext* cx,
1766 Handle<CalendarValue> calendar,
1767 Handle<PlainDateObject*> dateLike,
1768 MutableHandle<Value> result) {
1769 return CalendarWeekOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
1770 result);
1774 * CalendarWeekOfYear ( calendar, dateLike )
1776 bool js::temporal::CalendarWeekOfYear(JSContext* cx,
1777 Handle<CalendarValue> calendar,
1778 Handle<PlainDateTimeObject*> dateLike,
1779 MutableHandle<Value> result) {
1780 return CalendarWeekOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
1781 result);
1785 * CalendarWeekOfYear ( calendar, dateLike )
1787 bool js::temporal::CalendarWeekOfYear(JSContext* cx,
1788 Handle<CalendarValue> calendar,
1789 const PlainDateTime& dateTime,
1790 MutableHandle<Value> result) {
1791 Rooted<PlainDateTimeObject*> dateLike(
1792 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1793 if (!dateLike) {
1794 return false;
1797 return ::CalendarWeekOfYear(cx, calendar, dateLike, dateTime.date, result);
1801 * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )
1803 static bool BuiltinCalendarYearOfWeek(JSContext* cx, const PlainDate& date,
1804 MutableHandle<Value> result) {
1805 // Steps 1-4. (Not applicable.)
1807 // Steps 5-6.
1808 result.setInt32(ToISOWeekOfYear(date).year);
1809 return true;
1812 static bool Calendar_yearOfWeek(JSContext* cx, unsigned argc, Value* vp);
1815 * CalendarYearOfWeek ( calendar, dateLike )
1817 static bool CalendarYearOfWeek(JSContext* cx, Handle<CalendarValue> calendar,
1818 Handle<JSObject*> dateLike,
1819 const PlainDate& date,
1820 MutableHandle<Value> result) {
1821 // Steps 1-5.
1822 return CallCalendarMethod<BuiltinCalendarYearOfWeek, RequireIntegralNumber>(
1823 cx, cx->names().yearOfWeek, Calendar_yearOfWeek, calendar, dateLike, date,
1824 result);
1828 * CalendarYearOfWeek ( calendar, dateLike )
1830 bool js::temporal::CalendarYearOfWeek(JSContext* cx,
1831 Handle<CalendarValue> calendar,
1832 Handle<PlainDateObject*> dateLike,
1833 MutableHandle<Value> result) {
1834 return CalendarYearOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
1835 result);
1839 * CalendarYearOfWeek ( calendar, dateLike )
1841 bool js::temporal::CalendarYearOfWeek(JSContext* cx,
1842 Handle<CalendarValue> calendar,
1843 Handle<PlainDateTimeObject*> dateLike,
1844 MutableHandle<Value> result) {
1845 return CalendarYearOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
1846 result);
1850 * CalendarYearOfWeek ( calendar, dateLike )
1852 bool js::temporal::CalendarYearOfWeek(JSContext* cx,
1853 Handle<CalendarValue> calendar,
1854 const PlainDateTime& dateTime,
1855 MutableHandle<Value> result) {
1856 Rooted<PlainDateTimeObject*> dateLike(
1857 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1858 if (!dateLike) {
1859 return false;
1862 return ::CalendarYearOfWeek(cx, calendar, dateLike, dateTime.date, result);
1866 * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )
1868 static bool BuiltinCalendarDaysInWeek(JSContext* cx, const PlainDate& date,
1869 MutableHandle<Value> result) {
1870 // Steps 1-4. (Not applicable.)
1872 // Step 5.
1873 result.setInt32(7);
1874 return true;
1877 static bool Calendar_daysInWeek(JSContext* cx, unsigned argc, Value* vp);
1880 * CalendarDaysInWeek ( calendar, dateLike )
1882 static bool CalendarDaysInWeek(JSContext* cx, Handle<CalendarValue> calendar,
1883 Handle<JSObject*> dateLike,
1884 const PlainDate& date,
1885 MutableHandle<Value> result) {
1886 // Steps 1-6.
1887 return CallCalendarMethod<BuiltinCalendarDaysInWeek,
1888 RequireIntegralPositiveNumber>(
1889 cx, cx->names().daysInWeek, Calendar_daysInWeek, calendar, dateLike, date,
1890 result);
1894 * CalendarDaysInWeek ( calendar, dateLike )
1896 bool js::temporal::CalendarDaysInWeek(JSContext* cx,
1897 Handle<CalendarValue> calendar,
1898 Handle<PlainDateObject*> dateLike,
1899 MutableHandle<Value> result) {
1900 return CalendarDaysInWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
1901 result);
1905 * CalendarDaysInWeek ( calendar, dateLike )
1907 bool js::temporal::CalendarDaysInWeek(JSContext* cx,
1908 Handle<CalendarValue> calendar,
1909 Handle<PlainDateTimeObject*> dateLike,
1910 MutableHandle<Value> result) {
1911 return CalendarDaysInWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
1912 result);
1916 * CalendarDaysInWeek ( calendar, dateLike )
1918 bool js::temporal::CalendarDaysInWeek(JSContext* cx,
1919 Handle<CalendarValue> calendar,
1920 const PlainDateTime& dateTime,
1921 MutableHandle<Value> result) {
1922 Rooted<PlainDateTimeObject*> dateLike(
1923 cx, CreateTemporalDateTime(cx, dateTime, calendar));
1924 if (!dateLike) {
1925 return false;
1928 return ::CalendarDaysInWeek(cx, calendar, dateLike, dateTime.date, result);
1932 * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )
1934 static bool BuiltinCalendarDaysInMonth(JSContext* cx, const PlainDate& date,
1935 MutableHandle<Value> result) {
1936 // Steps 1-4. (Not applicable.)
1938 // Step 5.
1939 result.setInt32(::ISODaysInMonth(date.year, date.month));
1940 return true;
1943 static bool Calendar_daysInMonth(JSContext* cx, unsigned argc, Value* vp);
1946 * CalendarDaysInMonth ( calendar, dateLike )
1948 static bool CalendarDaysInMonth(JSContext* cx, Handle<CalendarValue> calendar,
1949 Handle<JSObject*> dateLike,
1950 const PlainDate& date,
1951 MutableHandle<Value> result) {
1952 // Step 1-6.
1953 return CallCalendarMethod<BuiltinCalendarDaysInMonth,
1954 RequireIntegralPositiveNumber>(
1955 cx, cx->names().daysInMonth, Calendar_daysInMonth, calendar, dateLike,
1956 date, result);
1960 * CalendarDaysInMonth ( calendar, dateLike )
1962 bool js::temporal::CalendarDaysInMonth(JSContext* cx,
1963 Handle<CalendarValue> calendar,
1964 Handle<PlainDateObject*> dateLike,
1965 MutableHandle<Value> result) {
1966 return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike),
1967 result);
1971 * CalendarDaysInMonth ( calendar, dateLike )
1973 bool js::temporal::CalendarDaysInMonth(JSContext* cx,
1974 Handle<CalendarValue> calendar,
1975 Handle<PlainDateTimeObject*> dateLike,
1976 MutableHandle<Value> result) {
1977 return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike),
1978 result);
1982 * CalendarDaysInMonth ( calendar, dateLike )
1984 bool js::temporal::CalendarDaysInMonth(JSContext* cx,
1985 Handle<CalendarValue> calendar,
1986 Handle<PlainYearMonthObject*> dateLike,
1987 MutableHandle<Value> result) {
1988 return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike),
1989 result);
1993 * CalendarDaysInMonth ( calendar, dateLike )
1995 bool js::temporal::CalendarDaysInMonth(JSContext* cx,
1996 Handle<CalendarValue> calendar,
1997 const PlainDateTime& dateTime,
1998 MutableHandle<Value> result) {
1999 Rooted<PlainDateTimeObject*> dateLike(
2000 cx, CreateTemporalDateTime(cx, dateTime, calendar));
2001 if (!dateLike) {
2002 return false;
2005 return ::CalendarDaysInMonth(cx, calendar, dateLike, dateTime.date, result);
2009 * Temporal.Calendar.prototype.daysInYear ( temporalDateLike )
2011 static bool BuiltinCalendarDaysInYear(JSContext* cx, const PlainDate& date,
2012 MutableHandle<Value> result) {
2013 // Steps 1-4. (Not applicable.)
2015 // Step 5.
2016 result.setInt32(ISODaysInYear(date.year));
2017 return true;
2020 static bool Calendar_daysInYear(JSContext* cx, unsigned argc, Value* vp);
2023 * CalendarDaysInYear ( calendar, dateLike )
2025 static bool CalendarDaysInYear(JSContext* cx, Handle<CalendarValue> calendar,
2026 Handle<JSObject*> dateLike,
2027 const PlainDate& date,
2028 MutableHandle<Value> result) {
2029 // Step 1-6.
2030 return CallCalendarMethod<BuiltinCalendarDaysInYear,
2031 RequireIntegralPositiveNumber>(
2032 cx, cx->names().daysInYear, Calendar_daysInYear, calendar, dateLike, date,
2033 result);
2037 * CalendarDaysInYear ( calendar, dateLike )
2039 bool js::temporal::CalendarDaysInYear(JSContext* cx,
2040 Handle<CalendarValue> calendar,
2041 Handle<PlainDateObject*> dateLike,
2042 MutableHandle<Value> result) {
2043 return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2044 result);
2048 * CalendarDaysInYear ( calendar, dateLike )
2050 bool js::temporal::CalendarDaysInYear(JSContext* cx,
2051 Handle<CalendarValue> calendar,
2052 Handle<PlainDateTimeObject*> dateLike,
2053 MutableHandle<Value> result) {
2054 return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2055 result);
2059 * CalendarDaysInYear ( calendar, dateLike )
2061 bool js::temporal::CalendarDaysInYear(JSContext* cx,
2062 Handle<CalendarValue> calendar,
2063 Handle<PlainYearMonthObject*> dateLike,
2064 MutableHandle<Value> result) {
2065 return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2066 result);
2070 * CalendarDaysInYear ( calendar, dateLike )
2072 bool js::temporal::CalendarDaysInYear(JSContext* cx,
2073 Handle<CalendarValue> calendar,
2074 const PlainDateTime& dateTime,
2075 MutableHandle<Value> result) {
2076 Rooted<PlainDateTimeObject*> dateLike(
2077 cx, CreateTemporalDateTime(cx, dateTime, calendar));
2078 if (!dateLike) {
2079 return false;
2082 return ::CalendarDaysInYear(cx, calendar, dateLike, dateTime.date, result);
2086 * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )
2088 static bool BuiltinCalendarMonthsInYear(JSContext* cx, const PlainDate& date,
2089 MutableHandle<Value> result) {
2090 // Steps 1-4. (Not applicable.)
2092 // Step 5.
2093 result.setInt32(12);
2094 return true;
2097 static bool Calendar_monthsInYear(JSContext* cx, unsigned argc, Value* vp);
2100 * CalendarMonthsInYear ( calendar, dateLike )
2102 static bool CalendarMonthsInYear(JSContext* cx, Handle<CalendarValue> calendar,
2103 Handle<JSObject*> dateLike,
2104 const PlainDate& date,
2105 MutableHandle<Value> result) {
2106 // Step 1-6.
2107 return CallCalendarMethod<BuiltinCalendarMonthsInYear,
2108 RequireIntegralPositiveNumber>(
2109 cx, cx->names().monthsInYear, Calendar_monthsInYear, calendar, dateLike,
2110 date, result);
2114 * CalendarMonthsInYear ( calendar, dateLike )
2116 bool js::temporal::CalendarMonthsInYear(JSContext* cx,
2117 Handle<CalendarValue> calendar,
2118 Handle<PlainDateObject*> dateLike,
2119 MutableHandle<Value> result) {
2120 return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2121 result);
2125 * CalendarMonthsInYear ( calendar, dateLike )
2127 bool js::temporal::CalendarMonthsInYear(JSContext* cx,
2128 Handle<CalendarValue> calendar,
2129 Handle<PlainDateTimeObject*> dateLike,
2130 MutableHandle<Value> result) {
2131 return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2132 result);
2136 * CalendarMonthsInYear ( calendar, dateLike )
2138 bool js::temporal::CalendarMonthsInYear(JSContext* cx,
2139 Handle<CalendarValue> calendar,
2140 Handle<PlainYearMonthObject*> dateLike,
2141 MutableHandle<Value> result) {
2142 return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2143 result);
2147 * CalendarMonthsInYear ( calendar, dateLike )
2149 bool js::temporal::CalendarMonthsInYear(JSContext* cx,
2150 Handle<CalendarValue> calendar,
2151 const PlainDateTime& dateTime,
2152 MutableHandle<Value> result) {
2153 Rooted<PlainDateTimeObject*> dateLike(
2154 cx, CreateTemporalDateTime(cx, dateTime, calendar));
2155 if (!dateLike) {
2156 return false;
2159 return ::CalendarMonthsInYear(cx, calendar, dateLike, dateTime.date, result);
2163 * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )
2165 static bool BuiltinCalendarInLeapYear(JSContext* cx, const PlainDate& date,
2166 MutableHandle<Value> result) {
2167 // Steps 1-4. (Not applicable.)
2169 // Steps 5-6.
2170 result.setBoolean(IsISOLeapYear(date.year));
2171 return true;
2174 static bool Calendar_inLeapYear(JSContext* cx, unsigned argc, Value* vp);
2177 * CalendarInLeapYear ( calendar, dateLike )
2179 static bool CalendarInLeapYear(JSContext* cx, Handle<CalendarValue> calendar,
2180 Handle<JSObject*> dateLike,
2181 const PlainDate& date,
2182 MutableHandle<Value> result) {
2183 // Step 1-4.
2184 return CallCalendarMethod<BuiltinCalendarInLeapYear, RequireBoolean>(
2185 cx, cx->names().inLeapYear, Calendar_inLeapYear, calendar, dateLike, date,
2186 result);
2190 * CalendarInLeapYear ( calendar, dateLike )
2192 bool js::temporal::CalendarInLeapYear(JSContext* cx,
2193 Handle<CalendarValue> calendar,
2194 Handle<PlainDateObject*> dateLike,
2195 MutableHandle<Value> result) {
2196 return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2197 result);
2201 * CalendarInLeapYear ( calendar, dateLike )
2203 bool js::temporal::CalendarInLeapYear(JSContext* cx,
2204 Handle<CalendarValue> calendar,
2205 Handle<PlainDateTimeObject*> dateLike,
2206 MutableHandle<Value> result) {
2207 return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2208 result);
2212 * CalendarInLeapYear ( calendar, dateLike )
2214 bool js::temporal::CalendarInLeapYear(JSContext* cx,
2215 Handle<CalendarValue> calendar,
2216 Handle<PlainYearMonthObject*> dateLike,
2217 MutableHandle<Value> result) {
2218 return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike),
2219 result);
2223 * CalendarInLeapYear ( calendar, dateLike )
2225 bool js::temporal::CalendarInLeapYear(JSContext* cx,
2226 Handle<CalendarValue> calendar,
2227 const PlainDateTime& dateTime,
2228 MutableHandle<Value> result) {
2229 Rooted<PlainDateTimeObject*> dateLike(
2230 cx, CreateTemporalDateTime(cx, dateTime, calendar));
2231 if (!dateLike) {
2232 return false;
2235 return ::CalendarInLeapYear(cx, calendar, dateLike, dateTime.date, result);
2239 * ISOResolveMonth ( fields )
2241 static bool ISOResolveMonth(JSContext* cx,
2242 MutableHandle<TemporalFields> fields) {
2243 // Step 1. (Not applicable in our implementation.)
2245 // Step 2.
2246 double month = fields.month();
2248 // Step 3.
2249 MOZ_ASSERT((IsInteger(month) && month > 0) || std::isnan(month));
2251 // Step 4.
2252 Handle<JSString*> monthCode = fields.monthCode();
2254 // Step 5.
2255 if (!monthCode) {
2256 // Step 5.a.
2257 if (std::isnan(month)) {
2258 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2259 JSMSG_TEMPORAL_CALENDAR_MISSING_FIELD,
2260 "monthCode");
2261 return false;
2264 // Step 5.b.
2265 return true;
2268 // Steps 6-7. (Not applicable in our implementation.)
2270 // Step 8.
2271 if (monthCode->length() != 3) {
2272 if (auto code = QuoteString(cx, monthCode)) {
2273 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2274 JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
2275 code.get());
2277 return false;
2280 JSLinearString* linear = monthCode->ensureLinear(cx);
2281 if (!linear) {
2282 return false;
2285 char16_t chars[3] = {
2286 linear->latin1OrTwoByteChar(0),
2287 linear->latin1OrTwoByteChar(1),
2288 linear->latin1OrTwoByteChar(2),
2291 // Steps 9-11. (Partial)
2292 if (chars[0] != 'M' || !mozilla::IsAsciiDigit(chars[1]) ||
2293 !mozilla::IsAsciiDigit(chars[2])) {
2294 if (auto code = QuoteString(cx, linear)) {
2295 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2296 JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
2297 code.get());
2299 return false;
2302 // Step 12.
2303 int32_t monthCodeInteger =
2304 AsciiDigitToNumber(chars[1]) * 10 + AsciiDigitToNumber(chars[2]);
2306 // Step 11. (Partial)
2307 if (monthCodeInteger < 1 || monthCodeInteger > 12) {
2308 if (auto code = QuoteString(cx, linear)) {
2309 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2310 JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
2311 code.get());
2313 return false;
2316 // Step 13. (Not applicable in our implementation.)
2318 // Step 14.
2319 if (!std::isnan(month) && month != monthCodeInteger) {
2320 if (auto code = QuoteString(cx, linear)) {
2321 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
2322 JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
2323 code.get());
2325 return false;
2328 // Step 15.
2329 fields.month() = monthCodeInteger;
2331 // Step 16.
2332 return true;
2336 * ISODateFromFields ( fields, overflow )
2338 static bool ISODateFromFields(JSContext* cx, Handle<TemporalFields> fields,
2339 TemporalOverflow overflow, PlainDate* result) {
2340 // Steps 1-2. (Not applicable in our implementation.)
2342 // Step 3.
2343 double year = fields.year();
2345 // Step 4.
2346 double month = fields.month();
2348 // Step 5.
2349 double day = fields.day();
2351 // Step 6.
2352 MOZ_ASSERT(!std::isnan(year) && !std::isnan(month) && !std::isnan(day));
2354 // Step 7.
2355 RegulatedISODate regulated;
2356 if (!RegulateISODate(cx, year, month, day, overflow, &regulated)) {
2357 return false;
2360 // The result is used to create a new PlainDateObject, so it's okay to
2361 // directly throw an error for invalid years. That way we don't have to worry
2362 // about representing doubles in PlainDate structs.
2363 int32_t intYear;
2364 if (!mozilla::NumberEqualsInt32(regulated.year, &intYear)) {
2365 // CreateTemporalDate, steps 1-2.
2366 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2367 JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
2368 return false;
2371 *result = {intYear, regulated.month, regulated.day};
2372 return true;
2376 * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )
2378 static PlainDateObject* BuiltinCalendarDateFromFields(
2379 JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) {
2380 // Steps 1-5. (Not applicable)
2382 // Step 6.
2383 Rooted<TemporalFields> dateFields(cx);
2384 if (!PrepareTemporalFields(cx, fields,
2385 {TemporalField::Day, TemporalField::Month,
2386 TemporalField::MonthCode, TemporalField::Year},
2387 {TemporalField::Day, TemporalField::Year},
2388 &dateFields)) {
2389 return nullptr;
2392 // Step 7.
2393 auto overflow = TemporalOverflow::Constrain;
2394 if (maybeOptions) {
2395 if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
2396 return nullptr;
2400 // Step 8.
2401 if (!ISOResolveMonth(cx, &dateFields)) {
2402 return nullptr;
2405 // Step 9.
2406 PlainDate result;
2407 if (!ISODateFromFields(cx, dateFields, overflow, &result)) {
2408 return nullptr;
2411 // Step 10.
2412 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
2413 return CreateTemporalDate(cx, result, calendar);
2417 * CalendarDateFromFields ( calendarRec, fields [ , options ] )
2419 static Wrapped<PlainDateObject*> CalendarDateFromFields(
2420 JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields,
2421 Handle<PlainObject*> maybeOptions) {
2422 MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar,
2423 CalendarMethod::DateFromFields));
2425 // Step 1. (Not applicable in our implemetation.)
2427 // Step 3. (Reordered)
2428 auto dateFromFields = calendar.dateFromFields();
2429 if (!dateFromFields) {
2430 return BuiltinCalendarDateFromFields(cx, fields, maybeOptions);
2433 // Step 2. (Inlined call to CalendarMethodsRecordCall.)
2435 Rooted<Value> dateFromFieldsFn(cx, ObjectValue(*dateFromFields));
2436 auto thisv = calendar.receiver().toValue();
2437 Rooted<Value> rval(cx);
2439 FixedInvokeArgs<2> args(cx);
2440 args[0].setObject(*fields);
2441 if (maybeOptions) {
2442 args[1].setObject(*maybeOptions);
2443 } else {
2444 args[1].setUndefined();
2447 if (!Call(cx, dateFromFieldsFn, thisv, args, &rval)) {
2448 return nullptr;
2451 // Step 4.
2452 if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainDateObject>()) {
2453 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
2454 nullptr, "not a PlainDate object");
2455 return nullptr;
2458 // Step 5.
2459 return &rval.toObject();
2463 * CalendarDateFromFields ( calendarRec, fields [ , options ] )
2465 Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields(
2466 JSContext* cx, Handle<CalendarRecord> calendar,
2467 Handle<PlainObject*> fields) {
2468 // Steps 1-6.
2469 return ::CalendarDateFromFields(cx, calendar, fields, nullptr);
2473 * CalendarDateFromFields ( calendarRec, fields [ , options ] )
2475 Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields(
2476 JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
2477 Handle<PlainObject*> options) {
2478 // Steps 1-6.
2479 return ::CalendarDateFromFields(cx, calendar, fields, options);
2483 * CalendarDateFromFields ( calendarRec, fields [ , options ] )
2485 Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields(
2486 JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
2487 TemporalOverflow overflow) {
2488 // FIXME: spec issue - unnecessary options object when using "constrain".
2489 // https://github.com/tc39/proposal-temporal/issues/2803
2491 Rooted<PlainObject*> options(cx, NewPlainObjectWithProto(cx, nullptr));
2492 if (!options) {
2493 return nullptr;
2496 Rooted<Value> value(cx);
2497 if (overflow == TemporalOverflow::Constrain) {
2498 value = StringValue(cx->names().constrain);
2499 } else {
2500 MOZ_ASSERT(overflow == TemporalOverflow::Reject);
2501 value = StringValue(cx->names().reject);
2503 if (!DefineDataProperty(cx, options, cx->names().overflow, value)) {
2504 return nullptr;
2507 // Steps 1-6.
2508 return ::CalendarDateFromFields(cx, calendar, fields, options);
2511 struct RegulatedISOYearMonth final {
2512 double year = 0;
2513 int32_t month = 0;
2517 * RegulateISOYearMonth ( year, month, overflow )
2519 static bool RegulateISOYearMonth(JSContext* cx, double year, double month,
2520 TemporalOverflow overflow,
2521 RegulatedISOYearMonth* result) {
2522 // Step 1.
2523 MOZ_ASSERT(IsInteger(year));
2524 MOZ_ASSERT(IsInteger(month));
2526 // Step 2. (Not applicable in our implementation.)
2528 // Step 3.
2529 if (overflow == TemporalOverflow::Constrain) {
2530 // Step 3.a.
2531 month = std::clamp(month, 1.0, 12.0);
2533 // Step 3.b.
2534 *result = {year, int32_t(month)};
2535 return true;
2538 // Step 4.a.
2539 MOZ_ASSERT(overflow == TemporalOverflow::Reject);
2541 // Step 4.b.
2542 if (month < 1 || month > 12) {
2543 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2544 JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
2545 return false;
2548 // Step 4.c.
2549 *result = {year, int32_t(month)};
2550 return true;
2554 * ISOYearMonthFromFields ( fields, overflow )
2556 static bool ISOYearMonthFromFields(JSContext* cx, Handle<TemporalFields> fields,
2557 TemporalOverflow overflow,
2558 PlainDate* result) {
2559 // Steps 1-2. (Not applicable in our implementation.)
2561 // Step 3.
2562 double year = fields.year();
2564 // Step 4.
2565 double month = fields.month();
2567 // Step 5.
2568 MOZ_ASSERT(!std::isnan(year) && !std::isnan(month));
2570 // Step 6.
2571 RegulatedISOYearMonth regulated;
2572 if (!RegulateISOYearMonth(cx, year, month, overflow, &regulated)) {
2573 return false;
2576 // Step 7.
2578 // The result is used to create a new PlainYearMonthObject, so it's okay to
2579 // directly throw an error for invalid years. That way we don't have to worry
2580 // about representing doubles in PlainDate structs.
2581 int32_t intYear;
2582 if (!mozilla::NumberEqualsInt32(regulated.year, &intYear)) {
2583 // CreateTemporalYearMonth, steps 1-2.
2584 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2585 JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
2586 return false;
2589 *result = {intYear, regulated.month, 1};
2590 return true;
2594 * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )
2596 static PlainYearMonthObject* BuiltinCalendarYearMonthFromFields(
2597 JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) {
2598 // Steps 1-5. (Not applicable)
2600 // Step 6.
2601 Rooted<TemporalFields> dateFields(cx);
2602 if (!PrepareTemporalFields(
2603 cx, fields,
2604 {TemporalField::Month, TemporalField::MonthCode, TemporalField::Year},
2605 {TemporalField::Year}, &dateFields)) {
2606 return nullptr;
2609 // Step 7.
2610 auto overflow = TemporalOverflow::Constrain;
2611 if (maybeOptions) {
2612 if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
2613 return nullptr;
2617 // Step 8.
2618 if (!ISOResolveMonth(cx, &dateFields)) {
2619 return nullptr;
2622 // Step 9.
2623 PlainDate result;
2624 if (!ISOYearMonthFromFields(cx, dateFields, overflow, &result)) {
2625 return nullptr;
2628 // Step 10.
2629 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
2630 return CreateTemporalYearMonth(cx, result, calendar);
2634 * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
2636 static Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields(
2637 JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields,
2638 Handle<PlainObject*> maybeOptions) {
2639 MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(
2640 calendar, CalendarMethod::YearMonthFromFields));
2642 // Step 1. (Not applicable in our implementation.)
2644 // Step 3. (Reordered)
2645 auto yearMonthFromFields = calendar.yearMonthFromFields();
2646 if (!yearMonthFromFields) {
2647 return BuiltinCalendarYearMonthFromFields(cx, fields, maybeOptions);
2650 // Step 2. (Inlined call to CalendarMethodsRecordCall.)
2652 Rooted<Value> yearMonthFromFieldsFn(cx, ObjectValue(*yearMonthFromFields));
2653 auto thisv = calendar.receiver().toValue();
2654 Rooted<Value> rval(cx);
2656 FixedInvokeArgs<2> args(cx);
2657 args[0].setObject(*fields);
2658 if (maybeOptions) {
2659 args[1].setObject(*maybeOptions);
2660 } else {
2661 args[1].setUndefined();
2664 if (!Call(cx, yearMonthFromFieldsFn, thisv, args, &rval)) {
2665 return nullptr;
2668 // Step 4.
2669 if (!rval.isObject() ||
2670 !rval.toObject().canUnwrapAs<PlainYearMonthObject>()) {
2671 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
2672 nullptr, "not a PlainYearMonth object");
2673 return nullptr;
2676 // Step 5.
2677 return &rval.toObject();
2681 * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
2683 Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields(
2684 JSContext* cx, Handle<CalendarRecord> calendar,
2685 Handle<PlainObject*> fields) {
2686 // Steps 1-4.
2687 return ::CalendarYearMonthFromFields(cx, calendar, fields, nullptr);
2691 * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
2693 Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields(
2694 JSContext* cx, Handle<CalendarRecord> calendar,
2695 Handle<PlainYearMonthObject*> fields) {
2696 // Steps 1-4.
2697 return ::CalendarYearMonthFromFields(cx, calendar, fields, nullptr);
2701 * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
2703 Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields(
2704 JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
2705 Handle<PlainObject*> options) {
2706 // Steps 1-4.
2707 return ::CalendarYearMonthFromFields(cx, calendar, fields, options);
2711 * ISOMonthDayFromFields ( fields, overflow )
2713 static bool ISOMonthDayFromFields(JSContext* cx, Handle<TemporalFields> fields,
2714 TemporalOverflow overflow,
2715 PlainDate* result) {
2716 // Steps 1-2. (Not applicable in our implementation.)
2718 // Step 3.
2719 double month = fields.month();
2721 // Step 4.
2722 double day = fields.day();
2724 // Step 5.
2725 MOZ_ASSERT(!std::isnan(month));
2726 MOZ_ASSERT(!std::isnan(day));
2728 // Step 6.
2729 double year = fields.year();
2731 // Step 7.
2732 int32_t referenceISOYear = 1972;
2734 // Steps 8-9.
2735 double y = std::isnan(year) ? referenceISOYear : year;
2736 RegulatedISODate regulated;
2737 if (!RegulateISODate(cx, y, month, day, overflow, &regulated)) {
2738 return false;
2741 // Step 10.
2742 *result = {referenceISOYear, regulated.month, regulated.day};
2743 return true;
2747 * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )
2749 static PlainMonthDayObject* BuiltinCalendarMonthDayFromFields(
2750 JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) {
2751 // Steps 1-5. (Not applicable)
2753 // Step 6.
2754 Rooted<TemporalFields> dateFields(cx);
2755 if (!PrepareTemporalFields(cx, fields,
2756 {TemporalField::Day, TemporalField::Month,
2757 TemporalField::MonthCode, TemporalField::Year},
2758 {TemporalField::Day}, &dateFields)) {
2759 return nullptr;
2762 // Step 7.
2763 auto overflow = TemporalOverflow::Constrain;
2764 if (maybeOptions) {
2765 if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
2766 return nullptr;
2770 // Step 8.
2771 if (!ISOResolveMonth(cx, &dateFields)) {
2772 return nullptr;
2775 // Step 9.
2776 PlainDate result;
2777 if (!ISOMonthDayFromFields(cx, dateFields, overflow, &result)) {
2778 return nullptr;
2781 // Step 10.
2782 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
2783 return CreateTemporalMonthDay(cx, result, calendar);
2787 * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
2789 static Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields(
2790 JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields,
2791 Handle<PlainObject*> maybeOptions) {
2792 MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(
2793 calendar, CalendarMethod::MonthDayFromFields));
2795 // Step 1. (Not applicable in our implementation.)
2797 // Step 3. (Reordered)
2798 auto monthDayFromFields = calendar.monthDayFromFields();
2799 if (!monthDayFromFields) {
2800 return BuiltinCalendarMonthDayFromFields(cx, fields, maybeOptions);
2803 // Step 2. (Inlined call to CalendarMethodsRecordCall.)
2805 Rooted<Value> monthDayFromFieldsFn(cx, ObjectValue(*monthDayFromFields));
2806 auto thisv = calendar.receiver().toValue();
2807 Rooted<Value> rval(cx);
2809 FixedInvokeArgs<2> args(cx);
2810 args[0].setObject(*fields);
2811 if (maybeOptions) {
2812 args[1].setObject(*maybeOptions);
2813 } else {
2814 args[1].setUndefined();
2817 if (!Call(cx, monthDayFromFieldsFn, thisv, args, &rval)) {
2818 return nullptr;
2821 // Step 4.
2822 if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainMonthDayObject>()) {
2823 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
2824 nullptr, "not a PlainMonthDay object");
2825 return nullptr;
2828 // Step 5.
2829 return &rval.toObject();
2833 * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
2835 Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields(
2836 JSContext* cx, Handle<CalendarRecord> calendar,
2837 Handle<PlainObject*> fields) {
2838 // Steps 1-4.
2839 return ::CalendarMonthDayFromFields(cx, calendar, fields, nullptr);
2843 * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
2845 Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields(
2846 JSContext* cx, Handle<CalendarRecord> calendar,
2847 Handle<PlainMonthDayObject*> fields) {
2848 // Steps 1-4.
2849 return ::CalendarMonthDayFromFields(cx, calendar, fields, nullptr);
2853 * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
2855 Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields(
2856 JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
2857 Handle<PlainObject*> options) {
2858 // Steps 1-4.
2859 return ::CalendarMonthDayFromFields(cx, calendar, fields, options);
2862 using PropertyHashSet = JS::GCHashSet<JS::PropertyKey>;
2863 using PropertyVector = JS::StackGCVector<JS::PropertyKey>;
2866 * ISOFieldKeysToIgnore ( keys )
2868 static bool ISOFieldKeysToIgnore(JSContext* cx, const PropertyVector& keys,
2869 PropertyHashSet& ignoredKeys) {
2870 MOZ_ASSERT(ignoredKeys.empty(), "expected an empty output hashset");
2872 // Step 1. (Not applicable in our implementation.)
2874 if (!ignoredKeys.reserve(keys.length())) {
2875 return false;
2878 // Step 2.
2879 bool seenMonthOrMonthCode = false;
2880 for (const auto& key : keys) {
2881 // Reorder the substeps in order to use |putNew| instead of |put|, because
2882 // the former is slightly faster.
2884 // Steps 2.a-c.
2885 if (key.isAtom(cx->names().month) || key.isAtom(cx->names().monthCode)) {
2886 // Steps 2.b-c.
2887 if (!seenMonthOrMonthCode) {
2888 seenMonthOrMonthCode = true;
2890 // Add both keys at once.
2891 if (!ignoredKeys.putNew(NameToId(cx->names().month)) ||
2892 !ignoredKeys.putNew(NameToId(cx->names().monthCode))) {
2893 return false;
2896 } else {
2897 // Step 2.a.
2898 if (!ignoredKeys.putNew(key)) {
2899 return false;
2904 // Step 3.
2905 return true;
2909 * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )
2911 static PlainObject* BuiltinCalendarMergeFields(
2912 JSContext* cx, Handle<JSObject*> fields,
2913 Handle<JSObject*> additionalFields) {
2914 // NOTE: This function is only called from CalendarMergeFields and its
2915 // result is always passed to PrepareTemporalFields. PrepareTemporalFields
2916 // sorts the incoming property keys, so it doesn't matter in which order the
2917 // properties are created in this function. This allows to reorder the steps
2918 // to process |additionalFields| first.
2920 // TODO: Consider additionally passing the fieldNames from the succeeding
2921 // PrepareTemporalFields call, so we don't have to process unnecessary keys.
2923 // Steps 1-2. (Not applicable in our implementation.)
2925 // Step 13.
2926 Rooted<PlainObject*> merged(cx, NewPlainObjectWithProto(cx, nullptr));
2927 if (!merged) {
2928 return nullptr;
2931 // Contrary to the spec, add the properties from |additionalFields| first.
2933 // Step 6. (Not applicable in our implementation.)
2935 // Steps 7-8.
2937 // PrepareTemporalFields ignores symbol property keys, so we don't need to
2938 // pass JSITER_SYMBOLS. |additionalFields| contains no non-enumerable
2939 // properties, therefore JSITER_HIDDEN isn't needed, too.
2940 JS::RootedVector<PropertyKey> keys(cx);
2941 if (!GetPropertyKeys(cx, additionalFields, JSITER_OWNONLY, &keys)) {
2942 return nullptr;
2945 // Steps 9-11. (Not applicable in our implementation.)
2947 // Step 12.
2948 Rooted<PropertyHashSet> ignoredKeys(cx, PropertyHashSet(cx));
2949 if (!ignoredKeys.reserve(keys.length())) {
2950 return nullptr;
2953 // Steps 7-8, 12, and 15.
2954 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
2955 Rooted<Value> propValue(cx);
2956 bool seenMonthOrMonthCode = false;
2957 for (size_t i = 0; i < keys.length(); i++) {
2958 Handle<PropertyKey> key = keys[i];
2960 if (!GetOwnPropertyDescriptor(cx, additionalFields, key, &desc)) {
2961 return nullptr;
2964 propValue.set(desc->value());
2966 // Skip |undefined| properties per step 8.
2967 if (propValue.isUndefined()) {
2968 continue;
2971 // Step 15.
2972 if (!DefineDataProperty(cx, merged, key, propValue)) {
2973 return nullptr;
2976 // Step 12. (Inlined ISOFieldKeysToIgnore)
2977 if (key.isAtom(cx->names().month) || key.isAtom(cx->names().monthCode)) {
2978 // ISOFieldKeysToIgnore, steps 2.b-c.
2979 if (!seenMonthOrMonthCode) {
2980 seenMonthOrMonthCode = true;
2982 if (!ignoredKeys.putNew(NameToId(cx->names().month)) ||
2983 !ignoredKeys.putNew(NameToId(cx->names().monthCode))) {
2984 return nullptr;
2987 } else {
2988 // ISOFieldKeysToIgnore, step 2.a.
2989 if (!ignoredKeys.putNew(key)) {
2990 return nullptr;
2995 // Now add the properties from |field|.
2997 // Step 3. (Not applicable in our implementation.)
2999 // Reuse |keys| to avoid extra allocations.
3000 keys.clear();
3002 // Steps 4-5.
3004 // See above why neither JSITER_SYMBOLS nor JSITER_HIDDEN is needed.
3005 if (!GetPropertyKeys(cx, fields, JSITER_OWNONLY, &keys)) {
3006 return nullptr;
3009 // Steps 4-5 and 14.
3010 for (size_t i = 0; i < keys.length(); i++) {
3011 Handle<PropertyKey> key = keys[i];
3013 // Skip ignored keys per step 14.
3014 if (ignoredKeys.has(key)) {
3015 continue;
3018 if (!GetOwnPropertyDescriptor(cx, fields, key, &desc)) {
3019 return nullptr;
3022 propValue.set(desc->value());
3024 // Skip |undefined| properties per step 5.
3025 if (propValue.isUndefined()) {
3026 continue;
3029 // Step 14.
3030 if (!DefineDataProperty(cx, merged, key, propValue)) {
3031 return nullptr;
3035 // Step 16.
3036 return merged;
3039 #ifdef DEBUG
3040 static bool IsPlainDataObject(PlainObject* obj) {
3041 for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
3042 if (iter->flags() != PropertyFlags::defaultDataPropFlags) {
3043 return false;
3046 return true;
3048 #endif
3051 * CalendarMergeFields ( calendarRec, fields, additionalFields )
3053 JSObject* js::temporal::CalendarMergeFields(
3054 JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
3055 Handle<PlainObject*> additionalFields) {
3056 MOZ_ASSERT(
3057 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::MergeFields));
3059 MOZ_ASSERT(IsPlainDataObject(fields));
3060 MOZ_ASSERT(IsPlainDataObject(additionalFields));
3062 // Step 2. (Reordered)
3063 auto mergeFields = calendar.mergeFields();
3064 if (!mergeFields) {
3065 return BuiltinCalendarMergeFields(cx, fields, additionalFields);
3068 // Step 1. (Inlined call to CalendarMethodsRecordCall.)
3070 Rooted<Value> mergeFieldsFn(cx, ObjectValue(*mergeFields));
3071 auto thisv = calendar.receiver().toValue();
3072 Rooted<Value> result(cx);
3074 FixedInvokeArgs<2> args(cx);
3075 args[0].setObject(*fields);
3076 args[1].setObject(*additionalFields);
3078 if (!Call(cx, mergeFieldsFn, thisv, args, &result)) {
3079 return nullptr;
3082 // Steps 3-4.
3083 return RequireObject(cx, result);
3087 * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
3089 static bool BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
3090 const NormalizedDuration& duration,
3091 Handle<JSObject*> options, PlainDate* result) {
3092 MOZ_ASSERT(IsValidISODate(date));
3093 MOZ_ASSERT(IsValidDuration(duration));
3095 // Steps 1-6. (Not applicable)
3097 // Step 7.
3098 auto overflow = TemporalOverflow::Constrain;
3099 if (options) {
3100 if (!ToTemporalOverflow(cx, options, &overflow)) {
3101 return false;
3105 // Step 8.
3106 const auto& timeDuration = duration.time;
3108 // Step 9.
3109 auto balanceResult = BalanceTimeDuration(timeDuration, TemporalUnit::Day);
3111 // Step 10.
3112 auto addDuration = DateDuration{
3113 duration.date.years,
3114 duration.date.months,
3115 duration.date.weeks,
3116 duration.date.days + balanceResult.days,
3118 return AddISODate(cx, date, addDuration, overflow, result);
3122 * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
3124 static bool BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
3125 const DateDuration& duration,
3126 Handle<JSObject*> options, PlainDate* result) {
3127 // Steps 1-6. (Not applicable)
3129 // Step 8. (Reordered)
3130 auto normalized = CreateNormalizedDurationRecord(duration, {});
3132 // Steps 7-10.
3133 return BuiltinCalendarAdd(cx, date, normalized, options, result);
3137 * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
3139 static PlainDateObject* BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
3140 const DateDuration& duration,
3141 Handle<JSObject*> options) {
3142 // Steps 1-10.
3143 PlainDate result;
3144 if (!BuiltinCalendarAdd(cx, date, duration, options, &result)) {
3145 return nullptr;
3148 // Step 11.
3149 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
3150 return CreateTemporalDate(cx, result, calendar);
3154 * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
3156 static PlainDateObject* BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
3157 const Duration& duration,
3158 Handle<JSObject*> options) {
3159 // Steps 1-6. (Not applicable)
3161 // Step 8. (Reordered)
3162 auto normalized = CreateNormalizedDurationRecord(duration);
3164 // Steps 7-10.
3165 PlainDate result;
3166 if (!BuiltinCalendarAdd(cx, date, normalized, options, &result)) {
3167 return nullptr;
3170 // Step 11.
3171 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
3172 return CreateTemporalDate(cx, result, calendar);
3176 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3178 static Wrapped<PlainDateObject*> CalendarDateAddSlow(
3179 JSContext* cx, Handle<CalendarRecord> calendar,
3180 Handle<Wrapped<PlainDateObject*>> date,
3181 Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) {
3182 MOZ_ASSERT(
3183 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3184 MOZ_ASSERT(calendar.receiver().isObject());
3185 MOZ_ASSERT(calendar.dateAdd());
3187 // Step 1. (Not applicable).
3189 // Step 2. (Inlined call to CalendarMethodsRecordCall.)
3190 Rooted<JS::Value> dateAdd(cx, ObjectValue(*calendar.dateAdd()));
3191 auto thisv = calendar.receiver().toValue();
3192 Rooted<Value> rval(cx);
3194 FixedInvokeArgs<3> args(cx);
3195 args[0].setObject(*date);
3196 args[1].setObject(*duration);
3197 if (options) {
3198 args[2].setObject(*options);
3199 } else {
3200 args[2].setUndefined();
3203 if (!Call(cx, dateAdd, thisv, args, &rval)) {
3204 return nullptr;
3207 // Step 3. (Not applicable)
3208 MOZ_ASSERT(!CalendarMethodsRecordIsBuiltin(calendar));
3210 // Step 4.
3211 if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainDateObject>()) {
3212 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
3213 nullptr, "not a PlainDate object");
3214 return nullptr;
3217 // Step 5.
3218 return &rval.toObject();
3222 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3224 static Wrapped<PlainDateObject*> CalendarDateAdd(
3225 JSContext* cx, Handle<CalendarRecord> calendar,
3226 Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
3227 Handle<JSObject*> options) {
3228 MOZ_ASSERT(
3229 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3231 // Step 1. (Not applicable).
3233 // Step 3. (Reordered)
3234 if (!calendar.dateAdd()) {
3235 auto* unwrappedDate = date.unwrap(cx);
3236 if (!unwrappedDate) {
3237 return nullptr;
3239 auto date = ToPlainDate(unwrappedDate);
3241 return BuiltinCalendarAdd(cx, date, duration, options);
3244 // Steps 2 and 4-5.
3245 Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration));
3246 if (!durationObj) {
3247 return nullptr;
3249 return CalendarDateAddSlow(cx, calendar, date, durationObj, options);
3253 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3255 static Wrapped<PlainDateObject*> CalendarDateAdd(
3256 JSContext* cx, Handle<CalendarRecord> calendar,
3257 Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration,
3258 Handle<JSObject*> options) {
3259 MOZ_ASSERT(
3260 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3262 // Step 1. (Not applicable).
3264 // Step 3. (Reordered)
3265 if (!calendar.dateAdd()) {
3266 auto* unwrappedDate = date.unwrap(cx);
3267 if (!unwrappedDate) {
3268 return nullptr;
3270 auto date = ToPlainDate(unwrappedDate);
3272 return BuiltinCalendarAdd(cx, date, duration, options);
3275 // Steps 2 and 4-5.
3276 Rooted<DurationObject*> durationObj(
3277 cx, CreateTemporalDuration(cx, duration.toDuration()));
3278 if (!durationObj) {
3279 return nullptr;
3281 return CalendarDateAddSlow(cx, calendar, date, durationObj, options);
3285 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3287 static Wrapped<PlainDateObject*> CalendarDateAdd(
3288 JSContext* cx, Handle<CalendarRecord> calendar,
3289 Handle<Wrapped<PlainDateObject*>> date,
3290 Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) {
3291 MOZ_ASSERT(
3292 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3294 // Step 1. (Not applicable).
3296 // Step 3. (Reordered)
3297 if (!calendar.dateAdd()) {
3298 auto* unwrappedDate = date.unwrap(cx);
3299 if (!unwrappedDate) {
3300 return nullptr;
3302 auto date = ToPlainDate(unwrappedDate);
3304 auto* unwrappedDuration = duration.unwrap(cx);
3305 if (!unwrappedDuration) {
3306 return nullptr;
3308 auto duration = ToDuration(unwrappedDuration);
3310 return BuiltinCalendarAdd(cx, date, duration, options);
3313 // Steps 2 and 4-5.
3314 return CalendarDateAddSlow(cx, calendar, date, duration, options);
3318 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3320 static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
3321 Handle<Wrapped<PlainDateObject*>> date,
3322 const DateDuration& duration,
3323 Handle<JSObject*> options, PlainDate* result) {
3324 MOZ_ASSERT(
3325 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3327 // Step 1. (Not applicable).
3329 // Step 3. (Reordered)
3330 if (!calendar.dateAdd()) {
3331 auto* unwrappedDate = date.unwrap(cx);
3332 if (!unwrappedDate) {
3333 return false;
3335 auto date = ToPlainDate(unwrappedDate);
3337 return BuiltinCalendarAdd(cx, date, duration, options, result);
3340 // Steps 2 and 4-5.
3342 Rooted<DurationObject*> durationObj(
3343 cx, CreateTemporalDuration(cx, duration.toDuration()));
3344 if (!durationObj) {
3345 return false;
3348 auto obj = CalendarDateAddSlow(cx, calendar, date, durationObj, options);
3349 if (!obj) {
3350 return false;
3353 *result = ToPlainDate(&obj.unwrap());
3354 return true;
3358 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3360 static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
3361 const PlainDate& date, const DateDuration& duration,
3362 Handle<JSObject*> options, PlainDate* result) {
3363 MOZ_ASSERT(
3364 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
3366 // Step 1. (Not applicable).
3368 // Step 3. (Reordered)
3369 if (!calendar.dateAdd()) {
3370 return BuiltinCalendarAdd(cx, date, duration, options, result);
3373 // Steps 2 and 4-5.
3375 Rooted<PlainDateObject*> dateObj(
3376 cx, CreateTemporalDate(cx, date, calendar.receiver()));
3377 if (!dateObj) {
3378 return false;
3381 Rooted<DurationObject*> durationObj(
3382 cx, CreateTemporalDuration(cx, duration.toDuration()));
3383 if (!durationObj) {
3384 return false;
3387 auto obj = CalendarDateAddSlow(cx, calendar, dateObj, durationObj, options);
3388 if (!obj) {
3389 return false;
3392 *result = ToPlainDate(&obj.unwrap());
3393 return true;
3397 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3399 Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
3400 JSContext* cx, Handle<CalendarRecord> calendar,
3401 Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
3402 Handle<JSObject*> options) {
3403 // Step 1. (Not applicable).
3405 // Steps 2-5.
3406 return ::CalendarDateAdd(cx, calendar, date, duration, options);
3410 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3412 Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
3413 JSContext* cx, Handle<CalendarRecord> calendar,
3414 Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration) {
3415 // Step 1.
3416 Handle<JSObject*> options = nullptr;
3418 // Steps 2-5.
3419 return ::CalendarDateAdd(cx, calendar, date, duration, options);
3423 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3425 Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
3426 JSContext* cx, Handle<CalendarRecord> calendar,
3427 Handle<Wrapped<PlainDateObject*>> date,
3428 Handle<Wrapped<DurationObject*>> duration) {
3429 // Step 1.
3430 Handle<JSObject*> options = nullptr;
3432 // Steps 2-5.
3433 return ::CalendarDateAdd(cx, calendar, date, duration, options);
3437 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3439 Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
3440 JSContext* cx, Handle<CalendarRecord> calendar,
3441 Handle<Wrapped<PlainDateObject*>> date,
3442 Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) {
3443 // Step 1. (Not applicable).
3445 // Steps 2-5.
3446 return ::CalendarDateAdd(cx, calendar, date, duration, options);
3450 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3452 bool js::temporal::CalendarDateAdd(JSContext* cx,
3453 Handle<CalendarRecord> calendar,
3454 const PlainDate& date,
3455 const DateDuration& duration,
3456 PlainDate* result) {
3457 // Step 1.
3458 Handle<JSObject*> options = nullptr;
3460 // Steps 2-5.
3461 return ::CalendarDateAdd(cx, calendar, date, duration, options, result);
3465 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3467 bool js::temporal::CalendarDateAdd(JSContext* cx,
3468 Handle<CalendarRecord> calendar,
3469 const PlainDate& date,
3470 const DateDuration& duration,
3471 Handle<JSObject*> options,
3472 PlainDate* result) {
3473 // Step 1. (Not applicable)
3475 // Steps 2-5.
3476 return ::CalendarDateAdd(cx, calendar, date, duration, options, result);
3480 * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
3482 bool js::temporal::CalendarDateAdd(JSContext* cx,
3483 Handle<CalendarRecord> calendar,
3484 Handle<Wrapped<PlainDateObject*>> date,
3485 const DateDuration& duration,
3486 PlainDate* result) {
3487 // Step 1.
3488 Handle<JSObject*> options = nullptr;
3490 // Steps 2-5.
3491 return ::CalendarDateAdd(cx, calendar, date, duration, options, result);
3495 * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
3497 static Duration BuiltinCalendarDateUntil(const PlainDate& one,
3498 const PlainDate& two,
3499 TemporalUnit largestUnit) {
3500 // Steps 1-3. (Not applicable)
3502 // Steps 4-8. (Not applicable)
3504 // Step 9.
3505 auto difference = DifferenceISODate(one, two, largestUnit);
3507 // Step 10.
3508 return difference.toDuration();
3512 * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
3514 static bool BuiltinCalendarDateUntil(JSContext* cx,
3515 Handle<Wrapped<PlainDateObject*>> one,
3516 Handle<Wrapped<PlainDateObject*>> two,
3517 TemporalUnit largestUnit,
3518 Duration* result) {
3519 MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
3521 auto* unwrappedOne = one.unwrap(cx);
3522 if (!unwrappedOne) {
3523 return false;
3525 auto dateOne = ToPlainDate(unwrappedOne);
3527 auto* unwrappedTwo = two.unwrap(cx);
3528 if (!unwrappedTwo) {
3529 return false;
3531 auto dateTwo = ToPlainDate(unwrappedTwo);
3533 // Steps 1-10.
3534 *result = BuiltinCalendarDateUntil(dateOne, dateTwo, largestUnit);
3535 return true;
3538 static bool CalendarDateUntilSlow(JSContext* cx,
3539 Handle<CalendarRecord> calendar,
3540 Handle<Wrapped<PlainDateObject*>> one,
3541 Handle<Wrapped<PlainDateObject*>> two,
3542 Handle<JSObject*> options, Duration* result) {
3543 MOZ_ASSERT(
3544 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3545 MOZ_ASSERT(calendar.receiver().isObject());
3546 MOZ_ASSERT(calendar.dateUntil());
3548 // Step 1. (Inlined call to CalendarMethodsRecordCall.)
3549 Rooted<JS::Value> dateUntil(cx, ObjectValue(*calendar.dateUntil()));
3550 auto thisv = calendar.receiver().toValue();
3551 Rooted<Value> rval(cx);
3553 FixedInvokeArgs<3> args(cx);
3554 args[0].setObject(*one);
3555 args[1].setObject(*two);
3556 args[2].setObject(*options);
3558 if (!Call(cx, dateUntil, thisv, args, &rval)) {
3559 return false;
3562 // Step 2. (Not applicable)
3563 MOZ_ASSERT(!CalendarMethodsRecordIsBuiltin(calendar));
3565 // Step 3.
3566 if (!rval.isObject() || !rval.toObject().canUnwrapAs<DurationObject>()) {
3567 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
3568 nullptr, "not a Duration object");
3569 return false;
3572 // Step 4.
3573 *result = ToDuration(&rval.toObject().unwrapAs<DurationObject>());
3574 return true;
3578 * CalendarDateUntil ( calendarRec, one, two, options )
3580 bool js::temporal::CalendarDateUntil(JSContext* cx,
3581 Handle<CalendarRecord> calendar,
3582 Handle<Wrapped<PlainDateObject*>> one,
3583 Handle<Wrapped<PlainDateObject*>> two,
3584 TemporalUnit largestUnit,
3585 Handle<PlainObject*> options,
3586 Duration* result) {
3587 MOZ_ASSERT(
3588 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3590 // As an optimization, our implementation only adds |largestUnit| to the
3591 // options object when taking the slow-path.
3592 #ifdef DEBUG
3593 // The object must be extensible, otherwise we'd need to throw an error when
3594 // attempting to add the "largestUnit" property to a non-extensible object.
3595 MOZ_ASSERT(options->isExtensible());
3597 // Similarily, if there's an existing "largestUnit" property, this property
3598 // must be configurable.
3599 auto largestUnitProp = options->lookupPure(cx->names().largestUnit);
3600 MOZ_ASSERT_IF(largestUnitProp, largestUnitProp->configurable());
3601 #endif
3603 // Step 2. (Reordered)
3604 if (!calendar.dateUntil()) {
3605 return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result);
3608 Rooted<Value> value(cx, StringValue(TemporalUnitToString(cx, largestUnit)));
3609 if (!DefineDataProperty(cx, options, cx->names().largestUnit, value)) {
3610 return false;
3613 // Steps 1 and 3-4.
3614 return CalendarDateUntilSlow(cx, calendar, one, two, options, result);
3618 * CalendarDateUntil ( calendarRec, one, two, options )
3620 bool js::temporal::CalendarDateUntil(JSContext* cx,
3621 Handle<CalendarRecord> calendar,
3622 Handle<Wrapped<PlainDateObject*>> one,
3623 Handle<Wrapped<PlainDateObject*>> two,
3624 TemporalUnit largestUnit,
3625 Duration* result) {
3626 MOZ_ASSERT(
3627 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
3628 MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
3630 // Step 2. (Reordered)
3631 if (!calendar.dateUntil()) {
3632 return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result);
3635 Rooted<PlainObject*> untilOptions(cx, NewPlainObjectWithProto(cx, nullptr));
3636 if (!untilOptions) {
3637 return false;
3640 Rooted<Value> value(cx, StringValue(TemporalUnitToString(cx, largestUnit)));
3641 if (!DefineDataProperty(cx, untilOptions, cx->names().largestUnit, value)) {
3642 return false;
3645 // Steps 1 and 3-4.
3646 return CalendarDateUntilSlow(cx, calendar, one, two, untilOptions, result);
3650 * CalendarEquals ( one, two )
3652 bool js::temporal::CalendarEquals(JSContext* cx, Handle<CalendarValue> one,
3653 Handle<CalendarValue> two, bool* equals) {
3654 // Step 1.
3655 if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
3656 *equals = true;
3657 return true;
3660 // Step 2.
3661 Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one));
3662 if (!calendarOne) {
3663 return false;
3666 // Step 3.
3667 JSString* calendarTwo = ToTemporalCalendarIdentifier(cx, two);
3668 if (!calendarTwo) {
3669 return false;
3672 // Steps 4-5.
3673 return EqualStrings(cx, calendarOne, calendarTwo, equals);
3677 * CalendarEquals ( one, two )
3679 bool js::temporal::CalendarEqualsOrThrow(JSContext* cx,
3680 Handle<CalendarValue> one,
3681 Handle<CalendarValue> two) {
3682 // Step 1.
3683 if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
3684 return true;
3687 // Step 2.
3688 Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one));
3689 if (!calendarOne) {
3690 return false;
3693 // Step 3.
3694 JSString* calendarTwo = ToTemporalCalendarIdentifier(cx, two);
3695 if (!calendarTwo) {
3696 return false;
3699 // Steps 4-5.
3700 bool equals;
3701 if (!EqualStrings(cx, calendarOne, calendarTwo, &equals)) {
3702 return false;
3704 if (equals) {
3705 return true;
3708 // Throw an error when the calendar identifiers don't match. Used when unequal
3709 // calendars throw a RangeError.
3710 if (auto charsOne = QuoteString(cx, calendarOne)) {
3711 if (auto charsTwo = QuoteString(cx, calendarTwo)) {
3712 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3713 JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
3714 charsOne.get(), charsTwo.get());
3717 return false;
3721 * ConsolidateCalendars ( one, two )
3723 bool js::temporal::ConsolidateCalendars(JSContext* cx,
3724 Handle<CalendarValue> one,
3725 Handle<CalendarValue> two,
3726 MutableHandle<CalendarValue> result) {
3727 // Step 1.
3728 if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
3729 result.set(two);
3730 return true;
3733 // Step 2.
3734 Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one));
3735 if (!calendarOne) {
3736 return false;
3739 // Step 3.
3740 Rooted<JSString*> calendarTwo(cx, ToTemporalCalendarIdentifier(cx, two));
3741 if (!calendarTwo) {
3742 return false;
3745 // Step 4.
3746 bool equals;
3747 if (!EqualStrings(cx, calendarOne, calendarTwo, &equals)) {
3748 return false;
3750 if (equals) {
3751 result.set(two);
3752 return true;
3755 // Step 5.
3756 bool isoCalendarOne;
3757 if (!IsISO8601Calendar(cx, calendarOne, &isoCalendarOne)) {
3758 return false;
3760 if (isoCalendarOne) {
3761 result.set(two);
3762 return true;
3765 // Step 6.
3766 bool isoCalendarTwo;
3767 if (!IsISO8601Calendar(cx, calendarTwo, &isoCalendarTwo)) {
3768 return false;
3770 if (isoCalendarTwo) {
3771 result.set(one);
3772 return true;
3775 // Step 7.
3776 if (auto charsOne = QuoteString(cx, calendarOne)) {
3777 if (auto charsTwo = QuoteString(cx, calendarTwo)) {
3778 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
3779 JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
3780 charsOne.get(), charsTwo.get());
3783 return false;
3787 * Temporal.Calendar ( id )
3789 static bool CalendarConstructor(JSContext* cx, unsigned argc, Value* vp) {
3790 CallArgs args = CallArgsFromVp(argc, vp);
3792 // Step 1.
3793 if (!ThrowIfNotConstructing(cx, args, "Temporal.Calendar")) {
3794 return false;
3797 // Step 2.
3798 if (!args.requireAtLeast(cx, "Temporal.Calendar", 1)) {
3799 return false;
3802 if (!args[0].isString()) {
3803 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
3804 nullptr, "not a string");
3805 return false;
3808 Rooted<JSLinearString*> identifier(cx, args[0].toString()->ensureLinear(cx));
3809 if (!identifier) {
3810 return false;
3813 // Step 3.
3814 identifier = ThrowIfNotBuiltinCalendar(cx, identifier);
3815 if (!identifier) {
3816 return false;
3819 // Step 4.
3820 auto* calendar = CreateTemporalCalendar(cx, args, identifier);
3821 if (!calendar) {
3822 return false;
3825 args.rval().setObject(*calendar);
3826 return true;
3830 * Temporal.Calendar.from ( item )
3832 static bool Calendar_from(JSContext* cx, unsigned argc, Value* vp) {
3833 CallArgs args = CallArgsFromVp(argc, vp);
3835 // Step 1.
3836 Rooted<CalendarValue> calendar(cx);
3837 if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
3838 return false;
3841 // Step 2.
3842 auto* obj = ToTemporalCalendarObject(cx, calendar);
3843 if (!obj) {
3844 return false;
3847 args.rval().setObject(*obj);
3848 return true;
3852 * get Temporal.Calendar.prototype.id
3854 static bool Calendar_id(JSContext* cx, const CallArgs& args) {
3855 auto* calendar = &args.thisv().toObject().as<CalendarObject>();
3857 // Step 3.
3858 args.rval().setString(calendar->identifier());
3859 return true;
3863 * get Temporal.Calendar.prototype.id
3865 static bool Calendar_id(JSContext* cx, unsigned argc, Value* vp) {
3866 // Steps 1-2.
3867 CallArgs args = CallArgsFromVp(argc, vp);
3868 return CallNonGenericMethod<IsCalendar, Calendar_id>(cx, args);
3872 * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )
3874 static bool Calendar_dateFromFields(JSContext* cx, const CallArgs& args) {
3875 // Step 3.
3876 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
3878 // Step 4.
3879 Rooted<JSObject*> fields(
3880 cx, RequireObjectArg(cx, "fields", "dateFromFields", args.get(0)));
3881 if (!fields) {
3882 return false;
3885 // Step 5.
3886 Rooted<JSObject*> options(cx);
3887 if (args.hasDefined(1)) {
3888 options = RequireObjectArg(cx, "options", "dateFromFields", args[1]);
3889 if (!options) {
3890 return false;
3894 // Steps 6-10.
3895 auto* obj = BuiltinCalendarDateFromFields(cx, fields, options);
3896 if (!obj) {
3897 return false;
3900 args.rval().setObject(*obj);
3901 return true;
3905 * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )
3907 static bool Calendar_dateFromFields(JSContext* cx, unsigned argc, Value* vp) {
3908 // Steps 1-2.
3909 CallArgs args = CallArgsFromVp(argc, vp);
3910 return CallNonGenericMethod<IsCalendar, Calendar_dateFromFields>(cx, args);
3914 * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )
3916 static bool Calendar_yearMonthFromFields(JSContext* cx, const CallArgs& args) {
3917 // Step 3.
3918 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
3920 // Step 4.
3921 Rooted<JSObject*> fields(
3922 cx, RequireObjectArg(cx, "fields", "yearMonthFromFields", args.get(0)));
3923 if (!fields) {
3924 return false;
3927 // Step 5.
3928 Rooted<JSObject*> options(cx);
3929 if (args.hasDefined(1)) {
3930 options = RequireObjectArg(cx, "options", "yearMonthFromFields", args[1]);
3931 if (!options) {
3932 return false;
3936 // Steps 6-10.
3937 auto* obj = BuiltinCalendarYearMonthFromFields(cx, fields, options);
3938 if (!obj) {
3939 return false;
3942 args.rval().setObject(*obj);
3943 return true;
3947 * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )
3949 static bool Calendar_yearMonthFromFields(JSContext* cx, unsigned argc,
3950 Value* vp) {
3951 // Steps 1-2.
3952 CallArgs args = CallArgsFromVp(argc, vp);
3953 return CallNonGenericMethod<IsCalendar, Calendar_yearMonthFromFields>(cx,
3954 args);
3958 * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )
3960 static bool Calendar_monthDayFromFields(JSContext* cx, const CallArgs& args) {
3961 // Step 3.
3962 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
3964 // Step 4.
3965 Rooted<JSObject*> fields(
3966 cx, RequireObjectArg(cx, "fields", "monthDayFromFields", args.get(0)));
3967 if (!fields) {
3968 return false;
3971 // Step 5.
3972 Rooted<JSObject*> options(cx);
3973 if (args.hasDefined(1)) {
3974 options = RequireObjectArg(cx, "options", "monthDayFromFields", args[1]);
3975 if (!options) {
3976 return false;
3980 // Steps 6-10.
3981 auto* obj = BuiltinCalendarMonthDayFromFields(cx, fields, options);
3982 if (!obj) {
3983 return false;
3986 args.rval().setObject(*obj);
3987 return true;
3991 * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )
3993 static bool Calendar_monthDayFromFields(JSContext* cx, unsigned argc,
3994 Value* vp) {
3995 // Steps 1-2.
3996 CallArgs args = CallArgsFromVp(argc, vp);
3997 return CallNonGenericMethod<IsCalendar, Calendar_monthDayFromFields>(cx,
3998 args);
4002 * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
4004 static bool Calendar_dateAdd(JSContext* cx, const CallArgs& args) {
4005 // Step 3.
4006 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4008 // Step 4.
4009 PlainDate date;
4010 if (!ToTemporalDate(cx, args.get(0), &date)) {
4011 return false;
4014 // Step 5.
4015 Duration duration;
4016 if (!ToTemporalDuration(cx, args.get(1), &duration)) {
4017 return false;
4020 // Step 6.
4021 Rooted<JSObject*> options(cx);
4022 if (args.hasDefined(2)) {
4023 options = RequireObjectArg(cx, "options", "dateAdd", args[2]);
4024 if (!options) {
4025 return false;
4029 // Steps 7-11.
4030 auto* obj = BuiltinCalendarAdd(cx, date, duration, options);
4031 if (!obj) {
4032 return false;
4035 args.rval().setObject(*obj);
4036 return true;
4040 * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
4042 static bool Calendar_dateAdd(JSContext* cx, unsigned argc, Value* vp) {
4043 // Steps 1-2.
4044 CallArgs args = CallArgsFromVp(argc, vp);
4045 return CallNonGenericMethod<IsCalendar, Calendar_dateAdd>(cx, args);
4049 * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
4051 static bool Calendar_dateUntil(JSContext* cx, const CallArgs& args) {
4052 // Step 3.
4053 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4055 // Step 4.
4056 PlainDate one;
4057 if (!ToTemporalDate(cx, args.get(0), &one)) {
4058 return false;
4061 // Step 5.
4062 PlainDate two;
4063 if (!ToTemporalDate(cx, args.get(1), &two)) {
4064 return false;
4067 // Steps 6-8.
4068 auto largestUnit = TemporalUnit::Day;
4069 if (args.hasDefined(2)) {
4070 Rooted<JSObject*> options(
4071 cx, RequireObjectArg(cx, "options", "dateUntil", args[2]));
4072 if (!options) {
4073 return false;
4076 // Steps 7-8.
4077 if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit,
4078 TemporalUnitGroup::Date, &largestUnit)) {
4079 return false;
4083 // Steps 9-10.
4084 auto duration = BuiltinCalendarDateUntil(one, two, largestUnit);
4086 auto* obj = CreateTemporalDuration(cx, duration);
4087 if (!obj) {
4088 return false;
4091 args.rval().setObject(*obj);
4092 return true;
4096 * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
4098 static bool Calendar_dateUntil(JSContext* cx, unsigned argc, Value* vp) {
4099 // Steps 1-2.
4100 CallArgs args = CallArgsFromVp(argc, vp);
4101 return CallNonGenericMethod<IsCalendar, Calendar_dateUntil>(cx, args);
4105 * Temporal.Calendar.prototype.year ( temporalDateLike )
4107 static bool Calendar_year(JSContext* cx, const CallArgs& args) {
4108 // Step 3.
4109 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4111 // Step 4.
4112 PlainDate date;
4113 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
4114 cx, args.get(0), &date)) {
4115 return false;
4118 // Steps 5-6.
4119 return BuiltinCalendarYear(cx, date, args.rval());
4123 * Temporal.Calendar.prototype.year ( temporalDateLike )
4125 static bool Calendar_year(JSContext* cx, unsigned argc, Value* vp) {
4126 // Steps 1-2.
4127 CallArgs args = CallArgsFromVp(argc, vp);
4128 return CallNonGenericMethod<IsCalendar, Calendar_year>(cx, args);
4132 * Temporal.Calendar.prototype.month ( temporalDateLike )
4134 static bool Calendar_month(JSContext* cx, const CallArgs& args) {
4135 // Step 3.
4136 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4138 Handle<Value> temporalDateLike = args.get(0);
4140 // Step 4.
4141 if (temporalDateLike.isObject() &&
4142 temporalDateLike.toObject().canUnwrapAs<PlainMonthDayObject>()) {
4143 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
4144 temporalDateLike, nullptr, "a PlainMonthDay object");
4145 return false;
4148 // Step 5.
4149 PlainDate date;
4150 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
4151 cx, temporalDateLike, &date)) {
4152 return false;
4155 // Steps 6-7.
4156 return BuiltinCalendarMonth(cx, date, args.rval());
4160 * Temporal.Calendar.prototype.month ( temporalDateLike )
4162 static bool Calendar_month(JSContext* cx, unsigned argc, Value* vp) {
4163 // Steps 1-2.
4164 CallArgs args = CallArgsFromVp(argc, vp);
4165 return CallNonGenericMethod<IsCalendar, Calendar_month>(cx, args);
4169 * Temporal.Calendar.prototype.monthCode ( temporalDateLike )
4171 static bool Calendar_monthCode(JSContext* cx, const CallArgs& args) {
4172 // Step 3.
4173 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4175 // Step 4.
4176 PlainDate date;
4177 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainMonthDayObject,
4178 PlainYearMonthObject>(cx, args.get(0), &date)) {
4179 return false;
4182 // Steps 5-6.
4183 return BuiltinCalendarMonthCode(cx, date, args.rval());
4187 * Temporal.Calendar.prototype.monthCode ( temporalDateLike )
4189 static bool Calendar_monthCode(JSContext* cx, unsigned argc, Value* vp) {
4190 // Steps 1-2.
4191 CallArgs args = CallArgsFromVp(argc, vp);
4192 return CallNonGenericMethod<IsCalendar, Calendar_monthCode>(cx, args);
4196 * Temporal.Calendar.prototype.day ( temporalDateLike )
4198 static bool Calendar_day(JSContext* cx, const CallArgs& args) {
4199 // Step 3.
4200 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4202 // Step 4.
4203 PlainDate date;
4204 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainMonthDayObject>(
4205 cx, args.get(0), &date)) {
4206 return false;
4209 // Steps 5-6.
4210 return BuiltinCalendarDay(date, args.rval());
4214 * Temporal.Calendar.prototype.day ( temporalDateLike )
4216 static bool Calendar_day(JSContext* cx, unsigned argc, Value* vp) {
4217 // Steps 1-2.
4218 CallArgs args = CallArgsFromVp(argc, vp);
4219 return CallNonGenericMethod<IsCalendar, Calendar_day>(cx, args);
4223 * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike )
4225 static bool Calendar_dayOfWeek(JSContext* cx, const CallArgs& args) {
4226 // Step 3.
4227 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4229 // Step 4.
4230 PlainDate date;
4231 if (!ToTemporalDate(cx, args.get(0), &date)) {
4232 return false;
4235 // Steps 5-9.
4236 return BuiltinCalendarDayOfWeek(cx, date, args.rval());
4240 * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike )
4242 static bool Calendar_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
4243 // Steps 1-2.
4244 CallArgs args = CallArgsFromVp(argc, vp);
4245 return CallNonGenericMethod<IsCalendar, Calendar_dayOfWeek>(cx, args);
4249 * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )
4251 static bool Calendar_dayOfYear(JSContext* cx, const CallArgs& args) {
4252 // Step 3.
4253 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4255 // Step 4.
4256 PlainDate date;
4257 if (!ToTemporalDate(cx, args.get(0), &date)) {
4258 return false;
4261 // Steps 5-7.
4262 return BuiltinCalendarDayOfYear(cx, date, args.rval());
4266 * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )
4268 static bool Calendar_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
4269 // Steps 1-2.
4270 CallArgs args = CallArgsFromVp(argc, vp);
4271 return CallNonGenericMethod<IsCalendar, Calendar_dayOfYear>(cx, args);
4275 * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )
4277 static bool Calendar_weekOfYear(JSContext* cx, const CallArgs& args) {
4278 // Step 3.
4279 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4281 // Step 4.
4282 PlainDate date;
4283 if (!ToTemporalDate(cx, args.get(0), &date)) {
4284 return false;
4287 // Steps 5-6.
4288 return BuiltinCalendarWeekOfYear(cx, date, args.rval());
4292 * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )
4294 static bool Calendar_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
4295 // Steps 1-2.
4296 CallArgs args = CallArgsFromVp(argc, vp);
4297 return CallNonGenericMethod<IsCalendar, Calendar_weekOfYear>(cx, args);
4301 * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )
4303 static bool Calendar_yearOfWeek(JSContext* cx, const CallArgs& args) {
4304 // Step 3.
4305 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4307 // Step 4.
4308 PlainDate date;
4309 if (!ToTemporalDate(cx, args.get(0), &date)) {
4310 return false;
4313 // Steps 5-6.
4314 return BuiltinCalendarYearOfWeek(cx, date, args.rval());
4318 * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )
4320 static bool Calendar_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
4321 // Steps 1-2.
4322 CallArgs args = CallArgsFromVp(argc, vp);
4323 return CallNonGenericMethod<IsCalendar, Calendar_yearOfWeek>(cx, args);
4327 * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )
4329 static bool Calendar_daysInWeek(JSContext* cx, const CallArgs& args) {
4330 // Step 3.
4331 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4333 // Step 4.
4334 PlainDate date;
4335 if (!ToTemporalDate(cx, args.get(0), &date)) {
4336 return false;
4339 // Step 5.
4340 return BuiltinCalendarDaysInWeek(cx, date, args.rval());
4344 * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )
4346 static bool Calendar_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
4347 // Steps 1-2.
4348 CallArgs args = CallArgsFromVp(argc, vp);
4349 return CallNonGenericMethod<IsCalendar, Calendar_daysInWeek>(cx, args);
4353 * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )
4355 static bool Calendar_daysInMonth(JSContext* cx, const CallArgs& args) {
4356 // Step 3.
4357 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4359 // Step 4.
4360 PlainDate date;
4361 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
4362 cx, args.get(0), &date)) {
4363 return false;
4366 // Step 5.
4367 return BuiltinCalendarDaysInMonth(cx, date, args.rval());
4371 * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )
4373 static bool Calendar_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
4374 // Steps 1-2.
4375 CallArgs args = CallArgsFromVp(argc, vp);
4376 return CallNonGenericMethod<IsCalendar, Calendar_daysInMonth>(cx, args);
4380 * Temporal.Calendar.prototype.daysInYear ( temporalDateLike )
4382 static bool Calendar_daysInYear(JSContext* cx, const CallArgs& args) {
4383 // Step 3.
4384 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4386 // Step 4.
4387 PlainDate date;
4388 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
4389 cx, args.get(0), &date)) {
4390 return false;
4393 // Step 5.
4394 return BuiltinCalendarDaysInYear(cx, date, args.rval());
4398 * Temporal.Calendar.prototype.daysInYear ( temporalDateLike )
4400 static bool Calendar_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
4401 // Steps 1-2.
4402 CallArgs args = CallArgsFromVp(argc, vp);
4403 return CallNonGenericMethod<IsCalendar, Calendar_daysInYear>(cx, args);
4407 * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )
4409 static bool Calendar_monthsInYear(JSContext* cx, const CallArgs& args) {
4410 // Step 3.
4411 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4413 // Step 4.
4414 PlainDate date;
4415 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
4416 cx, args.get(0), &date)) {
4417 return false;
4420 // Step 5.
4421 return BuiltinCalendarMonthsInYear(cx, date, args.rval());
4425 * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )
4427 static bool Calendar_monthsInYear(JSContext* cx, unsigned argc, Value* vp) {
4428 // Steps 1-2.
4429 CallArgs args = CallArgsFromVp(argc, vp);
4430 return CallNonGenericMethod<IsCalendar, Calendar_monthsInYear>(cx, args);
4434 * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )
4436 static bool Calendar_inLeapYear(JSContext* cx, const CallArgs& args) {
4437 // Step 3.
4438 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4440 // Step 4.
4441 PlainDate date;
4442 if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
4443 cx, args.get(0), &date)) {
4444 return false;
4447 // Steps 5-6.
4448 return BuiltinCalendarInLeapYear(cx, date, args.rval());
4452 * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )
4454 static bool Calendar_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
4455 // Steps 1-2.
4456 CallArgs args = CallArgsFromVp(argc, vp);
4457 return CallNonGenericMethod<IsCalendar, Calendar_inLeapYear>(cx, args);
4461 * Temporal.Calendar.prototype.fields ( fields )
4463 static bool Calendar_fields(JSContext* cx, const CallArgs& args) {
4464 // Step 3.
4465 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4467 // Step 4.
4468 JS::ForOfIterator iterator(cx);
4469 if (!iterator.init(args.get(0))) {
4470 return false;
4473 // Step 5.
4474 JS::RootedVector<Value> fieldNames(cx);
4475 mozilla::EnumSet<CalendarField> seen;
4477 // Step 6.
4478 Rooted<Value> nextValue(cx);
4479 Rooted<JSLinearString*> linear(cx);
4480 while (true) {
4481 // Step 6.a.
4482 bool done;
4483 if (!iterator.next(&nextValue, &done)) {
4484 return false;
4487 // Step 6.b.
4488 if (done) {
4489 auto* array =
4490 NewDenseCopiedArray(cx, fieldNames.length(), fieldNames.begin());
4491 if (!array) {
4492 return false;
4495 args.rval().setObject(*array);
4496 return true;
4499 // Step 6.c.
4500 if (!nextValue.isString()) {
4501 // Step 6.c.1.
4502 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
4503 nullptr, "not a string");
4505 // Step 6.c.2.
4506 iterator.closeThrow();
4507 return false;
4510 linear = nextValue.toString()->ensureLinear(cx);
4511 if (!linear) {
4512 return false;
4515 // Step 6.e. (Reordered)
4516 CalendarField field;
4517 if (!ToCalendarField(cx, linear, &field)) {
4518 iterator.closeThrow();
4519 return false;
4522 // Step 6.d.
4523 if (seen.contains(field)) {
4524 // Step 6.d.1.
4525 if (auto chars = QuoteString(cx, linear, '"')) {
4526 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
4527 JSMSG_TEMPORAL_CALENDAR_DUPLICATE_FIELD,
4528 chars.get());
4531 // Step 6.d.2.
4532 iterator.closeThrow();
4533 return false;
4536 // Step 6.f.
4537 if (!fieldNames.append(nextValue)) {
4538 return false;
4540 seen += field;
4545 * Temporal.Calendar.prototype.fields ( fields )
4547 static bool Calendar_fields(JSContext* cx, unsigned argc, Value* vp) {
4548 // Steps 1-2.
4549 CallArgs args = CallArgsFromVp(argc, vp);
4550 return CallNonGenericMethod<IsCalendar, Calendar_fields>(cx, args);
4554 * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )
4556 static bool Calendar_mergeFields(JSContext* cx, const CallArgs& args) {
4557 // Step 7. (Reordered)
4558 MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
4560 // Step 3.
4561 Rooted<JSObject*> fields(cx, JS::ToObject(cx, args.get(0)));
4562 if (!fields) {
4563 return false;
4566 Rooted<PlainObject*> fieldsCopy(
4567 cx, SnapshotOwnPropertiesIgnoreUndefined(cx, fields));
4568 if (!fieldsCopy) {
4569 return false;
4572 // Step 4.
4573 Rooted<JSObject*> additionalFields(cx, JS::ToObject(cx, args.get(1)));
4574 if (!additionalFields) {
4575 return false;
4578 Rooted<PlainObject*> additionalFieldsCopy(
4579 cx, SnapshotOwnPropertiesIgnoreUndefined(cx, additionalFields));
4580 if (!additionalFieldsCopy) {
4581 return false;
4584 // Steps 5-6.
4586 // JSITER_HIDDEN doesn't need to be passed, because CopyDataProperties creates
4587 // all properties as enumerable.
4588 JS::RootedVector<PropertyKey> additionalKeys(cx);
4589 if (!GetPropertyKeys(cx, additionalFieldsCopy,
4590 JSITER_OWNONLY | JSITER_SYMBOLS, &additionalKeys)) {
4591 return false;
4594 // Step 8.
4595 Rooted<PropertyHashSet> overriddenKeys(cx, PropertyHashSet(cx));
4596 if (!ISOFieldKeysToIgnore(cx, additionalKeys, overriddenKeys.get())) {
4597 return false;
4600 // Step 9.
4601 Rooted<PlainObject*> merged(cx, NewPlainObjectWithProto(cx, nullptr));
4602 if (!merged) {
4603 return false;
4606 // Steps 10-11.
4608 // JSITER_HIDDEN doesn't need to be passed, because CopyDataProperties creates
4609 // all properties as enumerable.
4610 JS::RootedVector<PropertyKey> fieldsKeys(cx);
4611 if (!GetPropertyKeys(cx, fieldsCopy, JSITER_OWNONLY | JSITER_SYMBOLS,
4612 &fieldsKeys)) {
4613 return false;
4616 // Step 12.
4617 Rooted<Value> propValue(cx);
4618 for (size_t i = 0; i < fieldsKeys.length(); i++) {
4619 Handle<PropertyKey> key = fieldsKeys[i];
4621 // Steps 12.a-b.
4622 if (overriddenKeys.has(key)) {
4623 if (!GetProperty(cx, additionalFieldsCopy, additionalFieldsCopy, key,
4624 &propValue)) {
4625 return false;
4628 // Step 12.c. (Reordered)
4629 if (propValue.isUndefined()) {
4630 // The property can be undefined if the key is "month" or "monthCode".
4631 MOZ_ASSERT(key.isAtom(cx->names().month) ||
4632 key.isAtom(cx->names().monthCode));
4634 continue;
4636 } else {
4637 if (!GetProperty(cx, fieldsCopy, fieldsCopy, key, &propValue)) {
4638 return false;
4641 // All properties of |fieldsCopy| have a non-undefined value.
4642 MOZ_ASSERT(!propValue.isUndefined());
4645 // Step 12.c.
4646 if (!DefineDataProperty(cx, merged, key, propValue)) {
4647 return false;
4651 // Step 13.
4652 if (!CopyDataProperties(cx, merged, additionalFieldsCopy)) {
4653 return false;
4656 // Step 14.
4657 args.rval().setObject(*merged);
4658 return true;
4662 * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )
4664 static bool Calendar_mergeFields(JSContext* cx, unsigned argc, Value* vp) {
4665 // Steps 1-2.
4666 CallArgs args = CallArgsFromVp(argc, vp);
4667 return CallNonGenericMethod<IsCalendar, Calendar_mergeFields>(cx, args);
4671 * Temporal.Calendar.prototype.toString ( )
4673 static bool Calendar_toString(JSContext* cx, const CallArgs& args) {
4674 auto* calendar = &args.thisv().toObject().as<CalendarObject>();
4676 // Step 3.
4677 args.rval().setString(calendar->identifier());
4678 return true;
4682 * Temporal.Calendar.prototype.toString ( )
4684 static bool Calendar_toString(JSContext* cx, unsigned argc, Value* vp) {
4685 // Steps 1-2.
4686 CallArgs args = CallArgsFromVp(argc, vp);
4687 return CallNonGenericMethod<IsCalendar, Calendar_toString>(cx, args);
4691 * Temporal.Calendar.prototype.toJSON ( )
4693 static bool Calendar_toJSON(JSContext* cx, const CallArgs& args) {
4694 auto* calendar = &args.thisv().toObject().as<CalendarObject>();
4696 // Step 3.
4697 args.rval().setString(calendar->identifier());
4698 return true;
4702 * Temporal.Calendar.prototype.toJSON ( )
4704 static bool Calendar_toJSON(JSContext* cx, unsigned argc, Value* vp) {
4705 // Steps 1-2.
4706 CallArgs args = CallArgsFromVp(argc, vp);
4707 return CallNonGenericMethod<IsCalendar, Calendar_toJSON>(cx, args);
4710 const JSClass CalendarObject::class_ = {
4711 "Temporal.Calendar",
4712 JSCLASS_HAS_RESERVED_SLOTS(CalendarObject::SLOT_COUNT) |
4713 JSCLASS_HAS_CACHED_PROTO(JSProto_Calendar),
4714 JS_NULL_CLASS_OPS,
4715 &CalendarObject::classSpec_,
4718 const JSClass& CalendarObject::protoClass_ = PlainObject::class_;
4720 static const JSFunctionSpec Calendar_methods[] = {
4721 JS_FN("from", Calendar_from, 1, 0),
4722 JS_FS_END,
4725 static const JSFunctionSpec Calendar_prototype_methods[] = {
4726 JS_FN("dateFromFields", Calendar_dateFromFields, 1, 0),
4727 JS_FN("yearMonthFromFields", Calendar_yearMonthFromFields, 1, 0),
4728 JS_FN("monthDayFromFields", Calendar_monthDayFromFields, 1, 0),
4729 JS_FN("dateAdd", Calendar_dateAdd, 2, 0),
4730 JS_FN("dateUntil", Calendar_dateUntil, 2, 0),
4731 JS_FN("year", Calendar_year, 1, 0),
4732 JS_FN("month", Calendar_month, 1, 0),
4733 JS_FN("monthCode", Calendar_monthCode, 1, 0),
4734 JS_FN("day", Calendar_day, 1, 0),
4735 JS_FN("dayOfWeek", Calendar_dayOfWeek, 1, 0),
4736 JS_FN("dayOfYear", Calendar_dayOfYear, 1, 0),
4737 JS_FN("weekOfYear", Calendar_weekOfYear, 1, 0),
4738 JS_FN("yearOfWeek", Calendar_yearOfWeek, 1, 0),
4739 JS_FN("daysInWeek", Calendar_daysInWeek, 1, 0),
4740 JS_FN("daysInMonth", Calendar_daysInMonth, 1, 0),
4741 JS_FN("daysInYear", Calendar_daysInYear, 1, 0),
4742 JS_FN("monthsInYear", Calendar_monthsInYear, 1, 0),
4743 JS_FN("inLeapYear", Calendar_inLeapYear, 1, 0),
4744 JS_FN("fields", Calendar_fields, 1, 0),
4745 JS_FN("mergeFields", Calendar_mergeFields, 2, 0),
4746 JS_FN("toString", Calendar_toString, 0, 0),
4747 JS_FN("toJSON", Calendar_toJSON, 0, 0),
4748 JS_FS_END,
4751 static const JSPropertySpec Calendar_prototype_properties[] = {
4752 JS_PSG("id", Calendar_id, 0),
4753 JS_STRING_SYM_PS(toStringTag, "Temporal.Calendar", JSPROP_READONLY),
4754 JS_PS_END,
4757 const ClassSpec CalendarObject::classSpec_ = {
4758 GenericCreateConstructor<CalendarConstructor, 1, gc::AllocKind::FUNCTION>,
4759 GenericCreatePrototype<CalendarObject>,
4760 Calendar_methods,
4761 nullptr,
4762 Calendar_prototype_methods,
4763 Calendar_prototype_properties,
4764 nullptr,
4765 ClassSpec::DontDefineConstructor,
4768 struct MOZ_STACK_CLASS CalendarNameAndNative final {
4769 PropertyName* name;
4770 JSNative native;
4773 static CalendarNameAndNative GetCalendarNameAndNative(JSContext* cx,
4774 CalendarField fieldName) {
4775 switch (fieldName) {
4776 case CalendarField::Year:
4777 return {cx->names().year, Calendar_year};
4778 case CalendarField::Month:
4779 return {cx->names().month, Calendar_month};
4780 case CalendarField::MonthCode:
4781 return {cx->names().monthCode, Calendar_monthCode};
4782 case CalendarField::Day:
4783 return {cx->names().day, Calendar_day};
4785 MOZ_CRASH("invalid temporal field name");
4788 bool js::temporal::IsBuiltinAccess(
4789 JSContext* cx, Handle<CalendarObject*> calendar,
4790 std::initializer_list<CalendarField> fieldNames) {
4791 // Don't optimize when the object has any own properties which may shadow the
4792 // built-in methods.
4793 if (!calendar->empty()) {
4794 return false;
4797 JSObject* proto = cx->global()->maybeGetPrototype(JSProto_Calendar);
4799 // Don't attempt to optimize when the class isn't yet initialized.
4800 if (!proto) {
4801 return false;
4804 // Don't optimize when the prototype isn't the built-in prototype.
4805 if (calendar->staticPrototype() != proto) {
4806 return false;
4809 auto* nproto = &proto->as<NativeObject>();
4810 for (auto fieldName : fieldNames) {
4811 auto [name, native] = GetCalendarNameAndNative(cx, fieldName);
4812 auto prop = nproto->lookupPure(name);
4814 // Return if the property isn't a data property.
4815 if (!prop || !prop->isDataProperty()) {
4816 return false;
4819 // Return if the property isn't the initial method.
4820 if (!IsNativeFunction(nproto->getSlot(prop->slot()), native)) {
4821 return false;
4825 // TODO: Pass accessor list from caller to avoid excessive checks.
4827 // Additionally check the various calendar fields operations.
4828 for (const auto& [name, native] : (CalendarNameAndNative[]){
4829 {cx->names().fields, Calendar_fields},
4830 {cx->names().mergeFields, Calendar_mergeFields},
4831 {cx->names().dateFromFields, Calendar_dateFromFields},
4832 {cx->names().monthDayFromFields, Calendar_monthDayFromFields},
4833 {cx->names().yearMonthFromFields, Calendar_yearMonthFromFields},
4834 }) {
4835 auto prop = nproto->lookupPure(name);
4837 // Return if the property isn't a data property.
4838 if (!prop || !prop->isDataProperty()) {
4839 return false;
4842 // Return if the property isn't the initial method.
4843 if (!IsNativeFunction(nproto->getSlot(prop->slot()), native)) {
4844 return false;
4848 // CalendarFields observably uses array iteration.
4849 bool arrayIterationSane;
4850 if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
4851 cx->recoverFromOutOfMemory();
4852 return false;
4854 if (!arrayIterationSane) {
4855 return false;
4858 // Success! The access can be optimized.
4859 return true;