diff --git a/src/binding.cc b/src/binding.cc index 3bc6e87985..5364ee83e6 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -14,6 +14,8 @@ #include "libplatform/libplatform.h" #include "support.h" #include "unicode/locid.h" +#include "unicode/timezone.h" +#include "unicode/unistr.h" #include "v8-callbacks.h" #include "v8-cppgc.h" #include "v8-fast-api-calls.h" @@ -4286,6 +4288,24 @@ void icu_set_default_locale(const char* locale) { icu::Locale::setDefault(icu::Locale(locale), status); } +size_t icu_get_default_timezone(char* output, size_t output_len) { + icu::UnicodeString id; + std::unique_ptr tz(icu::TimeZone::createDefault()); + tz->getID(id); + icu_77::CheckedArrayByteSink sink(output, static_cast(output_len)); + id.toUTF8(sink); + assert(!sink.Overflowed()); + return sink.NumberOfBytesAppended(); +} + +void icu_set_default_timezone(const char* timezone) { + // Takes ownership of the created TimeZone and installs it as the process + // wide default, so ICU (and therefore V8's Date implementation) resolves + // the given IANA id regardless of how the host reports its time zone. + icu::TimeZone::adoptDefault( + icu::TimeZone::createTimeZone(icu::UnicodeString::fromUTF8(timezone))); +} + } // extern "C" // v8::PropertyDescriptor diff --git a/src/icu.rs b/src/icu.rs index 7737f1aaca..99328996d8 100644 --- a/src/icu.rs +++ b/src/icu.rs @@ -5,6 +5,8 @@ use std::ffi::CString; unsafe extern "C" { fn icu_get_default_locale(output: *mut char, output_len: usize) -> usize; fn icu_set_default_locale(locale: *const char); + fn icu_get_default_timezone(output: *mut char, output_len: usize) -> usize; + fn icu_set_default_timezone(timezone: *const char); fn udata_setCommonData_77(this: *const u8, error_code: *mut i32); } @@ -69,3 +71,29 @@ pub fn set_default_locale(locale: &str) { icu_set_default_locale(c_str.as_ptr()); } } + +/// Returns the IANA id of ICU's current default time zone (e.g. +/// `"America/New_York"`). +pub fn get_default_time_zone() -> String { + let mut output = [0u8; 1024]; + let len = unsafe { + icu_get_default_timezone(output.as_mut_ptr() as *mut char, output.len()) + }; + std::str::from_utf8(&output[..len]).unwrap().to_owned() +} + +/// Sets ICU's default time zone from an IANA id (e.g. `"UTC"`, +/// `"Asia/Manila"`). This makes `Date` resolve the given zone on every +/// platform, including Windows, where ICU otherwise reads the host time +/// zone from the OS and ignores the `TZ` environment variable. +/// +/// After calling this, notify each isolate via +/// [`crate::Isolate::date_time_configuration_change_notification`] with +/// [`crate::TimeZoneDetection::Skip`] so cached values are refreshed +/// without re-detecting (and overwriting) the zone just set here. +pub fn set_default_time_zone(timezone: &str) { + unsafe { + let c_str = CString::new(timezone).expect("Invalid timezone"); + icu_set_default_timezone(c_str.as_ptr()); + } +} diff --git a/tests/test_api.rs b/tests/test_api.rs index bb3ae8d3ea..8e9b63c19c 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -9812,6 +9812,35 @@ fn icu_set_common_data_fail() { ); } +#[test] +fn icu_default_time_zone() { + // Mutates the process-wide ICU default time zone, which also affects how + // other tests' `Date`s render, so take the exclusive lock. + let _setup_guard = setup::sequential_test(); + let original = v8::icu::get_default_time_zone(); + + v8::icu::set_default_time_zone("America/New_York"); + assert_eq!(v8::icu::get_default_time_zone(), "America/New_York"); + + let isolate = &mut v8::Isolate::new(Default::default()); + isolate + .date_time_configuration_change_notification(v8::TimeZoneDetection::Skip); + { + v8::scope!(let scope, isolate); + let context = v8::Context::new(scope, Default::default()); + let scope = &mut v8::ContextScope::new(scope, context); + // 2020-06-26T07:00:00Z is 03:00 in America/New_York (EDT, UTC-4). + let source = r#" + new Date(Date.UTC(2020, 5, 26, 7, 0, 0)).getHours(); + "#; + let value = eval(scope, source).unwrap(); + assert_eq!(value.number_value(scope).unwrap(), 3.0); + } + + // Restore the original default so test ordering doesn't matter. + v8::icu::set_default_time_zone(&original); +} + #[test] fn icu_format() { let _setup_guard = setup::parallel_test();