Skip to content

Switch compiled_data APIs to new CompiledTzdbProvider #346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 0 additions & 51 deletions src/builtins/compiled/duration/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::string::ToString;

use crate::{
options::{
OffsetDisambiguation, RelativeTo, RoundingIncrement, RoundingMode, RoundingOptions, Unit,
Expand Down Expand Up @@ -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 {
Expand Down
61 changes: 61 additions & 0 deletions src/builtins/core/duration/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 7 additions & 1 deletion src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FsTzdbProvider> = LazyLock::new(FsTzdbProvider::default);
pub static TZ_PROVIDER: LazyLock<CompiledTzdbProvider> =
LazyLock::new(CompiledTzdbProvider::default);

#[cfg(all(test, feature = "compiled_data"))]
pub(crate) static FS_TZ_PROVIDER: LazyLock<FsTzdbProvider> = LazyLock::new(FsTzdbProvider::default);
98 changes: 98 additions & 0 deletions src/tzdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,104 @@ fn offset_range(offset_one: i64, offset_two: i64) -> core::ops::Range<i64> {
offset_two..offset_one
}

/// Timezone provider that uses compiled data.
///
/// Currently uses jiff_tzdb and performs parsing; will eventually
/// use pure compiled data (<https://github.com/boa-dev/temporal/pull/264>)
#[derive(Debug, Default)]
pub struct CompiledTzdbProvider {
cache: RwLock<BTreeMap<String, Tzif>>,
}

impl CompiledTzdbProvider {
/// Get timezone data for a single identifier
pub fn get(&self, identifier: &str) -> TemporalResult<Tzif> {
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<Vec<EpochNanoseconds>> {
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<TimeZoneOffset> {
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<Option<EpochNanoseconds>> {
Err(TemporalError::general("Not yet implemented."))
}
}

#[derive(Debug, Default)]
pub struct FsTzdbProvider {
cache: RwLock<BTreeMap<String, Tzif>>,
Expand Down