Skip to content

Commit d50ec3e

Browse files
Add PyTzInfo constructors (PyO3#5055)
* Add `PyTzInfo` constructors * Make `PyTzInfo::utc` failable * Deprecate `timezone_utc`
1 parent 6ccd7e4 commit d50ec3e

File tree

10 files changed

+131
-131
lines changed

10 files changed

+131
-131
lines changed

newsfragments/5055.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `PyTzInfo` constructors

newsfragments/5055.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecated `timezone_utc` in favor of `PyTzInfo::utc`

src/conversions/chrono.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,10 @@ use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError};
4747
use crate::intern;
4848
use crate::types::any::PyAnyMethods;
4949
use crate::types::PyNone;
50-
use crate::types::{
51-
datetime::timezone_from_offset, timezone_utc, PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo,
52-
PyTzInfoAccess,
53-
};
50+
use crate::types::{PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo, PyTzInfoAccess};
5451
#[cfg(not(Py_LIMITED_API))]
5552
use crate::types::{PyDateAccess, PyDeltaAccess, PyTimeAccess};
56-
use crate::{ffi, Bound, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyResult, Python};
53+
use crate::{ffi, Borrowed, Bound, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyResult, Python};
5754
use chrono::offset::{FixedOffset, Utc};
5855
use chrono::{
5956
DateTime, Datelike, Duration, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Offset,
@@ -363,7 +360,7 @@ impl<'py> IntoPyObject<'py> for FixedOffset {
363360
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
364361
let seconds_offset = self.local_minus_utc();
365362
let td = PyDelta::new(py, 0, seconds_offset, 0, true)?;
366-
timezone_from_offset(&td)
363+
PyTzInfo::fixed_offset(py, td)
367364
}
368365
}
369366

@@ -408,17 +405,17 @@ impl FromPyObject<'_> for FixedOffset {
408405

409406
impl<'py> IntoPyObject<'py> for Utc {
410407
type Target = PyTzInfo;
411-
type Output = Bound<'py, Self::Target>;
408+
type Output = Borrowed<'static, 'py, Self::Target>;
412409
type Error = PyErr;
413410

414411
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
415-
Ok(timezone_utc(py))
412+
PyTzInfo::utc(py)
416413
}
417414
}
418415

