diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b7bd9b..0476fe0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,11 @@ on: push: branches: - main + - v0.4.x pull_request: branches: - main + - v0.4.x schedule: - cron: "58 7 * * 3" @@ -31,7 +33,7 @@ jobs: fail-fast: false matrix: toolchain: - - "1.55" + - "1.60" - stable - nightly platform: @@ -82,12 +84,25 @@ jobs: - run: cd test-shadow && cargo run + test-artichoke: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Rust + uses: ./.github/actions/setup-rust + with: + key: artichoke + + - run: cd test-artichoke && cargo run + minimum-versions: strategy: fail-fast: false matrix: toolchain: - - "1.55" + - "1.60" - stable - nightly platform: @@ -201,7 +216,7 @@ jobs: fail-fast: false matrix: toolchain: - - "1.55" + - "1.60" - stable - nightly platform: @@ -224,14 +239,14 @@ jobs: run: cargo hack --version || cargo +stable install cargo-hack --force - name: Powerset - run: cargo hack test --feature-powerset --ignore-private --doc + run: cargo hack test --feature-powerset --exclude-features default --ignore-private --doc powerset-tests: strategy: fail-fast: false matrix: toolchain: - - "1.55" + - "1.60" - stable - nightly platform: @@ -254,7 +269,7 @@ jobs: run: cargo hack --version || cargo +stable install cargo-hack --force - name: Powerset - run: cargo hack test --feature-powerset --ignore-private --tests --lib + run: cargo hack test --feature-powerset --exclude-features default --ignore-private --tests --lib build-cross: strategy: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e62ee5..448c285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Changes between the versions +### 0.4.0 (????-??-??) + +* Increase msrv to 1.60 +* Add `now` module, which uses [`utcnow()`](https://crates.io/crates/utcnow), + and works in `#[no_std]` + ### 0.3.4 (2022-08-02) * Fix endianess issues for PowerPCs diff --git a/Cargo.toml b/Cargo.toml index 59538d7..378f38d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,54 +1,56 @@ [package] name = "tzdb" version = "0.3.4" -edition = "2018" +edition = "2021" authors = ["René Kijewski "] repository = "https://github.com/Kijewski/tzdb" description = "Static time zone information for tz-rs" license = "Apache-2.0" keywords = ["date", "time", "timezone", "zone", "calendar"] -categories = ["date-and-time"] +categories = ["date-and-time", "no-std"] readme = "README.md" -#rust-version = "1.55" +rust-version = "1.60" [dependencies] -# FIXME: using "default-features = false" is a breaking change -tz-rs = { version = "^0.6.12", features = ["const"] } +tz-rs = { version = "^0.6.12", default-features = false, features = ["const"] } # optional dependencies -iana-time-zone = { version = "^0.1.40", optional = true } -phf = { version = "^0.10.0", default-features = false, optional = true } -phf_shared = { version = "^0.10.0", default-features = false, optional = true } +iana-time-zone = { version = "^0.1.41", default-features = false, optional = true } +phf = { version = "^0.11.0", default-features = false, optional = true } +phf_shared = { version = "^0.11.0", default-features = false, optional = true } +utcnow = { version = "^0.1.3", default-features = false, optional = true } [dev-dependencies] -tz-rs = { version = "^0.6.12", default-features = false, features = ["const"] } proptest = "=1" -test-strategy = "=0.1.2" -structmeta = "=0.1.4" +test-strategy = "^0.2.0" +structmeta = "^0.1.5" [features] -default = ["by-name", "list", "local", "std", "fallback"] +default = ["by-name", "list", "local", "std", "fallback", "now"] -# Enables [tz_by_name()] to get a time zone at runtime by name: +# Enables [`tz_by_name()`] to get a time zone at runtime by name: by-name = ["phf", "phf_shared"] -# Enables [TZ_NAMES] to get a list of all shipped time zones: +# Enables [`TZ_NAMES`] to get a list of all shipped time zones: list = [] -# Enables [local_tz()] to get the system time zone: +# Enables [`local_tz()`] to get the system time zone: local = ["by-name", "iana-time-zone"] # Make the unparsed, binary tzdata of a time zone available: binary = [] # Enable features that need the standard library `std`: -std = ["alloc", "tz-rs/std"] +std = ["alloc", "tz-rs/std", "utcnow?/std"] # Enable features that need the standard library `alloc`: alloc = ["tz-rs/alloc"] -# Compile for unknown target platforms, too: -fallback = [] +# Do not fail to compile for unknown target platforms: +fallback = ["iana-time-zone?/fallback", "utcnow?/fallback"] + +# Enables the module [`now`] to get the current time: +now = ["utcnow"] [package.metadata.docs.rs] all-features = true diff --git a/README.md b/README.md index 8a16c81..ec056d7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Kijewski/tzdb/CI?logo=github)](https://github.com/Kijewski/tzdb/actions/workflows/ci.yml) [![Crates.io](https://img.shields.io/crates/v/tzdb?logo=rust)](https://crates.io/crates/tzdb) -![Minimum supported Rust version](https://img.shields.io/badge/rustc-1.55+-important?logo=rust "Minimum Supported Rust Version") +![Minimum supported Rust version](https://img.shields.io/badge/rustc-1.60+-important?logo=rust "Minimum Supported Rust Version") [![License](https://img.shields.io/crates/l/tzdb?color=informational&logo=apache)](/LICENSES) Static time zone information for [tz-rs](https://crates.io/crates/tz-rs). @@ -16,26 +16,36 @@ See the documentation for a full list the the contained time zones: ## Usage examples ```rust -use tz::{DateTime, TimeZone}; -use tzdb::{time_zone, tz_by_name}; +let time_zone = tzdb::local_tz()?; // tz::TimeZoneRef<'_> +let current_time = tzdb::now::local()?; // tz::DateTime // access by identifier -DateTime::now(time_zone::europe::KIEV); +let time_zone = tzdb::time_zone::europe::KIEV; +let current_time = tzdb::now::in_tz(tzdb::time_zone::europe::KIEV)?; + // access by name -DateTime::now(tz_by_name("Europe/Berlin").unwrap()); +let time_zone = tzdb::tz_by_name("Europe/Berlin")?; +let current_time = tzdb::now::in_named("Europe/Berlin")?; + // names are case insensitive -DateTime::now(tz_by_name("ArCtIc/LongYeArByEn").unwrap()); +let time_zone = tzdb::tz_by_name("ArCtIc/LongYeArByEn")?; +let current_time = tzdb::now::in_named("ArCtIc/LongYeArByEn")?; + +// provide a default time zone +let current_time = tzdb::now::local_or(tzdb::time_zone::GMT)?; +let current_time = tzdb::now::in_named_or(tzdb::time_zone::GMT, "Some/City")?; ``` ## Feature flags -* `by-name` *(enabled by default, enabled by* `local`*)* — enables tz_by_name() to get a time zone at runtime by name -* `list` *(enabled by default)* — enables TZ_NAMES to get a list of all shipped time zones -* `local` *(enabled by default)* — enables local_tz() to get the system time zone +* `by-name` (enabled by default, enabled by `local`) — enables `tz_by_name()` to get a time zone at runtime by name +* `list` (enabled by default) — enables `TZ_NAMES` to get a list of all shipped time zones +* `local` (enabled by default) — enables `local_tz()` to get the system time zone +* `now` (enabled by default) — enables the module `now` to get the current time * `binary` – make the unparsed, binary tzdata of a time zone available -* `std` *(enabled by default)* – enable features that need the standard library `std` -* `alloc` *(enabled by default, enabled by* `std`*)* – enable features that need the standard library `alloc` -* `fallback` *(enabled by default)* — compile for unknown target platforms, too +* `std` (enabled by default) – enable features that need the standard library `std` +* `alloc` (enabled by default, enabled by `std`) – enable features that need the standard library `alloc` +* `fallback` (enabled by default) — compile for unknown target platforms, too ## Git cloning diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..16caf02 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.60.0" diff --git a/examples/current-time.rs b/examples/current-time.rs index 41edaff..667ec4e 100644 --- a/examples/current-time.rs +++ b/examples/current-time.rs @@ -1,11 +1,9 @@ use std::env::args; use std::process::exit; -use tz::{DateTime, Result}; -use tzdb::time_zone::UTC; -use tzdb::{local_tz, tz_by_name, TZ_NAMES}; +use tzdb::{local_tz, now, time_zone, tz_by_name, TZ_NAMES}; -pub fn main() -> Result<()> { +pub fn main() -> Result<(), now::NowError> { let mut args = args().into_iter().fuse(); let exe = args.next(); let exe = exe.as_deref().unwrap_or("current-time"); @@ -42,10 +40,10 @@ pub fn main() -> Result<()> { eprintln!("No time zone selected, defaulting to the system time zone."); eprintln!("To see a list of all known time zones run: {} --list", exe); eprintln!(); - local_tz().unwrap_or(UTC) + local_tz().unwrap_or(time_zone::UTC) }; - let dt = DateTime::now(timezone)?; + let dt = now::in_tz(timezone)?; let dow = match DOW.get(dt.week_day() as usize) { Some(dow) => *dow, None => unreachable!("Impossible week_day: {}", dt.week_day()), diff --git a/src/lib.rs b/src/lib.rs index 62fdc6c..77ca387 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! //! [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Kijewski/tzdb/CI?logo=github)](https://github.com/Kijewski/tzdb/actions/workflows/ci.yml) //! [![Crates.io](https://img.shields.io/crates/v/tzdb?logo=rust)](https://crates.io/crates/tzdb) -//! ![Minimum supported Rust version](https://img.shields.io/badge/rustc-1.55+-important?logo=rust "Minimum Supported Rust Version") +//! ![Minimum supported Rust version](https://img.shields.io/badge/rustc-1.60+-important?logo=rust "Minimum Supported Rust Version") //! [![License](https://img.shields.io/crates/l/tzdb?color=informational&logo=apache)](/LICENSES) //! //! Static time zone information for [tz-rs](https://crates.io/crates/tz-rs). @@ -50,17 +50,27 @@ //! //! ## Usage examples //! -//! ``` -//! # #[cfg(feature = "by-name")] let _: () = { -//! use tz::{DateTime, TimeZone}; -//! use tzdb::{time_zone, tz_by_name}; +//! ```rust +//! # #[cfg(all(feature = "local", feature = "now"))] let _: () = { +//! // get the system time zone +//! let time_zone = tzdb::local_tz().unwrap(); // tz::TimeZoneRef<'_> +//! let current_time = tzdb::now::local().unwrap(); // tz::DateTime //! //! // access by identifier -//! DateTime::now(time_zone::europe::KIEV); +//! let time_zone = tzdb::time_zone::europe::KIEV; +//! let current_time = tzdb::now::in_tz(tzdb::time_zone::europe::KIEV).unwrap(); +//! //! // access by name -//! DateTime::now(tz_by_name("Europe/Berlin").unwrap()); +//! let time_zone = tzdb::tz_by_name("Europe/Berlin").unwrap(); +//! let current_time = tzdb::now::in_named("Europe/Berlin").unwrap(); +//! //! // names are case insensitive -//! DateTime::now(tz_by_name("ArCtIc/LongYeArByEn").unwrap()); +//! let time_zone = tzdb::tz_by_name("ArCtIc/LongYeArByEn").unwrap(); +//! let current_time = tzdb::now::in_named("ArCtIc/LongYeArByEn").unwrap(); +//! +//! // provide a default time zone +//! let current_time = tzdb::now::local_or(tzdb::time_zone::GMT).unwrap(); +//! let current_time = tzdb::now::in_named_or(tzdb::time_zone::GMT, "Some/City").unwrap(); //! # }; //! ``` //! @@ -72,6 +82,8 @@ //! //! * `local` *(enabled by default)* — enables [`local_tz()`] to get the system time zone //! +//! * `now` *(enabled by default)* — enables the module [`now`] to get the current time +//! //! * `binary` — make the unparsed, binary tzdata of a time zone available //! //! * `std` *(enabled by default)* — enable features that need the standard library [`std`] @@ -89,6 +101,9 @@ extern crate std; mod generated; #[cfg(feature = "by-name")] mod lower; +#[cfg(feature = "now")] +#[cfg_attr(docsrs, doc(cfg(feature = "now")))] +pub mod now; #[cfg(all(test, feature = "by-name"))] mod test_by_name; #[cfg(all(test, not(miri), feature = "by-name"))] diff --git a/src/now.rs b/src/now.rs new file mode 100644 index 0000000..bd1c797 --- /dev/null +++ b/src/now.rs @@ -0,0 +1,310 @@ +//! Get the current time in some time zone + +use core::fmt; + +#[cfg(feature = "local")] +use iana_time_zone::{get_timezone, GetTimezoneError}; +use tz::error::ProjectDateTimeError; +use tz::{DateTime, TimeZoneRef}; + +#[allow(unreachable_pub)] +mod opaque { + use core::fmt; + + #[derive(Copy, Clone)] + pub struct Opaque; + + #[derive(Copy, Clone)] + pub struct Impossible(core::convert::Infallible); + + impl fmt::Debug for Opaque { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("_") + } + } + + impl fmt::Debug for Impossible { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("_") + } + } +} + +/// An error as returned by [`local()`] and similart functions +/// +/// # See also: +/// +/// * [`local()`] / [`local_or()`] +/// * [`in_named()`] / [`in_named_or()`] +/// * [`in_tz()`] +#[allow(clippy::module_name_repetitions)] +#[derive(Debug)] +pub enum NowError { + /// Could not get time zone. Only returned by [`local()`]. + TimeZone( + #[cfg(feature = "local")] GetTimezoneError, + #[cfg(not(feature = "local"))] + #[doc(hidden)] + opaque::Impossible, + ), + /// Unknown system time zone. Only returned by [`local()`], and [`in_named()`]. + UnknownTimezone( + #[cfg(feature = "by-name")] + #[doc(hidden)] + opaque::Opaque, + #[cfg(not(feature = "by-name"))] + #[doc(hidden)] + opaque::Impossible, + ), + /// Could not project timestamp. + ProjectDateTime(ProjectDateTimeError), + /// Could not get current time. + Utcnow(utcnow::Error), +} + +#[cfg(feature = "local")] +#[cfg_attr(docsrs, doc(cfg(feature = "local")))] +impl From for NowError { + #[inline] + fn from(err: GetTimezoneError) -> Self { + Self::TimeZone(err) + } +} + +impl From for NowError { + #[inline] + fn from(err: ProjectDateTimeError) -> Self { + Self::ProjectDateTime(err) + } +} + +impl From for NowError { + #[inline] + fn from(err: utcnow::Error) -> Self { + Self::Utcnow(err) + } +} + +impl fmt::Display for NowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::TimeZone(_) => "could not get time zone", + Self::UnknownTimezone(_) => "unknown system time zone", + Self::ProjectDateTime(_) => "could not project timestamp", + Self::Utcnow(_) => "could not get current time", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for NowError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + #[cfg(feature = "local")] + Self::TimeZone(err) => Some(err), + #[cfg(not(feature = "local"))] + Self::TimeZone(_) => None, + Self::UnknownTimezone(_) => None, + Self::ProjectDateTime(err) => Some(err), + Self::Utcnow(err) => Some(err), + } + } +} + +/// Get the current time in the local system time zone +/// +/// # Errors +/// +/// Possible errors include: +/// +/// * The current [Unix time](https://en.wikipedia.org/w/index.php?title=Unix_time&oldid=1101650731) +/// could not be determined. +/// * The current Unix time could not be projected into the time zone. +/// Most likely the system time is off, or you are a time traveler trying run this code a few billion years in the future or past. +/// * The local time zone could not be determined. +/// * The local time zone is not a valid [IANA time zone](https://www.iana.org/time-zones). +/// +/// # Example +/// +/// ```rust +/// # fn main() -> Result<(), tzdb::now::NowError> { +/// # #[cfg(feature = "local")] let _: () = { +/// // Query the time zone of the local system: +/// let now = tzdb::now::local()?; +/// # }; +/// # Ok(()) } +/// ``` +/// +/// In most cases you will want to default to a specified time zone if the system timezone +/// could not be determined. Then use e.g. +/// +/// ```rust +/// # fn main() -> Result<(), tzdb::now::NowError> { +/// # #[cfg(feature = "local")] let _: () = { +/// let now = tzdb::now::local_or(tzdb::time_zone::GMT)?; +/// # }; +/// # Ok(()) } +/// ``` +/// +/// # See also: +/// +/// * `local()` / [`local_or()`] +/// * [`in_named()`] / [`in_named_or()`] +/// * [`in_tz()`] +#[cfg(feature = "local")] +#[cfg_attr(docsrs, doc(cfg(feature = "local")))] +pub fn local() -> Result { + in_named(get_timezone()?) +} + +/// Get the current time in the local system time zone with a fallback time zone +/// +/// # Errors +/// +/// Possible errors include: +/// +/// * The current [Unix time](https://en.wikipedia.org/w/index.php?title=Unix_time&oldid=1101650731) +/// could not be determined. +/// * The current Unix time could not be projected into the time zone. +/// Most likely the system time is off, or you are a time traveler trying run this code a few billion years in the future or past. +/// +/// # Example +/// +/// ```rust +/// # fn main() -> Result<(), tzdb::now::NowError> { +/// # #[cfg(feature = "local")] let _: () = { +/// // Query the time zone of the local system, or use GMT as default: +/// let now = tzdb::now::local_or(tzdb::time_zone::GMT)?; +/// # }; +/// # Ok(()) } +/// ``` +/// +/// # See also: +/// +/// * [`local()`] / `local_or()` +/// * [`in_named()`] / [`in_named_or()`] +/// * [`in_tz()`] +#[cfg(feature = "local")] +#[cfg_attr(docsrs, doc(cfg(feature = "local")))] +pub fn local_or(default: TimeZoneRef<'_>) -> Result { + let tz = get_timezone() + .ok() + .and_then(crate::tz_by_name) + .unwrap_or(default); + in_tz(tz) +} + +/// Get the current time a given time zone +/// +/// # Errors +/// +/// Possible errors include: +/// +/// * The current [Unix time](https://en.wikipedia.org/w/index.php?title=Unix_time&oldid=1101650731) +/// could not be determined. +/// * The current Unix time could not be projected into the time zone. +/// Most likely the system time is off, or you are a time traveler trying run this code a few billion years in the future or past. +/// +/// # Example +/// +/// ```rust +/// # fn main() -> Result<(), tzdb::now::NowError> { +/// // What is the time in Berlin? +/// let now = tzdb::now::in_tz(tzdb::time_zone::europe::BERLIN)?; +/// # Ok(()) } +/// ``` +/// +/// # See also: +/// +/// * [`local()`] / [`local_or()`] +/// * [`in_named()`] / [`in_named_or()`] +/// * `in_tz()` +pub fn in_tz(time_zone_ref: TimeZoneRef<'_>) -> Result { + let now = utcnow::utcnow()?; + Ok(DateTime::from_timespec( + now.as_secs(), + now.subsec_nanos(), + time_zone_ref, + )?) +} + +/// Get the current time in a given time zone, by name +/// +/// # Errors +/// +/// Possible errors include: +/// +/// * The current [Unix time](https://en.wikipedia.org/w/index.php?title=Unix_time&oldid=1101650731) +/// could not be determined. +/// * The current Unix time could not be projected into the time zone. +/// Most likely the system time is off, or you are a time traveler trying run this code a few billion years in the future or past. +/// * The time zone is not a valid [IANA time zone](https://www.iana.org/time-zones). +/// +/// # Example +/// +/// ```rust +/// # fn main() -> Result<(), tzdb::now::NowError> { +/// # #[cfg(feature = "by-name")] let _: () = { +/// // What is the time in Berlin? +/// let now = tzdb::now::in_named("Europe/Berlin")?; +/// # }; +/// # Ok(()) } +/// ``` +/// +/// In most cases you will want to default to a specified time zone if the time zone was not found. +/// Then use e.g. +/// +/// ```rust +/// # fn main() -> Result<(), tzdb::now::NowError> { +/// # #[cfg(feature = "by-name")] let _: () = { +/// let now = tzdb::now::in_named_or(tzdb::time_zone::GMT, "Some/City")?; +/// # }; +/// # Ok(()) } +/// ``` +/// +/// # See also: +/// +/// * [`local()`] / [`local_or()`] +/// * `in_named()` / [`in_named_or()`] +/// * [`in_tz()`] +#[cfg(feature = "by-name")] +#[cfg_attr(docsrs, doc(cfg(feature = "by-name")))] +pub fn in_named(tz: impl AsRef<[u8]>) -> Result { + in_tz(crate::tz_by_name(tz).ok_or(NowError::UnknownTimezone(opaque::Opaque))?) +} + +/// Get the current time in a given time zone, by name, or default to some static time zone +/// +/// # Errors +/// +/// Possible errors include: +/// +/// * The current [Unix time](https://en.wikipedia.org/w/index.php?title=Unix_time&oldid=1101650731) +/// could not be determined. +/// * The current Unix time could not be projected into the time zone. +/// Most likely the system time is off, or you are a time traveler trying run this code a few billion years in the future or past. +/// +/// # Example +/// +/// ```rust +/// # fn main() -> Result<(), tzdb::now::NowError> { +/// # #[cfg(feature = "by-name")] let _: () = { +/// // What is the time in Some City? +/// let now = tzdb::now::in_named_or(tzdb::time_zone::GMT, "Some/City")?; +/// # }; +/// # Ok(()) } +/// ``` +/// +/// # See also: +/// +/// * [`local()`] / [`local_or()`] +/// * [`in_named()`] / `in_named_or()` +/// * [`in_tz()`] +#[cfg(feature = "by-name")] +#[cfg_attr(docsrs, doc(cfg(feature = "by-name")))] +pub fn in_named_or(default: TimeZoneRef<'_>, tz: impl AsRef<[u8]>) -> Result { + in_tz(crate::tz_by_name(tz).unwrap_or(default)) +} diff --git a/test-artichoke/Cargo.toml b/test-artichoke/Cargo.toml new file mode 100644 index 0000000..a3229eb --- /dev/null +++ b/test-artichoke/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "test-shadow" +version = "0.1.0" +edition = "2018" + +[dependencies] +spinoso-time = { version = "0.5.0", git = "https://github.com/artichoke/artichoke", default-features = false, features = ["tzrs-local"] } + +anyhow = "1.0.59" + +[patch.crates-io] +tzdb = { path = ".." } + +[workspace] +members = ["."] diff --git a/test-artichoke/src/main.rs b/test-artichoke/src/main.rs new file mode 100644 index 0000000..50893b6 --- /dev/null +++ b/test-artichoke/src/main.rs @@ -0,0 +1,8 @@ +use spinoso_time::tzrs::Time; + +fn main() -> anyhow::Result<()> { + let now = Time::now()?; + println!("now = {}", &now); + println!("now? = {:#?}", &now); + Ok(()) +} diff --git a/test-shadow/Cargo.toml b/test-shadow/Cargo.toml index 5424705..e7baf30 100644 --- a/test-shadow/Cargo.toml +++ b/test-shadow/Cargo.toml @@ -6,6 +6,10 @@ edition = "2018" [dependencies] shadow-rs = "^0.16.1" +# FIXME: Remove explicit dependency when 0.4 is released. +# Shadow-rs then should use tzdb with `features = "std"`. +tzdb = { version = "0.3.0", features = ["std"] } + [build-dependencies] shadow-rs = "^0.16.1"