diff --git a/src/builtins/compiled/duration/tests.rs b/src/builtins/compiled/duration/tests.rs index 6d1f8ae13..9dec067dc 100644 --- a/src/builtins/compiled/duration/tests.rs +++ b/src/builtins/compiled/duration/tests.rs @@ -1,5 +1,3 @@ -use std::string::ToString; - use crate::{ options::{ OffsetDisambiguation, RelativeTo, RoundingIncrement, RoundingMode, RoundingOptions, Unit, @@ -508,55 +506,6 @@ fn round_relative_to_zoned_datetime() { assert_eq!(result.hours(), 1); } -#[test] -fn test_duration_compare() { - // TODO(#199): fix this on Windows - if cfg!(not(windows)) { - let one = Duration::from_partial_duration(PartialDuration { - hours: Some(79), - minutes: Some(10), - ..Default::default() - }) - .unwrap(); - let two = Duration::from_partial_duration(PartialDuration { - days: Some(3), - hours: Some(7), - seconds: Some(630), - ..Default::default() - }) - .unwrap(); - let three = Duration::from_partial_duration(PartialDuration { - days: Some(3), - hours: Some(6), - minutes: Some(50), - ..Default::default() - }) - .unwrap(); - - let mut arr = [&one, &two, &three]; - arr.sort_by(|a, b| Duration::compare(a, b, None).unwrap()); - assert_eq!( - arr.map(ToString::to_string), - [&three, &one, &two].map(ToString::to_string) - ); - - // Sorting relative to a date, taking DST changes into account: - let zdt = ZonedDateTime::from_str( - "2020-11-01T00:00-07:00[America/Los_Angeles]", - Default::default(), - OffsetDisambiguation::Reject, - ) - .unwrap(); - arr.sort_by(|a, b| { - Duration::compare(a, b, Some(RelativeTo::ZonedDateTime(zdt.clone()))).unwrap() - }); - assert_eq!( - arr.map(ToString::to_string), - [&one, &three, &two].map(ToString::to_string) - ) - } -} - #[test] fn test_duration_total() { let d1 = Duration::from_partial_duration(PartialDuration { diff --git a/src/builtins/core/duration/tests.rs b/src/builtins/core/duration/tests.rs index 64c88bf1d..825a48207 100644 --- a/src/builtins/core/duration/tests.rs +++ b/src/builtins/core/duration/tests.rs @@ -264,6 +264,67 @@ fn duration_round_negative() { assert_eq!(result.days(), -3); } +#[test] +#[cfg(feature = "compiled_data")] +fn test_duration_compare() { + use crate::builtins::FS_TZ_PROVIDER; + use crate::options::{OffsetDisambiguation, RelativeTo}; + use crate::ZonedDateTime; + use alloc::string::ToString; + // TODO(#199): Make this work with Windows + // This should also ideally use the compiled data APIs and live under builtins/compiled + if cfg!(not(windows)) { + let one = Duration::from_partial_duration(PartialDuration { + hours: Some(79), + minutes: Some(10), + ..Default::default() + }) + .unwrap(); + let two = Duration::from_partial_duration(PartialDuration { + days: Some(3), + hours: Some(7), + seconds: Some(630), + ..Default::default() + }) + .unwrap(); + let three = Duration::from_partial_duration(PartialDuration { + days: Some(3), + hours: Some(6), + minutes: Some(50), + ..Default::default() + }) + .unwrap(); + + let mut arr = [&one, &two, &three]; + arr.sort_by(|a, b| Duration::compare_with_provider(a, b, None, &*FS_TZ_PROVIDER).unwrap()); + assert_eq!( + arr.map(ToString::to_string), + [&three, &one, &two].map(ToString::to_string) + ); + + // Sorting relative to a date, taking DST changes into account: + let zdt = ZonedDateTime::from_str_with_provider( + "2020-11-01T00:00-07:00[America/Los_Angeles]", + Default::default(), + OffsetDisambiguation::Reject, + &*FS_TZ_PROVIDER, + ) + .unwrap(); + arr.sort_by(|a, b| { + Duration::compare_with_provider( + a, + b, + Some(RelativeTo::ZonedDateTime(zdt.clone())), + &*FS_TZ_PROVIDER, + ) + .unwrap() + }); + assert_eq!( + arr.map(ToString::to_string), + [&one, &three, &two].map(ToString::to_string) + ) + } +} /* TODO: Uncomment diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index bb2fbd7d6..76fa8d015 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -7,9 +7,15 @@ pub mod core; pub use core::*; #[cfg(feature = "compiled_data")] +use crate::tzdb::CompiledTzdbProvider; +#[cfg(all(test, feature = "compiled_data"))] use crate::tzdb::FsTzdbProvider; #[cfg(feature = "compiled_data")] use std::sync::LazyLock; #[cfg(feature = "compiled_data")] -pub static TZ_PROVIDER: LazyLock = LazyLock::new(FsTzdbProvider::default); +pub static TZ_PROVIDER: LazyLock = + LazyLock::new(CompiledTzdbProvider::default); + +#[cfg(all(test, feature = "compiled_data"))] +pub(crate) static FS_TZ_PROVIDER: LazyLock = LazyLock::new(FsTzdbProvider::default); diff --git a/src/tzdb.rs b/src/tzdb.rs index 0d9475988..88ebfb8af 100644 --- a/src/tzdb.rs +++ b/src/tzdb.rs @@ -610,6 +610,104 @@ fn offset_range(offset_one: i64, offset_two: i64) -> core::ops::Range { offset_two..offset_one } +/// Timezone provider that uses compiled data. +/// +/// Currently uses jiff_tzdb and performs parsing; will eventually +/// use pure compiled data () +#[derive(Debug, Default)] +pub struct CompiledTzdbProvider { + cache: RwLock>, +} + +impl CompiledTzdbProvider { + /// Get timezone data for a single identifier + pub fn get(&self, identifier: &str) -> TemporalResult { + if let Some(tzif) = self + .cache + .read() + .map_err(|_| TemporalError::general("poisoned RWLock"))? + .get(identifier) + { + return Ok(tzif.clone()); + } + + let (identifier, tzif) = { + let Some((canonical_name, data)) = jiff_tzdb::get(identifier) else { + return Err( + TemporalError::range().with_message("Time zone identifier does not exist.") + ); + }; + (canonical_name, Tzif::from_bytes(data)?) + }; + + Ok(self + .cache + .write() + .map_err(|_| TemporalError::general("poisoned RWLock"))? + .entry(identifier.into()) + .or_insert(tzif) + .clone()) + } +} + +impl TimeZoneProvider for CompiledTzdbProvider { + fn check_identifier(&self, identifier: &str) -> bool { + if let Some(index) = SINGLETON_IANA_NORMALIZER.available_id_index.get(identifier) { + return SINGLETON_IANA_NORMALIZER + .normalized_identifiers + .get(index) + .is_some(); + } + false + } + + fn get_named_tz_epoch_nanoseconds( + &self, + identifier: &str, + iso_datetime: IsoDateTime, + ) -> TemporalResult> { + let epoch_nanos = iso_datetime.as_nanoseconds()?; + let seconds = (epoch_nanos.0 / 1_000_000_000) as i64; + let tzif = self.get(identifier)?; + let local_time_record_result = tzif.v2_estimate_tz_pair(&Seconds(seconds))?; + let result = match local_time_record_result { + LocalTimeRecordResult::Empty => Vec::default(), + LocalTimeRecordResult::Single(r) => { + let epoch_ns = + EpochNanoseconds::try_from(epoch_nanos.0 - seconds_to_nanoseconds(r.offset))?; + vec![epoch_ns] + } + LocalTimeRecordResult::Ambiguous { std, dst } => { + let std_epoch_ns = + EpochNanoseconds::try_from(epoch_nanos.0 - seconds_to_nanoseconds(std.offset))?; + let dst_epoch_ns = + EpochNanoseconds::try_from(epoch_nanos.0 - seconds_to_nanoseconds(dst.offset))?; + vec![std_epoch_ns, dst_epoch_ns] + } + }; + Ok(result) + } + + fn get_named_tz_offset_nanoseconds( + &self, + identifier: &str, + utc_epoch: i128, + ) -> TemporalResult { + let tzif = self.get(identifier)?; + let seconds = (utc_epoch / 1_000_000_000) as i64; + tzif.get(&Seconds(seconds)) + } + + fn get_named_tz_transition( + &self, + _identifier: &str, + _epoch_nanoseconds: i128, + _direction: TransitionDirection, + ) -> TemporalResult> { + Err(TemporalError::general("Not yet implemented.")) + } +} + #[derive(Debug, Default)] pub struct FsTzdbProvider { cache: RwLock>,