419416
impl<'py> IntoPyObject<'py> for &Utc {
420417
type Target = PyTzInfo;
421-
type Output = Bound<'py, Self::Target>;
418+
type Output = Borrowed<'static, 'py, Self::Target>;
422419
type Error = PyErr;
423420

424421
#[inline]
@@ -429,7 +426,7 @@ impl<'py> IntoPyObject<'py> for &Utc {
429426

430427
impl FromPyObject<'_> for Utc {
431428
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Utc> {
432-
let py_utc = timezone_utc(ob.py());
429+
let py_utc = PyTzInfo::utc(ob.py())?;
433430
if ob.eq(py_utc)? {
434431
Ok(Utc)
435432
} else {
@@ -926,7 +923,7 @@ mod tests {
926923
let minute = 8;
927924
let second = 9;
928925
let micro = 999_999;
929-
let tz_utc = timezone_utc(py);
926+
let tz_utc = PyTzInfo::utc(py).unwrap();
930927
let py_datetime = new_py_datetime_ob(
931928
py,
932929
"datetime",

src/conversions/chrono_tz.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,23 @@
3737
use crate::conversion::IntoPyObject;
3838
use crate::exceptions::PyValueError;
3939
use crate::pybacked::PyBackedStr;
40-
use crate::sync::GILOnceCell;
41-
use crate::types::{any::PyAnyMethods, PyType};
42-
use crate::{intern, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
40+
use crate::types::{any::PyAnyMethods, PyTzInfo};
41+
use crate::{intern, Bound, FromPyObject, PyAny, PyErr, PyResult, Python};
4342
use chrono_tz::Tz;
4443
use std::str::FromStr;
4544

4645
impl<'py> IntoPyObject<'py> for Tz {
47-
type Target = PyAny;
46+
type Target = PyTzInfo;
4847
type Output = Bound<'py, Self::Target>;
4948
type Error = PyErr;
5049

5150
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
52-
static ZONE_INFO: GILOnceCell<Py<PyType>> = GILOnceCell::new();
53-
ZONE_INFO
54-
.import(py, "zoneinfo", "ZoneInfo")
55-
.and_then(|obj| obj.call1((self.name(),)))
51+
PyTzInfo::timezone(py, self.name())
5652
}
5753
}
5854

5955
impl<'py> IntoPyObject<'py> for &Tz {
60-
type Target = PyAny;
56+
type Target = PyTzInfo;
6157
type Output = Bound<'py, Self::Target>;
6258
type Error = PyErr;
6359

@@ -81,6 +77,7 @@ impl FromPyObject<'_> for Tz {
8177
mod tests {
8278
use super::*;
8379
use crate::prelude::PyAnyMethods;
80+
use crate::types::PyTzInfo;
8481
use crate::Python;
8582
use chrono::{DateTime, Utc};
8683
use chrono_tz::Tz;
@@ -152,7 +149,7 @@ mod tests {
152149
#[cfg(not(Py_GIL_DISABLED))] // https://github.com/python/cpython/issues/116738#issuecomment-2404360445
153150
fn test_into_pyobject() {
154151
Python::with_gil(|py| {
155-
let assert_eq = |l: Bound<'_, PyAny>, r: Bound<'_, PyAny>| {
152+
let assert_eq = |l: Bound<'_, PyTzInfo>, r: Bound<'_, PyTzInfo>| {
156153
assert!(l.eq(&r).unwrap(), "{:?} != {:?}", l, r);
157154
};
158155

@@ -168,11 +165,7 @@ mod tests {
168165
});
169166
}
170167

171-
fn new_zoneinfo<'py>(py: Python<'py>, name: &str) -> Bound<'py, PyAny> {
172-
zoneinfo_class(py).call1((name,)).unwrap()
173-
}
174-
175-
fn zoneinfo_class(py: Python<'_>) -> Bound<'_, PyAny> {
176-
py.import("zoneinfo").unwrap().getattr("ZoneInfo").unwrap()
168+
fn new_zoneinfo<'py>(py: Python<'py>, name: &str) -> Bound<'py, PyTzInfo> {
169+
PyTzInfo::timezone(py, name).unwrap()
177170
}
178171
}

src/conversions/jiff.rs

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,11 @@
4848
//! ```
4949
use crate::exceptions::{PyTypeError, PyValueError};
5050
use crate::pybacked::PyBackedStr;
51-
use crate::sync::GILOnceCell;
52-
use crate::types::{
53-
datetime::timezone_from_offset, timezone_utc, PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo,
54-
PyTzInfoAccess,
55-
};
56-
use crate::types::{PyAnyMethods, PyNone, PyType};
51+
use crate::types::{PyAnyMethods, PyNone};
52+
use crate::types::{PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo, PyTzInfoAccess};
5753
#[cfg(not(Py_LIMITED_API))]
5854
use crate::types::{PyDateAccess, PyDeltaAccess, PyTimeAccess};
59-
use crate::{intern, Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyResult, Python};
55+
use crate::{intern, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python};
6056
use jiff::civil::{Date, DateTime, Time};
6157
use jiff::tz::{Offset, TimeZone};
6258
use jiff::{SignedDuration, Span, Timestamp, Zoned};
@@ -333,17 +329,15 @@ impl<'py> IntoPyObject<'py> for &TimeZone {
333329

334330
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
335331
if self == &TimeZone::UTC {
336-
Ok(timezone_utc(py))
337-
} else if let Some(iana_name) = self.iana_name() {
338-
static ZONE_INFO: GILOnceCell<Py<PyType>> = GILOnceCell::new();
339-
let tz = ZONE_INFO
340-
.import(py, "zoneinfo", "ZoneInfo")
341-
.and_then(|obj| obj.call1((iana_name,)))?
342-
.downcast_into()?;
343-
Ok(tz)
344-
} else {
345-
self.to_fixed_offset()?.into_pyobject(py)
332+
return Ok(PyTzInfo::utc(py)?.to_owned());
333+
}
334+
335+
#[cfg(Py_3_9)]
336+
if let Some(iana_name) = self.iana_name() {
337+
return PyTzInfo::timezone(py, iana_name);
346338
}
339+
340+
self.to_fixed_offset()?.into_pyobject(py)
347341
}
348342
}
349343

@@ -367,12 +361,10 @@ impl<'py> IntoPyObject<'py> for &Offset {
367361

368362
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
369363
if self == &Offset::UTC {
370-
return Ok(timezone_utc(py));
364+
return Ok(PyTzInfo::utc(py)?.to_owned());
371365
}
372366

373-
let delta = self.duration_since(Offset::UTC).into_pyobject(py)?;
374-
375-
timezone_from_offset(&delta)
367+
PyTzInfo::fixed_offset(py, self.duration_since(Offset::UTC))
376368
}
377369
}
378370

@@ -476,8 +468,6 @@ impl From<jiff::Error> for PyErr {
476468
#[cfg(test)]
477469
mod tests {
478470
use super::*;
479-
#[cfg(not(Py_LIMITED_API))]
480-
use crate::types::timezone_utc;
481471
use crate::{types::PyTuple, BoundObject};
482472
use jiff::tz::Offset;
483473
use std::cmp::Ordering;
@@ -727,7 +717,7 @@ mod tests {
727717
let minute = 8;
728718
let second = 9;
729719
let micro = 999_999;
730-
let tz_utc = timezone_utc(py);
720+
let tz_utc = PyTzInfo::utc(py).unwrap();
731721
let py_datetime = new_py_datetime_ob(
732722
py,
733723
"datetime",

src/conversions/std/time.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::sync::GILOnceCell;
66
use crate::types::any::PyAnyMethods;
77
#[cfg(not(Py_LIMITED_API))]
88
use crate::types::PyDeltaAccess;
9-
use crate::types::{timezone_utc, PyDateTime, PyDelta};
9+
use crate::types::{PyDateTime, PyDelta, PyTzInfo};
1010
use crate::{Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
1111
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1212

@@ -128,17 +128,16 @@ fn unix_epoch_py(py: Python<'_>) -> PyResult<Borrowed<'_, '_, PyDateTime>> {
128128
static UNIX_EPOCH: GILOnceCell<Py<PyDateTime>> = GILOnceCell::new();
129129
Ok(UNIX_EPOCH
130130
.get_or_try_init(py, || {
131-
Ok::<_, PyErr>(
132-
PyDateTime::new(py, 1970, 1, 1, 0, 0, 0, 0, Some(&timezone_utc(py)))?.into(),
133-
)
131+
let utc = PyTzInfo::utc(py)?;
132+
Ok::<_, PyErr>(PyDateTime::new(py, 1970, 1, 1, 0, 0, 0, 0, Some(&utc))?.into())
134133
})?
135134
.bind_borrowed(py))
136135
}
137136

138137
#[cfg(test)]
139138
mod tests {
140139
use super::*;
141-
use crate::types::{timezone_utc, PyDict};
140+
use crate::types::PyDict;
142141

143142
#[test]
144143
fn test_duration_frompyobject() {
@@ -320,6 +319,7 @@ mod tests {
320319
second: u8,
321320
microsecond: u32,
322321
) -> Bound<'_, PyDateTime> {
322+
let utc = PyTzInfo::utc(py).unwrap();
323323
PyDateTime::new(
324324
py,
325325
year,
@@ -329,15 +329,17 @@ mod tests {
329329
minute,
330330
second,
331331
microsecond,
332-
Some(&timezone_utc(py)),
332+
Some(&utc),
333333
)
334334
.unwrap()
335335
}
336336

337337
fn max_datetime(py: Python<'_>) -> Bound<'_, PyDateTime> {
338338
let naive_max = datetime_class(py).getattr("max").unwrap();
339339
let kargs = PyDict::new(py);
340-
kargs.set_item("tzinfo", timezone_utc(py)).unwrap();
340+
kargs
341+
.set_item("tzinfo", PyTzInfo::utc(py).unwrap())
342+
.unwrap();
341343
naive_max
342344
.call_method("replace", (), Some(&kargs))
343345
.unwrap()

src/conversions/time.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,9 @@
5353
use crate::exceptions::{PyTypeError, PyValueError};
5454
#[cfg(Py_LIMITED_API)]
5555
use crate::intern;
56-
use crate::types::datetime::timezone_from_offset;
5756
#[cfg(not(Py_LIMITED_API))]
5857
use crate::types::datetime::{PyDateAccess, PyDeltaAccess};
59-
use crate::types::{
60-
timezone_utc, PyAnyMethods, PyDate, PyDateTime, PyDelta, PyNone, PyTime, PyTzInfo,
61-
};
58+
use crate::types::{PyAnyMethods, PyDate, PyDateTime, PyDelta, PyNone, PyTime, PyTzInfo};
6259
#[cfg(not(Py_LIMITED_API))]
6360
use crate::types::{PyTimeAccess, PyTzInfoAccess};
6461
use crate::{Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python};
@@ -359,7 +356,7 @@ impl<'py> IntoPyObject<'py> for UtcOffset {
359356
// Get offset in seconds
360357
let seconds_offset = self.whole_seconds();
361358
let td = PyDelta::new(py, 0, seconds_offset, 0, true)?;
362-
timezone_from_offset(&td)
359+
PyTzInfo::fixed_offset(py, td)
363360
}
364361
}
365362

@@ -460,7 +457,7 @@ impl<'py> IntoPyObject<'py> for UtcDateTime {
460457
let date = self.date();
461458
let time = self.time();
462459

463-
let py_tzinfo = &timezone_utc(py);
460+
let py_tzinfo = PyTzInfo::utc(py)?;
464461

465462
let year = date.year();
466463
let month = date.month() as u8;
@@ -479,7 +476,7 @@ impl<'py> IntoPyObject<'py> for UtcDateTime {
479476
minute,
480477
second,
481478
microsecond,
482-
Some(py_tzinfo),
479+
Some(&py_tzinfo),
483480
)
484481
}
485482
}
@@ -508,7 +505,7 @@ impl FromPyObject<'_> for UtcDateTime {
508505
};
509506

510507
// Verify that the tzinfo is UTC
511-
let is_utc = tzinfo.eq(timezone_utc(ob.py()))?;
508+
let is_utc = tzinfo.eq(PyTzInfo::utc(ob.py())?)?;
512509

513510
if !is_utc {
514511
return Err(PyValueError::new_err(
@@ -978,7 +975,7 @@ mod tests {
978975
// Create Python time with timezone (just to ensure we can handle it properly)
979976
let datetime = py.import("datetime").unwrap();
980977
let time_type = datetime.getattr(intern!(py, "time")).unwrap();
981-
let tz_utc = timezone_utc(py);
978+
let tz_utc = PyTzInfo::utc(py).unwrap();
982979

983980
// Create time with timezone
984981
let py_time_with_tz = time_type.call1((12, 30, 45, 0, tz_utc)).unwrap();
@@ -1071,7 +1068,7 @@ mod tests {
10711068
let timedelta = datetime.getattr(intern!(py, "timedelta")).unwrap();
10721069

10731070
// Test UTC
1074-
let tz_utc = timezone_utc(py);
1071+
let tz_utc = PyTzInfo::utc(py).unwrap();
10751072
let utc_offset: UtcOffset = tz_utc.extract().unwrap();
10761073
assert_eq!(utc_offset.whole_hours(), 0);
10771074
assert_eq!(utc_offset.minutes_past_hour(), 0);
@@ -1222,7 +1219,7 @@ mod tests {
12221219
// Create Python UTC datetime
12231220
let datetime = py.import("datetime").unwrap();
12241221
let datetime_type = datetime.getattr(intern!(py, "datetime")).unwrap();
1225-
let tz_utc = timezone_utc(py);
1222+
let tz_utc = PyTzInfo::utc(py).unwrap();
12261223

12271224
// Create datetime with UTC timezone
12281225
let py_dt = datetime_type

src/ffi/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,12 @@ fn ucs4() {
250250
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
251251
#[cfg(not(PyPy))]
252252
fn test_get_tzinfo() {
253-
use crate::types::timezone_utc;
253+
use crate::types::PyTzInfo;
254254

255255
crate::Python::with_gil(|py| {
256256
use crate::types::{PyDateTime, PyTime};
257257

258-
let utc = &timezone_utc(py);
258+
let utc: &Bound<'_, _> = &PyTzInfo::utc(py).unwrap();
259259

260260
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap();
261261

0 commit comments

Comments
 (0)