Skip to content

Add POSIX time zone string support to zoneinfo #265

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

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
12221b7
Implement zoneinfo parsing and tzif structures
nekevss Mar 31, 2025
b7607a0
Skip tests on windows, for obvious reasons
nekevss Apr 2, 2025
9b1497c
Fixes to zoneinfo + rename
nekevss Apr 4, 2025
9576622
Migrate temporal_provider + run bakeddata tool
nekevss Apr 4, 2025
7e4ce05
cargo fmt
nekevss Apr 4, 2025
2a03bad
Finish plugging in concept
nekevss Apr 4, 2025
0306fec
Fix some issues with zoneinfo
nekevss Apr 4, 2025
b2aefc8
Updates to tzif structs in provider
nekevss Apr 4, 2025
a12f14e
Add tzif to bakedata and run
nekevss Apr 4, 2025
3845c3c
Uncomment out no_std
nekevss Apr 4, 2025
d38a028
Make tzif struct fields pub
nekevss Apr 4, 2025
0d3230e
Change zoneinfo debug data approach
nekevss Apr 4, 2025
f9a6908
Remove generated baked data for now
nekevss Apr 4, 2025
da856a3
cargo fmt
nekevss Apr 4, 2025
a5577b4
Rename some structs
nekevss Apr 4, 2025
d8281cd
Merge branch 'main' into impl-zoneinfo-support
nekevss Apr 4, 2025
3fbf7cf
Some fixes + tests and general comments/notes
nekevss Apr 5, 2025
0bde4e8
Apply review feedback and fix bugs
nekevss Apr 15, 2025
f9e62ed
Merge branch 'main' into impl-zoneinfo-support
nekevss Apr 15, 2025
d036e49
Cleanup around the first transitions time type
nekevss Apr 17, 2025
42071e5
Merge branch 'main' into impl-zoneinfo-support
nekevss Apr 17, 2025
dbe95dc
Test local time type offsets as well + fixes
nekevss Apr 17, 2025
2f115fe
Adjustments to preserve local record order in tzif
nekevss Apr 17, 2025
b3059ee
Add some docs to zoneinfo + some api changes
nekevss Apr 22, 2025
d36bc8f
Updates to provider to address feedback
nekevss Apr 22, 2025
12e02ca
Merge branch 'main' into impl-zoneinfo-support
nekevss May 2, 2025
ed68510
Apply review feedback
nekevss May 3, 2025
ed4b8c5
General clean up and restructing after feedback
nekevss May 4, 2025
377095a
Rename from zoneinfo_compiler -> zoneinfo_rs
nekevss May 4, 2025
fd00296
Update tests to be data based
nekevss May 5, 2025
b2da01e
Adjust to functional iterator style
nekevss May 5, 2025
e6f985b
Fix lint
nekevss May 5, 2025
216b9d7
Update zoneinfo/src/rule.rs
nekevss May 5, 2025
75661dc
Merge branch 'main' into impl-zoneinfo-support
nekevss May 6, 2025
261926e
Add posix time zone string support
nekevss Apr 20, 2025
c9ea2b6
Merge branch 'main' into add-posix-tz-string
nekevss Jun 21, 2025
56ed71b
Address imports in timezone_provider
nekevss Jun 22, 2025
45839ec
Adjust features flag
nekevss Jun 22, 2025
0c0868b
Fix more of the imports
nekevss Jun 22, 2025
cee7767
Use zerovec over cow to handle posix
nekevss Jun 22, 2025
4fd4c85
Fix dependencies
nekevss Jun 22, 2025
7e434a2
Update CompiledTransitions to use struct over string
nekevss Jun 22, 2025
14848d5
Merge branch 'main' into add-posix-tz-string
nekevss Jul 4, 2025
137e5d7
Update datagen to use a PosixZone structure
nekevss Jul 4, 2025
85c5a44
Merge branch 'main' into add-posix-tz-string
nekevss Jul 6, 2025
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions provider/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ datagen = [
"dep:databake",
"dep:yoke",
"dep:serde_json",
"tinystr/serde",
"tinystr/databake",
"zerotrie/serde",
"zerotrie/databake",
"zerovec/serde",
Expand All @@ -36,6 +38,7 @@ std = []
# Provider dependency
zerotrie = "0.2.2"
zerovec = { version = "0.11.2", features = ["derive", "alloc"] }
tinystr = { workspace = true, features = ["zerovec"] }

# IANA dependency
zoneinfo_rs = { workspace = true, features = ["std"], optional = true }
Expand Down
1 change: 1 addition & 0 deletions provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

pub mod posix;
mod tzdb;
pub mod tzif;

Expand Down
130 changes: 130 additions & 0 deletions provider/src/posix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use tinystr::TinyAsciiStr;
#[cfg(feature = "datagen")]
use zoneinfo_rs::posix::{MonthWeekDay, PosixDate, PosixDateTime, PosixTimeZone, PosixTransition};

#[zerovec::make_ule(PosixZoneULE)]
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(
feature = "datagen",
derive(yoke::Yokeable, serde::Serialize, databake::Bake)
)]
#[cfg_attr(feature = "datagen", databake(path = timezone_provider::tzif))]
pub struct PosixZone {
abbr: TinyAsciiStr<5>,
offset: i64,
transition: Option<ZeroPosixTransition>,
}

