mirror of
https://github.com/RGBCube/serenity
synced 2025-06-01 09:58:14 +00:00
LibJS: Implement Temporal.Calendar.prototype.dateUntil()
This commit is contained in:
parent
045c85af4b
commit
99adb54391
6 changed files with 307 additions and 0 deletions
|
@ -117,6 +117,7 @@ namespace JS {
|
|||
P(currencySign) \
|
||||
P(dateAdd) \
|
||||
P(dateFromFields) \
|
||||
P(dateUntil) \
|
||||
P(day) \
|
||||
P(dayOfWeek) \
|
||||
P(dayOfYear) \
|
||||
|
|
|
@ -41,6 +41,7 @@ void CalendarPrototype::initialize(GlobalObject& global_object)
|
|||
define_native_function(vm.names.yearMonthFromFields, year_month_from_fields, 1, attr);
|
||||
define_native_function(vm.names.monthDayFromFields, month_day_from_fields, 1, attr);
|
||||
define_native_function(vm.names.dateAdd, date_add, 2, attr);
|
||||
define_native_function(vm.names.dateUntil, date_until, 2, attr);
|
||||
define_native_function(vm.names.year, year, 1, attr);
|
||||
define_native_function(vm.names.month, month, 1, attr);
|
||||
define_native_function(vm.names.monthCode, month_code, 1, attr);
|
||||
|
@ -199,6 +200,38 @@ JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::date_add)
|
|||
return TRY_OR_DISCARD(create_temporal_date(global_object, result.year, result.month, result.day, *calendar));
|
||||
}
|
||||
|
||||
// 12.4.8 Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.dateuntil
|
||||
// NOTE: This is the minimum dateUntil implementation for engines without ECMA-402.
|
||||
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::date_until)
|
||||
{
|
||||
// 1. Let calendar be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
|
||||
auto* calendar = typed_this_object(global_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
// 3. Assert: calendar.[[Identifier]] is "iso8601".
|
||||
VERIFY(calendar->identifier() == "iso8601"sv);
|
||||
|
||||
// 4. Set one to ? ToTemporalDate(one).
|
||||
auto* one = TRY_OR_DISCARD(to_temporal_date(global_object, vm.argument(0)));
|
||||
|
||||
// 5. Set two to ? ToTemporalDate(two).
|
||||
auto* two = TRY_OR_DISCARD(to_temporal_date(global_object, vm.argument(1)));
|
||||
|
||||
// 6. Set options to ? GetOptionsObject(options).
|
||||
auto* options = TRY_OR_DISCARD(get_options_object(global_object, vm.argument(2)));
|
||||
|
||||
// 7. Let largestUnit be ? ToLargestTemporalUnit(options, « "hour", "minute", "second", "millisecond", "microsecond", "nanosecond" », "auto", "day").
|
||||
auto largest_unit = TRY_OR_DISCARD(to_largest_temporal_unit(global_object, *options, { "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv }, "auto"sv, "day"sv));
|
||||
|
||||
// 8. Let result be ! DifferenceISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]], largestUnit).
|
||||
auto result = difference_iso_date(global_object, one->iso_year(), one->iso_month(), one->iso_day(), two->iso_year(), two->iso_month(), two->iso_day(), largest_unit);
|
||||
|
||||
// 9. Return ? CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], 0, 0, 0, 0, 0, 0).
|
||||
return TRY_OR_DISCARD(create_temporal_duration(global_object, result.years, result.months, result.weeks, result.days, 0, 0, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
// 12.4.9 Temporal.Calendar.prototype.year ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.year
|
||||
// NOTE: This is the minimum year implementation for engines without ECMA-402.
|
||||
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::year)
|
||||
|
|
|
@ -25,6 +25,7 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(year_month_from_fields);
|
||||
JS_DECLARE_NATIVE_FUNCTION(month_day_from_fields);
|
||||
JS_DECLARE_NATIVE_FUNCTION(date_add);
|
||||
JS_DECLARE_NATIVE_FUNCTION(date_until);
|
||||
JS_DECLARE_NATIVE_FUNCTION(year);
|
||||
JS_DECLARE_NATIVE_FUNCTION(month);
|
||||
JS_DECLARE_NATIVE_FUNCTION(month_code);
|
||||
|
|
|
@ -142,6 +142,191 @@ ThrowCompletionOr<PlainDate*> to_temporal_date(GlobalObject& global_object, Valu
|
|||
return create_temporal_date(global_object, result.year, result.month, result.day, *calendar);
|
||||
}
|
||||
|
||||
// 3.5.3 DifferenceISODate ( y1, m1, d1, y2, m2, d2, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-differenceisodate
|
||||
DifferenceISODateResult difference_iso_date(GlobalObject& global_object, i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2, StringView largest_unit)
|
||||
{
|
||||
// 1. Assert: largestUnit is one of "year", "month", "week", or "day".
|
||||
VERIFY(largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv));
|
||||
|
||||
// 2. If largestUnit is "year" or "month", then
|
||||
if (largest_unit.is_one_of("year"sv, "month"sv)) {
|
||||
// a. Let sign be -(! CompareISODate(y1, m1, d1, y2, m2, d2)).
|
||||
auto sign = -compare_iso_date(year1, month1, day1, year2, month2, day2);
|
||||
|
||||
// b. If sign is 0, return the Record { [[Years]]: 0, [[Months]]: 0, [[Weeks]]: 0, [[Days]]: 0 }.
|
||||
if (sign == 0)
|
||||
return { .years = 0, .months = 0, .weeks = 0, .days = 0 };
|
||||
|
||||
// c. Let start be the Record { [[Year]]: y1, [[Month]]: m1, [[Day]]: d1 }.
|
||||
auto start = ISODate { .year = year1, .month = month1, .day = day1 };
|
||||
|
||||
// d. Let end be the Record { [[Year]]: y2, [[Month]]: m2, [[Day]]: d2 }.
|
||||
auto end = ISODate { .year = year2, .month = month2, .day = day2 };
|
||||
|
||||
// e. Let years be end.[[Year]] − start.[[Year]].
|
||||
double years = end.year - start.year;
|
||||
|
||||
// f. Let mid be ! AddISODate(y1, m1, d1, years, 0, 0, 0, "constrain").
|
||||
auto mid = MUST(add_iso_date(global_object, year1, month1, day1, years, 0, 0, 0, "constrain"sv));
|
||||
|
||||
// g. Let midSign be -(! CompareISODate(mid.[[Year]], mid.[[Month]], mid.[[Day]], y2, m2, d2)).
|
||||
auto mid_sign = -compare_iso_date(mid.year, mid.month, mid.day, year2, month2, day2);
|
||||
|
||||
// h. If midSign is 0, then
|
||||
if (mid_sign == 0) {
|
||||
// i. If largestUnit is "year", return the Record { [[Years]]: years, [[Months]]: 0, [[Weeks]]: 0, [[Days]]: 0 }.
|
||||
if (largest_unit == "year"sv)
|
||||
return { .years = years, .months = 0, .weeks = 0, .days = 0 };
|
||||
|
||||
// ii. Return the Record { [[Years]]: 0, [[Months]]: years × 12, [[Weeks]]: 0, [[Days]]: 0 }.
|
||||
return { .years = 0, .months = years * 12, .weeks = 0, .days = 0 };
|
||||
}
|
||||
|
||||
// i. Let months be end.[[Month]] − start.[[Month]].
|
||||
double months = end.month - start.month;
|
||||
|
||||
// j. If midSign is not equal to sign, then
|
||||
if (mid_sign != sign) {
|
||||
// i. Set years to years - sign.
|
||||
years -= sign;
|
||||
|
||||
// ii. Set months to months + sign × 12.
|
||||
months += sign * 12;
|
||||
}
|
||||
|
||||
// k. Set mid to ! AddISODate(y1, m1, d1, years, months, 0, 0, "constrain").
|
||||
mid = MUST(add_iso_date(global_object, year1, month1, day1, years, months, 0, 0, "constrain"sv));
|
||||
|
||||
// l. Set midSign to -(! CompareISODate(mid.[[Year]], mid.[[Month]], mid.[[Day]], y2, m2, d2)).
|
||||
mid_sign = -compare_iso_date(mid.year, mid.month, mid.day, year2, month2, day2);
|
||||
|
||||
// m. If midSign is 0, then
|
||||
if (mid_sign == 0) {
|
||||
// i. If largestUnit is "year", return the Record { [[Years]]: years, [[Months]]: months, [[Weeks]]: 0, [[Days]]: 0 }.
|
||||
if (largest_unit == "year"sv)
|
||||
return { .years = years, .months = months, .weeks = 0, .days = 0 };
|
||||
|
||||
// ii. Return the Record { [[Years]]: 0, [[Months]]: months + years × 12, [[Weeks]]: 0, [[Days]]: 0 }.
|
||||
return { .years = 0, .months = months + years * 12, .weeks = 0, .days = 0 };
|
||||
}
|
||||
|
||||
// n. If midSign is not equal to sign, then
|
||||
if (mid_sign != sign) {
|
||||
// i. Set months to months - sign.
|
||||
months -= sign;
|
||||
|
||||
// ii. If months is equal to -sign, then
|
||||
if (months == -sign) {
|
||||
// 1. Set years to years - sign.
|
||||
years -= sign;
|
||||
|
||||
// 2. Set months to 11 × sign.
|
||||
months = 11 * sign;
|
||||
}
|
||||
|
||||
// iii. Set mid to ! AddISODate(y1, m1, d1, years, months, 0, 0, "constrain").
|
||||
mid = MUST(add_iso_date(global_object, year1, month1, day1, years, months, 0, 0, "constrain"sv));
|
||||
|
||||
// FIXME: This is not used (spec issue, see https://github.com/tc39/proposal-temporal/issues/1483).
|
||||
// iv. Set midSign to -(! CompareISODate(mid.[[Year]], mid.[[Month]], mid.[[Day]], y2, m2, d2)).
|
||||
mid_sign = -compare_iso_date(mid.year, mid.month, mid.day, year2, month2, day2);
|
||||
}
|
||||
|
||||
// o. Let days be 0.
|
||||
double days = 0;
|
||||
|
||||
// p. If mid.[[Month]] = end.[[Month]], then
|
||||
if (mid.month == end.month) {
|
||||
// i. Assert: mid.[[Year]] = end.[[Year]].
|
||||
VERIFY(mid.year == end.year);
|
||||
|
||||
// ii. Set days to end.[[Day]] - mid.[[Day]].
|
||||
days = end.day - mid.day;
|
||||
}
|
||||
// q. Else if sign < 0, set days to -mid.[[Day]] - (! ISODaysInMonth(end.[[Year]], end.[[Month]]) - end.[[Day]]).
|
||||
else if (sign < 0) {
|
||||
days = -mid.day - (iso_days_in_month(end.year, end.month) - end.day);
|
||||
}
|
||||
// r. Else, set days to end.[[Day]] + (! ISODaysInMonth(mid.[[Year]], mid.[[Month]]) - mid.[[Day]]).
|
||||
else {
|
||||
days = end.day + (iso_days_in_month(mid.year, mid.month) - mid.day);
|
||||
}
|
||||
|
||||
// s. If largestUnit is "month", then
|
||||
if (largest_unit == "month"sv) {
|
||||
// i. Set months to months + years × 12.
|
||||
months += years * 12;
|
||||
|
||||
// ii. Set years to 0.
|
||||
years = 0;
|
||||
}
|
||||
|
||||
// t. Return the Record { [[Years]]: years, [[Months]]: months, [[Weeks]]: 0, [[Days]]: days }.
|
||||
return { .years = years, .months = months, .weeks = 0, .days = days };
|
||||
}
|
||||
// 3. If largestUnit is "day" or "week", then
|
||||
else {
|
||||
ISODate smaller;
|
||||
ISODate greater;
|
||||
i8 sign;
|
||||
|
||||
// a. If ! CompareISODate(y1, m1, d1, y2, m2, d2) < 0, then
|
||||
if (compare_iso_date(year1, month1, day1, year2, month2, day2) < 0) {
|
||||
// i. Let smaller be the Record { [[Year]]: y1, [[Month]]: m1, [[Day]]: d1 }.
|
||||
smaller = { .year = year1, .month = month1, .day = day1 };
|
||||
|
||||
// ii. Let greater be the Record { [[Year]]: y2, [[Month]]: m2, [[Day]]: d2 }.
|
||||
greater = { .year = year2, .month = month2, .day = day2 };
|
||||
|
||||
// iii. Let sign be 1.
|
||||
sign = 1;
|
||||
}
|
||||
// b. Else,
|
||||
else {
|
||||
// i. Let smaller be the Record { [[Year]]: y2, [[Month]]: m2, [[Day]]: d2 }.
|
||||
smaller = { .year = year2, .month = month2, .day = day2 };
|
||||
|
||||
// ii. Let greater be the Record { [[Year]]: y1, [[Month]]: m1, [[Day]]: d1 }.
|
||||
greater = { .year = year1, .month = month1, .day = day1 };
|
||||
|
||||
// iii. Let sign be −1.
|
||||
sign = -1;
|
||||
}
|
||||
|
||||
// c. Let days be ! ToISODayOfYear(greater.[[Year]], greater.[[Month]], greater.[[Day]]) − ! ToISODayOfYear(smaller.[[Year]], smaller.[[Month]], smaller.[[Day]]).
|
||||
double days = to_iso_day_of_year(greater.year, greater.month, greater.day) - to_iso_day_of_year(smaller.year, smaller.month, smaller.day);
|
||||
|
||||
// d. Let year be smaller.[[Year]].
|
||||
auto year = smaller.year;
|
||||
|
||||
// e. Repeat, while year < greater.[[Year]],
|
||||
while (year < greater.year) {
|
||||
// i. Set days to days + ! ISODaysInYear(year).
|
||||
days += iso_days_in_year(year);
|
||||
|
||||
// ii. Set year to year + 1.
|
||||
year++;
|
||||
}
|
||||
|
||||
// f. Let weeks be 0.
|
||||
double weeks = 0;
|
||||
|
||||
// g. If largestUnit is "week", then
|
||||
if (largest_unit == "week"sv) {
|
||||
// i. Set weeks to floor(days / 7).
|
||||
weeks = floor(days / 7);
|
||||
|
||||
// ii. Set days to days modulo 7.
|
||||
days = fmod(days, 7);
|
||||
}
|
||||
|
||||
// h. Return the Record { [[Years]]: 0, [[Months]]: 0, [[Weeks]]: weeks × sign, [[Days]]: days × sign }.
|
||||
// NOTE: We set weeks and days conditionally to avoid negative zero for 0 * -1.
|
||||
return { .years = 0, .months = 0, .weeks = (weeks != 0) ? weeks * sign : 0, .days = (days != 0) ? days * sign : 0 };
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// 3.5.4 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
|
||||
ThrowCompletionOr<ISODate> regulate_iso_date(GlobalObject& global_object, double year, double month, double day, StringView overflow)
|
||||
{
|
||||
|
|
|
@ -41,8 +41,16 @@ struct ISODate {
|
|||
u8 day;
|
||||
};
|
||||
|
||||
struct DifferenceISODateResult {
|
||||
double years;
|
||||
double months;
|
||||
double weeks;
|
||||
double days;
|
||||
};
|
||||
|
||||
ThrowCompletionOr<PlainDate*> create_temporal_date(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, Object& calendar, FunctionObject const* new_target = nullptr);
|
||||
ThrowCompletionOr<PlainDate*> to_temporal_date(GlobalObject&, Value item, Object* options = nullptr);
|
||||
DifferenceISODateResult difference_iso_date(GlobalObject&, i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2, StringView largest_unit);
|
||||
ThrowCompletionOr<ISODate> regulate_iso_date(GlobalObject&, double year, double month, double day, StringView overflow);
|
||||
bool is_valid_iso_date(i32 year, u8 month, u8 day);
|
||||
ISODate balance_iso_date(double year, double month, double day);
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(Temporal.Calendar.prototype.dateUntil).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
const one = new Temporal.PlainDate(2021, 7, 6);
|
||||
const two = new Temporal.PlainDate(2021, 10, 10);
|
||||
|
||||
const oneToTwo = calendar.dateUntil(one, two);
|
||||
expect(oneToTwo.years).toBe(0);
|
||||
expect(oneToTwo.months).toBe(0);
|
||||
expect(oneToTwo.weeks).toBe(0);
|
||||
expect(oneToTwo.days).toBe(96);
|
||||
expect(oneToTwo.hours).toBe(0);
|
||||
expect(oneToTwo.minutes).toBe(0);
|
||||
expect(oneToTwo.seconds).toBe(0);
|
||||
expect(oneToTwo.milliseconds).toBe(0);
|
||||
expect(oneToTwo.microseconds).toBe(0);
|
||||
expect(oneToTwo.nanoseconds).toBe(0);
|
||||
|
||||
const twoToOne = calendar.dateUntil(two, one);
|
||||
expect(twoToOne.years).toBe(0);
|
||||
expect(twoToOne.months).toBe(0);
|
||||
expect(twoToOne.weeks).toBe(0);
|
||||
expect(twoToOne.days).toBe(-96);
|
||||
expect(twoToOne.hours).toBe(0);
|
||||
expect(twoToOne.minutes).toBe(0);
|
||||
expect(twoToOne.seconds).toBe(0);
|
||||
expect(twoToOne.milliseconds).toBe(0);
|
||||
expect(twoToOne.microseconds).toBe(0);
|
||||
expect(twoToOne.nanoseconds).toBe(0);
|
||||
});
|
||||
|
||||
test("largestUnit option", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
const one = new Temporal.PlainDate(1970, 1, 1);
|
||||
const two = new Temporal.PlainDate(2021, 7, 6);
|
||||
|
||||
const values = [
|
||||
["years", 51, 6, 0, 5],
|
||||
["months", 0, 618, 0, 5],
|
||||
["weeks", 0, 0, 2687, 5],
|
||||
["days", 0, 0, 0, 18814],
|
||||
];
|
||||
for (const [largestUnit, years, months, weeks, days] of values) {
|
||||
const duration = calendar.dateUntil(one, two, { largestUnit });
|
||||
expect(duration.years).toBe(years);
|
||||
expect(duration.months).toBe(months);
|
||||
expect(duration.weeks).toBe(weeks);
|
||||
expect(duration.days).toBe(days);
|
||||
expect(duration.hours).toBe(0);
|
||||
expect(duration.minutes).toBe(0);
|
||||
expect(duration.seconds).toBe(0);
|
||||
expect(duration.milliseconds).toBe(0);
|
||||
expect(duration.microseconds).toBe(0);
|
||||
expect(duration.nanoseconds).toBe(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("forbidden largestUnit option values", () => {
|
||||
const calendar = new Temporal.Calendar("iso8601");
|
||||
const one = new Temporal.PlainDate(1970, 1, 1);
|
||||
const two = new Temporal.PlainDate(2021, 7, 6);
|
||||
|
||||
const values = ["hour", "minute", "second", "millisecond", "microsecond", "nanosecond"];
|
||||
for (const largestUnit of values) {
|
||||
expect(() => {
|
||||
calendar.dateUntil(one, two, { largestUnit });
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
`${largestUnit} is not a valid value for option largestUnit`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue