Conversion between chrono_tz::Tz and zoneinfo.ZoneInfo

This commit is contained in:
Tpt 2024-01-08 15:19:49 +01:00
parent 4b172874dc
commit 72f0c73925
7 changed files with 129 additions and 4 deletions

View File

@ -35,6 +35,7 @@ inventory = { version = "0.3.0", optional = true }
# crate integrations that can be added using the eponymous features # crate integrations that can be added using the eponymous features
anyhow = { version = "1.0", optional = true } anyhow = { version = "1.0", optional = true }
chrono = { version = "0.4.25", default-features = false, optional = true } chrono = { version = "0.4.25", default-features = false, optional = true }
chrono-tz = { version = ">= 0.6, < 0.9", default-features = false, optional = true }
either = { version = "1.9", optional = true } either = { version = "1.9", optional = true }
eyre = { version = ">= 0.4, < 0.7", optional = true } eyre = { version = ">= 0.4, < 0.7", optional = true }
hashbrown = { version = ">= 0.9, < 0.15", optional = true } hashbrown = { version = ">= 0.9, < 0.15", optional = true }
@ -47,7 +48,8 @@ smallvec = { version = "1.0", optional = true }
[dev-dependencies] [dev-dependencies]
assert_approx_eq = "1.1.0" assert_approx_eq = "1.1.0"
chrono = { version = "0.4.25" } chrono = "0.4.25"
chrono-tz = ">= 0.6, < 0.9"
# Required for "and $N others" normalization # Required for "and $N others" normalization
trybuild = ">=1.0.70" trybuild = ">=1.0.70"
proptest = { version = "1.0", default-features = false, features = ["std"] } proptest = { version = "1.0", default-features = false, features = ["std"] }
@ -108,6 +110,7 @@ full = [
"macros", "macros",
# "multiple-pymethods", # TODO re-add this when MSRV is greater than 1.62 # "multiple-pymethods", # TODO re-add this when MSRV is greater than 1.62
"chrono", "chrono",
"chrono-tz",
"num-bigint", "num-bigint",
"num-complex", "num-complex",
"hashbrown", "hashbrown",

View File

@ -115,6 +115,12 @@ Adds a dependency on [chrono](https://docs.rs/chrono). Enables a conversion from
- [NaiveTime](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html) - [NaiveTime](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html)
- [DateTime](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) - [DateTime](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html)
### `chrono-tz`
Adds a dependency on [chrono-tz](https://docs.rs/chrono-tz).
Enables conversion from and to [`Tz`](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html).
It requires at least Python 3.9.
### `either` ### `either`
Adds a dependency on [either](https://docs.rs/either). Enables a conversions into [either](https://docs.rs/either)s [`Either`](https://docs.rs/either/latest/either/struct.Report.html) type. Adds a dependency on [either](https://docs.rs/either). Enables a conversions into [either](https://docs.rs/either)s [`Either`](https://docs.rs/either/latest/either/struct.Report.html) type.

View File

@ -0,0 +1 @@
`chrono-tz` feature allowing conversion between `chrono_tz::Tz` and `zoneinfo.ZoneInfo`

View File

@ -9,8 +9,6 @@
//! //!
//! ```toml //! ```toml
//! [dependencies] //! [dependencies]
//! # change * to the latest versions
//! pyo3 = { version = "*", features = ["chrono"] }
//! chrono = "0.4" //! chrono = "0.4"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"chrono\"] }")] #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"chrono\"] }")]
//! ``` //! ```
@ -18,7 +16,7 @@
//! Note that you must use compatible versions of chrono and PyO3. //! Note that you must use compatible versions of chrono and PyO3.
//! The required chrono version may vary based on the version of PyO3. //! The required chrono version may vary based on the version of PyO3.
//! //!
//! # Example: Convert a `PyDateTime` to chrono's `DateTime<Utc>` //! # Example: Convert a `datetime.datetime` to chrono's `DateTime<Utc>`
//! //!
//! ```rust //! ```rust
//! use chrono::{DateTime, Duration, TimeZone, Utc}; //! use chrono::{DateTime, Duration, TimeZone, Utc};

View File

@ -0,0 +1,113 @@
#![cfg(all(Py_3_9, feature = "chrono-tz"))]
//! Conversions to and from [chrono-tz](https://docs.rs/chrono-tz/)s `Tz`.
//!
//! This feature requires at least Python 3.9.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! chrono-tz = "0.8"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"chrono-tz\"] }")]
//! ```
//!
//! Note that you must use compatible versions of chrono, chrono-tz and PyO3.
//! The required chrono version may vary based on the version of PyO3.
//!
//! # Example: Convert a `zoneinfo.ZoneInfo` to chrono-tz's `Tz`
//!
//! ```rust,no_run
//! use chrono_tz::Tz;
//! use pyo3::{Python, ToPyObject};
//!
//! fn main() {
//! pyo3::prepare_freethreaded_python();
//! Python::with_gil(|py| {
//! // Convert to Python
//! let py_tzinfo = Tz::Europe__Paris.to_object(py);
//! // Convert back to Rust
//! assert_eq!(py_tzinfo.extract::<Tz>(py).unwrap(), Tz::Europe__Paris);
//! });
//! }
//! ```
use crate::exceptions::PyValueError;
use crate::sync::GILOnceCell;
use crate::types::PyType;
use crate::{intern, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject};
use chrono_tz::Tz;
use std::str::FromStr;
impl ToPyObject for Tz {
fn to_object(&self, py: Python<'_>) -> PyObject {
static ZONE_INFO: GILOnceCell<Py<PyType>> = GILOnceCell::new();
ZONE_INFO
.get_or_try_init_type_ref(py, "zoneinfo", "ZoneInfo")
.unwrap()
.call1((self.name(),))
.unwrap()
.into()
}
}
impl IntoPy<PyObject> for Tz {
fn into_py(self, py: Python<'_>) -> PyObject {
self.to_object(py)
}
}
impl FromPyObject<'_> for Tz {
fn extract(ob: &PyAny) -> PyResult<Tz> {
Tz::from_str(ob.getattr(intern!(ob.py(), "key"))?.extract()?)
.map_err(|e| PyValueError::new_err(e.to_string()))
}
}
#[cfg(all(test, not(windows)))] // Troubles loading timezones on Windows
mod tests {
use super::*;
#[test]
fn test_frompyobject() {
Python::with_gil(|py| {
assert_eq!(
new_zoneinfo(py, "Europe/Paris").extract::<Tz>().unwrap(),
Tz::Europe__Paris
);
assert_eq!(new_zoneinfo(py, "UTC").extract::<Tz>().unwrap(), Tz::UTC);
assert_eq!(
new_zoneinfo(py, "Etc/GMT-5").extract::<Tz>().unwrap(),
Tz::Etc__GMTMinus5
);
});
}
#[test]
fn test_topyobject() {
Python::with_gil(|py| {
let assert_eq = |l: PyObject, r: &PyAny| {
assert!(l.as_ref(py).eq(r).unwrap());
};
assert_eq(
Tz::Europe__Paris.to_object(py),
new_zoneinfo(py, "Europe/Paris"),
);
assert_eq(Tz::UTC.to_object(py), new_zoneinfo(py, "UTC"));
assert_eq(
Tz::Etc__GMTMinus5.to_object(py),
new_zoneinfo(py, "Etc/GMT-5"),
);
});
}
fn new_zoneinfo<'a>(py: Python<'a>, name: &str) -> &'a PyAny {
zoneinfo_class(py).call1((name,)).unwrap()
}
fn zoneinfo_class(py: Python<'_>) -> &PyAny {
py.import("zoneinfo").unwrap().getattr("ZoneInfo").unwrap()
}
}

View File

@ -2,6 +2,7 @@
pub mod anyhow; pub mod anyhow;
pub mod chrono; pub mod chrono;
pub mod chrono_tz;
pub mod either; pub mod either;
pub mod eyre; pub mod eyre;
pub mod hashbrown; pub mod hashbrown;

View File

@ -82,6 +82,7 @@
//! The following features enable interactions with other crates in the Rust ecosystem: //! The following features enable interactions with other crates in the Rust ecosystem:
//! - [`anyhow`]: Enables a conversion from [anyhow]s [`Error`][anyhow_error] type to [`PyErr`]. //! - [`anyhow`]: Enables a conversion from [anyhow]s [`Error`][anyhow_error] type to [`PyErr`].
//! - [`chrono`]: Enables a conversion from [chrono]'s structures to the equivalent Python ones. //! - [`chrono`]: Enables a conversion from [chrono]'s structures to the equivalent Python ones.
//! - [`chrono-tz`]: Enables a conversion from [chrono-tz]'s `Tz` enum. Requires Python 3.9+.
//! - [`either`]: Enables conversions between Python objects and [either]'s [`Either`] type. //! - [`either`]: Enables conversions between Python objects and [either]'s [`Either`] type.
//! - [`eyre`]: Enables a conversion from [eyre]s [`Report`] type to [`PyErr`]. //! - [`eyre`]: Enables a conversion from [eyre]s [`Report`] type to [`PyErr`].
//! - [`hashbrown`]: Enables conversions between Python objects and [hashbrown]'s [`HashMap`] and //! - [`hashbrown`]: Enables conversions between Python objects and [hashbrown]'s [`HashMap`] and
@ -257,7 +258,9 @@
//! [`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html //! [`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html
//! [`Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html //! [`Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html
//! [chrono]: https://docs.rs/chrono/ "Date and Time for Rust." //! [chrono]: https://docs.rs/chrono/ "Date and Time for Rust."
//! [chrono-tz]: https://docs.rs/chrono-tz/ "TimeZone implementations for chrono from the IANA database."
//! [`chrono`]: ./chrono/index.html "Documentation about the `chrono` feature." //! [`chrono`]: ./chrono/index.html "Documentation about the `chrono` feature."
//! [`chrono-tz`]: ./chrono-tz/index.html "Documentation about the `chrono-tz` feature."
//! [either]: https://docs.rs/either/ "A type that represents one of two alternatives." //! [either]: https://docs.rs/either/ "A type that represents one of two alternatives."
//! [`either`]: ./either/index.html "Documentation about the `either` feature." //! [`either`]: ./either/index.html "Documentation about the `either` feature."
//! [`Either`]: https://docs.rs/either/latest/either/enum.Either.html //! [`Either`]: https://docs.rs/either/latest/either/enum.Either.html