From 6becd13a83a910047a25a980f70fbee8498a44f5 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 3 Sep 2024 10:26:03 -0400 Subject: [PATCH] LibJS: Add a non-BigInt overload of GetNamedTimeZoneOffsetNanoseconds In some cases, we have a timestamp as a double in milliseconds. We then would convert it to nanoseconds as a BigInt, just to bring it back to a double for TZDB lookups. Add an overload to avoid this needless round trip. --- Userland/Libraries/LibJS/Runtime/Date.cpp | 32 +++++++++++++++++-- Userland/Libraries/LibJS/Runtime/Date.h | 1 + .../Libraries/LibJS/Runtime/DatePrototype.cpp | 3 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Date.cpp b/Userland/Libraries/LibJS/Runtime/Date.cpp index e73fa826493..1623c48a356 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.cpp +++ b/Userland/Libraries/LibJS/Runtime/Date.cpp @@ -382,6 +382,22 @@ static i64 clip_bigint_to_sane_time(Crypto::SignedBigInteger const& value) return value.to_base_deprecated(10).to_number().value(); } +static i64 clip_double_to_sane_time(double value) +{ + static constexpr auto min_double = static_cast(NumericLimits::min()); + static constexpr auto max_double = static_cast(NumericLimits::max()); + + // The provided epoch millseconds value is potentially out of range for AK::Duration and subsequently + // get_time_zone_offset(). We can safely assume that the TZDB has no useful information that far + // into the past and future anyway, so clamp it to the i64 range. + if (value < min_double) + return NumericLimits::min(); + if (value > max_double) + return NumericLimits::max(); + + return static_cast(value); +} + // 21.4.1.20 GetNamedTimeZoneEpochNanoseconds ( timeZoneIdentifier, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/ecma262/#sec-getnamedtimezoneepochnanoseconds Vector get_named_time_zone_epoch_nanoseconds(StringView time_zone_identifier, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond) { @@ -411,6 +427,19 @@ Unicode::TimeZoneOffset get_named_time_zone_offset_nanoseconds(StringView time_z return offset.release_value(); } +// 21.4.1.21 GetNamedTimeZoneOffsetNanoseconds ( timeZoneIdentifier, epochNanoseconds ), https://tc39.es/ecma262/#sec-getnamedtimezoneoffsetnanoseconds +// OPTIMIZATION: This overload is provided to allow callers to avoid BigInt construction if they do not need infinitely precise nanosecond resolution. +Unicode::TimeZoneOffset get_named_time_zone_offset_milliseconds(StringView time_zone_identifier, double epoch_milliseconds) +{ + auto seconds = epoch_milliseconds / 1000.0; + auto time = UnixDateTime::from_seconds_since_epoch(clip_double_to_sane_time(seconds)); + + auto offset = Unicode::time_zone_offset(time_zone_identifier, time); + VERIFY(offset.has_value()); + + return offset.release_value(); +} + static Optional cached_system_time_zone_identifier; // 21.4.1.24 SystemTimeZoneIdentifier ( ), https://tc39.es/ecma262/#sec-systemtimezoneidentifier @@ -460,8 +489,7 @@ double local_time(double time) // 3. Else, else { // a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(t) × 10^6)). - auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(s_one_million_bigint); - auto offset = get_named_time_zone_offset_nanoseconds(system_time_zone_identifier, time_bigint); + auto offset = get_named_time_zone_offset_milliseconds(system_time_zone_identifier, time); offset_nanoseconds = static_cast(offset.offset.to_nanoseconds()); } diff --git a/Userland/Libraries/LibJS/Runtime/Date.h b/Userland/Libraries/LibJS/Runtime/Date.h index aca9a0c3015..58103963c54 100644 --- a/Userland/Libraries/LibJS/Runtime/Date.h +++ b/Userland/Libraries/LibJS/Runtime/Date.h @@ -76,6 +76,7 @@ u16 ms_from_time(double); Crypto::SignedBigInteger get_utc_epoch_nanoseconds(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); Vector get_named_time_zone_epoch_nanoseconds(StringView time_zone_identifier, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); Unicode::TimeZoneOffset get_named_time_zone_offset_nanoseconds(StringView time_zone_identifier, Crypto::SignedBigInteger const& epoch_nanoseconds); +Unicode::TimeZoneOffset get_named_time_zone_offset_milliseconds(StringView time_zone_identifier, double epoch_milliseconds); String system_time_zone_identifier(); void clear_system_time_zone_cache(); double local_time(double time); diff --git a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp index 3bb54f17fd6..e123edf6d34 100644 --- a/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/DatePrototype.cpp @@ -1123,8 +1123,7 @@ ByteString time_zone_string(double time) // 3. Else, else { // a. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(systemTimeZoneIdentifier, ℤ(ℝ(tv) × 10^6)). - auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(Crypto::UnsignedBigInteger { 1'000'000 }); - auto offset = get_named_time_zone_offset_nanoseconds(system_time_zone_identifier, time_bigint); + auto offset = get_named_time_zone_offset_milliseconds(system_time_zone_identifier, time); offset_nanoseconds = static_cast(offset.offset.to_nanoseconds()); in_dst = offset.in_dst;