From 741cc0822112de170c07e96f9276efa209c05414 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Thu, 25 Aug 2022 22:28:11 +0100 Subject: [PATCH] LibJS: Use fully spec'd algorithm for ToISOWeekOfYear This is an editorial change in the Temporal spec. See: https://github.com/tc39/proposal-temporal/commit/33b62a3 --- .../LibJS/Runtime/Temporal/Calendar.cpp | 130 ++++++++++++++---- .../LibJS/Runtime/Temporal/Calendar.h | 2 + .../Runtime/Temporal/CalendarPrototype.cpp | 29 +--- 3 files changed, 113 insertions(+), 48 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 97e4610d62..5039a197f0 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -607,35 +607,78 @@ u8 iso_days_in_month(i32 year, u8 month) // 12.2.31 ToISOWeekOfYear ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-toisoweekofyear u8 to_iso_week_of_year(i32 year, u8 month, u8 day) { - // 1. Assert: year is an integer. - // 2. Assert: month is an integer. - // 3. Assert: day is an integer. + // 1. Assert: IsValidISODate(year, month, day) is true. + VERIFY(is_valid_iso_date(year, month, day)); - // 4. Let date be the date given by year, month, and day. - // 5. Return date's week number according to ISO-8601 as an integer. - auto t = make_date(make_day(year, month - 1, day), 0); - auto day_of_year = day_within_year(t) + 1; - auto day_of_week = week_day(t); - if (day_of_week == 0) - day_of_week = 7; - auto week = (day_of_year - day_of_week + 10) / 7; + // 2. Let wednesday be 3. + constexpr auto wednesday = 3; + // 3. Let thursday be 4. + constexpr auto thursday = 4; + + // 4. Let friday be 5. + constexpr auto friday = 5; + + // 5. Let saturday be 6. + constexpr auto saturday = 6; + + // 6. Let daysInWeek be 7. + constexpr auto days_in_week = 7; + + // 7. Let maxWeekNumber be 53. + constexpr auto max_week_number = 53; + + // 8. Let dayOfYear be ToISODayOfYear(year, month, day). + auto day_of_year = to_iso_day_of_year(year, month, day); + + // 9. Let dayOfWeek be ToISODayOfWeek(year, month, day). + auto day_of_week = to_iso_day_of_week(year, month, day); + + // 10. Let week be floor((dayOfYear + daysInWeek - dayOfWeek + wednesday ) / daysInWeek). + auto week = static_cast(floor(static_cast(day_of_year + days_in_week - day_of_week + wednesday) / days_in_week)); + + // 11. If week < 1, then if (week < 1) { - // NOTE: The resulting week is actually part of the previous year. If that year ends with a - // Thursday (i.e. the first day of the given year is a Friday, or day 5), or the previous - // year is a leap year and ends with a Friday (i.e. the first day of the given year is a - // Saturday, or day 6), it has 53 weeks, and 52 weeks otherwise. - auto day_of_jump = week_day(make_date(make_day(year, 0, 1), 0)); - if (day_of_jump == 5 || (in_leap_year(time_from_year(year - 1)) && day_of_jump == 6)) - return 53; - else - return 52; - } else if (week == 53) { - auto days_in_year = JS::days_in_year(year); - if (days_in_year - day_of_year < 4 - day_of_week) - return 1; + // a. NOTE: This is the last week of the previous year. + + // b. Let dayOfJan1st be ToISODayOfWeek(year, 1, 1). + auto day_of_jan_1st = to_iso_day_of_week(year, 1, 1); + + // c. If dayOfJan1st is friday, then + if (day_of_jan_1st == friday) { + // i. Return maxWeekNumber. + return max_week_number; + } + + // d. If dayOfJan1st is saturday, and InLeapYear(TimeFromYear(𝔽(year - 1))) is 1𝔽, then + if (day_of_jan_1st == saturday && in_leap_year(time_from_year(year - 1))) { + // i. Return maxWeekNumber. + return max_week_number; + } + + // e. Return maxWeekNumber - 1. + return max_week_number - 1; } + // 12. If week is maxWeekNumber, then + if (week == max_week_number) { + // a. Let daysInYear be DaysInYear(𝔽(year)). + auto days_in_year = JS::days_in_year(year); + + // b. Let daysLaterInYear be daysInYear - dayOfYear. + auto days_later_in_year = days_in_year - day_of_year; + + // c. Let daysAfterThursday be thursday - dayOfWeek. + auto days_after_thursday = thursday - day_of_week; + + // d. If daysLaterInYear < daysAfterThursday, then + if (days_later_in_year < days_after_thursday) { + // i. Return 1. + return 1; + } + } + + // 13. Return week. return week; } @@ -971,4 +1014,43 @@ ThrowCompletionOr default_merge_calendar_fields(VM& vm, Object const& f return merged; } +// 12.2.42 ToISODayOfYear ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-toisodayofyear +u16 to_iso_day_of_year(i32 year, u8 month, u8 day) +{ + // 1. Assert: IsValidISODate(year, month, day) is true. + VERIFY(is_valid_iso_date(year, month, day)); + + // 2. Let epochDays be MakeDay(𝔽(year), 𝔽(month - 1), 𝔽(day)). + auto epoch_days = make_day(year, month - 1, day); + + // 3. Assert: epochDays is finite. + VERIFY(isfinite(epoch_days)); + + // 4. Return ℝ(DayWithinYear(MakeDate(epochDays, +0𝔽))) + 1. + return day_within_year(make_date(epoch_days, 0)) + 1; +} + +// 12.2.43 ToISODayOfWeek ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-toisodayofweek +u8 to_iso_day_of_week(i32 year, u8 month, u8 day) +{ + // 1. Assert: IsValidISODate(year, month, day) is true. + VERIFY(is_valid_iso_date(year, month, day)); + + // 2. Let epochDays be MakeDay(𝔽(year), 𝔽(month - 1), 𝔽(day)). + auto epoch_days = make_day(year, month - 1, day); + + // 3. Assert: epochDays is finite. + VERIFY(isfinite(epoch_days)); + + // 4. Let dayOfWeek be WeekDay(MakeDate(epochDays, +0𝔽)). + auto day_of_week = week_day(make_date(epoch_days, 0)); + + // 5. If dayOfWeek = +0𝔽, return 7. + if (day_of_week == 0) + return 7; + + // 6. Return ℝ(dayOfWeek). + return day_of_week; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h index 568239b3be..e176c8d516 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -75,5 +75,7 @@ u8 iso_month(Object& temporal_object); String iso_month_code(Object& temporal_object); u8 iso_day(Object& temporal_object); ThrowCompletionOr default_merge_calendar_fields(VM&, Object const& fields, Object const& additional_fields); +u16 to_iso_day_of_year(i32 year, u8 month, u8 day); +u8 to_iso_day_of_week(i32 year, u8 month, u8 day); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp index 29727665cf..77a3829e24 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp @@ -328,21 +328,8 @@ JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::day_of_week) // 4. Let temporalDate be ? ToTemporalDate(temporalDateLike). auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0))); - // 5. Let epochDays be MakeDay(𝔽(temporalDate.[[ISOYear]]), 𝔽(temporalDate.[[ISOMonth]] - 1), 𝔽(temporalDate.[[ISODay]])). - auto epoch_days = make_day(temporal_date->iso_year(), temporal_date->iso_month() - 1, temporal_date->iso_day()); - - // 6. Assert: epochDays is finite. - VERIFY(isfinite(epoch_days)); - - // 7. Let dayOfWeek be WeekDay(MakeDate(epochDays, +0𝔽)). - auto day_of_week = week_day(make_date(epoch_days, 0)); - - // 8. If dayOfWeek = +0𝔽, return 7𝔽. - if (day_of_week == 0) - return Value(7); - - // 9. Return dayOfWeek. - return Value(day_of_week); + // 5. Return 𝔽(ToISODayOfWeek(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]])). + return Value(to_iso_day_of_week(temporal_date->iso_year(), temporal_date->iso_month(), temporal_date->iso_day())); } // 12.4.14 Temporal.Calendar.prototype.dayOfYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.dayofyear @@ -359,14 +346,8 @@ JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::day_of_year) // 4. Let temporalDate be ? ToTemporalDate(temporalDateLike). auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0))); - // 5. Let epochDays be MakeDay(𝔽(temporalDate.[[ISOYear]]), 𝔽(temporalDate.[[ISOMonth]] - 1), 𝔽(temporalDate.[[ISODay]])). - auto epoch_days = make_day(temporal_date->iso_year(), temporal_date->iso_month() - 1, temporal_date->iso_day()); - - // 6. Assert: epochDays is finite. - VERIFY(isfinite(epoch_days)); - - // 7. Return DayWithinYear(MakeDate(epochDays, +0𝔽)) + 1𝔽. - return Value(day_within_year(make_date(epoch_days, 0)) + 1); + // 5. Return 𝔽(ToISODayOfYear(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]])). + return Value(to_iso_day_of_year(temporal_date->iso_year(), temporal_date->iso_month(), temporal_date->iso_day())); } // 12.4.15 Temporal.Calendar.prototype.weekOfYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.weekofyear @@ -383,7 +364,7 @@ JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::week_of_year) // 4. Let temporalDate be ? ToTemporalDate(temporalDateLike). auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0))); - // 5. Return 𝔽(! ToISODayOfYear(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]])). + // 5. Return 𝔽(ToISODayOfYear(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]])). return Value(to_iso_week_of_year(temporal_date->iso_year(), temporal_date->iso_month(), temporal_date->iso_day())); }