#[cfg(feature = "datagen")]
impl From<&PosixTimeZone> for PosixZone {
fn from(value: &PosixTimeZone) -> Self {
let abbr = TinyAsciiStr::<5>::try_from_str(&value.abbr.formatted).unwrap();
let offset = value.offset.as_secs();
let transition = value
.transition_info
.as_ref()
.map(ZeroPosixTransition::from);

Self {
abbr,
offset,
transition,
}
}
}

#[zerovec::make_ule(PosixTransitionULE)]
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(
feature = "datagen",
derive(yoke::Yokeable, serde::Serialize, databake::Bake)
)]
#[cfg_attr(feature = "datagen", databake(path = timezone_provider::tzif))]
pub struct ZeroPosixTransition {
abbr: TinyAsciiStr<5>,
savings: i64,
start: ZeroTransitionDateTime,
end: ZeroTransitionDateTime,
}

#[cfg(feature = "datagen")]
impl From<&PosixTransition> for ZeroPosixTransition {
fn from(value: &PosixTransition) -> Self {
let abbr = TinyAsciiStr::<5>::try_from_str(&value.abbr.formatted).unwrap();
let savings = value.savings.as_secs();
let start = ZeroTransitionDateTime::from(&value.start);
let end = ZeroTransitionDateTime::from(&value.end);
Self {
abbr,
savings,
start,
end,
}
}
}

#[zerovec::make_ule(TransitionDateTimeULE)]
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(
feature = "datagen",
derive(yoke::Yokeable, serde::Serialize, databake::Bake)
)]
#[cfg_attr(feature = "datagen", databake(path = timezone_provider::tzif))]
pub struct ZeroTransitionDateTime {
/// The date at which a transition should occur.
date: ZeroTransitionDate,
/// The time of day in seconds.
time: i64,
}

#[cfg(feature = "datagen")]
impl From<&PosixDateTime> for ZeroTransitionDateTime {
fn from(value: &PosixDateTime) -> Self {
Self {
date: value.date.into(),
time: value.time.as_secs(),
}
}
}

#[zerovec::make_ule(DateULE)]
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(
feature = "datagen",
derive(yoke::Yokeable, serde::Serialize, databake::Bake)
)]
#[cfg_attr(feature = "datagen", databake(path = timezone_provider::tzif))]
pub struct ZeroTransitionDate {
kind: DateKind,
day: Option<u16>,
mwd: Option<(u8, u8, u8)>,
}

#[cfg(feature = "datagen")]
impl From<PosixDate> for ZeroTransitionDate {
fn from(value: PosixDate) -> Self {
let (kind, day, mwd) = match value {
PosixDate::JulianLeap(day) => (DateKind::Julian, Some(day), None),
PosixDate::JulianNoLeap(day) => (DateKind::JulianNoLeap, Some(day), None),
PosixDate::MonthWeekDay(MonthWeekDay(month, week, day)) => (
DateKind::MonthWeekDay,
None,
Some((month as u8, week, day as u8)),
),
};
Self { kind, day, mwd }
}
}
#[zerovec::make_ule(DateKindULE)]
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(
feature = "datagen",
derive(yoke::Yokeable, serde::Serialize, databake::Bake)
)]
#[cfg_attr(feature = "datagen", databake(path = timezone_provider::tzif))]
#[repr(u8)]
pub enum DateKind {
JulianNoLeap = 0,
Julian = 1,
MonthWeekDay = 2,
}
2 changes: 1 addition & 1 deletion provider/src/tzdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ pub enum IanaDataError {
Build(zerotrie::ZeroTrieBuildError),
}

