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"
16 #include <initializer_list>
18 #include <type_traits>
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"
50 #include "js/ErrorReport.h"
51 #include "js/friend/ErrorMessages.h"
52 #include "js/GCVector.h"
54 #include "js/PropertyDescriptor.h"
55 #include "js/PropertySpec.h"
56 #include "js/RootingAPI.h"
57 #include "js/TypeDecls.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"
68 #include "vm/StringType.h"
70 #include "vm/JSObject-inl.h"
71 #include "vm/NativeObject-inl.h"
72 #include "vm/ObjectOperations-inl.h"
75 using namespace js::temporal
;
77 static inline bool IsPlainDate(Handle
<Value
> v
) {
78 return v
.isObject() && v
.toObject().is
<PlainDateObject
>();
83 * IsValidISODate ( year, month, day )
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>);
90 MOZ_ASSERT(IsInteger(year
));
91 MOZ_ASSERT(IsInteger(month
));
92 MOZ_ASSERT(IsInteger(day
));
95 if (month
< 1 || month
> 12) {
100 int32_t daysInMonth
= js::temporal::ISODaysInMonth(year
, int32_t(month
));
103 if (day
< 1 || day
> daysInMonth
) {
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
);
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
) {
149 ReportInvalidDateValue(cx
, name
, min
, max
, num
);
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>);
161 MOZ_ASSERT(IsInteger(year
));
162 MOZ_ASSERT(IsInteger(month
));
163 MOZ_ASSERT(IsInteger(day
));
166 if (!ThrowIfInvalidDateValue(cx
, "month", 1, 12, month
)) {
171 int32_t daysInMonth
= js::temporal::ISODaysInMonth(year
, int32_t(month
));
174 if (!ThrowIfInvalidDateValue(cx
, "day", 1, daysInMonth
, day
)) {
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
;
207 int32_t m
= std::clamp(month
, 1, 12);
210 int32_t daysInMonth
= temporal::ISODaysInMonth(year
, m
);
213 int32_t d
= std::clamp(day
, 1, daysInMonth
);
220 * RegulateISODate ( year, month, day, overflow )
222 bool js::temporal::RegulateISODate(JSContext
* cx
, const PlainDate
& date
,
223 TemporalOverflow overflow
,
226 if (overflow
== TemporalOverflow::Constrain
) {
227 *result
= ::ConstrainISODate(date
);
232 MOZ_ASSERT(overflow
== TemporalOverflow::Reject
);
235 if (!ThrowIfInvalidISODate(cx
, date
)) {
239 // Step 2.b. (Inlined call to CreateISODateRecord.)
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
));
255 if (overflow
== TemporalOverflow::Constrain
) {
257 int32_t m
= int32_t(std::clamp(month
, 1.0, 12.0));
260 double daysInMonth
= double(ISODaysInMonth(year
, m
));
263 int32_t d
= int32_t(std::clamp(day
, 1.0, daysInMonth
));
266 *result
= {year
, m
, d
};
271 MOZ_ASSERT(overflow
== TemporalOverflow::Reject
);
274 if (!ThrowIfInvalidISODate(cx
, year
, month
, day
)) {
278 // Step 2.b. (Inlined call to CreateISODateRecord.)
279 *result
= {year
, int32_t(month
), int32_t(day
)};
284 * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
286 static PlainDateObject
* CreateTemporalDate(JSContext
* cx
, const CallArgs
& args
,
287 double isoYear
, double isoMonth
,
289 Handle
<CalendarValue
> calendar
) {
290 MOZ_ASSERT(IsInteger(isoYear
));
291 MOZ_ASSERT(IsInteger(isoMonth
));
292 MOZ_ASSERT(IsInteger(isoDay
));
295 if (!ThrowIfInvalidISODate(cx
, isoYear
, isoMonth
, isoDay
)) {
300 if (!ISODateTimeWithinLimits(isoYear
, isoMonth
, isoDay
)) {
301 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
302 JSMSG_TEMPORAL_PLAIN_DATE_INVALID
);
307 Rooted
<JSObject
*> proto(cx
);
308 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_PlainDate
,
313 auto* object
= NewObjectWithClassProto
<PlainDateObject
>(cx
, proto
);
319 object
->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT
, Int32Value(isoYear
));
322 object
->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT
, Int32Value(isoMonth
));
325 object
->setFixedSlot(PlainDateObject::ISO_DAY_SLOT
, Int32Value(isoDay
));
328 object
->setFixedSlot(PlainDateObject::CALENDAR_SLOT
, calendar
.toValue());
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
;
342 if (!ThrowIfInvalidISODate(cx
, date
)) {
347 if (!ISODateTimeWithinLimits(date
)) {
348 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
349 JSMSG_TEMPORAL_PLAIN_DATE_INVALID
);
354 auto* object
= NewBuiltinClassInstance
<PlainDateObject
>(cx
);
360 object
->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT
, Int32Value(isoYear
));
363 object
->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT
, Int32Value(isoMonth
));
366 object
->setFixedSlot(PlainDateObject::ISO_DAY_SLOT
, Int32Value(isoDay
));
369 object
->setFixedSlot(PlainDateObject::CALENDAR_SLOT
, calendar
.toValue());
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
) {
382 if (!ThrowIfInvalidISODate(cx
, date
)) {
387 if (!ISODateTimeWithinLimits(date
)) {
388 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
389 JSMSG_TEMPORAL_PLAIN_DATE_INVALID
);
394 result
.set(PlainDateWithCalendar
{date
, calendar
});
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.)
406 if (item
->canUnwrapAs
<PlainDateObject
>()) {
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
)) {
419 if (!calendar
.wrap(cx
)) {
425 TemporalOverflow ignored
;
426 if (!ToTemporalOverflow(cx
, maybeOptions
, &ignored
)) {
432 PlainDateTime dateTime
;
433 if (!GetPlainDateTimeFor(cx
, timeZone
, epochInstant
, &dateTime
)) {
438 return CreateTemporalDate(cx
, dateTime
.date
, calendar
);
442 if (auto* dateTime
= item
->maybeUnwrapIf
<PlainDateTimeObject
>()) {
443 auto date
= ToPlainDate(dateTime
);
444 Rooted
<CalendarValue
> calendar(cx
, dateTime
->calendar());
445 if (!calendar
.wrap(cx
)) {
451 TemporalOverflow ignored
;
452 if (!ToTemporalOverflow(cx
, maybeOptions
, &ignored
)) {
458 return CreateTemporalDate(cx
, date
, calendar
);
462 Rooted
<CalendarValue
> calendarValue(cx
);
463 if (!GetTemporalCalendarWithISODefault(cx
, item
, &calendarValue
)) {
468 Rooted
<CalendarRecord
> calendar(cx
);
469 if (!CreateCalendarMethodsRecord(cx
, calendarValue
,
471 CalendarMethod::DateFromFields
,
472 CalendarMethod::Fields
,
479 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
480 if (!CalendarFields(cx
, calendar
,
481 {CalendarField::Day
, CalendarField::Month
,
482 CalendarField::MonthCode
, CalendarField::Year
},
488 Rooted
<PlainObject
*> fields(cx
, PrepareTemporalFields(cx
, item
, fieldNames
));
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.)
508 Rooted
<PlainObject
*> maybeResolvedOptions(cx
);
510 maybeResolvedOptions
= SnapshotOwnProperties(cx
, maybeOptions
);
511 if (!maybeResolvedOptions
) {
517 if (item
.isObject()) {
518 Rooted
<JSObject
*> itemObj(cx
, &item
.toObject());
519 return ::ToTemporalDate(cx
, itemObj
, maybeResolvedOptions
);
523 if (!item
.isString()) {
524 ReportValueError(cx
, JSMSG_UNEXPECTED_TYPE
, JSDVG_IGNORE_STACK
, item
,
525 nullptr, "not a string");
528 Rooted
<JSString
*> string(cx
, item
.toString());
532 Rooted
<JSString
*> calendarString(cx
);
533 if (!ParseTemporalDateString(cx
, string
, &result
, &calendarString
)) {
538 MOZ_ASSERT(IsValidISODate(result
));
541 Rooted
<CalendarValue
> calendar(cx
, CalendarValue(cx
->names().iso8601
));
542 if (calendarString
) {
543 if (!ToBuiltinCalendar(cx
, calendarString
, &calendar
)) {
549 if (maybeResolvedOptions
) {
550 TemporalOverflow ignored
;
551 if (!ToTemporalOverflow(cx
, maybeResolvedOptions
, &ignored
)) {
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
,
573 auto obj
= ::ToTemporalDate(cx
, item
, nullptr);
578 *result
= ToPlainDate(&obj
.unwrap());
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();
592 auto date
= ToPlainDate(obj
);
593 Rooted
<CalendarValue
> calendar(cx
, obj
->calendar());
594 if (!calendar
.wrap(cx
)) {
598 result
.set(PlainDateWithCalendar
{date
, calendar
});
603 * Mathematical Operations, "modulo" notation.
605 static int32_t NonNegativeModulo(double x
, int32_t y
) {
606 MOZ_ASSERT(IsInteger(x
));
609 double r
= std::fmod(x
, y
);
612 MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt32(r
, &result
));
614 return (result
< 0) ? (result
+ y
) : result
;
617 struct BalancedYearMonth final
{
623 * BalanceISOYearMonth ( year, month )
625 static BalancedYearMonth
BalanceISOYearMonth(double year
, double month
) {
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.
636 year
= year
+ std::floor((month
- 1) / 12);
637 MOZ_ASSERT(IsInteger(year
) || std::isinf(year
));
640 int32_t mon
= NonNegativeModulo(month
- 1, 12) + 1;
641 MOZ_ASSERT(1 <= mon
&& mon
<= 12);
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
,
684 MOZ_ASSERT(1 <= month
&& month
<= 12);
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.
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
);
711 *result
= BalanceISODate(year
, month
, int32_t(day
));
716 * BalanceISODate ( year, month, day )
718 PlainDate
js::temporal::BalanceISODate(int32_t year
, int32_t month
,
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
) {
743 int32_t testYear
= month
> 2 ? year
: year
- 1;
746 while (day
< -ISODaysInYear(testYear
)) {
748 day
+= ISODaysInYear(testYear
);
763 while (day
> ISODaysInYear(testYear
)) {
765 day
-= ISODaysInYear(testYear
);
779 // Steps 13.a-b. (Inlined call to BalanceISOYearMonth.)
786 day
+= ISODaysInMonth(year
, month
);
792 while (day
> ISODaysInMonth(year
, month
)) {
794 day
-= ISODaysInMonth(year
, month
);
796 // Steps 15.b-d. (Inlined call to BalanceISOYearMonth.)
803 MOZ_ASSERT(1 <= month
&& month
<= 12);
804 MOZ_ASSERT(1 <= day
&& day
<= 31);
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
823 MOZ_ASSERT(IsValidDuration(duration
));
825 // Steps 1-2. (Not applicable in our implementation.)
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:
842 // js> Temporal.PlainDate.from("2021-05-31").since("2021-04-30", {largestUnit:"months"}).toString()
844 // js> Temporal.PlainDate.from("2021-05-31").subtract("P1M1D").toString()
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
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
);
869 if (!RegulateISODate(cx
, {int32_t(yearMonth
.year
), yearMonth
.month
, date
.day
},
870 overflow
, ®ulated
)) {
874 // NB: BalanceISODate will reject too large days, so we don't have to worry
875 // about imprecise number arithmetic here.
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
);
889 auto balanced
= BalanceISODate(regulated
.year
, regulated
.month
, int32_t(d
));
890 MOZ_ASSERT(IsValidISODate(balanced
));
896 struct YearMonthDuration
{
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);
933 } else if (month
<= 0) {
938 MOZ_ASSERT(1 <= month
&& month
<= 12);
939 MOZ_ASSERT(CanBalanceISOYear(year
));
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
;
968 int64_t balancedDays
=
969 BalanceTimeDuration(timeDuration
, TemporalUnit::Day
).days
;
970 int64_t days
= duration
.date
.days
+ balancedDays
;
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
) {
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
) {
995 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
997 // Step 2. (Not applicable in our implementation.)
1000 if (HasYearsMonthsOrWeeks(duration
)) {
1001 return temporal::CalendarDateAdd(cx
, calendar
, date
, duration
, options
);
1005 auto overflow
= TemporalOverflow::Constrain
;
1006 if (!ToTemporalOverflow(cx
, options
, &overflow
)) {
1011 auto normalized
= CreateNormalizedDurationRecord(duration
);
1014 PlainDate resultDate
;
1015 if (!::AddDate(cx
, date
, normalized
, overflow
, &resultDate
)) {
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
) {
1031 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1033 // Step 2. (Not applicable in our implementation.)
1036 if (HasYearsMonthsOrWeeks(duration
)) {
1037 return CalendarDateAdd(cx
, calendar
, date
, duration
);
1041 auto overflow
= TemporalOverflow::Constrain
;
1044 auto normalized
= NormalizedDuration
{duration
};
1047 PlainDate resultDate
;
1048 if (!::AddDate(cx
, date
, normalized
, overflow
, &resultDate
)) {
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
) {
1067 auto duration
= ToDuration(unwrappedDuration
);
1071 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1073 // Step 2. (Not applicable in our implementation.)
1076 if (HasYearsMonthsOrWeeks(duration
)) {
1077 return temporal::CalendarDateAdd(cx
, calendar
, date
, durationObj
, options
);
1081 auto overflow
= TemporalOverflow::Constrain
;
1082 if (!ToTemporalOverflow(cx
, options
, &overflow
)) {
1087 auto normalized
= CreateNormalizedDurationRecord(duration
);
1090 PlainDate resultDate
;
1091 if (!::AddDate(cx
, date
, normalized
, overflow
, &resultDate
)) {
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
) {
1107 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1109 // Step 2. (Not applicable in our implementation.)
1112 if (HasYearsMonthsOrWeeks(duration
)) {
1113 return temporal::CalendarDateAdd(cx
, calendar
, date
, duration
, options
,
1118 auto overflow
= TemporalOverflow::Constrain
;
1119 if (!ToTemporalOverflow(cx
, options
, &overflow
)) {
1124 auto normalized
= NormalizedDuration
{duration
};
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
) {
1138 CalendarMethodsRecordHasLookedUp(calendar
, CalendarMethod::DateAdd
));
1140 // Step 2. (Not applicable in our implementation.)
1143 if (HasYearsMonthsOrWeeks(duration
)) {
1144 return CalendarDateAdd(cx
, calendar
, date
, duration
, result
);
1148 auto overflow
= TemporalOverflow::Constrain
;
1151 auto normalized
= NormalizedDuration
{duration
};
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
) {
1170 auto oneDate
= ToPlainDate(unwrappedOne
);
1172 auto* unwrappedTwo
= two
.unwrap(cx
);
1173 if (!unwrappedTwo
) {
1176 auto twoDate
= ToPlainDate(unwrappedTwo
);
1178 // Steps 1-2. (Not applicable in our implementation.)
1181 MOZ_ASSERT(options
->staticPrototype() == nullptr);
1184 MOZ_ASSERT(options
->containsPure(cx
->names().largestUnit
));
1187 if (oneDate
== twoDate
) {
1193 Rooted
<JS::Value
> largestUnit(cx
);
1194 if (!GetProperty(cx
, options
, options
, cx
->names().largestUnit
,
1199 if (largestUnit
.isString()) {
1201 if (!EqualStrings(cx
, largestUnit
.toString(), cx
->names().day
, &isDay
)) {
1207 int32_t days
= DaysUntil(oneDate
, twoDate
);
1210 *result
= {0, 0, 0, days
};
1217 if (!CalendarDateUntil(cx
, calendar
, one
, two
, options
, &duration
)) {
1220 *result
= duration
.toDateDuration();
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
) {
1237 auto oneDate
= ToPlainDate(unwrappedOne
);
1239 auto* unwrappedTwo
= two
.unwrap(cx
);
1240 if (!unwrappedTwo
) {
1243 auto twoDate
= ToPlainDate(unwrappedTwo
);
1245 // Steps 1-4. (Not applicable in our implementation.)
1248 if (oneDate
== twoDate
) {
1254 if (largestUnit
== TemporalUnit::Day
) {
1256 int32_t days
= DaysUntil(oneDate
, twoDate
);
1259 *result
= {0, 0, 0, days
};
1265 if (!CalendarDateUntil(cx
, calendar
, one
, two
, largestUnit
, &duration
)) {
1268 *result
= duration
.toDateDuration();
1273 * CompareISODate ( y1, m1, d1, y2, m2, d2 )
1275 int32_t js::temporal::CompareISODate(const PlainDate
& one
,
1276 const PlainDate
& two
) {
1278 if (one
.year
!= two
.year
) {
1279 return one
.year
< two
.year
? -1 : 1;
1283 if (one
.month
!= two
.month
) {
1284 return one
.month
< two
.month
? -1 : 1;
1288 if (one
.day
!= two
.day
) {
1289 return one
.day
< two
.day
? -1 : 1;
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
) {
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
);
1327 if (largestUnit
== TemporalUnit::Year
|| largestUnit
== TemporalUnit::Month
) {
1329 int32_t sign
= -CompareISODate(start
, end
);
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
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()
1344 // js> var start = new Temporal.PlainDate(1970, 1, 29)
1345 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1347 // js> var start = new Temporal.PlainDate(1970, 1, 30)
1348 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1350 // js> var start = new Temporal.PlainDate(1970, 1, 31)
1351 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
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)
1363 // jshell> var start = LocalDate.of(1970, 1, 29)
1364 // start ==> 1970-01-29
1365 // jshell> start.until(end)
1367 // jshell> var start = LocalDate.of(1970, 1, 30)
1368 // start ==> 1970-01-30
1369 // jshell> start.until(end)
1371 // jshell> var start = LocalDate.of(1970, 1, 31)
1372 // start ==> 1970-01-31
1373 // jshell> start.until(end)
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()
1382 // js> var start = new Temporal.PlainDate(1970, 1, 28)
1383 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1385 // js> var start = new Temporal.PlainDate(1970, 1, 29)
1386 // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
1391 // Steps 3.c-d. (Not applicable in our implementation.)
1393 // FIXME: spec issue - consistently use either |end.[[Year]]| or |y2|.
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.
1405 auto mid
= ::AddISODate(start
, {years
, 0});
1408 int32_t midSign
= -CompareISODate(mid
, end
);
1413 if (largestUnit
== TemporalUnit::Year
) {
1414 return CreateDateDurationRecord(years
, 0, 0, 0);
1418 return CreateDateDurationRecord(0, years
* 12, 0, 0);
1422 int32_t months
= end
.month
- start
.month
;
1425 if (midSign
!= sign
) {
1430 months
+= sign
* 12;
1434 mid
= ::AddISODate(start
, {years
, months
});
1437 midSign
= -CompareISODate(mid
, end
);
1442 if (largestUnit
== TemporalUnit::Year
) {
1443 return CreateDateDurationRecord(years
, months
, 0, 0);
1447 return CreateDateDurationRecord(0, months
+ years
* 12, 0, 0);
1451 if (midSign
!= sign
) {
1456 mid
= ::AddISODate(start
, {years
, months
});
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
);
1468 days
= end
.day
+ (ISODaysInMonth(mid
.year
, mid
.month
) - mid
.day
);
1472 if (largestUnit
== TemporalUnit::Month
) {
1474 months
+= years
* 12;
1481 return CreateDateDurationRecord(years
, months
, 0, days
);
1485 MOZ_ASSERT(largestUnit
== TemporalUnit::Week
||
1486 largestUnit
== TemporalUnit::Day
);
1489 int32_t epochDaysStart
= MakeDay(start
);
1492 int32_t epochDaysEnd
= MakeDay(end
);
1495 int32_t days
= epochDaysEnd
- epochDaysStart
;
1501 if (largestUnit
== TemporalUnit::Week
) {
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)
1526 auto wrappedOther
= ::ToTemporalDate(cx
, args
.get(0));
1527 if (!wrappedOther
) {
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
)) {
1540 if (!CalendarEqualsOrThrow(cx
, calendarValue
, otherCalendar
)) {
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]));
1555 resolvedOptions
= SnapshotOwnProperties(cx
, options
);
1556 if (!resolvedOptions
) {
1561 if (!GetDifferenceSettings(cx
, operation
, resolvedOptions
,
1562 TemporalUnitGroup::Date
, TemporalUnit::Day
,
1563 TemporalUnit::Day
, &settings
)) {
1571 TemporalRoundingMode::Trunc
,
1577 if (ToPlainDate(temporalDate
) == otherDate
) {
1578 auto* obj
= CreateTemporalDuration(cx
, {});
1583 args
.rval().setObject(*obj
);
1588 Rooted
<CalendarRecord
> calendar(cx
);
1589 if (!CreateCalendarMethodsRecord(cx
, calendarValue
,
1591 CalendarMethod::DateAdd
,
1592 CalendarMethod::DateUntil
,
1599 DateDuration difference
;
1600 if (resolvedOptions
) {
1602 Rooted
<Value
> largestUnitValue(
1603 cx
, StringValue(TemporalUnitToString(cx
, settings
.largestUnit
)));
1604 if (!DefineDataProperty(cx
, resolvedOptions
, cx
->names().largestUnit
,
1605 largestUnitValue
)) {
1610 if (!DifferenceDate(cx
, calendar
, temporalDate
, other
, resolvedOptions
,
1616 if (!DifferenceDate(cx
, calendar
, temporalDate
, other
, settings
.largestUnit
,
1623 bool roundingGranularityIsNoop
= settings
.smallestUnit
== TemporalUnit::Day
&&
1624 settings
.roundingIncrement
== Increment
{1};
1627 if (!roundingGranularityIsNoop
) {
1629 NormalizedDuration roundResult
;
1630 if (!temporal::RoundDuration(cx
, {difference
, {}},
1631 settings
.roundingIncrement
,
1632 settings
.smallestUnit
, settings
.roundingMode
,
1633 temporalDate
, calendar
, &roundResult
)) {
1638 DateDuration balanceResult
;
1639 if (!temporal::BalanceDateDurationRelative(
1640 cx
, roundResult
.date
, settings
.largestUnit
, settings
.smallestUnit
,
1641 temporalDate
, calendar
, &balanceResult
)) {
1644 difference
= balanceResult
;
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
);
1659 args
.rval().setObject(*obj
);
1664 * Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendarLike ] )
1666 static bool PlainDateConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1667 CallArgs args
= CallArgsFromVp(argc
, vp
);
1670 if (!ThrowIfNotConstructing(cx
, args
, "Temporal.PlainDate")) {
1676 if (!ToIntegerWithTruncation(cx
, args
.get(0), "year", &isoYear
)) {
1682 if (!ToIntegerWithTruncation(cx
, args
.get(1), "month", &isoMonth
)) {
1688 if (!ToIntegerWithTruncation(cx
, args
.get(2), "day", &isoDay
)) {
1693 Rooted
<CalendarValue
> calendar(cx
);
1694 if (!ToTemporalCalendarWithISODefault(cx
, args
.get(3), &calendar
)) {
1699 auto* temporalDate
=
1700 CreateTemporalDate(cx
, args
, isoYear
, isoMonth
, isoDay
, calendar
);
1701 if (!temporalDate
) {
1705 args
.rval().setObject(*temporalDate
);
1710 * Temporal.PlainDate.from ( item [ , options ] )
1712 static bool PlainDate_from(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1713 CallArgs args
= CallArgsFromVp(argc
, vp
);
1716 Rooted
<JSObject
*> options(cx
);
1717 if (args
.hasDefined(1)) {
1718 options
= RequireObjectArg(cx
, "options", "from", args
[1]);
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
)) {
1737 TemporalOverflow ignored
;
1738 if (!ToTemporalOverflow(cx
, options
, &ignored
)) {
1744 auto* result
= CreateTemporalDate(cx
, date
, calendar
);
1749 args
.rval().setObject(*result
);
1755 auto result
= ToTemporalDate(cx
, args
.get(0), options
);
1760 args
.rval().setObject(*result
);
1765 * Temporal.PlainDate.compare ( one, two )
1767 static bool PlainDate_compare(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1768 CallArgs args
= CallArgsFromVp(argc
, vp
);
1772 if (!ToTemporalDate(cx
, args
.get(0), &one
)) {
1778 if (!ToTemporalDate(cx
, args
.get(1), &two
)) {
1783 args
.rval().setInt32(CompareISODate(one
, two
));
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());
1795 auto* calendarId
= ToTemporalCalendarIdentifier(cx
, calendar
);
1800 args
.rval().setString(calendarId
);
1805 * get Temporal.PlainDate.prototype.calendarId
1807 static bool PlainDate_calendarId(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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
) {
1818 Rooted
<PlainDateObject
*> temporalDate(
1819 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1820 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1840 Rooted
<PlainDateObject
*> temporalDate(
1841 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1842 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1862 Rooted
<PlainDateObject
*> temporalDate(
1863 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1864 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1884 Rooted
<PlainDateObject
*> temporalDate(
1885 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1886 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1906 Rooted
<PlainDateObject
*> temporalDate(
1907 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1908 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1928 Rooted
<PlainDateObject
*> temporalDate(
1929 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1930 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1950 Rooted
<PlainDateObject
*> temporalDate(
1951 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1952 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1972 Rooted
<PlainDateObject
*> temporalDate(
1973 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1974 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
1994 Rooted
<PlainDateObject
*> temporalDate(
1995 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
1996 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
2016 Rooted
<PlainDateObject
*> temporalDate(
2017 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
2018 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
2038 Rooted
<PlainDateObject
*> temporalDate(
2039 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
2040 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
2060 Rooted
<PlainDateObject
*> temporalDate(
2061 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
2062 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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
) {
2082 Rooted
<PlainDateObject
*> temporalDate(
2083 cx
, &args
.thisv().toObject().as
<PlainDateObject
>());
2084 Rooted
<CalendarValue
> calendar(cx
, temporalDate
->calendar());
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
) {
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());
2108 Rooted
<CalendarRecord
> calendar(cx
);
2109 if (!CreateCalendarMethodsRecord(cx
, calendarValue
,
2111 CalendarMethod::Fields
,
2112 CalendarMethod::YearMonthFromFields
,
2119 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2120 if (!CalendarFields(cx
, calendar
,
2121 {CalendarField::MonthCode
, CalendarField::Year
},
2127 Rooted
<PlainObject
*> fields(
2128 cx
, PrepareTemporalFields(cx
, temporalDate
, fieldNames
));
2134 auto obj
= CalendarYearMonthFromFields(cx
, calendar
, fields
);
2139 args
.rval().setObject(*obj
);
2144 * Temporal.PlainDate.prototype.toPlainYearMonth ( )
2146 static bool PlainDate_toPlainYearMonth(JSContext
* cx
, unsigned argc
,
2149 CallArgs args
= CallArgsFromVp(argc
, vp
);
2150 return CallNonGenericMethod
<IsPlainDate
, PlainDate_toPlainYearMonth
>(cx
,
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.
2167 static constexpr std::initializer_list
<CalendarField
> fieldNames
= {
2168 CalendarField::Day
, CalendarField::MonthCode
};
2171 if (calendarValue
.isObject()) {
2172 Rooted
<JSObject
*> calendarObj(cx
, calendarValue
.toObject());
2173 if (!calendarObj
->is
<CalendarObject
>()) {
2176 auto builtinCalendar
= calendarObj
.as
<CalendarObject
>();
2179 if (!IsBuiltinAccess(cx
, builtinCalendar
, fieldNames
)) {
2183 if (!IsBuiltinAccess(cx
, temporalDate
, fieldNames
)) {
2188 auto date
= ToPlainDate(temporalDate
);
2189 auto result
= PlainDate
{1972 /* referenceISOYear */, date
.month
, date
.day
};
2191 auto* obj
= CreateTemporalMonthDay(cx
, result
, calendarValue
);
2196 args
.rval().setObject(*obj
);
2201 Rooted
<CalendarRecord
> calendar(cx
);
2202 if (!CreateCalendarMethodsRecord(cx
, calendarValue
,
2204 CalendarMethod::Fields
,
2205 CalendarMethod::MonthDayFromFields
,
2212 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2213 if (!CalendarFields(cx
, calendar
,
2214 {CalendarField::Day
, CalendarField::MonthCode
},
2220 Rooted
<PlainObject
*> fields(
2221 cx
, PrepareTemporalFields(cx
, temporalDate
, fieldNames
));
2227 auto obj
= CalendarMonthDayFromFields(cx
, calendar
, fields
);
2232 args
.rval().setObject(*obj
);
2237 * Temporal.PlainDate.prototype.toPlainMonthDay ( )
2239 static bool PlainDate_toPlainMonthDay(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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
)) {
2263 auto* obj
= CreateTemporalDateTime(cx
, dateTime
, calendar
);
2268 args
.rval().setObject(*obj
);
2273 * Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )
2275 static bool PlainDate_toPlainDateTime(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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();
2290 Rooted
<IdValueVector
> fields(cx
, IdValueVector(cx
));
2293 if (!fields
.emplaceBack(NameToId(cx
->names().calendar
), calendar
.toValue())) {
2298 if (!fields
.emplaceBack(NameToId(cx
->names().isoDay
), Int32Value(date
.day
))) {
2303 if (!fields
.emplaceBack(NameToId(cx
->names().isoMonth
),
2304 Int32Value(date
.month
))) {
2309 if (!fields
.emplaceBack(NameToId(cx
->names().isoYear
),
2310 Int32Value(date
.year
))) {
2315 auto* obj
= NewPlainObjectWithUniqueNames(cx
, fields
);
2320 args
.rval().setObject(*obj
);
2325 * Temporal.PlainDate.prototype.getISOFields ( )
2327 static bool PlainDate_getISOFields(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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());
2341 auto* obj
= ToTemporalCalendarObject(cx
, calendar
);
2346 args
.rval().setObject(*obj
);
2351 * Temporal.PlainDate.prototype.getCalendar ( )
2353 static bool PlainDate_getCalendar(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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());
2368 Rooted
<Wrapped
<DurationObject
*>> duration(
2369 cx
, ToTemporalDuration(cx
, args
.get(0)));
2375 Rooted
<JSObject
*> options(cx
);
2376 if (args
.hasDefined(1)) {
2377 options
= RequireObjectArg(cx
, "options", "add", args
[1]);
2379 options
= NewPlainObjectWithProto(cx
, nullptr);
2386 Rooted
<CalendarRecord
> calendar(cx
);
2387 if (!CreateCalendarMethodsRecord(cx
, calendarValue
,
2389 CalendarMethod::DateAdd
,
2396 auto result
= AddDate(cx
, calendar
, temporalDate
, duration
, options
);
2401 args
.rval().setObject(*result
);
2406 * Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] )
2408 static bool PlainDate_add(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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());
2424 if (!ToTemporalDuration(cx
, args
.get(0), &duration
)) {
2429 Rooted
<JSObject
*> options(cx
);
2430 if (args
.hasDefined(1)) {
2431 options
= RequireObjectArg(cx
, "options", "subtract", args
[1]);
2433 options
= NewPlainObjectWithProto(cx
, nullptr);
2440 auto negatedDuration
= duration
.negate();
2443 Rooted
<CalendarRecord
> calendar(cx
);
2444 if (!CreateCalendarMethodsRecord(cx
, calendarValue
,
2446 CalendarMethod::DateAdd
,
2454 temporal::AddDate(cx
, calendar
, temporalDate
, negatedDuration
, options
);
2459 args
.rval().setObject(*result
);
2464 * Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] )
2466 static bool PlainDate_subtract(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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
>());
2480 Rooted
<JSObject
*> temporalDateLike(
2481 cx
, RequireObjectArg(cx
, "temporalDateLike", "with", args
.get(0)));
2482 if (!temporalDateLike
) {
2487 if (!RejectTemporalLikeObject(cx
, temporalDateLike
)) {
2492 Rooted
<PlainObject
*> resolvedOptions(cx
);
2493 if (args
.hasDefined(1)) {
2494 Rooted
<JSObject
*> options(cx
,
2495 RequireObjectArg(cx
, "options", "with", args
[1]));
2499 resolvedOptions
= SnapshotOwnProperties(cx
, options
);
2501 resolvedOptions
= NewPlainObjectWithProto(cx
, nullptr);
2503 if (!resolvedOptions
) {
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
,
2521 JS::RootedVector
<PropertyKey
> fieldNames(cx
);
2522 if (!CalendarFields(cx
, calendar
,
2523 {CalendarField::Day
, CalendarField::Month
,
2524 CalendarField::MonthCode
, CalendarField::Year
},
2530 Rooted
<PlainObject
*> fields(
2531 cx
, PrepareTemporalFields(cx
, temporalDate
, fieldNames
));
2537 Rooted
<PlainObject
*> partialDate(
2538 cx
, PreparePartialTemporalFields(cx
, temporalDateLike
, fieldNames
));
2544 Rooted
<JSObject
*> mergedFields(
2545 cx
, CalendarMergeFields(cx
, calendar
, fields
, partialDate
));
2546 if (!mergedFields
) {
2551 fields
= PrepareTemporalFields(cx
, mergedFields
, fieldNames
);
2558 temporal::CalendarDateFromFields(cx
, calendar
, fields
, resolvedOptions
);
2563 args
.rval().setObject(*result
);
2568 * Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )
2570 static bool PlainDate_with(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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
);
2584 Rooted
<CalendarValue
> calendar(cx
);
2585 if (!ToTemporalCalendar(cx
, args
.get(0), &calendar
)) {
2590 auto* result
= CreateTemporalDate(cx
, date
, calendar
);
2595 args
.rval().setObject(*result
);
2600 * Temporal.PlainDate.prototype.withCalendar ( calendar )
2602 static bool PlainDate_withCalendar(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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
) {
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
) {
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
) {
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
) {
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());
2651 Rooted
<PlainDateWithCalendar
> other(cx
);
2652 if (!ToTemporalDate(cx
, args
.get(0), &other
)) {
2657 bool equals
= date
== other
.date();
2658 if (equals
&& !CalendarEquals(cx
, calendar
, other
.calendar(), &equals
)) {
2662 args
.rval().setBoolean(equals
);
2667 * Temporal.PlainDate.prototype.equals ( other )
2669 static bool PlainDate_equals(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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());
2696 Rooted
<TimeZoneValue
> timeZone(cx
);
2697 Rooted
<Value
> temporalTime(cx
);
2698 if (args
.get(0).isObject()) {
2699 Rooted
<JSObject
*> item(cx
, &args
[0].toObject());
2702 if (item
->canUnwrapAs
<TimeZoneObject
>()) {
2704 timeZone
.set(TimeZoneValue(item
));
2707 temporalTime
.setUndefined();
2710 Rooted
<Value
> timeZoneLike(cx
);
2711 if (!GetProperty(cx
, item
, item
, cx
->names().timeZone
, &timeZoneLike
)) {
2715 // Steps 3.b.ii-iii.
2716 if (timeZoneLike
.isUndefined()) {
2718 if (!ToTemporalTimeZone(cx
, args
[0], &timeZone
)) {
2723 temporalTime
.setUndefined();
2726 if (!ToTemporalTimeZone(cx
, timeZoneLike
, &timeZone
)) {
2731 if (!GetProperty(cx
, item
, item
, cx
->names().plainTime
,
2739 if (!ToTemporalTimeZone(cx
, args
.get(0), &timeZone
)) {
2744 temporalTime
.setUndefined();
2748 PlainTime time
= {};
2749 if (!temporalTime
.isUndefined()) {
2750 if (!ToTemporalTime(cx
, temporalTime
, &time
)) {
2755 // Steps 5.a and 6.b
2756 Rooted
<PlainDateTimeWithCalendar
> temporalDateTime(cx
);
2757 if (!CreateTemporalDateTime(cx
, {date
, time
}, calendar
, &temporalDateTime
)) {
2763 if (!GetInstantFor(cx
, timeZone
, temporalDateTime
,
2764 TemporalDisambiguation::Compatible
, &instant
)) {
2769 auto* result
= CreateTemporalZonedDateTime(cx
, instant
, timeZone
, calendar
);
2774 args
.rval().setObject(*result
);
2779 * Temporal.PlainDate.prototype.toZonedDateTime ( item )
2781 static bool PlainDate_toZonedDateTime(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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)) {
2797 Rooted
<JSObject
*> options(
2798 cx
, RequireObjectArg(cx
, "options", "toString", args
[0]));
2804 if (!ToCalendarNameOption(cx
, options
, &showCalendar
)) {
2810 JSString
* str
= TemporalDateToString(cx
, temporalDate
, showCalendar
);
2815 args
.rval().setString(str
);
2820 * Temporal.PlainDate.prototype.toString ( [ options ] )
2822 static bool PlainDate_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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
>());
2836 JSString
* str
= TemporalDateToString(cx
, temporalDate
, CalendarOption::Auto
);
2841 args
.rval().setString(str
);
2846 * Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )
2848 static bool PlainDate_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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
>());
2862 JSString
* str
= TemporalDateToString(cx
, temporalDate
, CalendarOption::Auto
);
2867 args
.rval().setString(str
);
2872 * Temporal.PlainDate.prototype.toJSON ( )
2874 static bool PlainDate_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
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");
2889 const JSClass
PlainDateObject::class_
= {
2890 "Temporal.PlainDate",
2891 JSCLASS_HAS_RESERVED_SLOTS(PlainDateObject::SLOT_COUNT
) |
2892 JSCLASS_HAS_CACHED_PROTO(JSProto_PlainDate
),
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),
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),
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
),
2945 const ClassSpec
PlainDateObject::classSpec_
= {
2946 GenericCreateConstructor
<PlainDateConstructor
, 3, gc::AllocKind::FUNCTION
>,
2947 GenericCreatePrototype
<PlainDateObject
>,
2950 PlainDate_prototype_methods
,
2951 PlainDate_prototype_properties
,
2953 ClassSpec::DontDefineConstructor
,
2956 struct PlainDateNameAndNative final
{
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) {
2985 JSObject
* proto
= cx
->global()->maybeGetPrototype(JSProto_PlainDate
);
2987 // Don't attempt to optimize when the class isn't yet initialized.
2992 // Don't optimize when the prototype isn't the built-in prototype.
2993 if (date
->staticPrototype() != proto
) {
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()) {
3007 // Return if the property isn't the initial method.
3008 if (!IsNativeFunction(nproto
->getSlot(prop
->slot()), native
)) {
3013 // Success! The access can be optimized.