From a1b854c17a092947725c31e42965c8b7ae486490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sat, 30 Dec 2023 12:23:36 +0100 Subject: [PATCH] Add `tzdb_data` crate Splitting up the generated data makes maintenance easier. --- .github/workflows/ci.yml | 84 ++----------- .gitmodules | 2 +- Cargo.toml | 11 +- Makefile | 12 +- benchmarks/Cargo.toml | 2 +- benchmarks/benches/by-name.rs | 10 +- examples/current-time/src/main.rs | 4 +- fuzz/Cargo.toml | 2 +- fuzz/bin/afl.rs | 2 +- fuzz/bin/honggfuzz.rs | 2 +- fuzz/bin/libfuzzer.rs | 2 +- make-tzdb/generate_lookup_table.py | 27 ++--- make-tzdb/src/main.rs | 155 ++++++------------------ make-tzdb/src/parse.rs | 16 +-- src/generated | 1 - src/lib.rs | 21 +--- testing/Cargo.toml | 2 +- tzdb_data/Cargo.toml | 19 +++ tzdb_data/LICENSE.md | 16 +++ tzdb_data/README.md | 1 + tzdb_data/src/generated | 1 + tzdb_data/src/lib.rs | 186 +++++++++++++++++++++++++++++ 22 files changed, 328 insertions(+), 250 deletions(-) delete mode 160000 src/generated create mode 100644 tzdb_data/Cargo.toml create mode 100644 tzdb_data/LICENSE.md create mode 100644 tzdb_data/README.md create mode 160000 tzdb_data/src/generated create mode 100644 tzdb_data/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df0421d..2328751 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,8 +69,8 @@ jobs: RUSTC_BOOTSTRAP: 1 run: cargo generate-lockfile ${{ matrix.versions }} - - run: cargo check --package tzdb --all-targets - - run: cargo clippy --package tzdb --all-targets -- -D warnings + - run: cargo check --workspace --all-targets + - run: cargo clippy --workspace --all-targets -- -D warnings clippy-pedantic: runs-on: ubuntu-20.04 @@ -85,7 +85,7 @@ jobs: with: components: clippy - - run: cargo clippy --package tzdb --all-targets -- -D clippy::pedantic + - run: cargo clippy --workspace --all-targets -- -D clippy::pedantic test: runs-on: ubuntu-20.04 @@ -100,9 +100,9 @@ jobs: with: components: clippy - - run: cargo check --package tzdb --all-targets - - run: cargo clippy --package tzdb --all-targets -- -D warnings - - run: cargo test --package tzdb --all-targets + - run: cargo check --workspace --all-targets + - run: cargo clippy --workspace --all-targets -- -D warnings + - run: cargo test --workspace --all-targets - run: cargo run --package current-time - run: cd testing && cargo test @@ -122,10 +122,10 @@ jobs: components: miri - name: Test (tzdb) - run: cargo miri test --package tzdb --all-targets + run: cargo miri test --workspace --all-targets - name: Test (testing) - run: cd testing && cargo miri test --package tzdb --all-targets + run: cd testing && cargo miri test --workspace --all-targets cross-miri: strategy: @@ -153,7 +153,7 @@ jobs: target: ${{ matrix.target }} - name: Test - run: cargo miri test --package tzdb --target ${{ matrix.target }} + run: cargo miri test --workspace --target ${{ matrix.target }} doc: runs-on: ubuntu-20.04 @@ -169,7 +169,7 @@ jobs: toolchain: nightly components: rust-docs - - run: cargo doc --package tzdb --all-features --no-deps + - run: cargo doc --workspace --all-features --no-deps env: RUSTDOCFLAGS: -D warnings --cfg docsrs @@ -189,68 +189,6 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} - build-cross: - strategy: - fail-fast: false - matrix: - target: - - x86_64-unknown-freebsd - - x86_64-unknown-illumos - - x86_64-unknown-netbsd - - x86_64-linux-android - - i686-linux-android - - arm-linux-androideabi - - aarch64-linux-android - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup Rust - uses: ./.github/actions/setup-rust - with: - toolchain: stable - target: ${{ matrix.target }} - components: clippy - - - name: Install "cross" - run: curl --location --silent --show-error --fail https://github.com/cargo-bins/cargo-quickinstall/releases/download/cross-0.2.5-x86_64-unknown-linux-gnu/cross-0.2.5-x86_64-unknown-linux-gnu.tar.gz | tar -xzvvf - -C $HOME/.cargo/bin - - run: cross check --target ${{ matrix.target }} --package tzdb --all-targets - - run: cross clippy --target ${{ matrix.target }} --package tzdb --all-targets -- -D warnings - - run: cross build --target ${{ matrix.target }} --package tzdb --lib - - run: cross build --target ${{ matrix.target }} --examples - - build-cross-ios: - strategy: - fail-fast: false - matrix: - target: - - aarch64-apple-ios-sim - - aarch64-apple-ios - - x86_64-apple-ios - runs-on: macos-12 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup Rust - uses: ./.github/actions/setup-rust - with: - toolchain: stable - target: ${{ matrix.target }} - components: clippy - - - name: Install "cross" - run: curl --location --silent --show-error --fail https://github.com/cargo-bins/cargo-quickinstall/releases/download/cross-0.2.5-x86_64-apple-darwin/cross-0.2.5-x86_64-apple-darwin.tar.gz | tar -xzvvf - -C $HOME/.cargo/bin - - run: cross check --target ${{ matrix.target }} --package tzdb --all-targets - - run: cross clippy --target ${{ matrix.target }} --package tzdb --all-targets -- -D warnings - - run: cross build --target ${{ matrix.target }} --package tzdb --lib - - run: cross build --target ${{ matrix.target }} --examples - package: runs-on: ubuntu-20.04 steps: @@ -264,7 +202,7 @@ jobs: with: toolchain: stable - - run: cargo package --package tzdb + - run: cargo package --workspace devskim: name: DevSkim diff --git a/.gitmodules b/.gitmodules index 91b8050..5187e85 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "src/generated"] - path = src/generated + path = tzdb_data/src/generated url = ./ branch = generated diff --git a/Cargo.toml b/Cargo.toml index 223d77f..b986860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tzdb" -version = "0.6.0" +version = "0.6.1-pre.1" edition = "2018" authors = ["René Kijewski "] repository = "https://github.com/Kijewski/tzdb" @@ -12,8 +12,9 @@ readme = "README.md" # rust-version = "1.56" [dependencies] -tz-rs = { version = "^0.6.14", default-features = false, features = ["const", "std"] } iana-time-zone = { version = "^0.1.50", default-features = false, features = ["fallback"], optional = true } +tzdb_data = { version = "0.1.0-pre.1", default-features = false, path = "tzdb_data" } +tz-rs = { version = "^0.6.14", default-features = false, features = ["const", "std"] } [features] default = ["local"] @@ -25,4 +26,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [workspace] -members = [".", "examples/current-time"] +members = [ + ".", + "examples/current-time", + "tzdb_data", +] diff --git a/Makefile b/Makefile index 4bb9939..b0211f2 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,16 @@ -.DELETE_ON_ERROR: +#.DELETE_ON_ERROR: TZDB_VERSION := tzdb-2023d -src/generated/mod.rs: tmp/${TZDB_VERSION}/usr/share/zoneinfo/ tzdb.tar.lz.sha +tzdb_data/src/generated/mod.rs: tmp/${TZDB_VERSION}/usr/share/zoneinfo/ tzdb.tar.lz.sha cd make-tzdb && cargo r -- ../$(@D) ../$< ../tzdb.tar.lz.sha - cargo +nightly fmt -- $(@D)/mod.rs $(@D)/by_name.rs $(@D)/raw_tzdata.rs $(@D)/test_all_names.rs + cargo +nightly fmt -- \ + $(@D)/by_name.rs \ + $(@D)/mod.rs \ + $(@D)/raw_tzdata.rs \ + $(@D)/test_all_names.rs \ + $(@D)/time_zone.rs \ + $(@D)/tzdata.rs tmp/${TZDB_VERSION}/usr/share/zoneinfo/: tmp/${TZDB_VERSION}/ cd tmp/${TZDB_VERSION}/ && make PACKRATDATA=backzone PACKRATLIST=zone.tab TOPDIR="." install diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 300bb55..a4cd99f 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" publish = false [dependencies] -tzdb = { version = "0.6.0", path = ".." } +tzdb_data = { path = "../tzdb_data" } [dev-dependencies] criterion = { version = "0.5", default-features = false } diff --git a/benchmarks/benches/by-name.rs b/benchmarks/benches/by-name.rs index 32dec8a..56e748f 100644 --- a/benchmarks/benches/by-name.rs +++ b/benchmarks/benches/by-name.rs @@ -4,14 +4,14 @@ use std::time::{Duration, Instant}; use rand::seq::{IteratorRandom, SliceRandom}; use rand::SeedableRng; use rand_xoshiro::Xoroshiro128PlusPlus; -use tzdb::{raw_tz_by_name, TZ_NAMES}; +use tzdb_data::{find_raw, TZ_NAMES}; fn benchmark_by_name(c: &mut criterion::Criterion) { // collect all names with "random" capitalization let mut names: Vec<(String, usize)> = TZ_NAMES .iter() .flat_map(|&name| { - let raw_len = raw_tz_by_name(name).unwrap().len(); + let raw_len = find_raw(name.as_bytes()).unwrap().len(); let upper = name.to_uppercase(); let lower = name.to_lowercase(); let inverted = name @@ -75,12 +75,12 @@ fn benchmark_by_name(c: &mut criterion::Criterion) { let city = std::str::from_utf8(city).unwrap(); let raw_name = format!("{}/{}", continent, city); - let raw_len = crate::raw_tz_by_name(&raw_name).unwrap_or_default().len(); + let raw_len = crate::find_raw(raw_name.as_bytes()).unwrap_or_default().len(); names.push((raw_name, raw_len)); } // benchmark per name lookup time - c.bench_function("tzdb::raw_tz_by_name", |b| { + c.bench_function("tzdb::find_raw", |b| { b.iter_custom(|iters| { let mut nanos = 0; for i in 0..iters { @@ -91,7 +91,7 @@ fn benchmark_by_name(c: &mut criterion::Criterion) { for &(ref name, raw_len) in names { assert_eq!( raw_len, - crate::raw_tz_by_name(name).unwrap_or_default().len(), + crate::find_raw(name.as_bytes()).unwrap_or_default().len(), ); } nanos += start.elapsed().as_nanos(); diff --git a/examples/current-time/src/main.rs b/examples/current-time/src/main.rs index 667ec4e..09daf74 100644 --- a/examples/current-time/src/main.rs +++ b/examples/current-time/src/main.rs @@ -1,10 +1,12 @@ +#![allow(clippy::pedantic)] + use std::env::args; use std::process::exit; use tzdb::{local_tz, now, time_zone, tz_by_name, TZ_NAMES}; pub fn main() -> Result<(), now::NowError> { - let mut args = args().into_iter().fuse(); + let mut args = args().fuse(); let exe = args.next(); let exe = exe.as_deref().unwrap_or("current-time"); let argument = args.next(); diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index bc4dcf9..d97d0e5 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" cargo-fuzz = true [dependencies] -tzdb = { version = "0.6.0", path = ".." } +tzdb_data = { path = "../tzdb_data" } afl = { version = "0.15", optional = true } honggfuzz = { version = "0.5", optional = true } diff --git a/fuzz/bin/afl.rs b/fuzz/bin/afl.rs index 40c58ed..c7d612a 100644 --- a/fuzz/bin/afl.rs +++ b/fuzz/bin/afl.rs @@ -1,5 +1,5 @@ fn main() { afl::fuzz!(|name: &[u8]| { - let _ = tzdb::tz_by_name(name); + let _ = tzdb_data::find_tz(name); }); } diff --git a/fuzz/bin/honggfuzz.rs b/fuzz/bin/honggfuzz.rs index db72f3f..c7edce1 100644 --- a/fuzz/bin/honggfuzz.rs +++ b/fuzz/bin/honggfuzz.rs @@ -1,7 +1,7 @@ fn main() { loop { honggfuzz::fuzz!(|name: &[u8]| { - let _ = tzdb::tz_by_name(name); + let _ = tzdb_data::find_tz(name); }); } } diff --git a/fuzz/bin/libfuzzer.rs b/fuzz/bin/libfuzzer.rs index c586f6c..002b273 100644 --- a/fuzz/bin/libfuzzer.rs +++ b/fuzz/bin/libfuzzer.rs @@ -1,5 +1,5 @@ #![no_main] libfuzzer_sys::fuzz_target!(|name: &[u8]| { - let _ = tzdb::tz_by_name(name); + let _ = tzdb_data::find_tz(name); }); diff --git a/make-tzdb/generate_lookup_table.py b/make-tzdb/generate_lookup_table.py index 3001108..6b554c7 100644 --- a/make-tzdb/generate_lookup_table.py +++ b/make-tzdb/generate_lookup_table.py @@ -137,13 +137,14 @@ def convert(stdin, stdout): print('use tz::TimeZoneRef;', file=stdout) print(file=stdout) + print('use crate::eq_ignore_ascii_case;', file=stdout) print('use super::raw_tzdata;', file=stdout) print('use super::tzdata;', file=stdout) print(file=stdout) print('#[derive(Clone, Copy)]', file=stdout) print('#[repr(u16)]', file=stdout) - print('enum Index {', file=stdout) + print('pub(crate) enum Index {', file=stdout) idx = 0 for entry in table: match entry: @@ -174,7 +175,7 @@ def convert(stdin, stdout): print('];', file=stdout) print(file=stdout) - print(f'const TIME_ZONES: [&TimeZoneRef<\'static>; {entry_count}] = [', file=stdout) + print(f'pub(crate) const TIME_ZONES: [&TimeZoneRef<\'static>; {entry_count}] = [', file=stdout) for entry in table: match entry: case (name, canon): @@ -182,7 +183,7 @@ def convert(stdin, stdout): print('];', file=stdout) print(file=stdout) - print(f'const RAW_TIME_ZONES: [&[u8]; {entry_count}] = [', file=stdout) + print(f'pub(crate) const RAW_TIME_ZONES: [&[u8]; {entry_count}] = [', file=stdout) for entry in table: match entry: case (name, canon): @@ -197,7 +198,7 @@ def convert(stdin, stdout): print(f'{max_hash_value + 1}];', file=stdout) print(file=stdout) - print('fn find_key(s: &[u8]) -> Option {', file=stdout) + print('pub(crate) const fn find_key(s: &[u8]) -> Option {', file=stdout) print(' let len = s.len();', file=stdout) print(f' if !matches!(len, {min_word_length}..={max_word_length}) {{', file=stdout) print(' return None;', file=stdout) @@ -239,8 +240,11 @@ def hash_add(idx, offs): print(f' if key > {max_hash_value} {{', file=stdout) print(' return None;', file=stdout) print(' }', file=stdout) - print(' let key = WORDLIST[key]?;', file=stdout) - print(' if !NAMES[key as u16 as usize].eq_ignore_ascii_case(s) {', file=stdout) + print(' let key = match WORDLIST[key] {', file=stdout) + print(' Some(key) => key,', file=stdout) + print(' None => return None,', file=stdout) + print(' };', file=stdout) + print(' if !eq_ignore_ascii_case(s, NAMES[key as u16 as usize]) {', file=stdout) print(' return None;', file=stdout) print(' }', file=stdout) print(file=stdout) @@ -248,17 +252,6 @@ def hash_add(idx, offs): print('}', file=stdout) print(file=stdout) - print('#[inline]') - print('pub(crate) fn find_tz(s: &[u8]) -> Option> {', file=stdout) - print(' Some(*TIME_ZONES[find_key(s)? as u16 as usize])', file=stdout) - print('}', file=stdout) - print(file=stdout) - - print('#[inline]') - print('pub(crate) fn find_raw(s: &[u8]) -> Option<&\'static [u8]> {', file=stdout) - print(' Some(RAW_TIME_ZONES[find_key(s)? as u16 as usize])', file=stdout) - print('}', file=stdout) - if __name__ == '__main__': basicConfig(level=INFO) diff --git a/make-tzdb/src/main.rs b/make-tzdb/src/main.rs index 28442ac..8ae36c2 100644 --- a/make-tzdb/src/main.rs +++ b/make-tzdb/src/main.rs @@ -196,14 +196,12 @@ pub fn main() -> anyhow::Result<()> { writeln!( f, - r#"// SPDX-License-Identifier: MIT-0 -// -// GENERATED FILE + r#"// GENERATED FILE // ALL CHANGES MADE IN THIS FOLDER WILL BE LOST! // // MIT No Attribution // -// Copyright 2022 René Kijewski +// Copyright 2022-2023 René Kijewski // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, @@ -221,88 +219,19 @@ pub fn main() -> anyhow::Result<()> { #[cfg(all(test, not(miri)))] mod test_all_names; + pub(crate) mod by_name; +mod raw_tzdata; +mod tzdata; + +/// All defined time zones statically accessible +pub mod time_zone; -pub(crate) const VERSION: &str = {version:?}; -pub(crate) const VERSION_HASH: &str = {hash:?}; - -pub(crate) const fn new_time_zone_ref( - transitions: &'static [tz::timezone::Transition], - local_time_types: &'static [tz::LocalTimeType], - leap_seconds: &'static [tz::timezone::LeapSecond], - extra_rule: &'static Option, -) -> tz::timezone::TimeZoneRef<'static> {{ - match tz::timezone::TimeZoneRef::new(transitions, local_time_types, leap_seconds, extra_rule) {{ - Ok(value) => value, - Err(_) => {{ - #[allow(unconditional_panic)] - let err = [][0]; - err - }}, - }} -}} - -pub(crate) const fn new_local_time_type( - ut_offset: i32, - is_dst: bool, - time_zone_designation: Option<&[u8]>, -) -> tz::LocalTimeType {{ - match tz::LocalTimeType::new(ut_offset, is_dst, time_zone_designation) {{ - Ok(value) => value, - Err(_) => {{ - #[allow(unconditional_panic)] - let err = [][0]; - err - }}, - }} -}} - -pub(crate) const fn new_transition( - unix_leap_time: i64, - local_time_type_index: usize, -) -> tz::timezone::Transition {{ - tz::timezone::Transition::new(unix_leap_time, local_time_type_index) -}} - -pub(crate) const fn new_alternate_time( - std: tz::LocalTimeType, - dst: tz::LocalTimeType, - dst_start: tz::timezone::RuleDay, - dst_start_time: i32, - dst_end: tz::timezone::RuleDay, - dst_end_time: i32, -) -> tz::timezone::AlternateTime {{ - match tz::timezone::AlternateTime::new( - std, - dst, - dst_start, - dst_start_time, - dst_end, - dst_end_time, - ) {{ - Ok(value) => value, - Err(_) => {{ - #[allow(unconditional_panic)] - let err = [][0]; - err - }}, - }} -}} - -pub(crate) const fn new_month_week_day( - month: u8, - week: u8, - week_day: u8, -) -> tz::timezone::MonthWeekDay {{ - match tz::timezone::MonthWeekDay::new(month, week, week_day) {{ - Ok(value) => value, - Err(_) => {{ - #[allow(unconditional_panic)] - let err = [][0]; - err - }}, - }} -}} +/// The version of the source Time Zone Database +pub const VERSION: &str = {version:?}; + +/// The SHA512 hash of the source Time Zone Database (using the "Complete Distribution") +pub const VERSION_HASH: &str = {hash:?}; "# )?; @@ -344,12 +273,12 @@ pub(crate) const fn new_month_week_day( writeln!(r, "fn test() {{")?; writeln!( r, - " use crate::{{time_zone, tz_by_name, raw_tz_by_name}};" + " use crate::{{find_raw, find_tz, time_zone}};" )?; writeln!(r)?; writeln!( r, - " const TIME_ZONES: &[(tz::TimeZoneRef<'static>, &[u8], &[&str])] = &[" + " const TIME_ZONES: &[(&tz::TimeZoneRef<'static>, &[u8], &[&[u8]])] = &[" )?; for entries in entries_by_bytes.values() { for entry in entries { @@ -363,7 +292,7 @@ pub(crate) const fn new_month_week_day( }; writeln!(r, " (")?; - writeln!(r, " time_zone::{name},")?; + writeln!(r, " &time_zone::{name},")?; writeln!(r, " time_zone::{raw_name},")?; writeln!(r, " &[")?; for f in [ @@ -404,7 +333,7 @@ pub(crate) const fn new_month_week_day( .collect() }, ] { - writeln!(r, " {:?},", f(&entry.full))?; + writeln!(r, " b{:?},", f(&entry.full))?; } writeln!(r, " ],")?; writeln!(r, " ),")?; @@ -418,28 +347,27 @@ pub(crate) const fn new_month_week_day( )?; writeln!( r, - " assert_eq!(Some(tz), tz_by_name(name), \"tz_by_name({{:?}})\", name);", + " assert_eq!(Some(tz), find_tz(name), \"find_tz({{:?}})\", name);", )?; writeln!( r, - " assert_eq!(Some(raw), raw_tz_by_name(name), \"raw_tz_by_name({{:?}})\", name);", + " assert_eq!(Some(raw), find_raw(name), \"find_raw({{:?}})\", name);", )?; writeln!(r, " }} }}")?; writeln!(r, "}}")?; write_string(r, target_dir.join("test_all_names.rs"))?; // all known time zones as reference to (raw_)tzdata - writeln!(f, "/// All defined time zones statically accessible")?; - writeln!(f, "pub mod time_zone {{")?; + let mut r = String::new(); for (folder, entries) in &entries_by_major { if let Some(folder) = folder { - writeln!(f, "/// {}", folder)?; - writeln!(f, "pub mod {} {{", folder)?; + writeln!(r, "/// {}", folder)?; + writeln!(r, "pub mod {} {{", folder)?; } for entry in entries { - writeln!(f, " /// Time zone data for {},", entry.full)?; + writeln!(r, " /// Time zone data for {},", entry.full)?; writeln!( - f, + r, "pub const {}: tz::TimeZoneRef<'static> = crate::generated::tzdata::{};", entry.minor, entry.canon, )?; @@ -447,23 +375,22 @@ pub(crate) const fn new_month_week_day( for entry in entries { writeln!( - f, + r, " /// Raw, unparsed time zone data for {},", entry.full )?; writeln!( - f, + r, "pub const RAW_{}: &[u8] = crate::generated::raw_tzdata::{};", entry.minor, entry.canon, )?; } if folder.is_some() { - writeln!(f, "}}")?; + writeln!(r, "}}")?; } } - writeln!(f, "}}")?; - writeln!(f)?; + write_string(r, target_dir.join("time_zone.rs"))?; // list of known time zone names let mut time_zones_list = entries_by_major @@ -472,38 +399,32 @@ pub(crate) const fn new_month_week_day( .map(|entry| entry.full.as_str()) .collect_vec(); time_zones_list.sort_by_key(|l| l.to_ascii_lowercase()); + writeln!(f, "/// A list of all known time zones")?; writeln!( f, - "pub(crate) const TIME_ZONES_LIST: [&str; {}] = include!(\"time_zones_list.inc.rs\");", - time_zones_list.len() + "pub const TZ_NAMES: &[&str] = &[", )?; - writeln!(f)?; - let mut r = String::new(); - writeln!(r, "[")?; for name in time_zones_list { - writeln!(r, "{:?},", name)?; + writeln!(f, " {:?},", name)?; } - writeln!(r, "]")?; - write_string(r, target_dir.join("time_zones_list.inc.rs"))?; + writeln!(f, "];")?; + writeln!(f)?; // parsed time zone data by canonical name - writeln!(f, "mod tzdata {{")?; - writeln!(f, " use tz::timezone::*;")?; + let mut r = String::new(); + writeln!(r, "use tz::timezone::*;")?; for (bytes, entries) in &entries_by_bytes { - writeln!(f)?; + writeln!(r)?; writeln!( - f, + r, "pub(crate) const {}: tz::TimeZoneRef<'static> = {};", &entries[0].canon, tz_convert(bytes), )?; } - writeln!(f, "}}")?; - writeln!(f)?; + write_string(r, target_dir.join("tzdata.rs"))?; // raw time zone data by canonical name - writeln!(f, "mod raw_tzdata;")?; - writeln!(f)?; let mut r = String::new(); for (bytes, entries) in &entries_by_bytes { writeln!( diff --git a/make-tzdb/src/parse.rs b/make-tzdb/src/parse.rs index 3f95c1f..e930303 100644 --- a/make-tzdb/src/parse.rs +++ b/make-tzdb/src/parse.rs @@ -85,7 +85,7 @@ impl fmt::Display for Transition { } = &self; writeln!( f, - "crate::generated::new_transition({}, {})", + "crate::new_transition({}, {})", unix_leap_time, local_time_type_index )?; Ok(()) @@ -102,7 +102,7 @@ impl fmt::Display for LocalTimeType { let time_zone_designation = time_zone_designation.as_deref().map(DisplayTzd); writeln!( f, - "crate::generated::new_local_time_type({}, {}, {})", + "crate::new_local_time_type({}, {}, {})", ut_offset, is_dst, DisplayOption(time_zone_designation.as_ref()), @@ -119,7 +119,7 @@ impl fmt::Display for LeapSecond { } = self; writeln!( f, - "crate::generated::new_leap_second({}, {})", + "crate::new_leap_second({}, {})", unix_leap_time, correction )?; Ok(()) @@ -150,7 +150,7 @@ impl fmt::Display for AlternateTime { } = self; writeln!( f, - "crate::generated::new_alternate_time({}, {}, {}, {}, {}, {})", + "crate::new_alternate_time({}, {}, {}, {}, {}, {})", std, dst, dst_start, dst_start_time, dst_end, dst_end_time, ) } @@ -165,7 +165,7 @@ impl fmt::Display for MonthWeekDay { } = self; writeln!( f, - "crate::generated::new_month_week_day({}, {}, {})", + "crate::new_month_week_day({}, {}, {})", month, week, week_day ) } @@ -174,14 +174,14 @@ impl fmt::Display for MonthWeekDay { impl fmt::Display for Julian0WithLeap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Julian0WithLeap::Julian0WithLeap(t) = self; - writeln!(f, "crate::generated::new_julian0_with_leap({})", t) + writeln!(f, "crate::new_julian0_with_leap({})", t) } } impl fmt::Display for Julian1WithoutLeap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Julian1WithoutLeap::Julian1WithoutLeap(t) = self; - writeln!(f, "crate::generated::new_julian1_without_leap({})", t) + writeln!(f, "crate::new_julian1_without_leap({})", t) } } @@ -239,7 +239,7 @@ impl fmt::Display for TimeZone { } = self; writeln!( f, - "crate::generated::new_time_zone_ref(&{}, &{}, &{}, &{})", + "crate::new_time_zone_ref(&{}, &{}, &{}, &{})", DisplayVec(transitions), DisplayVec(local_time_types), DisplayVec(leap_seconds), diff --git a/src/generated b/src/generated deleted file mode 160000 index 1b3e5a1..0000000 --- a/src/generated +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1b3e5a186d3fc93554ed6b32b40533f08eb60fda diff --git a/src/lib.rs b/src/lib.rs index dd7282a..bf3e81b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // -// Copyright 2022 René Kijewski +// Copyright 2022-2024 René Kijewski // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ // limitations under the License. #![cfg_attr(docsrs, feature(doc_cfg))] -#![allow(unused_attributes)] +#![allow(unknown_lints)] #![forbid(unsafe_code)] #![warn(absolute_paths_not_starting_with_crate)] #![warn(elided_lifetimes_in_paths)] @@ -86,16 +86,10 @@ extern crate alloc; #[cfg(docsrs)] pub mod changelog; -mod generated; pub mod now; -pub use crate::generated::time_zone; - -/// The version of the source Time Zone Database -pub const VERSION: &str = generated::VERSION; - -/// The SHA512 hash of the source Time Zone Database (using the "Complete Distribution") -pub const VERSION_HASH: &str = generated::VERSION_HASH; +#[cfg_attr(docsrs, doc(no_inline))] +pub use tzdb_data::{time_zone, TZ_NAMES, VERSION, VERSION_HASH}; /// Find a time zone by name, e.g. `"Europe/Berlin"` (case-insensitive) /// @@ -109,7 +103,7 @@ pub const VERSION_HASH: &str = generated::VERSION_HASH; /// ``` #[inline] pub fn tz_by_name>(s: S) -> Option> { - generated::by_name::find_tz(s.as_ref()) + Some(*tzdb_data::find_tz(s.as_ref())?) } /// Find the raw, unparsed time zone data by name, e.g. `"Europe/Berlin"` (case-insensitive) @@ -124,12 +118,9 @@ pub fn tz_by_name>(s: S) -> Option> { /// ``` #[inline] pub fn raw_tz_by_name>(s: S) -> Option<&'static [u8]> { - generated::by_name::find_raw(s.as_ref()) + tzdb_data::find_raw(s.as_ref()) } -/// A list of all known time zones -pub const TZ_NAMES: &[&str] = &crate::generated::TIME_ZONES_LIST; - /// Find the time zone of the current system /// /// This function uses [`iana_time_zone::get_timezone()`] in the background. diff --git a/testing/Cargo.toml b/testing/Cargo.toml index a333a60..8cdf27f 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" publish = false [dependencies] -tzdb = { version = "0.6.0", path = ".." } +tzdb = { path = ".." } proptest = "1.2.0" structmeta = "0.2.0" diff --git a/tzdb_data/Cargo.toml b/tzdb_data/Cargo.toml new file mode 100644 index 0000000..5cb796d --- /dev/null +++ b/tzdb_data/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tzdb_data" +version = "0.1.0-pre.1" +edition = "2018" +authors = ["René Kijewski "] +repository = "https://github.com/Kijewski/tzdb" +description = "Static, #![no_std] time zone information for tz-rs" +license = "MIT-0" +keywords = ["timezone", "no_std"] +categories = ["date-and-time", "no-std"] +readme = "README.md" +# rust-version = "1.56" + +[dependencies] +tz-rs = { version = "^0.6.14", default-features = false, features = ["const"] } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/tzdb_data/LICENSE.md b/tzdb_data/LICENSE.md new file mode 100644 index 0000000..79b5018 --- /dev/null +++ b/tzdb_data/LICENSE.md @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright 2022-2024 René Kijewski + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tzdb_data/README.md b/tzdb_data/README.md new file mode 100644 index 0000000..637af25 --- /dev/null +++ b/tzdb_data/README.md @@ -0,0 +1 @@ +Static, `#![no_std]` time zone information for tz-rs diff --git a/tzdb_data/src/generated b/tzdb_data/src/generated new file mode 160000 index 0000000..36045f0 --- /dev/null +++ b/tzdb_data/src/generated @@ -0,0 +1 @@ +Subproject commit 36045f081369dba1e46afa0ebd1b7cc0ba0cbfef diff --git a/tzdb_data/src/lib.rs b/tzdb_data/src/lib.rs new file mode 100644 index 0000000..21d6524 --- /dev/null +++ b/tzdb_data/src/lib.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT-0 +// +// Copyright 2022-2024 René Kijewski + +#![cfg_attr(docsrs, feature(doc_cfg))] +#![allow(unknown_lints)] +#![forbid(unsafe_code)] +#![warn(absolute_paths_not_starting_with_crate)] +#![warn(elided_lifetimes_in_paths)] +#![warn(explicit_outlives_requirements)] +#![warn(meta_variable_misuse)] +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(non_ascii_idents)] +#![warn(noop_method_call)] +#![warn(single_use_lifetimes)] +#![warn(trivial_casts)] +#![warn(unreachable_pub)] +#![warn(unused_extern_crates)] +#![warn(unused_lifetimes)] +#![warn(unused_results)] +#![allow(clippy::single_match_else)] +#![allow(clippy::type_complexity)] +#![no_std] + +//! Static, `#![no_std]` time zone information for tz-rs +//! + +mod generated; + +#[cfg_attr(docsrs, doc(inline))] +pub use crate::generated::{time_zone, TZ_NAMES, VERSION, VERSION_HASH}; + +/// Find a time zone by name, e.g. `"Europe/Berlin"` (case-insensitive) +/// +/// # Example +/// +/// ``` +/// assert_eq!( +/// &tzdb_data::time_zone::europe::BERLIN, +/// tzdb_data::find_tz(b"Europe/Berlin").unwrap(), +/// ); +/// ``` +#[inline] +#[must_use] +pub const fn find_tz(s: &[u8]) -> Option<&'static tz::TimeZoneRef<'static>> { + match generated::by_name::find_key(s) { + Some(key) => Some(generated::by_name::TIME_ZONES[key as u16 as usize]), + None => None, + } +} + +/// Find the raw, unparsed time zone data by name, e.g. `"Europe/Berlin"` (case-insensitive) +/// +/// # Example +/// +/// ``` +/// assert_eq!( +/// tzdb_data::time_zone::europe::RAW_BERLIN, +/// tzdb_data::find_raw(b"Europe/Berlin").unwrap(), +/// ); +/// ``` +#[inline] +#[must_use] +pub const fn find_raw(s: &[u8]) -> Option<&'static [u8]> { + match generated::by_name::find_key(s) { + Some(key) => Some(generated::by_name::RAW_TIME_ZONES[key as u16 as usize]), + None => None, + } +} + +#[allow(clippy::out_of_bounds_indexing)] +#[must_use] +const fn new_time_zone_ref( + transitions: &'static [tz::timezone::Transition], + local_time_types: &'static [tz::LocalTimeType], + leap_seconds: &'static [tz::timezone::LeapSecond], + extra_rule: &'static Option, +) -> tz::timezone::TimeZoneRef<'static> { + match tz::timezone::TimeZoneRef::new(transitions, local_time_types, leap_seconds, extra_rule) { + Ok(value) => value, + Err(_) => { + #[allow(unconditional_panic)] + let err = [][0]; + err + }, + } +} + +#[allow(clippy::out_of_bounds_indexing)] +#[must_use] +const fn new_local_time_type( + ut_offset: i32, + is_dst: bool, + time_zone_designation: Option<&[u8]>, +) -> tz::LocalTimeType { + match tz::LocalTimeType::new(ut_offset, is_dst, time_zone_designation) { + Ok(value) => value, + Err(_) => { + #[allow(unconditional_panic)] + let err = [][0]; + err + }, + } +} + +#[must_use] +const fn new_transition( + unix_leap_time: i64, + local_time_type_index: usize, +) -> tz::timezone::Transition { + tz::timezone::Transition::new(unix_leap_time, local_time_type_index) +} + +#[allow(clippy::out_of_bounds_indexing)] +#[must_use] +const fn new_alternate_time( + std: tz::LocalTimeType, + dst: tz::LocalTimeType, + dst_start: tz::timezone::RuleDay, + dst_start_time: i32, + dst_end: tz::timezone::RuleDay, + dst_end_time: i32, +) -> tz::timezone::AlternateTime { + match tz::timezone::AlternateTime::new( + std, + dst, + dst_start, + dst_start_time, + dst_end, + dst_end_time, + ) { + Ok(value) => value, + Err(_) => { + #[allow(unconditional_panic)] + let err = [][0]; + err + }, + } +} + +#[allow(clippy::out_of_bounds_indexing)] +#[must_use] +const fn new_month_week_day(month: u8, week: u8, week_day: u8) -> tz::timezone::MonthWeekDay { + match tz::timezone::MonthWeekDay::new(month, week, week_day) { + Ok(value) => value, + Err(_) => { + #[allow(unconditional_panic)] + let err = [][0]; + err + }, + } +} + +// This implementation allows for invalid equalities like `b'-' == b'\x7f'`, but that's OK. +// +// The only troublesome characters are: +// @ -> ` +// [ -> { +// \ -> | +// ] -> } +// ^ -> ~ +// _ -> DEL +// +// None the these characters have a "false lower case" variant which can occur in the input. +// This function is 40% faster than the variant in rust's core library, which is implemented +// more strictly. +#[inline] +#[must_use] +const fn eq_ignore_ascii_case(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + + // Cannot use for-loops in const fn. + let mut i = 0; + while i < a.len() { + if (a[i] | 0x20) != (b[i] | 0x20) { + return false; + } + i += 1; + } + + true +}