#[cfg(feature = "datagen")]
impl IanaIdentifierNormalizer<'_> {
#[cfg(feature = "datagen")]
pub fn build(tzdata_path: &Path) -> Result<Self, IanaDataError> {
let provider = TzdbDataSource::try_from_zoneinfo_directory(tzdata_path)
.map_err(IanaDataError::Provider)?;
Expand Down
19 changes: 12 additions & 7 deletions provider/src/tzif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
#[cfg(feature = "datagen")]
use alloc::vec::Vec;

#[cfg(feature = "datagen")]
use std::{collections::BTreeMap, path::Path};
use zerotrie::ZeroAsciiIgnoreCaseTrie;
use zerovec::{vecs::Index32, VarZeroVec, ZeroVec};

#[cfg(feature = "datagen")]
use alloc::collections::BTreeMap;
#[cfg(feature = "datagen")]
use std::path::Path;
#[cfg(feature = "datagen")]
use zerotrie::ZeroTrieBuildError;
use zerovec::{vecs::Index32, VarZeroVec, ZeroVec};
#[cfg(feature = "datagen")]
use zoneinfo_rs::{compiler::CompiledTransitions, ZoneInfoCompiler, ZoneInfoData};

use crate::posix::PosixZone;
#[cfg(feature = "datagen")]
use crate::tzdb::TzdbDataSource;

Expand Down Expand Up @@ -47,7 +51,7 @@ pub struct ZeroTzif<'data> {
pub transition_types: ZeroVec<'data, u8>,
// NOTE: zoneinfo64 does a fun little bitmap str
pub types: ZeroVec<'data, LocalTimeRecord>,
pub posix: &'data str,
pub posix: PosixZone,
}

#[zerovec::make_ule(LocalTimeRecordULE)]
Expand All @@ -70,16 +74,17 @@ impl From<&zoneinfo_rs::tzif::LocalTimeRecord> for LocalTimeRecord {
}
}

#[cfg(feature = "datagen")]
impl ZeroTzif<'_> {
#[cfg(feature = "datagen")]
fn from_transition_data(data: &CompiledTransitions) -> Self {
let tzif = data.to_v2_data_block();
let transitions = ZeroVec::alloc_from_slice(&tzif.transition_times);
let transition_types = ZeroVec::alloc_from_slice(&tzif.transition_types);
let mapped_local_records: Vec<LocalTimeRecord> =
tzif.local_time_types.iter().map(Into::into).collect();
let types = ZeroVec::alloc_from_slice(&mapped_local_records);
let posix = "TODO";
// TODO: handle this much better.
let posix = PosixZone::from(&data.posix_time_zone);

Self {
transitions,
Expand All @@ -96,8 +101,8 @@ pub enum ZoneInfoDataError {
Build(ZeroTrieBuildError),
}

#[cfg(feature = "datagen")]
impl ZoneInfoProvider<'_> {
#[cfg(feature = "datagen")]
pub fn build(tzdata: &Path) -> Result<Self, ZoneInfoDataError> {
let tzdb_source = TzdbDataSource::try_from_zoneinfo_directory(tzdata).unwrap();
let compiled_transitions = ZoneInfoCompiler::new(tzdb_source.data.clone()).build();
Expand Down
17 changes: 13 additions & 4 deletions zoneinfo/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub struct CompiledTransitions {
/// The POSIX time zone string
///
/// This string should be used to calculate the time zone beyond the last available transition.
pub posix_string: String, // TODO: Implement POSIX string building
pub posix_time_zone: PosixTimeZone,
}

// NOTE: candidate for removal? Should this library offer TZif structs long term?
Expand All @@ -122,9 +122,10 @@ pub struct CompiledTransitionsMap {
// ==== ZoneInfoCompiler build / compile methods ====

use crate::{
posix::PosixTimeZone,
types::{QualifiedTimeKind, Time},
tzif::TzifBlockV2,
zone::ZoneBuildContext,
zone::{ZoneBuildContext, ZoneRecord},
ZoneInfoData,
};

Expand Down Expand Up @@ -185,15 +186,23 @@ impl ZoneInfoCompiler {
}
}

// TODO: POSIX tz string handling
let posix_time_zone = zone_table.get_posix_time_zone();

CompiledTransitions {
initial_record,
transitions,
posix_string: String::default(),
posix_time_zone,
}
}

pub fn get_posix_time_zone(&mut self, target: &str) -> Option<PosixTimeZone> {
self.associate();
self.data
.zones
.get(target)
.map(ZoneRecord::get_posix_time_zone)
}

/// Associates the current `ZoneTables` with their applicable rules.
pub fn associate(&mut self) {
for zones in self.data.zones.values_mut() {
Expand Down
1 change: 1 addition & 0 deletions zoneinfo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) mod utils;

pub mod compiler;
pub mod parser;
pub mod posix;
pub mod rule;
pub mod types;
pub mod tzif;
Expand Down
Loading