Skip to content

Commit

Permalink
feat: extract timezone info from python datetimes
Browse files Browse the repository at this point in the history
  • Loading branch information
bschoenmaeckers committed Jan 21, 2025
1 parent 91f1551 commit f33c0a8
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 12 deletions.
5 changes: 4 additions & 1 deletion crates/polars-core/src/frame/row/av_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ impl<'a> AnyValueBuffer<'a> {
#[cfg(feature = "dtype-datetime")]
(Datetime(builder, _, _), AnyValue::Null) => builder.append_null(),
#[cfg(feature = "dtype-datetime")]
(Datetime(builder, tu_l, _), AnyValue::Datetime(v, tu_r, _)) => {
(
Datetime(builder, tu_l, _),
AnyValue::Datetime(v, tu_r, _) | AnyValue::DatetimeOwned(v, tu_r, _),
) => {
// we convert right tu to left tu
// so we swap.
let v = convert_time_units(v, tu_r, *tu_l);
Expand Down
3 changes: 3 additions & 0 deletions crates/polars-plan/src/plans/lit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ impl From<AnyValue<'_>> for LiteralValue {
AnyValue::Date(v) => LiteralValue::Date(v),
#[cfg(feature = "dtype-datetime")]
AnyValue::Datetime(value, tu, tz) => LiteralValue::DateTime(value, tu, tz.cloned()),
AnyValue::DatetimeOwned(value, tu, tz) => {
LiteralValue::DateTime(value, tu, tz.as_ref().map(AsRef::as_ref).cloned())
},
#[cfg(feature = "dtype-duration")]
AnyValue::Duration(value, tu) => LiteralValue::Duration(value, tu),
#[cfg(feature = "dtype-time")]
Expand Down
35 changes: 24 additions & 11 deletions crates/polars-python/src/conversion/any_value.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::{Borrow, Cow};
use std::sync::Arc;

use chrono_tz::Tz;
#[cfg(feature = "object")]
Expand Down Expand Up @@ -254,31 +255,43 @@ pub(crate) fn py_object_to_any_value<'py>(
let py = ob.py();
let tzinfo = ob.getattr(intern!(py, "tzinfo"))?;

let timestamp = if tzinfo.is_none() {
if tzinfo.is_none() {
let datetime = ob.extract::<NaiveDateTime>()?;
let delta = datetime - NaiveDateTime::UNIX_EPOCH;
delta.num_microseconds().unwrap()
} else if tzinfo.hasattr(intern!(py, "key"))? {
let timestamp = delta.num_microseconds().unwrap();
return Ok(AnyValue::Datetime(timestamp, TimeUnit::Microseconds, None));
}

let (timestamp, tz) = if tzinfo.hasattr(intern!(py, "key"))? {
let datetime = ob.extract::<DateTime<Tz>>()?;
let tz = datetime.timezone().name().into();
if datetime.year() >= 2100 {
// chrono-tz does not support dates after 2100
// https://github.com/chronotope/chrono-tz/issues/135
pl_utils(py)
.bind(py)
.getattr(intern!(py, "datetime_to_int"))?
.call1((ob, intern!(py, "us")))?
.extract::<i64>()?
(
pl_utils(py)
.bind(py)
.getattr(intern!(py, "datetime_to_int"))?
.call1((ob, intern!(py, "us")))?
.extract::<i64>()?,
tz,
)
} else {
let delta = datetime.to_utc() - DateTime::UNIX_EPOCH;
delta.num_microseconds().unwrap()
(delta.num_microseconds().unwrap(), tz)
}
} else {
let datetime = ob.extract::<DateTime<FixedOffset>>()?;
let tz = datetime.timezone().to_string().into();
let delta = datetime.to_utc() - DateTime::UNIX_EPOCH;
delta.num_microseconds().unwrap()
(delta.num_microseconds().unwrap(), tz)
};

Ok(AnyValue::Datetime(timestamp, TimeUnit::Microseconds, None))
Ok(AnyValue::DatetimeOwned(
timestamp,
TimeUnit::Microseconds,
Some(Arc::new(tz)),
))
}

fn get_timedelta(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
Expand Down

0 comments on commit f33c0a8

Please sign in to comment.