Bug 1874684 - Part 14: Return DateDuration from DifferenceDate. r=sfink
[gecko.git] / js / src / builtin / temporal / PlainDate.cpp
blob8b16d47891f87213a10d80d0d0403368b5b9720d
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/PlainDate.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/FloatingPoint.h"
11 #include "mozilla/Maybe.h"
13 #include <algorithm>
14 #include <cmath>
15 #include <cstdlib>
16 #include <initializer_list>
17 #include <stdint.h>
18 #include <type_traits>
19 #include <utility>
21 #include "jsnum.h"
22 #include "jspubtd.h"
23 #include "jstypes.h"
24 #include "NamespaceImports.h"
26 #include "builtin/temporal/Calendar.h"
27 #include "builtin/temporal/Duration.h"
28 #include "builtin/temporal/PlainDateTime.h"
29 #include "builtin/temporal/PlainMonthDay.h"
30 #include "builtin/temporal/PlainTime.h"
31 #include "builtin/temporal/PlainYearMonth.h"
32 #include "builtin/temporal/Temporal.h"
33 #include "builtin/temporal/TemporalFields.h"
34 #include "builtin/temporal/TemporalParser.h"
35 #include "builtin/temporal/TemporalRoundingMode.h"
36 #include "builtin/temporal/TemporalTypes.h"
37 #include "builtin/temporal/TemporalUnit.h"
38 #include "builtin/temporal/TimeZone.h"
39 #include "builtin/temporal/ToString.h"
40 #include "builtin/temporal/Wrapped.h"
41 #include "builtin/temporal/ZonedDateTime.h"
42 #include "ds/IdValuePair.h"
43 #include "gc/AllocKind.h"
44 #include "gc/Barrier.h"
45 #include "js/AllocPolicy.h"
46 #include "js/CallArgs.h"
47 #include "js/CallNonGenericMethod.h"
48 #include "js/Class.h"
49 #include "js/Date.h"
50 #include "js/ErrorReport.h"
51 #include "js/friend/ErrorMessages.h"
52 #include "js/GCVector.h"
53 #include "js/Id.h"
54 #include "js/PropertyDescriptor.h"
55 #include "js/PropertySpec.h"
56 #include "js/RootingAPI.h"
57 #include "js/TypeDecls.h"
58 #include "js/Value.h"
59 #include "vm/BytecodeUtil.h"
60 #include "vm/GlobalObject.h"
61 #include "vm/JSAtomState.h"
62 #include "vm/JSContext.h"
63 #include "vm/JSObject.h"
64 #include "vm/PlainObject.h"
65 #include "vm/PropertyInfo.h"
66 #include "vm/Realm.h"
67 #include "vm/Shape.h"
68 #include "vm/StringType.h"
70 #include "vm/JSObject-inl.h"
71 #include "vm/NativeObject-inl.h"
72 #include "vm/ObjectOperations-inl.h"
74 using namespace js;
75 using namespace js::temporal;
77 static inline bool IsPlainDate(Handle<Value> v) {
78 return v.isObject() && v.toObject().is<PlainDateObject>();
81 #ifdef DEBUG
82 /**
83 * IsValidISODate ( year, month, day )
85 template <typename T>
86 static bool IsValidISODate(T year, T month, T day) {
87 static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
89 // Step 1.
90 MOZ_ASSERT(IsInteger(year));
91 MOZ_ASSERT(IsInteger(month));
92 MOZ_ASSERT(IsInteger(day));
94 // Step 2.
95 if (month < 1 || month > 12) {
96 return false;
99 // Step 3.
100 int32_t daysInMonth = js::temporal::ISODaysInMonth(year, int32_t(month));
102 // Step 4.
103 if (day < 1 || day > daysInMonth) {
104 return false;
107 // Step 5.
108 return true;
112 * IsValidISODate ( year, month, day )
114 bool js::temporal::IsValidISODate(const PlainDate& date) {
115 auto& [year, month, day] = date;
116 return ::IsValidISODate(year, month, day);
120 * IsValidISODate ( year, month, day )
122 bool js::temporal::IsValidISODate(double year, double month, double day) {
123 return ::IsValidISODate(year, month, day);
125 #endif
127 static void ReportInvalidDateValue(JSContext* cx, const char* name, int32_t min,
128 int32_t max, double num) {
129 Int32ToCStringBuf minCbuf;
130 const char* minStr = Int32ToCString(&minCbuf, min);
132 Int32ToCStringBuf maxCbuf;
133 const char* maxStr = Int32ToCString(&maxCbuf, max);
135 ToCStringBuf numCbuf;
136 const char* numStr = NumberToCString(&numCbuf, num);
138 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
139 JSMSG_TEMPORAL_PLAIN_DATE_INVALID_VALUE, name,
140 minStr, maxStr, numStr);
143 template <typename T>
144 static inline bool ThrowIfInvalidDateValue(JSContext* cx, const char* name,
145 int32_t min, int32_t max, T num) {
146 if (min <= num && num <= max) {
147 return true;
149 ReportInvalidDateValue(cx, name, min, max, num);
150 return false;
154 * IsValidISODate ( year, month, day )
156 template <typename T>
157 static bool ThrowIfInvalidISODate(JSContext* cx, T year, T month, T day) {
158 static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
160 // Step 1.
161 MOZ_ASSERT(IsInteger(year));
162 MOZ_ASSERT(IsInteger(month));
163 MOZ_ASSERT(IsInteger(day));
165 // Step 2.
166 if (!ThrowIfInvalidDateValue(cx, "month", 1, 12, month)) {
167 return false;
170 // Step 3.
171 int32_t daysInMonth = js::temporal::ISODaysInMonth(year, int32_t(month));
173 // Step 4.
174 if (!ThrowIfInvalidDateValue(cx, "day", 1, daysInMonth, day)) {
175 return false;
178 // Step 5.
179 return true;
183 * IsValidISODate ( year, month, day )
185 bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, const PlainDate& date) {
186 auto& [year, month, day] = date;
187 return ::ThrowIfInvalidISODate(cx, year, month, day);
191 * IsValidISODate ( year, month, day )
193 bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, double year,
194 double month, double day) {
195 return ::ThrowIfInvalidISODate(cx, year, month, day);
199 * RegulateISODate ( year, month, day, overflow )
201 * With |overflow = "constrain"|.
203 static PlainDate ConstrainISODate(const PlainDate& date) {
204 auto& [year, month, day] = date;
206 // Step 1.a.
207 int32_t m = std::clamp(month, 1, 12);
209 // Step 1.b.
210 int32_t daysInMonth = temporal::ISODaysInMonth(year, m);
212 // Step 1.c.
213 int32_t d = std::clamp(day, 1, daysInMonth);
215 // Step 1.d.
216 return {year, m, d};
220 * RegulateISODate ( year, month, day, overflow )
222 bool js::temporal::RegulateISODate(JSContext* cx, const PlainDate& date,
223 TemporalOverflow overflow,
224 PlainDate* result) {
225 // Step 1.
226 if (overflow == TemporalOverflow::Constrain) {
227 *result = ::ConstrainISODate(date);
228 return true;
231 // Step 2.a.
232 MOZ_ASSERT(overflow == TemporalOverflow::Reject);
234 // Step 2.b.
235 if (!ThrowIfInvalidISODate(cx, date)) {
236 return false;
239 // Step 2.b. (Inlined call to CreateISODateRecord.)
240 *result = date;
241 return true;
245 * RegulateISODate ( year, month, day, overflow )
247 bool js::temporal::RegulateISODate(JSContext* cx, double year, double month,
248 double day, TemporalOverflow overflow,
249 RegulatedISODate* result) {
250 MOZ_ASSERT(IsInteger(year));
251 MOZ_ASSERT(IsInteger(month));
252 MOZ_ASSERT(IsInteger(day));
254 // Step 1.
255 if (overflow == TemporalOverflow::Constrain) {
256 // Step 1.a.
257 int32_t m = int32_t(std::clamp(month, 1.0, 12.0));
259 // Step 1.b.
260 double daysInMonth = double(ISODaysInMonth(year, m));
262 // Step 1.c.
263 int32_t d = int32_t(std::clamp(day, 1.0, daysInMonth));
265 // Step 1.d.
266 *result = {year, m, d};
267 return true;
270 // Step 2.a.
271 MOZ_ASSERT(overflow == TemporalOverflow::Reject);
273 // Step 2.b.
274 if (!ThrowIfInvalidISODate(cx, year, month, day)) {
275 return false;
278 // Step 2.b. (Inlined call to CreateISODateRecord.)
279 *result = {year, int32_t(month), int32_t(day)};
280 return true;
284 * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
286 static PlainDateObject* CreateTemporalDate(JSContext* cx, const CallArgs& args,
287 double isoYear, double isoMonth,
288 double isoDay,
289 Handle<CalendarValue> calendar) {
290 MOZ_ASSERT(IsInteger(isoYear));
291 MOZ_ASSERT(IsInteger(isoMonth));
292 MOZ_ASSERT(IsInteger(isoDay));
294 // Step 1.
295 if (!ThrowIfInvalidISODate(cx, isoYear, isoMonth, isoDay)) {
296 return nullptr;
299 // Step 2.
300 if (!ISODateTimeWithinLimits(isoYear, isoMonth, isoDay)) {
301 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
302 JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
303 return nullptr;
306 // Steps 3-4.
307 Rooted<JSObject*> proto(cx);
308 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainDate,
309 &proto)) {
310 return nullptr;
313 auto* object = NewObjectWithClassProto<PlainDateObject>(cx, proto);
314 if (!object) {
315 return nullptr;
318 // Step 5.
319 object->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT, Int32Value(isoYear));
321 // Step 6.
322 object->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
324 // Step 7.
325 object->setFixedSlot(PlainDateObject::ISO_DAY_SLOT, Int32Value(isoDay));
327 // Step 8.
328 object->setFixedSlot(PlainDateObject::CALENDAR_SLOT, calendar.toValue());
330 // Step 9.
331 return object;
335 * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
337 PlainDateObject* js::temporal::CreateTemporalDate(
338 JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar) {
339 auto& [isoYear, isoMonth, isoDay] = date;
341 // Step 1.
342 if (!ThrowIfInvalidISODate(cx, date)) {
343 return nullptr;
346 // Step 2.
347 if (!ISODateTimeWithinLimits(date)) {
348 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
349 JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
350 return nullptr;
353 // Steps 3-4.
354 auto* object = NewBuiltinClassInstance<PlainDateObject>(cx);
355 if (!object) {
356 return nullptr;
359 // Step 5.
360 object->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT, Int32Value(isoYear));
362 // Step 6.
363 object->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
365 // Step 7.
366 object->setFixedSlot(PlainDateObject::ISO_DAY_SLOT, Int32Value(isoDay));
368 // Step 8.
369 object->setFixedSlot(PlainDateObject::CALENDAR_SLOT, calendar.toValue());
371 // Step 9.
372 return object;
376 * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
378 bool js::temporal::CreateTemporalDate(
379 JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar,
380 MutableHandle<PlainDateWithCalendar> result) {
381 // Step 1.
382 if (!ThrowIfInvalidISODate(cx, date)) {
383 return false;
386 // Step 2.
387 if (!ISODateTimeWithinLimits(date)) {
388 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
389 JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
390 return false;
393 // Steps 3-9.
394 result.set(PlainDateWithCalendar{date, calendar});
395 return true;
399 * ToTemporalDate ( item [ , options ] )
401 static Wrapped<PlainDateObject*> ToTemporalDate(
402 JSContext* cx, Handle<JSObject*> item, Handle<PlainObject*> maybeOptions) {
403 // Step 1-2. (Not applicable in our implementation.)
405 // Step 3.a.
406 if (item->canUnwrapAs<PlainDateObject>()) {
407 return item;
410 // Step 3.b.
411 if (auto* zonedDateTime = item->maybeUnwrapIf<ZonedDateTimeObject>()) {
412 auto epochInstant = ToInstant(zonedDateTime);
413 Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
414 Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
416 if (!timeZone.wrap(cx)) {
417 return nullptr;
419 if (!calendar.wrap(cx)) {
420 return nullptr;
423 // Step 3.b.i.
424 if (maybeOptions) {
425 TemporalOverflow ignored;
426 if (!ToTemporalOverflow(cx, maybeOptions, &ignored)) {
427 return nullptr;
431 // Steps 3.b.ii-iv.
432 PlainDateTime dateTime;
433 if (!GetPlainDateTimeFor(cx, timeZone, epochInstant, &dateTime)) {
434 return nullptr;
437 // Step 3.b.v.
438 return CreateTemporalDate(cx, dateTime.date, calendar);
441 // Step 3.c.
442 if (auto* dateTime = item->maybeUnwrapIf<PlainDateTimeObject>()) {
443 auto date = ToPlainDate(dateTime);
444 Rooted<CalendarValue> calendar(cx, dateTime->calendar());
445 if (!calendar.wrap(cx)) {
446 return nullptr;
449 // Step 3.c.i.
450 if (maybeOptions) {
451 TemporalOverflow ignored;
452 if (!ToTemporalOverflow(cx, maybeOptions, &ignored)) {
453 return nullptr;
457 // Step 3.c.ii.
458 return CreateTemporalDate(cx, date, calendar);
461 // Step 3.d.
462 Rooted<CalendarValue> calendarValue(cx);
463 if (!GetTemporalCalendarWithISODefault(cx, item, &calendarValue)) {
464 return nullptr;
467 // Step 3.e.
468 Rooted<CalendarRecord> calendar(cx);
469 if (!CreateCalendarMethodsRecord(cx, calendarValue,
471 CalendarMethod::DateFromFields,
472 CalendarMethod::Fields,
474 &calendar)) {
475 return nullptr;
478 // Step 3.f.
479 JS::RootedVector<PropertyKey> fieldNames(cx);
480 if (!CalendarFields(cx, calendar,
481 {CalendarField::Day, CalendarField::Month,
482 CalendarField::MonthCode, CalendarField::Year},
483 &fieldNames)) {
484 return nullptr;
487 // Step 3.g.
488 Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, item, fieldNames));
489 if (!fields) {
490 return nullptr;
493 // Step 3.h.
494 if (maybeOptions) {
495 return temporal::CalendarDateFromFields(cx, calendar, fields, maybeOptions);
497 return temporal::CalendarDateFromFields(cx, calendar, fields);
501 * ToTemporalDate ( item [ , options ] )
503 static Wrapped<PlainDateObject*> ToTemporalDate(
504 JSContext* cx, Handle<Value> item, Handle<JSObject*> maybeOptions) {
505 // Step 1. (Not applicable in our implementation.)
507 // Step 2.
508 Rooted<PlainObject*> maybeResolvedOptions(cx);
509 if (maybeOptions) {
510 maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions);
511 if (!maybeResolvedOptions) {
512 return nullptr;
516 // Step 3.
517 if (item.isObject()) {
518 Rooted<JSObject*> itemObj(cx, &item.toObject());
519 return ::ToTemporalDate(cx, itemObj, maybeResolvedOptions);
522 // Step 4.
523 if (!item.isString()) {
524 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
525 nullptr, "not a string");
526 return nullptr;
528 Rooted<JSString*> string(cx, item.toString());
530 // Step 5.
531 PlainDate result;
532 Rooted<JSString*> calendarString(cx);
533 if (!ParseTemporalDateString(cx, string, &result, &calendarString)) {
534 return nullptr;
537 // Step 6.
538 MOZ_ASSERT(IsValidISODate(result));
540 // Steps 7-10.
541 Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
542 if (calendarString) {
543 if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
544 return nullptr;
548 // Step 11.
549 if (maybeResolvedOptions) {
550 TemporalOverflow ignored;
551 if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
552 return nullptr;
556 // Step 12.
557 return CreateTemporalDate(cx, result, calendar);
561 * ToTemporalDate ( item [ , options ] )
563 static Wrapped<PlainDateObject*> ToTemporalDate(JSContext* cx,
564 Handle<Value> item) {
565 return ::ToTemporalDate(cx, item, nullptr);
569 * ToTemporalDate ( item [ , options ] )
571 bool js::temporal::ToTemporalDate(JSContext* cx, Handle<Value> item,
572 PlainDate* result) {
573 auto obj = ::ToTemporalDate(cx, item, nullptr);
574 if (!obj) {
575 return false;
578 *result = ToPlainDate(&obj.unwrap());
579 return true;
583 * ToTemporalDate ( item [ , options ] )
585 bool js::temporal::ToTemporalDate(JSContext* cx, Handle<Value> item,
586 MutableHandle<PlainDateWithCalendar> result) {
587 auto* obj = ::ToTemporalDate(cx, item, nullptr).unwrapOrNull();
588 if (!obj) {
589 return false;
592 auto date = ToPlainDate(obj);
593 Rooted<CalendarValue> calendar(cx, obj->calendar());
594 if (!calendar.wrap(cx)) {
595 return false;
598 result.set(PlainDateWithCalendar{date, calendar});
599 return true;
603 * Mathematical Operations, "modulo" notation.
605 static int32_t NonNegativeModulo(double x, int32_t y) {
606 MOZ_ASSERT(IsInteger(x));
607 MOZ_ASSERT(y > 0);
609 double r = std::fmod(x, y);
611 int32_t result;
612 MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt32(r, &result));
614 return (result < 0) ? (result + y) : result;
617 struct BalancedYearMonth final {
618 double year = 0;
619 int32_t month = 0;
623 * BalanceISOYearMonth ( year, month )
625 static BalancedYearMonth BalanceISOYearMonth(double year, double month) {
626 // Step 1.
627 MOZ_ASSERT(IsInteger(year));
628 MOZ_ASSERT(IsInteger(month));
630 // Note: If either abs(year) or abs(month) is greater than 2^53 (the double
631 // integral precision limit), the additions resp. subtractions below are
632 // imprecise. This doesn't matter for us, because the single caller to this
633 // function (AddISODate) will throw an error for large values anyway.
635 // Step 2.
636 year = year + std::floor((month - 1) / 12);
637 MOZ_ASSERT(IsInteger(year) || std::isinf(year));
639 // Step 3.
640 int32_t mon = NonNegativeModulo(month - 1, 12) + 1;
641 MOZ_ASSERT(1 <= mon && mon <= 12);
643 // Step 4.
644 return {year, mon};
647 static bool CanBalanceISOYear(double year) {
648 // TODO: Export these values somewhere.
649 constexpr int32_t minYear = -271821;
650 constexpr int32_t maxYear = 275760;
652 // If the year is below resp. above the min-/max-year, no value of |day| will
653 // make the resulting date valid.
654 return minYear <= year && year <= maxYear;
657 static bool CanBalanceISODay(double day) {
658 // The maximum number of seconds from the epoch is 8.64 * 10^12.
659 constexpr int64_t maxInstantSeconds = 8'640'000'000'000;
661 // In days that makes 10^8.
662 constexpr int64_t maxInstantDays = maxInstantSeconds / 60 / 60 / 24;
664 // Multiply by two to take both directions into account and add twenty to
665 // account for the day number of the minimum date "-271821-02-20".
666 constexpr int64_t maximumDayDifference = 2 * maxInstantDays + 20;
668 // When |day| is below |maximumDayDifference|, it can be represented as int32.
669 static_assert(maximumDayDifference <= INT32_MAX);
671 // When the day difference exceeds the maximum valid day difference, the
672 // overall result won't be a valid date. Detect this early so we don't have to
673 // struggle with floating point precision issues in BalanceISODate.
675 // This also means BalanceISODate, step 1 doesn't apply to our implementation.
676 return std::abs(day) <= maximumDayDifference;
680 * BalanceISODate ( year, month, day )
682 PlainDate js::temporal::BalanceISODateNew(int32_t year, int32_t month,
683 int32_t day) {
684 MOZ_ASSERT(1 <= month && month <= 12);
686 // Steps 1-3.
687 int64_t ms = MakeDate(year, month, day);
689 // FIXME: spec issue - |ms| can be non-finite
690 // https://github.com/tc39/proposal-temporal/issues/2315
692 // TODO: This approach isn't efficient, because MonthFromTime and DayFromTime
693 // both recompute YearFromTime.
695 // Step 4.
696 return {int32_t(JS::YearFromTime(ms)), int32_t(JS::MonthFromTime(ms) + 1),
697 int32_t(JS::DayFromTime(ms))};
701 * BalanceISODate ( year, month, day )
703 bool js::temporal::BalanceISODate(JSContext* cx, int32_t year, int32_t month,
704 int64_t day, PlainDate* result) {
705 if (!CanBalanceISODay(day)) {
706 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
707 JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
708 return false;
711 *result = BalanceISODate(year, month, int32_t(day));
712 return true;
716 * BalanceISODate ( year, month, day )
718 PlainDate js::temporal::BalanceISODate(int32_t year, int32_t month,
719 int32_t day) {
720 // Check no inputs can lead to floating point precision issues below. This
721 // also ensures all loops can finish in reasonable time, so we don't need to
722 // worry about interrupts here. And it ensures there won't be overflows when
723 // using int32_t values.
724 MOZ_ASSERT(CanBalanceISOYear(year));
725 MOZ_ASSERT(1 <= month && month <= 12);
726 MOZ_ASSERT(CanBalanceISODay(day));
728 // TODO: BalanceISODate now works using MakeDate
729 // TODO: Can't use JS::MakeDate, because it expects valid month/day values.
730 // https://github.com/tc39/proposal-temporal/issues/2315
732 // Step 1. (Not applicable in our implementation.)
734 // Steps 3-4. (Not applicable in our implementation.)
736 constexpr int32_t daysInNonLeapYear = 365;
738 // Skip steps 5-11 for the common case when abs(day) doesn't exceed 365.
739 if (std::abs(day) > daysInNonLeapYear) {
740 // Step 5. (Note)
742 // Steps 6-7.
743 int32_t testYear = month > 2 ? year : year - 1;
745 // Step 8.
746 while (day < -ISODaysInYear(testYear)) {
747 // Step 8.a.
748 day += ISODaysInYear(testYear);
750 // Step 8.b.
751 year -= 1;
753 // Step 8.c.
754 testYear -= 1;
757 // Step 9. (Note)
759 // Step 10.
760 testYear += 1;
762 // Step 11.
763 while (day > ISODaysInYear(testYear)) {
764 // Step 11.a.
765 day -= ISODaysInYear(testYear);
767 // Step 11.b.
768 year += 1;
770 // Step 11.c.
771 testYear += 1;
775 // Step 12. (Note)
777 // Step 13.
778 while (day < 1) {
779 // Steps 13.a-b. (Inlined call to BalanceISOYearMonth.)
780 if (--month == 0) {
781 month = 12;
782 year -= 1;
785 // Step 13.d
786 day += ISODaysInMonth(year, month);
789 // Step 14. (Note)
791 // Step 15.
792 while (day > ISODaysInMonth(year, month)) {
793 // Step 15.a.
794 day -= ISODaysInMonth(year, month);
796 // Steps 15.b-d. (Inlined call to BalanceISOYearMonth.)
797 if (++month == 13) {
798 month = 1;
799 year += 1;
803 MOZ_ASSERT(1 <= month && month <= 12);
804 MOZ_ASSERT(1 <= day && day <= 31);
806 // Step 16.
807 return {year, month, day};
811 * AddISODate ( year, month, day, years, months, weeks, days, overflow )
813 bool js::temporal::AddISODate(JSContext* cx, const PlainDate& date,
814 const DateDuration& duration,
815 TemporalOverflow overflow, PlainDate* result) {
816 MOZ_ASSERT(IsValidISODate(date));
817 MOZ_ASSERT(ISODateTimeWithinLimits(date));
819 // TODO: Not quite sure if this holds for all callers. But if it does hold,
820 // then we can directly reject any numbers which can't be represented with
821 // int32_t. That in turn avoids the precision loss issue noted in
822 // BalanceISODate.
823 MOZ_ASSERT(IsValidDuration(duration));
825 // Steps 1-2. (Not applicable in our implementation.)
827 // Step 3.
828 auto yearMonth = BalanceISOYearMonth(date.year + duration.years,
829 date.month + duration.months);
830 MOZ_ASSERT(IsInteger(yearMonth.year) || std::isinf(yearMonth.year));
831 MOZ_ASSERT(1 <= yearMonth.month && yearMonth.month <= 12);
833 // FIXME: spec issue?
834 // new Temporal.PlainDate(2021, 5, 31).subtract({months:1, days:1}).toString()
835 // returns "2021-04-29", but "2021-04-30" seems more likely expected.
836 // Note: "2021-04-29" agrees with java.time, though.
838 // Example where this creates inconsistent results:
840 // clang-format off
842 // js> Temporal.PlainDate.from("2021-05-31").since("2021-04-30", {largestUnit:"months"}).toString()
843 // "P1M1D"
844 // js> Temporal.PlainDate.from("2021-05-31").subtract("P1M1D").toString()
845 // "2021-04-29"
847 // clang-format on
849 // Later: This now returns "P1M" instead "P1M1D", so the results are at least
850 // consistent. Let's add a test case for this behaviour.
852 // Revisit when <https://github.com/tc39/proposal-temporal/issues/2535> has
853 // been addressed.
855 // |yearMonth.year| can only exceed the valid years range when called from
856 // `Temporal.Calendar.prototype.dateAdd`. And because `dateAdd` uses the
857 // result of AddISODate to create a new Temporal.PlainDate, we can directly
858 // throw an error if the result isn't within the valid date-time limits. This
859 // in turn allows to work on integer values and we don't have to worry about
860 // imprecise double value computations.
861 if (!CanBalanceISOYear(yearMonth.year)) {
862 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
863 JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
864 return false;
867 // Step 4.
868 PlainDate regulated;
869 if (!RegulateISODate(cx, {int32_t(yearMonth.year), yearMonth.month, date.day},
870 overflow, &regulated)) {
871 return false;
874 // NB: BalanceISODate will reject too large days, so we don't have to worry
875 // about imprecise number arithmetic here.
877 // Steps 5-6.
878 double d = regulated.day + (duration.days + duration.weeks * 7);
880 // Just as with |yearMonth.year|, also directly throw an error if the |days|
881 // value is too large.
882 if (!CanBalanceISODay(d)) {
883 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
884 JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
885 return false;
888 // Step 7.
889 auto balanced = BalanceISODate(regulated.year, regulated.month, int32_t(d));
890 MOZ_ASSERT(IsValidISODate(balanced));
892 *result = balanced;
893 return true;
896 struct YearMonthDuration {
897 int32_t years = 0;
898 int32_t months = 0;
902 * AddISODate ( year, month, day, years, months, weeks, days, overflow )
904 * With |overflow = "constrain"|.
906 static PlainDate AddISODate(const PlainDate& date,
907 const YearMonthDuration& duration) {
908 MOZ_ASSERT(IsValidISODate(date));
909 MOZ_ASSERT(ISODateTimeWithinLimits(date));
911 MOZ_ASSERT_IF(duration.years < 0, duration.months <= 0);
912 MOZ_ASSERT_IF(duration.years > 0, duration.months >= 0);
914 // TODO: Export these values somewhere.
915 [[maybe_unused]] constexpr int32_t minYear = -271821;
916 [[maybe_unused]] constexpr int32_t maxYear = 275760;
918 MOZ_ASSERT(std::abs(duration.years) <= (maxYear - minYear),
919 "years doesn't exceed the maximum duration between valid years");
920 MOZ_ASSERT(std::abs(duration.months) <= 12,
921 "months duration is at most one year");
923 // Steps 1-2. (Not applicable)
925 // Step 3. (Inlined BalanceISOYearMonth)
926 int32_t year = date.year + duration.years;
927 int32_t month = date.month + duration.months;
928 MOZ_ASSERT(-11 <= month && month <= 24);
930 if (month > 12) {
931 month -= 12;
932 year += 1;
933 } else if (month <= 0) {
934 month += 12;
935 year -= 1;
938 MOZ_ASSERT(1 <= month && month <= 12);
939 MOZ_ASSERT(CanBalanceISOYear(year));
941 // Steps 4-7.
942 return ::ConstrainISODate({year, month, date.day});
945 static bool HasYearsMonthsOrWeeks(const Duration& duration) {
946 return duration.years != 0 || duration.months != 0 || duration.weeks != 0;
949 static bool HasYearsMonthsOrWeeks(const DateDuration& duration) {
950 return duration.years != 0 || duration.months != 0 || duration.weeks != 0;
954 * AddDate ( calendarRec, plainDate, duration [ , options ] )
956 static bool AddDate(JSContext* cx, const PlainDate& date,
957 const NormalizedDuration& duration,
958 TemporalOverflow overflow, PlainDate* result) {
959 MOZ_ASSERT(!HasYearsMonthsOrWeeks(duration.date));
960 MOZ_ASSERT(IsValidDuration(duration));
962 // Steps 1-4. (Not applicable)
964 // Step 5. (Not applicable)
965 auto& timeDuration = duration.time;
967 // Step 6.
968 int64_t balancedDays =
969 BalanceTimeDuration(timeDuration, TemporalUnit::Day).days;
970 int64_t days = duration.date.days + balancedDays;
972 // Step 7.
973 return AddISODate(cx, date, {0, 0, 0, days}, overflow, result);
976 static bool AddDate(JSContext* cx, Handle<Wrapped<PlainDateObject*>> date,
977 const NormalizedDuration& duration,
978 TemporalOverflow overflow, PlainDate* result) {
979 auto* unwrappedDate = date.unwrap(cx);
980 if (!unwrappedDate) {
981 return false;
983 return ::AddDate(cx, ToPlainDate(unwrappedDate), duration, overflow, result);
987 * AddDate ( calendarRec, plainDate, duration [ , options ] )
989 Wrapped<PlainDateObject*> js::temporal::AddDate(
990 JSContext* cx, Handle<CalendarRecord> calendar,
991 Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
992 Handle<JSObject*> options) {
993 // Step 1.
994 MOZ_ASSERT(
995 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
997 // Step 2. (Not applicable in our implementation.)
999 // Step 3.
1000 if (HasYearsMonthsOrWeeks(duration)) {
1001 return temporal::CalendarDateAdd(cx, calendar, date, duration, options);
1004 // Step 4.
1005 auto overflow = TemporalOverflow::Constrain;
1006 if (!ToTemporalOverflow(cx, options, &overflow)) {
1007 return nullptr;
1010 // Step 5.
1011 auto normalized = CreateNormalizedDurationRecord(duration);
1013 // Steps 6-7.
1014 PlainDate resultDate;
1015 if (!::AddDate(cx, date, normalized, overflow, &resultDate)) {
1016 return nullptr;
1019 // Step 8.
1020 return CreateTemporalDate(cx, resultDate, calendar.receiver());
1024 * AddDate ( calendarRec, plainDate, duration [ , options ] )
1026 Wrapped<PlainDateObject*> js::temporal::AddDate(
1027 JSContext* cx, Handle<CalendarRecord> calendar,
1028 Handle<Wrapped<PlainDateObject*>> date, const DateDuration& duration) {
1029 // Step 1.
1030 MOZ_ASSERT(
1031 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1033 // Step 2. (Not applicable in our implementation.)
1035 // Step 3.
1036 if (HasYearsMonthsOrWeeks(duration)) {
1037 return CalendarDateAdd(cx, calendar, date, duration);
1040 // Step 4.
1041 auto overflow = TemporalOverflow::Constrain;
1043 // Step 5.
1044 auto normalized = NormalizedDuration{duration};
1046 // Steps 6-7.
1047 PlainDate resultDate;
1048 if (!::AddDate(cx, date, normalized, overflow, &resultDate)) {
1049 return nullptr;
1052 // Step 8.
1053 return CreateTemporalDate(cx, resultDate, calendar.receiver());
1057 * AddDate ( calendarRec, plainDate, duration [ , options ] )
1059 static Wrapped<PlainDateObject*> AddDate(
1060 JSContext* cx, Handle<CalendarRecord> calendar,
1061 Handle<Wrapped<PlainDateObject*>> date,
1062 Handle<Wrapped<DurationObject*>> durationObj, Handle<JSObject*> options) {
1063 auto* unwrappedDuration = durationObj.unwrap(cx);
1064 if (!unwrappedDuration) {
1065 return nullptr;
1067 auto duration = ToDuration(unwrappedDuration);
1069 // Step 1.
1070 MOZ_ASSERT(
1071 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1073 // Step 2. (Not applicable in our implementation.)
1075 // Step 3.
1076 if (HasYearsMonthsOrWeeks(duration)) {
1077 return temporal::CalendarDateAdd(cx, calendar, date, durationObj, options);
1080 // Step 4.
1081 auto overflow = TemporalOverflow::Constrain;
1082 if (!ToTemporalOverflow(cx, options, &overflow)) {
1083 return nullptr;
1086 // Step 5.
1087 auto normalized = CreateNormalizedDurationRecord(duration);
1089 // Steps 6-7.
1090 PlainDate resultDate;
1091 if (!::AddDate(cx, date, normalized, overflow, &resultDate)) {
1092 return nullptr;
1095 // Step 8.
1096 return CreateTemporalDate(cx, resultDate, calendar.receiver());
1100 * AddDate ( calendarRec, plainDate, duration [ , options ] )
1102 bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
1103 const PlainDate& date, const DateDuration& duration,
1104 Handle<JSObject*> options, PlainDate* result) {
1105 // Step 1.
1106 MOZ_ASSERT(
1107 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1109 // Step 2. (Not applicable in our implementation.)
1111 // Step 3.
1112 if (HasYearsMonthsOrWeeks(duration)) {
1113 return temporal::CalendarDateAdd(cx, calendar, date, duration, options,
1114 result);
1117 // Step 4.
1118 auto overflow = TemporalOverflow::Constrain;
1119 if (!ToTemporalOverflow(cx, options, &overflow)) {
1120 return false;
1123 // Step 5.
1124 auto normalized = NormalizedDuration{duration};
1126 // Steps 5-8.
1127 return ::AddDate(cx, date, normalized, overflow, result);
1131 * AddDate ( calendarRec, plainDate, duration [ , options ] )
1133 bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
1134 Handle<Wrapped<PlainDateObject*>> date,
1135 const DateDuration& duration, PlainDate* result) {
1136 // Step 1.
1137 MOZ_ASSERT(
1138 CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
1140 // Step 2. (Not applicable in our implementation.)
1142 // Step 3.
1143 if (HasYearsMonthsOrWeeks(duration)) {
1144 return CalendarDateAdd(cx, calendar, date, duration, result);
1147 // Step 4.
1148 auto overflow = TemporalOverflow::Constrain;
1150 // Step 5.
1151 auto normalized = NormalizedDuration{duration};
1153 // Steps 6-8.
1154 return ::AddDate(cx, date, normalized, overflow, result);
1158 * DifferenceDate ( calendarRec, one, two, options )
1160 bool js::temporal::DifferenceDate(JSContext* cx,
1161 Handle<CalendarRecord> calendar,
1162 Handle<Wrapped<PlainDateObject*>> one,
1163 Handle<Wrapped<PlainDateObject*>> two,
1164 Handle<PlainObject*> options,
1165 DateDuration* result) {
1166 auto* unwrappedOne = one.unwrap(cx);
1167 if (!unwrappedOne) {
1168 return false;
1170 auto oneDate = ToPlainDate(unwrappedOne);
1172 auto* unwrappedTwo = two.unwrap(cx);
1173 if (!unwrappedTwo) {
1174 return false;
1176 auto twoDate = ToPlainDate(unwrappedTwo);
1178 // Steps 1-2. (Not applicable in our implementation.)
1180 // Step 3.
1181 MOZ_ASSERT(options->staticPrototype() == nullptr);
1183 // Step 4.
1184 MOZ_ASSERT(options->containsPure(cx->names().largestUnit));
1186 // Step 5.
1187 if (oneDate == twoDate) {
1188 *result = {};
1189 return true;
1192 // Step 6.
1193 Rooted<JS::Value> largestUnit(cx);
1194 if (!GetProperty(cx, options, options, cx->names().largestUnit,
1195 &largestUnit)) {
1196 return false;
1199 if (largestUnit.isString()) {
1200 bool isDay;
1201 if (!EqualStrings(cx, largestUnit.toString(), cx->names().day, &isDay)) {
1202 return false;
1205 if (isDay) {
1206 // Step 6.a.
1207 int32_t days = DaysUntil(oneDate, twoDate);
1209 // Step 6.b.
1210 *result = {0, 0, 0, days};
1211 return true;
1215 // Step 7.
1216 Duration duration;
1217 if (!CalendarDateUntil(cx, calendar, one, two, options, &duration)) {
1218 return false;
1220 *result = duration.toDateDuration();
1221 return true;
1225 * DifferenceDate ( calendarRec, one, two, options )
1227 bool js::temporal::DifferenceDate(JSContext* cx,
1228 Handle<CalendarRecord> calendar,
1229 Handle<Wrapped<PlainDateObject*>> one,
1230 Handle<Wrapped<PlainDateObject*>> two,
1231 TemporalUnit largestUnit,
1232 DateDuration* result) {
1233 auto* unwrappedOne = one.unwrap(cx);
1234 if (!unwrappedOne) {
1235 return false;
1237 auto oneDate = ToPlainDate(unwrappedOne);
1239 auto* unwrappedTwo = two.unwrap(cx);
1240 if (!unwrappedTwo) {
1241 return false;
1243 auto twoDate = ToPlainDate(unwrappedTwo);
1245 // Steps 1-4. (Not applicable in our implementation.)
1247 // Step 5.
1248 if (oneDate == twoDate) {
1249 *result = {};
1250 return true;
1253 // Step 6.
1254 if (largestUnit == TemporalUnit::Day) {
1255 // Step 6.a.
1256 int32_t days = DaysUntil(oneDate, twoDate);
1258 // Step 6.b.
1259 *result = {0, 0, 0, days};
1260 return true;
1263 // Step 7.
1264 Duration duration;
1265 if (!CalendarDateUntil(cx, calendar, one, two, largestUnit, &duration)) {
1266 return false;
1268 *result = duration.toDateDuration();
1269 return true;
1273 * CompareISODate ( y1, m1, d1, y2, m2, d2 )
1275 int32_t js::temporal::CompareISODate(const PlainDate& one,
1276 const PlainDate& two) {
1277 // Steps 1-2.
1278 if (one.year != two.year) {
1279 return one.year < two.year ? -1 : 1;
1282 // Steps 3-4.
1283 if (one.month != two.month) {
1284 return one.month < two.month ? -1 : 1;
1287 // Steps 5-6.
1288 if (one.day != two.day) {
1289 return one.day < two.day ? -1 : 1;
1292 // Step 7.
1293 return 0;
1297 * CreateDateDurationRecord ( years, months, weeks, days )
1299 static DateDuration CreateDateDurationRecord(int32_t years, int32_t months,
1300 int32_t weeks, int32_t days) {
1301 MOZ_ASSERT(IsValidDuration(
1302 Duration{double(years), double(months), double(weeks), double(days)}));
1303 return {years, months, weeks, days};
1307 * DifferenceISODate ( y1, m1, d1, y2, m2, d2, largestUnit )
1309 DateDuration js::temporal::DifferenceISODate(const PlainDate& start,
1310 const PlainDate& end,
1311 TemporalUnit largestUnit) {
1312 // Steps 1-2.
1313 MOZ_ASSERT(IsValidISODate(start));
1314 MOZ_ASSERT(IsValidISODate(end));
1316 // Both inputs are also within the date-time limits.
1317 MOZ_ASSERT(ISODateTimeWithinLimits(start));
1318 MOZ_ASSERT(ISODateTimeWithinLimits(end));
1320 // Because both inputs are valid dates, we don't need to worry about integer
1321 // overflow in any of the computations below.
1323 MOZ_ASSERT(TemporalUnit::Year <= largestUnit &&
1324 largestUnit <= TemporalUnit::Day);
1326 // Step 3.
1327 if (largestUnit == TemporalUnit::Year || largestUnit == TemporalUnit::Month) {
1328 // Step 3.a.
1329 int32_t sign = -CompareISODate(start, end);
1331 // Step 3.b.
1332 if (sign == 0) {
1333 return CreateDateDurationRecord(0, 0, 0, 0);
1336 // FIXME: spec issue - results can be ambiguous, is this intentional?
1337 // https://github.com/tc39/proposal-temporal/issues/2535
1339 // clang-format off
1340 // js> var end = new Temporal.PlainDate(1970, 2, 28)
1341 // js> var start = new Temporal.PlainDate(1970, 1, 28)
1342 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1343 // "P1M"
1344 // js> var start = new Temporal.PlainDate(1970, 1, 29)
1345 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1346 // "P1M"
1347 // js> var start = new Temporal.PlainDate(1970, 1, 30)
1348 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1349 // "P1M"
1350 // js> var start = new Temporal.PlainDate(1970, 1, 31)
1351 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1352 // "P1M"
1354 // Compare to java.time.temporal
1356 // jshell> import java.time.LocalDate
1357 // jshell> var end = LocalDate.of(1970, 2, 28)
1358 // end ==> 1970-02-28
1359 // jshell> var start = LocalDate.of(1970, 1, 28)
1360 // start ==> 1970-01-28
1361 // jshell> start.until(end)
1362 // $27 ==> P1M
1363 // jshell> var start = LocalDate.of(1970, 1, 29)
1364 // start ==> 1970-01-29
1365 // jshell> start.until(end)
1366 // $29 ==> P30D
1367 // jshell> var start = LocalDate.of(1970, 1, 30)
1368 // start ==> 1970-01-30
1369 // jshell> start.until(end)
1370 // $31 ==> P29D
1371 // jshell> var start = LocalDate.of(1970, 1, 31)
1372 // start ==> 1970-01-31
1373 // jshell> start.until(end)
1374 // $33 ==> P28D
1376 // Also compare to:
1378 // js> var end = new Temporal.PlainDate(1970, 2, 27)
1379 // js> var start = new Temporal.PlainDate(1970, 1, 27)
1380 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1381 // "P1M"
1382 // js> var start = new Temporal.PlainDate(1970, 1, 28)
1383 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1384 // "P30D"
1385 // js> var start = new Temporal.PlainDate(1970, 1, 29)
1386 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1387 // "P29D"
1389 // clang-format on
1391 // Steps 3.c-d. (Not applicable in our implementation.)
1393 // FIXME: spec issue - consistently use either |end.[[Year]]| or |y2|.
1395 // Step 3.e.
1396 int32_t years = end.year - start.year;
1398 // TODO: We could inline this, because the AddISODate call is just a more
1399 // complicated way to perform:
1400 // mid = ConstrainISODate(end.year, start.month, start.day)
1402 // The remaining computations can probably simplified similarily.
1404 // Step 3.f.
1405 auto mid = ::AddISODate(start, {years, 0});
1407 // Step 3.g.
1408 int32_t midSign = -CompareISODate(mid, end);
1410 // Step 3.h.
1411 if (midSign == 0) {
1412 // Step 3.h.i.
1413 if (largestUnit == TemporalUnit::Year) {
1414 return CreateDateDurationRecord(years, 0, 0, 0);
1417 // Step 3.h.ii.
1418 return CreateDateDurationRecord(0, years * 12, 0, 0);
1421 // Step 3.i.
1422 int32_t months = end.month - start.month;
1424 // Step 3.j.
1425 if (midSign != sign) {
1426 // Step 3.j.i.
1427 years -= sign;
1429 // Step 3.j.ii.
1430 months += sign * 12;
1433 // Step 3.k.
1434 mid = ::AddISODate(start, {years, months});
1436 // Step 3.l.
1437 midSign = -CompareISODate(mid, end);
1439 // Step 3.m.
1440 if (midSign == 0) {
1441 // Step 3.m.i.
1442 if (largestUnit == TemporalUnit::Year) {
1443 return CreateDateDurationRecord(years, months, 0, 0);
1446 // Step 3.m.ii.
1447 return CreateDateDurationRecord(0, months + years * 12, 0, 0);
1450 // Step 3.n.
1451 if (midSign != sign) {
1452 // Step 3.n.i.
1453 months -= sign;
1455 // Step 3.n.ii.
1456 mid = ::AddISODate(start, {years, months});
1459 // Steps 3.o-q.
1460 int32_t days;
1461 if (mid.month == end.month) {
1462 MOZ_ASSERT(mid.year == end.year);
1464 days = end.day - mid.day;
1465 } else if (sign < 0) {
1466 days = -mid.day - (ISODaysInMonth(end.year, end.month) - end.day);
1467 } else {
1468 days = end.day + (ISODaysInMonth(mid.year, mid.month) - mid.day);
1471 // Step 3.r.
1472 if (largestUnit == TemporalUnit::Month) {
1473 // Step 3.r.i.
1474 months += years * 12;
1476 // Step 3.r.ii.
1477 years = 0;
1480 // Step 3.s.
1481 return CreateDateDurationRecord(years, months, 0, days);
1484 // Step 4.a.
1485 MOZ_ASSERT(largestUnit == TemporalUnit::Week ||
1486 largestUnit == TemporalUnit::Day);
1488 // Step 4.b.
1489 int32_t epochDaysStart = MakeDay(start);
1491 // Step 4.c.
1492 int32_t epochDaysEnd = MakeDay(end);
1494 // Step 4.d.
1495 int32_t days = epochDaysEnd - epochDaysStart;
1497 // Step 4.e.
1498 int32_t weeks = 0;
1500 // Step 4.f.
1501 if (largestUnit == TemporalUnit::Week) {
1502 // Step 4.f.i
1503 weeks = days / 7;
1505 // Step 4.f.ii.
1506 days = days % 7;
1509 // Step 4.g.
1510 return CreateDateDurationRecord(0, 0, weeks, days);
1514 * DifferenceTemporalPlainDate ( operation, temporalDate, other, options )
1516 static bool DifferenceTemporalPlainDate(JSContext* cx,
1517 TemporalDifference operation,
1518 const CallArgs& args) {
1519 Rooted<PlainDateObject*> temporalDate(
1520 cx, &args.thisv().toObject().as<PlainDateObject>());
1521 Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
1523 // Step 1. (Not applicable in our implementation)
1525 // Step 2.
1526 auto wrappedOther = ::ToTemporalDate(cx, args.get(0));
1527 if (!wrappedOther) {
1528 return false;
1530 auto* unwrappedOther = &wrappedOther.unwrap();
1531 auto otherDate = ToPlainDate(unwrappedOther);
1533 Rooted<Wrapped<PlainDateObject*>> other(cx, wrappedOther);
1534 Rooted<CalendarValue> otherCalendar(cx, unwrappedOther->calendar());
1535 if (!otherCalendar.wrap(cx)) {
1536 return false;
1539 // Step 3.
1540 if (!CalendarEqualsOrThrow(cx, calendarValue, otherCalendar)) {
1541 return false;
1544 // Steps 4-5.
1545 DifferenceSettings settings;
1546 Rooted<PlainObject*> resolvedOptions(cx);
1547 if (args.hasDefined(1)) {
1548 Rooted<JSObject*> options(
1549 cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
1550 if (!options) {
1551 return false;
1554 // Step 4.
1555 resolvedOptions = SnapshotOwnProperties(cx, options);
1556 if (!resolvedOptions) {
1557 return false;
1560 // Step 5.
1561 if (!GetDifferenceSettings(cx, operation, resolvedOptions,
1562 TemporalUnitGroup::Date, TemporalUnit::Day,
1563 TemporalUnit::Day, &settings)) {
1564 return false;
1566 } else {
1567 // Steps 4-5.
1568 settings = {
1569 TemporalUnit::Day,
1570 TemporalUnit::Day,
1571 TemporalRoundingMode::Trunc,
1572 Increment{1},
1576 // Step 6.
1577 if (ToPlainDate(temporalDate) == otherDate) {
1578 auto* obj = CreateTemporalDuration(cx, {});
1579 if (!obj) {
1580 return false;
1583 args.rval().setObject(*obj);
1584 return true;
1587 // Step 7.
1588 Rooted<CalendarRecord> calendar(cx);
1589 if (!CreateCalendarMethodsRecord(cx, calendarValue,
1591 CalendarMethod::DateAdd,
1592 CalendarMethod::DateUntil,
1594 &calendar)) {
1595 return false;
1598 // Steps 8-9.
1599 DateDuration difference;
1600 if (resolvedOptions) {
1601 // Step 8.
1602 Rooted<Value> largestUnitValue(
1603 cx, StringValue(TemporalUnitToString(cx, settings.largestUnit)));
1604 if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit,
1605 largestUnitValue)) {
1606 return false;
1609 // Step 9.
1610 if (!DifferenceDate(cx, calendar, temporalDate, other, resolvedOptions,
1611 &difference)) {
1612 return false;
1614 } else {
1615 // Steps 8-9.
1616 if (!DifferenceDate(cx, calendar, temporalDate, other, settings.largestUnit,
1617 &difference)) {
1618 return false;
1622 // Step 10.
1623 bool roundingGranularityIsNoop = settings.smallestUnit == TemporalUnit::Day &&
1624 settings.roundingIncrement == Increment{1};
1626 // Step 11.
1627 if (!roundingGranularityIsNoop) {
1628 // Steps 11.a-b.
1629 NormalizedDuration roundResult;
1630 if (!temporal::RoundDuration(cx, {difference, {}},
1631 settings.roundingIncrement,
1632 settings.smallestUnit, settings.roundingMode,
1633 temporalDate, calendar, &roundResult)) {
1634 return false;
1637 // Step 11.c.
1638 DateDuration balanceResult;
1639 if (!temporal::BalanceDateDurationRelative(
1640 cx, roundResult.date, settings.largestUnit, settings.smallestUnit,
1641 temporalDate, calendar, &balanceResult)) {
1642 return false;
1644 difference = balanceResult;
1647 // Step 12.
1648 auto duration = difference.toDuration();
1649 if (operation == TemporalDifference::Since) {
1650 duration = duration.negate();
1652 MOZ_ASSERT(IsValidDuration(duration));
1654 auto* obj = CreateTemporalDuration(cx, duration);
1655 if (!obj) {
1656 return false;
1659 args.rval().setObject(*obj);
1660 return true;
1664 * Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendarLike ] )
1666 static bool PlainDateConstructor(JSContext* cx, unsigned argc, Value* vp) {
1667 CallArgs args = CallArgsFromVp(argc, vp);
1669 // Step 1.
1670 if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainDate")) {
1671 return false;
1674 // Step 2.
1675 double isoYear;
1676 if (!ToIntegerWithTruncation(cx, args.get(0), "year", &isoYear)) {
1677 return false;
1680 // Step 3.
1681 double isoMonth;
1682 if (!ToIntegerWithTruncation(cx, args.get(1), "month", &isoMonth)) {
1683 return false;
1686 // Step 4.
1687 double isoDay;
1688 if (!ToIntegerWithTruncation(cx, args.get(2), "day", &isoDay)) {
1689 return false;
1692 // Step 5.
1693 Rooted<CalendarValue> calendar(cx);
1694 if (!ToTemporalCalendarWithISODefault(cx, args.get(3), &calendar)) {
1695 return false;
1698 // Step 6.
1699 auto* temporalDate =
1700 CreateTemporalDate(cx, args, isoYear, isoMonth, isoDay, calendar);
1701 if (!temporalDate) {
1702 return false;
1705 args.rval().setObject(*temporalDate);
1706 return true;
1710 * Temporal.PlainDate.from ( item [ , options ] )
1712 static bool PlainDate_from(JSContext* cx, unsigned argc, Value* vp) {
1713 CallArgs args = CallArgsFromVp(argc, vp);
1715 // Step 1.
1716 Rooted<JSObject*> options(cx);
1717 if (args.hasDefined(1)) {
1718 options = RequireObjectArg(cx, "options", "from", args[1]);
1719 if (!options) {
1720 return false;
1724 // Step 2.
1725 if (args.get(0).isObject()) {
1726 JSObject* item = &args[0].toObject();
1727 if (auto* temporalDate = item->maybeUnwrapIf<PlainDateObject>()) {
1728 auto date = ToPlainDate(temporalDate);
1730 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1731 if (!calendar.wrap(cx)) {
1732 return false;
1735 if (options) {
1736 // Step 2.a.
1737 TemporalOverflow ignored;
1738 if (!ToTemporalOverflow(cx, options, &ignored)) {
1739 return false;
1743 // Step 2.b.
1744 auto* result = CreateTemporalDate(cx, date, calendar);
1745 if (!result) {
1746 return false;
1749 args.rval().setObject(*result);
1750 return true;
1754 // Step 3.
1755 auto result = ToTemporalDate(cx, args.get(0), options);
1756 if (!result) {
1757 return false;
1760 args.rval().setObject(*result);
1761 return true;
1765 * Temporal.PlainDate.compare ( one, two )
1767 static bool PlainDate_compare(JSContext* cx, unsigned argc, Value* vp) {
1768 CallArgs args = CallArgsFromVp(argc, vp);
1770 // Step 1.
1771 PlainDate one;
1772 if (!ToTemporalDate(cx, args.get(0), &one)) {
1773 return false;
1776 // Step 2.
1777 PlainDate two;
1778 if (!ToTemporalDate(cx, args.get(1), &two)) {
1779 return false;
1782 // Step 3.
1783 args.rval().setInt32(CompareISODate(one, two));
1784 return true;
1788 * get Temporal.PlainDate.prototype.calendarId
1790 static bool PlainDate_calendarId(JSContext* cx, const CallArgs& args) {
1791 auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
1792 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1794 // Step 3.
1795 auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar);
1796 if (!calendarId) {
1797 return false;
1800 args.rval().setString(calendarId);
1801 return true;
1805 * get Temporal.PlainDate.prototype.calendarId
1807 static bool PlainDate_calendarId(JSContext* cx, unsigned argc, Value* vp) {
1808 // Steps 1-2.
1809 CallArgs args = CallArgsFromVp(argc, vp);
1810 return CallNonGenericMethod<IsPlainDate, PlainDate_calendarId>(cx, args);
1814 * get Temporal.PlainDate.prototype.year
1816 static bool PlainDate_year(JSContext* cx, const CallArgs& args) {
1817 // Step 3.
1818 Rooted<PlainDateObject*> temporalDate(
1819 cx, &args.thisv().toObject().as<PlainDateObject>());
1820 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1822 // Step 4.
1823 return CalendarYear(cx, calendar, temporalDate, args.rval());
1827 * get Temporal.PlainDate.prototype.year
1829 static bool PlainDate_year(JSContext* cx, unsigned argc, Value* vp) {
1830 // Steps 1-2.
1831 CallArgs args = CallArgsFromVp(argc, vp);
1832 return CallNonGenericMethod<IsPlainDate, PlainDate_year>(cx, args);
1836 * get Temporal.PlainDate.prototype.month
1838 static bool PlainDate_month(JSContext* cx, const CallArgs& args) {
1839 // Step 3.
1840 Rooted<PlainDateObject*> temporalDate(
1841 cx, &args.thisv().toObject().as<PlainDateObject>());
1842 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1844 // Step 4.
1845 return CalendarMonth(cx, calendar, temporalDate, args.rval());
1849 * get Temporal.PlainDate.prototype.month
1851 static bool PlainDate_month(JSContext* cx, unsigned argc, Value* vp) {
1852 // Steps 1-2.
1853 CallArgs args = CallArgsFromVp(argc, vp);
1854 return CallNonGenericMethod<IsPlainDate, PlainDate_month>(cx, args);
1858 * get Temporal.PlainDate.prototype.monthCode
1860 static bool PlainDate_monthCode(JSContext* cx, const CallArgs& args) {
1861 // Step 3.
1862 Rooted<PlainDateObject*> temporalDate(
1863 cx, &args.thisv().toObject().as<PlainDateObject>());
1864 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1866 // Step 4.
1867 return CalendarMonthCode(cx, calendar, temporalDate, args.rval());
1871 * get Temporal.PlainDate.prototype.monthCode
1873 static bool PlainDate_monthCode(JSContext* cx, unsigned argc, Value* vp) {
1874 // Steps 1-2.
1875 CallArgs args = CallArgsFromVp(argc, vp);
1876 return CallNonGenericMethod<IsPlainDate, PlainDate_monthCode>(cx, args);
1880 * get Temporal.PlainDate.prototype.day
1882 static bool PlainDate_day(JSContext* cx, const CallArgs& args) {
1883 // Step 3.
1884 Rooted<PlainDateObject*> temporalDate(
1885 cx, &args.thisv().toObject().as<PlainDateObject>());
1886 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1888 // Step 4.
1889 return CalendarDay(cx, calendar, temporalDate, args.rval());
1893 * get Temporal.PlainDate.prototype.day
1895 static bool PlainDate_day(JSContext* cx, unsigned argc, Value* vp) {
1896 // Steps 1-2.
1897 CallArgs args = CallArgsFromVp(argc, vp);
1898 return CallNonGenericMethod<IsPlainDate, PlainDate_day>(cx, args);
1902 * get Temporal.PlainDate.prototype.dayOfWeek
1904 static bool PlainDate_dayOfWeek(JSContext* cx, const CallArgs& args) {
1905 // Step 3.
1906 Rooted<PlainDateObject*> temporalDate(
1907 cx, &args.thisv().toObject().as<PlainDateObject>());
1908 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1910 // Step 4.
1911 return CalendarDayOfWeek(cx, calendar, temporalDate, args.rval());
1915 * get Temporal.PlainDate.prototype.dayOfWeek
1917 static bool PlainDate_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
1918 // Steps 1-2.
1919 CallArgs args = CallArgsFromVp(argc, vp);
1920 return CallNonGenericMethod<IsPlainDate, PlainDate_dayOfWeek>(cx, args);
1924 * get Temporal.PlainDate.prototype.dayOfYear
1926 static bool PlainDate_dayOfYear(JSContext* cx, const CallArgs& args) {
1927 // Step 3.
1928 Rooted<PlainDateObject*> temporalDate(
1929 cx, &args.thisv().toObject().as<PlainDateObject>());
1930 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1932 // Step 4.
1933 return CalendarDayOfYear(cx, calendar, temporalDate, args.rval());
1937 * get Temporal.PlainDate.prototype.dayOfYear
1939 static bool PlainDate_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
1940 // Steps 1-2.
1941 CallArgs args = CallArgsFromVp(argc, vp);
1942 return CallNonGenericMethod<IsPlainDate, PlainDate_dayOfYear>(cx, args);
1946 * get Temporal.PlainDate.prototype.weekOfYear
1948 static bool PlainDate_weekOfYear(JSContext* cx, const CallArgs& args) {
1949 // Step 3.
1950 Rooted<PlainDateObject*> temporalDate(
1951 cx, &args.thisv().toObject().as<PlainDateObject>());
1952 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1954 // Step 4.
1955 return CalendarWeekOfYear(cx, calendar, temporalDate, args.rval());
1959 * get Temporal.PlainDate.prototype.weekOfYear
1961 static bool PlainDate_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
1962 // Steps 1-2.
1963 CallArgs args = CallArgsFromVp(argc, vp);
1964 return CallNonGenericMethod<IsPlainDate, PlainDate_weekOfYear>(cx, args);
1968 * get Temporal.PlainDate.prototype.yearOfWeek
1970 static bool PlainDate_yearOfWeek(JSContext* cx, const CallArgs& args) {
1971 // Step 3.
1972 Rooted<PlainDateObject*> temporalDate(
1973 cx, &args.thisv().toObject().as<PlainDateObject>());
1974 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1976 // Step 4.
1977 return CalendarYearOfWeek(cx, calendar, temporalDate, args.rval());
1981 * get Temporal.PlainDate.prototype.yearOfWeek
1983 static bool PlainDate_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
1984 // Steps 1-2.
1985 CallArgs args = CallArgsFromVp(argc, vp);
1986 return CallNonGenericMethod<IsPlainDate, PlainDate_yearOfWeek>(cx, args);
1990 * get Temporal.PlainDate.prototype.daysInWeek
1992 static bool PlainDate_daysInWeek(JSContext* cx, const CallArgs& args) {
1993 // Step 3.
1994 Rooted<PlainDateObject*> temporalDate(
1995 cx, &args.thisv().toObject().as<PlainDateObject>());
1996 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
1998 // Step 4.
1999 return CalendarDaysInWeek(cx, calendar, temporalDate, args.rval());
2003 * get Temporal.PlainDate.prototype.daysInWeek
2005 static bool PlainDate_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
2006 // Steps 1-2.
2007 CallArgs args = CallArgsFromVp(argc, vp);
2008 return CallNonGenericMethod<IsPlainDate, PlainDate_daysInWeek>(cx, args);
2012 * get Temporal.PlainDate.prototype.daysInMonth
2014 static bool PlainDate_daysInMonth(JSContext* cx, const CallArgs& args) {
2015 // Step 3.
2016 Rooted<PlainDateObject*> temporalDate(
2017 cx, &args.thisv().toObject().as<PlainDateObject>());
2018 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2020 // Step 4.
2021 return CalendarDaysInMonth(cx, calendar, temporalDate, args.rval());
2025 * get Temporal.PlainDate.prototype.daysInMonth
2027 static bool PlainDate_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
2028 // Steps 1-2.
2029 CallArgs args = CallArgsFromVp(argc, vp);
2030 return CallNonGenericMethod<IsPlainDate, PlainDate_daysInMonth>(cx, args);
2034 * get Temporal.PlainDate.prototype.daysInYear
2036 static bool PlainDate_daysInYear(JSContext* cx, const CallArgs& args) {
2037 // Step 3.
2038 Rooted<PlainDateObject*> temporalDate(
2039 cx, &args.thisv().toObject().as<PlainDateObject>());
2040 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2042 // Step 4.
2043 return CalendarDaysInYear(cx, calendar, temporalDate, args.rval());
2047 * get Temporal.PlainDate.prototype.daysInYear
2049 static bool PlainDate_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
2050 // Steps 1-2.
2051 CallArgs args = CallArgsFromVp(argc, vp);
2052 return CallNonGenericMethod<IsPlainDate, PlainDate_daysInYear>(cx, args);
2056 * get Temporal.PlainDate.prototype.monthsInYear
2058 static bool PlainDate_monthsInYear(JSContext* cx, const CallArgs& args) {
2059 // Step 3.
2060 Rooted<PlainDateObject*> temporalDate(
2061 cx, &args.thisv().toObject().as<PlainDateObject>());
2062 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2064 // Step 4.
2065 return CalendarMonthsInYear(cx, calendar, temporalDate, args.rval());
2069 * get Temporal.PlainDate.prototype.monthsInYear
2071 static bool PlainDate_monthsInYear(JSContext* cx, unsigned argc, Value* vp) {
2072 // Steps 1-2.
2073 CallArgs args = CallArgsFromVp(argc, vp);
2074 return CallNonGenericMethod<IsPlainDate, PlainDate_monthsInYear>(cx, args);
2078 * get Temporal.PlainDate.prototype.inLeapYear
2080 static bool PlainDate_inLeapYear(JSContext* cx, const CallArgs& args) {
2081 // Step 3.
2082 Rooted<PlainDateObject*> temporalDate(
2083 cx, &args.thisv().toObject().as<PlainDateObject>());
2084 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2086 // Step 4.
2087 return CalendarInLeapYear(cx, calendar, temporalDate, args.rval());
2091 * get Temporal.PlainDate.prototype.inLeapYear
2093 static bool PlainDate_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
2094 // Steps 1-2.
2095 CallArgs args = CallArgsFromVp(argc, vp);
2096 return CallNonGenericMethod<IsPlainDate, PlainDate_inLeapYear>(cx, args);
2100 * Temporal.PlainDate.prototype.toPlainYearMonth ( )
2102 static bool PlainDate_toPlainYearMonth(JSContext* cx, const CallArgs& args) {
2103 Rooted<PlainDateObject*> temporalDate(
2104 cx, &args.thisv().toObject().as<PlainDateObject>());
2105 Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
2107 // Step 3.
2108 Rooted<CalendarRecord> calendar(cx);
2109 if (!CreateCalendarMethodsRecord(cx, calendarValue,
2111 CalendarMethod::Fields,
2112 CalendarMethod::YearMonthFromFields,
2114 &calendar)) {
2115 return false;
2118 // Step 4.
2119 JS::RootedVector<PropertyKey> fieldNames(cx);
2120 if (!CalendarFields(cx, calendar,
2121 {CalendarField::MonthCode, CalendarField::Year},
2122 &fieldNames)) {
2123 return false;
2126 // Step 5.
2127 Rooted<PlainObject*> fields(
2128 cx, PrepareTemporalFields(cx, temporalDate, fieldNames));
2129 if (!fields) {
2130 return false;
2133 // Step 6.
2134 auto obj = CalendarYearMonthFromFields(cx, calendar, fields);
2135 if (!obj) {
2136 return false;
2139 args.rval().setObject(*obj);
2140 return true;
2144 * Temporal.PlainDate.prototype.toPlainYearMonth ( )
2146 static bool PlainDate_toPlainYearMonth(JSContext* cx, unsigned argc,
2147 Value* vp) {
2148 // Steps 1-2.
2149 CallArgs args = CallArgsFromVp(argc, vp);
2150 return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainYearMonth>(cx,
2151 args);
2155 * Temporal.PlainDate.prototype.toPlainMonthDay ( )
2157 static bool PlainDate_toPlainMonthDay(JSContext* cx, const CallArgs& args) {
2158 Rooted<PlainDateObject*> temporalDate(
2159 cx, &args.thisv().toObject().as<PlainDateObject>());
2160 Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
2162 // Example for the optimisation described in TemporalFields.cpp
2164 // Optimization for built-in objects.
2165 do {
2166 // Step 4.
2167 static constexpr std::initializer_list<CalendarField> fieldNames = {
2168 CalendarField::Day, CalendarField::MonthCode};
2170 // Step 5.
2171 if (calendarValue.isObject()) {
2172 Rooted<JSObject*> calendarObj(cx, calendarValue.toObject());
2173 if (!calendarObj->is<CalendarObject>()) {
2174 break;
2176 auto builtinCalendar = calendarObj.as<CalendarObject>();
2178 // Step 5.
2179 if (!IsBuiltinAccess(cx, builtinCalendar, fieldNames)) {
2180 break;
2183 if (!IsBuiltinAccess(cx, temporalDate, fieldNames)) {
2184 break;
2187 // Step 6.
2188 auto date = ToPlainDate(temporalDate);
2189 auto result = PlainDate{1972 /* referenceISOYear */, date.month, date.day};
2191 auto* obj = CreateTemporalMonthDay(cx, result, calendarValue);
2192 if (!obj) {
2193 return false;
2196 args.rval().setObject(*obj);
2197 return true;
2198 } while (false);
2200 // Step 3.
2201 Rooted<CalendarRecord> calendar(cx);
2202 if (!CreateCalendarMethodsRecord(cx, calendarValue,
2204 CalendarMethod::Fields,
2205 CalendarMethod::MonthDayFromFields,
2207 &calendar)) {
2208 return false;
2211 // Step 4.
2212 JS::RootedVector<PropertyKey> fieldNames(cx);
2213 if (!CalendarFields(cx, calendar,
2214 {CalendarField::Day, CalendarField::MonthCode},
2215 &fieldNames)) {
2216 return false;
2219 // Step 5.
2220 Rooted<PlainObject*> fields(
2221 cx, PrepareTemporalFields(cx, temporalDate, fieldNames));
2222 if (!fields) {
2223 return false;
2226 // Steps 6-7.
2227 auto obj = CalendarMonthDayFromFields(cx, calendar, fields);
2228 if (!obj) {
2229 return false;
2232 args.rval().setObject(*obj);
2233 return true;
2237 * Temporal.PlainDate.prototype.toPlainMonthDay ( )
2239 static bool PlainDate_toPlainMonthDay(JSContext* cx, unsigned argc, Value* vp) {
2240 // Steps 1-2.
2241 CallArgs args = CallArgsFromVp(argc, vp);
2242 return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainMonthDay>(cx, args);
2246 * Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )
2248 static bool PlainDate_toPlainDateTime(JSContext* cx, const CallArgs& args) {
2249 auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
2250 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2252 // Default initialize the time component to all zero.
2253 PlainDateTime dateTime = {ToPlainDate(temporalDate), {}};
2255 // Step 4. (Reordered)
2256 if (args.hasDefined(0)) {
2257 if (!ToTemporalTime(cx, args[0], &dateTime.time)) {
2258 return false;
2262 // Steps 3 and 5.
2263 auto* obj = CreateTemporalDateTime(cx, dateTime, calendar);
2264 if (!obj) {
2265 return false;
2268 args.rval().setObject(*obj);
2269 return true;
2273 * Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )
2275 static bool PlainDate_toPlainDateTime(JSContext* cx, unsigned argc, Value* vp) {
2276 // Steps 1-2.
2277 CallArgs args = CallArgsFromVp(argc, vp);
2278 return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainDateTime>(cx, args);
2282 * Temporal.PlainDate.prototype.getISOFields ( )
2284 static bool PlainDate_getISOFields(JSContext* cx, const CallArgs& args) {
2285 auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
2286 auto date = ToPlainDate(temporalDate);
2287 auto calendar = temporalDate->calendar();
2289 // Step 3.
2290 Rooted<IdValueVector> fields(cx, IdValueVector(cx));
2292 // Step 4.
2293 if (!fields.emplaceBack(NameToId(cx->names().calendar), calendar.toValue())) {
2294 return false;
2297 // Step 5.
2298 if (!fields.emplaceBack(NameToId(cx->names().isoDay), Int32Value(date.day))) {
2299 return false;
2302 // Step 6.
2303 if (!fields.emplaceBack(NameToId(cx->names().isoMonth),
2304 Int32Value(date.month))) {
2305 return false;
2308 // Step 7.
2309 if (!fields.emplaceBack(NameToId(cx->names().isoYear),
2310 Int32Value(date.year))) {
2311 return false;
2314 // Step 8.
2315 auto* obj = NewPlainObjectWithUniqueNames(cx, fields);
2316 if (!obj) {
2317 return false;
2320 args.rval().setObject(*obj);
2321 return true;
2325 * Temporal.PlainDate.prototype.getISOFields ( )
2327 static bool PlainDate_getISOFields(JSContext* cx, unsigned argc, Value* vp) {
2328 // Steps 1-2.
2329 CallArgs args = CallArgsFromVp(argc, vp);
2330 return CallNonGenericMethod<IsPlainDate, PlainDate_getISOFields>(cx, args);
2334 * Temporal.PlainDate.prototype.getCalendar ( )
2336 static bool PlainDate_getCalendar(JSContext* cx, const CallArgs& args) {
2337 auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
2338 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2340 // Step 3.
2341 auto* obj = ToTemporalCalendarObject(cx, calendar);
2342 if (!obj) {
2343 return false;
2346 args.rval().setObject(*obj);
2347 return true;
2351 * Temporal.PlainDate.prototype.getCalendar ( )
2353 static bool PlainDate_getCalendar(JSContext* cx, unsigned argc, Value* vp) {
2354 // Steps 1-2.
2355 CallArgs args = CallArgsFromVp(argc, vp);
2356 return CallNonGenericMethod<IsPlainDate, PlainDate_getCalendar>(cx, args);
2360 * Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] )
2362 static bool PlainDate_add(JSContext* cx, const CallArgs& args) {
2363 Rooted<PlainDateObject*> temporalDate(
2364 cx, &args.thisv().toObject().as<PlainDateObject>());
2365 Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
2367 // Step 3.
2368 Rooted<Wrapped<DurationObject*>> duration(
2369 cx, ToTemporalDuration(cx, args.get(0)));
2370 if (!duration) {
2371 return false;
2374 // Step 4.
2375 Rooted<JSObject*> options(cx);
2376 if (args.hasDefined(1)) {
2377 options = RequireObjectArg(cx, "options", "add", args[1]);
2378 } else {
2379 options = NewPlainObjectWithProto(cx, nullptr);
2381 if (!options) {
2382 return false;
2385 // Step 5.
2386 Rooted<CalendarRecord> calendar(cx);
2387 if (!CreateCalendarMethodsRecord(cx, calendarValue,
2389 CalendarMethod::DateAdd,
2391 &calendar)) {
2392 return false;
2395 // Step 6.
2396 auto result = AddDate(cx, calendar, temporalDate, duration, options);
2397 if (!result) {
2398 return false;
2401 args.rval().setObject(*result);
2402 return true;
2406 * Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] )
2408 static bool PlainDate_add(JSContext* cx, unsigned argc, Value* vp) {
2409 // Steps 1-2.
2410 CallArgs args = CallArgsFromVp(argc, vp);
2411 return CallNonGenericMethod<IsPlainDate, PlainDate_add>(cx, args);
2415 * Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] )
2417 static bool PlainDate_subtract(JSContext* cx, const CallArgs& args) {
2418 Rooted<PlainDateObject*> temporalDate(
2419 cx, &args.thisv().toObject().as<PlainDateObject>());
2420 Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
2422 // Step 3.
2423 Duration duration;
2424 if (!ToTemporalDuration(cx, args.get(0), &duration)) {
2425 return false;
2428 // Step 4.
2429 Rooted<JSObject*> options(cx);
2430 if (args.hasDefined(1)) {
2431 options = RequireObjectArg(cx, "options", "subtract", args[1]);
2432 } else {
2433 options = NewPlainObjectWithProto(cx, nullptr);
2435 if (!options) {
2436 return false;
2439 // Step 5.
2440 auto negatedDuration = duration.negate();
2442 // Step 6.
2443 Rooted<CalendarRecord> calendar(cx);
2444 if (!CreateCalendarMethodsRecord(cx, calendarValue,
2446 CalendarMethod::DateAdd,
2448 &calendar)) {
2449 return false;
2452 // Step 7.
2453 auto result =
2454 temporal::AddDate(cx, calendar, temporalDate, negatedDuration, options);
2455 if (!result) {
2456 return false;
2459 args.rval().setObject(*result);
2460 return true;
2464 * Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] )
2466 static bool PlainDate_subtract(JSContext* cx, unsigned argc, Value* vp) {
2467 // Steps 1-2.
2468 CallArgs args = CallArgsFromVp(argc, vp);
2469 return CallNonGenericMethod<IsPlainDate, PlainDate_subtract>(cx, args);
2473 * Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )
2475 static bool PlainDate_with(JSContext* cx, const CallArgs& args) {
2476 Rooted<PlainDateObject*> temporalDate(
2477 cx, &args.thisv().toObject().as<PlainDateObject>());
2479 // Step 3.
2480 Rooted<JSObject*> temporalDateLike(
2481 cx, RequireObjectArg(cx, "temporalDateLike", "with", args.get(0)));
2482 if (!temporalDateLike) {
2483 return false;
2486 // Step 4.
2487 if (!RejectTemporalLikeObject(cx, temporalDateLike)) {
2488 return false;
2491 // Step 5.
2492 Rooted<PlainObject*> resolvedOptions(cx);
2493 if (args.hasDefined(1)) {
2494 Rooted<JSObject*> options(cx,
2495 RequireObjectArg(cx, "options", "with", args[1]));
2496 if (!options) {
2497 return false;
2499 resolvedOptions = SnapshotOwnProperties(cx, options);
2500 } else {
2501 resolvedOptions = NewPlainObjectWithProto(cx, nullptr);
2503 if (!resolvedOptions) {
2504 return false;
2507 // Step 6.
2508 Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
2509 Rooted<CalendarRecord> calendar(cx);
2510 if (!CreateCalendarMethodsRecord(cx, calendarValue,
2512 CalendarMethod::DateFromFields,
2513 CalendarMethod::Fields,
2514 CalendarMethod::MergeFields,
2516 &calendar)) {
2517 return false;
2520 // Step 7.
2521 JS::RootedVector<PropertyKey> fieldNames(cx);
2522 if (!CalendarFields(cx, calendar,
2523 {CalendarField::Day, CalendarField::Month,
2524 CalendarField::MonthCode, CalendarField::Year},
2525 &fieldNames)) {
2526 return false;
2529 // Step 8.
2530 Rooted<PlainObject*> fields(
2531 cx, PrepareTemporalFields(cx, temporalDate, fieldNames));
2532 if (!fields) {
2533 return false;
2536 // Step 9.
2537 Rooted<PlainObject*> partialDate(
2538 cx, PreparePartialTemporalFields(cx, temporalDateLike, fieldNames));
2539 if (!partialDate) {
2540 return false;
2543 // Step 10.
2544 Rooted<JSObject*> mergedFields(
2545 cx, CalendarMergeFields(cx, calendar, fields, partialDate));
2546 if (!mergedFields) {
2547 return false;
2550 // Step 11.
2551 fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
2552 if (!fields) {
2553 return false;
2556 // Step 12.
2557 auto result =
2558 temporal::CalendarDateFromFields(cx, calendar, fields, resolvedOptions);
2559 if (!result) {
2560 return false;
2563 args.rval().setObject(*result);
2564 return true;
2568 * Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )
2570 static bool PlainDate_with(JSContext* cx, unsigned argc, Value* vp) {
2571 // Steps 1-2.
2572 CallArgs args = CallArgsFromVp(argc, vp);
2573 return CallNonGenericMethod<IsPlainDate, PlainDate_with>(cx, args);
2577 * Temporal.PlainDate.prototype.withCalendar ( calendar )
2579 static bool PlainDate_withCalendar(JSContext* cx, const CallArgs& args) {
2580 auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
2581 auto date = ToPlainDate(temporalDate);
2583 // Step 3.
2584 Rooted<CalendarValue> calendar(cx);
2585 if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
2586 return false;
2589 // Step 4.
2590 auto* result = CreateTemporalDate(cx, date, calendar);
2591 if (!result) {
2592 return false;
2595 args.rval().setObject(*result);
2596 return true;
2600 * Temporal.PlainDate.prototype.withCalendar ( calendar )
2602 static bool PlainDate_withCalendar(JSContext* cx, unsigned argc, Value* vp) {
2603 // Steps 1-2.
2604 CallArgs args = CallArgsFromVp(argc, vp);
2605 return CallNonGenericMethod<IsPlainDate, PlainDate_withCalendar>(cx, args);
2609 * Temporal.PlainDate.prototype.until ( other [ , options ] )
2611 static bool PlainDate_until(JSContext* cx, const CallArgs& args) {
2612 // Step 3.
2613 return DifferenceTemporalPlainDate(cx, TemporalDifference::Until, args);
2617 * Temporal.PlainDate.prototype.until ( other [ , options ] )
2619 static bool PlainDate_until(JSContext* cx, unsigned argc, Value* vp) {
2620 // Steps 1-2.
2621 CallArgs args = CallArgsFromVp(argc, vp);
2622 return CallNonGenericMethod<IsPlainDate, PlainDate_until>(cx, args);
2626 * Temporal.PlainDate.prototype.since ( other [ , options ] )
2628 static bool PlainDate_since(JSContext* cx, const CallArgs& args) {
2629 // Step 3.
2630 return DifferenceTemporalPlainDate(cx, TemporalDifference::Since, args);
2634 * Temporal.PlainDate.prototype.since ( other [ , options ] )
2636 static bool PlainDate_since(JSContext* cx, unsigned argc, Value* vp) {
2637 // Steps 1-2.
2638 CallArgs args = CallArgsFromVp(argc, vp);
2639 return CallNonGenericMethod<IsPlainDate, PlainDate_since>(cx, args);
2643 * Temporal.PlainDate.prototype.equals ( other )
2645 static bool PlainDate_equals(JSContext* cx, const CallArgs& args) {
2646 auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
2647 auto date = ToPlainDate(temporalDate);
2648 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2650 // Step 3.
2651 Rooted<PlainDateWithCalendar> other(cx);
2652 if (!ToTemporalDate(cx, args.get(0), &other)) {
2653 return false;
2656 // Steps 4-7.
2657 bool equals = date == other.date();
2658 if (equals && !CalendarEquals(cx, calendar, other.calendar(), &equals)) {
2659 return false;
2662 args.rval().setBoolean(equals);
2663 return true;
2667 * Temporal.PlainDate.prototype.equals ( other )
2669 static bool PlainDate_equals(JSContext* cx, unsigned argc, Value* vp) {
2670 // Steps 1-2.
2671 CallArgs args = CallArgsFromVp(argc, vp);
2672 return CallNonGenericMethod<IsPlainDate, PlainDate_equals>(cx, args);
2676 * Temporal.PlainDate.prototype.toZonedDateTime ( item )
2678 * The |item| argument represents either a time zone or an options object. The
2679 * following cases are supported:
2680 * - |item| is a `Temporal.TimeZone` object.
2681 * - |item| is a user-defined time zone object.
2682 * - |item| is an options object with `timeZone` and `plainTime` properties.
2683 * - |item| is a time zone identifier string.
2685 * User-defined time zone objects are distinguished from options objects by the
2686 * `timeZone` property, i.e. if a `timeZone` property is present, the object is
2687 * treated as an options object, otherwise an object is treated as a
2688 * user-defined time zone.
2690 static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
2691 auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
2692 auto date = ToPlainDate(temporalDate);
2693 Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
2695 // Steps 3-4
2696 Rooted<TimeZoneValue> timeZone(cx);
2697 Rooted<Value> temporalTime(cx);
2698 if (args.get(0).isObject()) {
2699 Rooted<JSObject*> item(cx, &args[0].toObject());
2701 // Steps 3.a-b.
2702 if (item->canUnwrapAs<TimeZoneObject>()) {
2703 // Step 3.a.i.
2704 timeZone.set(TimeZoneValue(item));
2706 // Step 3.a.ii.
2707 temporalTime.setUndefined();
2708 } else {
2709 // Step 3.b.i.
2710 Rooted<Value> timeZoneLike(cx);
2711 if (!GetProperty(cx, item, item, cx->names().timeZone, &timeZoneLike)) {
2712 return false;
2715 // Steps 3.b.ii-iii.
2716 if (timeZoneLike.isUndefined()) {
2717 // Step 3.b.ii.1.
2718 if (!ToTemporalTimeZone(cx, args[0], &timeZone)) {
2719 return false;
2722 // Step 3.b.ii.2.
2723 temporalTime.setUndefined();
2724 } else {
2725 // Step 3.b.iii.1.
2726 if (!ToTemporalTimeZone(cx, timeZoneLike, &timeZone)) {
2727 return false;
2730 // Step 3.b.iii.2.
2731 if (!GetProperty(cx, item, item, cx->names().plainTime,
2732 &temporalTime)) {
2733 return false;
2737 } else {
2738 // Step 4.a.
2739 if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
2740 return false;
2743 // Step 4.b.
2744 temporalTime.setUndefined();
2747 // Step 6.a.
2748 PlainTime time = {};
2749 if (!temporalTime.isUndefined()) {
2750 if (!ToTemporalTime(cx, temporalTime, &time)) {
2751 return false;
2755 // Steps 5.a and 6.b
2756 Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
2757 if (!CreateTemporalDateTime(cx, {date, time}, calendar, &temporalDateTime)) {
2758 return false;
2761 // Steps 7-8.
2762 Instant instant;
2763 if (!GetInstantFor(cx, timeZone, temporalDateTime,
2764 TemporalDisambiguation::Compatible, &instant)) {
2765 return false;
2768 // Step 9.
2769 auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
2770 if (!result) {
2771 return false;
2774 args.rval().setObject(*result);
2775 return true;
2779 * Temporal.PlainDate.prototype.toZonedDateTime ( item )
2781 static bool PlainDate_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) {
2782 // Steps 1-2.
2783 CallArgs args = CallArgsFromVp(argc, vp);
2784 return CallNonGenericMethod<IsPlainDate, PlainDate_toZonedDateTime>(cx, args);
2788 * Temporal.PlainDate.prototype.toString ( [ options ] )
2790 static bool PlainDate_toString(JSContext* cx, const CallArgs& args) {
2791 Rooted<PlainDateObject*> temporalDate(
2792 cx, &args.thisv().toObject().as<PlainDateObject>());
2794 auto showCalendar = CalendarOption::Auto;
2795 if (args.hasDefined(0)) {
2796 // Step 3.
2797 Rooted<JSObject*> options(
2798 cx, RequireObjectArg(cx, "options", "toString", args[0]));
2799 if (!options) {
2800 return false;
2803 // Step 4.
2804 if (!ToCalendarNameOption(cx, options, &showCalendar)) {
2805 return false;
2809 // Step 5.
2810 JSString* str = TemporalDateToString(cx, temporalDate, showCalendar);
2811 if (!str) {
2812 return false;
2815 args.rval().setString(str);
2816 return true;
2820 * Temporal.PlainDate.prototype.toString ( [ options ] )
2822 static bool PlainDate_toString(JSContext* cx, unsigned argc, Value* vp) {
2823 // Steps 1-2.
2824 CallArgs args = CallArgsFromVp(argc, vp);
2825 return CallNonGenericMethod<IsPlainDate, PlainDate_toString>(cx, args);
2829 * Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )
2831 static bool PlainDate_toLocaleString(JSContext* cx, const CallArgs& args) {
2832 Rooted<PlainDateObject*> temporalDate(
2833 cx, &args.thisv().toObject().as<PlainDateObject>());
2835 // Step 3.
2836 JSString* str = TemporalDateToString(cx, temporalDate, CalendarOption::Auto);
2837 if (!str) {
2838 return false;
2841 args.rval().setString(str);
2842 return true;
2846 * Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )
2848 static bool PlainDate_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
2849 // Steps 1-2.
2850 CallArgs args = CallArgsFromVp(argc, vp);
2851 return CallNonGenericMethod<IsPlainDate, PlainDate_toLocaleString>(cx, args);
2855 * Temporal.PlainDate.prototype.toJSON ( )
2857 static bool PlainDate_toJSON(JSContext* cx, const CallArgs& args) {
2858 Rooted<PlainDateObject*> temporalDate(
2859 cx, &args.thisv().toObject().as<PlainDateObject>());
2861 // Step 3.
2862 JSString* str = TemporalDateToString(cx, temporalDate, CalendarOption::Auto);
2863 if (!str) {
2864 return false;
2867 args.rval().setString(str);
2868 return true;
2872 * Temporal.PlainDate.prototype.toJSON ( )
2874 static bool PlainDate_toJSON(JSContext* cx, unsigned argc, Value* vp) {
2875 // Steps 1-2.
2876 CallArgs args = CallArgsFromVp(argc, vp);
2877 return CallNonGenericMethod<IsPlainDate, PlainDate_toJSON>(cx, args);
2881 * Temporal.PlainDate.prototype.valueOf ( )
2883 static bool PlainDate_valueOf(JSContext* cx, unsigned argc, Value* vp) {
2884 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
2885 "PlainDate", "primitive type");
2886 return false;
2889 const JSClass PlainDateObject::class_ = {
2890 "Temporal.PlainDate",
2891 JSCLASS_HAS_RESERVED_SLOTS(PlainDateObject::SLOT_COUNT) |
2892 JSCLASS_HAS_CACHED_PROTO(JSProto_PlainDate),
2893 JS_NULL_CLASS_OPS,
2894 &PlainDateObject::classSpec_,
2897 const JSClass& PlainDateObject::protoClass_ = PlainObject::class_;
2899 static const JSFunctionSpec PlainDate_methods[] = {
2900 JS_FN("from", PlainDate_from, 1, 0),
2901 JS_FN("compare", PlainDate_compare, 2, 0),
2902 JS_FS_END,
2905 static const JSFunctionSpec PlainDate_prototype_methods[] = {
2906 JS_FN("toPlainMonthDay", PlainDate_toPlainMonthDay, 0, 0),
2907 JS_FN("toPlainYearMonth", PlainDate_toPlainYearMonth, 0, 0),
2908 JS_FN("toPlainDateTime", PlainDate_toPlainDateTime, 0, 0),
2909 JS_FN("getISOFields", PlainDate_getISOFields, 0, 0),
2910 JS_FN("getCalendar", PlainDate_getCalendar, 0, 0),
2911 JS_FN("add", PlainDate_add, 1, 0),
2912 JS_FN("subtract", PlainDate_subtract, 1, 0),
2913 JS_FN("with", PlainDate_with, 1, 0),
2914 JS_FN("withCalendar", PlainDate_withCalendar, 1, 0),
2915 JS_FN("until", PlainDate_until, 1, 0),
2916 JS_FN("since", PlainDate_since, 1, 0),
2917 JS_FN("equals", PlainDate_equals, 1, 0),
2918 JS_FN("toZonedDateTime", PlainDate_toZonedDateTime, 1, 0),
2919 JS_FN("toString", PlainDate_toString, 0, 0),
2920 JS_FN("toLocaleString", PlainDate_toLocaleString, 0, 0),
2921 JS_FN("toJSON", PlainDate_toJSON, 0, 0),
2922 JS_FN("valueOf", PlainDate_valueOf, 0, 0),
2923 JS_FS_END,
2926 static const JSPropertySpec PlainDate_prototype_properties[] = {
2927 JS_PSG("calendarId", PlainDate_calendarId, 0),
2928 JS_PSG("year", PlainDate_year, 0),
2929 JS_PSG("month", PlainDate_month, 0),
2930 JS_PSG("monthCode", PlainDate_monthCode, 0),
2931 JS_PSG("day", PlainDate_day, 0),
2932 JS_PSG("dayOfWeek", PlainDate_dayOfWeek, 0),
2933 JS_PSG("dayOfYear", PlainDate_dayOfYear, 0),
2934 JS_PSG("weekOfYear", PlainDate_weekOfYear, 0),
2935 JS_PSG("yearOfWeek", PlainDate_yearOfWeek, 0),
2936 JS_PSG("daysInWeek", PlainDate_daysInWeek, 0),
2937 JS_PSG("daysInMonth", PlainDate_daysInMonth, 0),
2938 JS_PSG("daysInYear", PlainDate_daysInYear, 0),
2939 JS_PSG("monthsInYear", PlainDate_monthsInYear, 0),
2940 JS_PSG("inLeapYear", PlainDate_inLeapYear, 0),
2941 JS_STRING_SYM_PS(toStringTag, "Temporal.PlainDate", JSPROP_READONLY),
2942 JS_PS_END,
2945 const ClassSpec PlainDateObject::classSpec_ = {
2946 GenericCreateConstructor<PlainDateConstructor, 3, gc::AllocKind::FUNCTION>,
2947 GenericCreatePrototype<PlainDateObject>,
2948 PlainDate_methods,
2949 nullptr,
2950 PlainDate_prototype_methods,
2951 PlainDate_prototype_properties,
2952 nullptr,
2953 ClassSpec::DontDefineConstructor,
2956 struct PlainDateNameAndNative final {
2957 PropertyName* name;
2958 JSNative native;
2961 static PlainDateNameAndNative GetPlainDateNameAndNative(
2962 JSContext* cx, CalendarField fieldName) {
2963 switch (fieldName) {
2964 case CalendarField::Year:
2965 return {cx->names().year, PlainDate_year};
2966 case CalendarField::Month:
2967 return {cx->names().month, PlainDate_month};
2968 case CalendarField::MonthCode:
2969 return {cx->names().monthCode, PlainDate_monthCode};
2970 case CalendarField::Day:
2971 return {cx->names().day, PlainDate_day};
2973 MOZ_CRASH("invalid temporal field name");
2976 bool js::temporal::IsBuiltinAccess(
2977 JSContext* cx, Handle<PlainDateObject*> date,
2978 std::initializer_list<CalendarField> fieldNames) {
2979 // Don't optimize when the object has any own properties which may shadow the
2980 // built-in methods.
2981 if (date->shape()->propMapLength() > 0) {
2982 return false;
2985 JSObject* proto = cx->global()->maybeGetPrototype(JSProto_PlainDate);
2987 // Don't attempt to optimize when the class isn't yet initialized.
2988 if (!proto) {
2989 return false;
2992 // Don't optimize when the prototype isn't the built-in prototype.
2993 if (date->staticPrototype() != proto) {
2994 return false;
2997 auto* nproto = &proto->as<NativeObject>();
2998 for (auto fieldName : fieldNames) {
2999 auto [name, native] = GetPlainDateNameAndNative(cx, fieldName);
3000 auto prop = nproto->lookupPure(name);
3002 // Return if the property isn't a data property.
3003 if (!prop || !prop->isDataProperty()) {
3004 return false;
3007 // Return if the property isn't the initial method.
3008 if (!IsNativeFunction(nproto->getSlot(prop->slot()), native)) {
3009 return false;
3013 // Success! The access can be optimized.
3014 return true;