Path: blob/main/crates/polars-arrow/src/temporal_conversions.rs
6939 views
//! Conversion methods for dates and times.12use chrono::format::{Parsed, StrftimeItems, parse};3use chrono::{DateTime, Duration, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta};4use polars_error::{PolarsResult, polars_err};56use crate::datatypes::TimeUnit;78/// Number of seconds in a day9pub const SECONDS_IN_DAY: i64 = 86_400;10/// Number of milliseconds in a second11pub const MILLISECONDS: i64 = 1_000;12/// Number of microseconds in a second13pub const MICROSECONDS: i64 = 1_000_000;14/// Number of nanoseconds in a second15pub const NANOSECONDS: i64 = 1_000_000_000;16/// Number of milliseconds in a day17pub const MILLISECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MILLISECONDS;18/// Number of microseconds in a day19pub const MICROSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MICROSECONDS;20/// Number of nanoseconds in a day21pub const NANOSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * NANOSECONDS;22/// Number of days between 0001-01-01 and 1970-01-0123pub const EPOCH_DAYS_FROM_CE: i32 = 719_163;2425#[inline]26fn unix_epoch() -> NaiveDateTime {27DateTime::UNIX_EPOCH.naive_utc()28}2930/// converts a `i32` representing a `date32` to [`NaiveDateTime`]31#[inline]32pub fn date32_to_datetime(v: i32) -> NaiveDateTime {33date32_to_datetime_opt(v).expect("invalid or out-of-range datetime")34}3536/// converts a `i32` representing a `date32` to [`NaiveDateTime`]37#[inline]38pub fn date32_to_datetime_opt(v: i32) -> Option<NaiveDateTime> {39let delta = TimeDelta::try_days(v.into())?;40unix_epoch().checked_add_signed(delta)41}4243/// converts a `i32` representing a `date32` to [`NaiveDate`]44#[inline]45pub fn date32_to_date(days: i32) -> NaiveDate {46date32_to_date_opt(days).expect("out-of-range date")47}4849/// converts a `i32` representing a `date32` to [`NaiveDate`]50#[inline]51pub fn date32_to_date_opt(days: i32) -> Option<NaiveDate> {52NaiveDate::from_num_days_from_ce_opt(EPOCH_DAYS_FROM_CE + days)53}5455/// converts a `i64` representing a `date64` to [`NaiveDateTime`]56#[inline]57pub fn date64_to_datetime(v: i64) -> NaiveDateTime {58TimeDelta::try_milliseconds(v)59.and_then(|delta| unix_epoch().checked_add_signed(delta))60.expect("invalid or out-of-range datetime")61}6263/// converts a `i64` representing a `date64` to [`NaiveDate`]64#[inline]65pub fn date64_to_date(milliseconds: i64) -> NaiveDate {66date64_to_datetime(milliseconds).date()67}6869/// converts a `i32` representing a `time32(s)` to [`NaiveTime`]70#[inline]71pub fn time32s_to_time(v: i32) -> NaiveTime {72NaiveTime::from_num_seconds_from_midnight_opt(v as u32, 0).expect("invalid time")73}7475/// converts a `i64` representing a `duration(s)` to [`Duration`]76#[inline]77pub fn duration_s_to_duration(v: i64) -> Duration {78Duration::try_seconds(v).expect("out-of-range duration")79}8081/// converts a `i64` representing a `duration(ms)` to [`Duration`]82#[inline]83pub fn duration_ms_to_duration(v: i64) -> Duration {84Duration::try_milliseconds(v).expect("out-of-range in duration conversion")85}8687/// converts a `i64` representing a `duration(us)` to [`Duration`]88#[inline]89pub fn duration_us_to_duration(v: i64) -> Duration {90Duration::microseconds(v)91}9293/// converts a `i64` representing a `duration(ns)` to [`Duration`]94#[inline]95pub fn duration_ns_to_duration(v: i64) -> Duration {96Duration::nanoseconds(v)97}9899/// converts a `i32` representing a `time32(ms)` to [`NaiveTime`]100#[inline]101pub fn time32ms_to_time(v: i32) -> NaiveTime {102let v = v as i64;103let seconds = v / MILLISECONDS;104105let milli_to_nano = 1_000_000;106let nano = (v - seconds * MILLISECONDS) * milli_to_nano;107NaiveTime::from_num_seconds_from_midnight_opt(seconds as u32, nano as u32)108.expect("invalid time")109}110111/// converts a `i64` representing a `time64(us)` to [`NaiveTime`]112#[inline]113pub fn time64us_to_time(v: i64) -> NaiveTime {114time64us_to_time_opt(v).expect("invalid time")115}116117/// converts a `i64` representing a `time64(us)` to [`NaiveTime`]118#[inline]119pub fn time64us_to_time_opt(v: i64) -> Option<NaiveTime> {120NaiveTime::from_num_seconds_from_midnight_opt(121// extract seconds from microseconds122(v / MICROSECONDS) as u32,123// discard extracted seconds and convert microseconds to124// nanoseconds125(v % MICROSECONDS * MILLISECONDS) as u32,126)127}128129/// converts a `i64` representing a `time64(ns)` to [`NaiveTime`]130#[inline]131pub fn time64ns_to_time(v: i64) -> NaiveTime {132time64ns_to_time_opt(v).expect("invalid time")133}134135/// converts a `i64` representing a `time64(ns)` to [`NaiveTime`]136#[inline]137pub fn time64ns_to_time_opt(v: i64) -> Option<NaiveTime> {138NaiveTime::from_num_seconds_from_midnight_opt(139// extract seconds from nanoseconds140(v / NANOSECONDS) as u32,141// discard extracted seconds142(v % NANOSECONDS) as u32,143)144}145146/// converts a `i64` representing a `timestamp(s)` to [`NaiveDateTime`]147#[inline]148pub fn timestamp_s_to_datetime(seconds: i64) -> NaiveDateTime {149timestamp_s_to_datetime_opt(seconds).expect("invalid or out-of-range datetime")150}151152/// converts a `i64` representing a `timestamp(s)` to [`NaiveDateTime`]153#[inline]154pub fn timestamp_s_to_datetime_opt(seconds: i64) -> Option<NaiveDateTime> {155Some(DateTime::from_timestamp(seconds, 0)?.naive_utc())156}157158/// converts a `i64` representing a `timestamp(ms)` to [`NaiveDateTime`]159#[inline]160pub fn timestamp_ms_to_datetime(v: i64) -> NaiveDateTime {161timestamp_ms_to_datetime_opt(v).expect("invalid or out-of-range datetime")162}163164/// converts a `i64` representing a `timestamp(ms)` to [`NaiveDateTime`]165#[inline]166pub fn timestamp_ms_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {167let delta = TimeDelta::try_milliseconds(v)?;168unix_epoch().checked_add_signed(delta)169}170171/// converts a `i64` representing a `timestamp(us)` to [`NaiveDateTime`]172#[inline]173pub fn timestamp_us_to_datetime(v: i64) -> NaiveDateTime {174timestamp_us_to_datetime_opt(v).expect("invalid or out-of-range datetime")175}176177/// converts a `i64` representing a `timestamp(us)` to [`NaiveDateTime`]178#[inline]179pub fn timestamp_us_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {180let delta = TimeDelta::microseconds(v);181unix_epoch().checked_add_signed(delta)182}183184/// converts a `i64` representing a `timestamp(ns)` to [`NaiveDateTime`]185#[inline]186pub fn timestamp_ns_to_datetime(v: i64) -> NaiveDateTime {187timestamp_ns_to_datetime_opt(v).expect("invalid or out-of-range datetime")188}189190/// converts a `i64` representing a `timestamp(ns)` to [`NaiveDateTime`]191#[inline]192pub fn timestamp_ns_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {193let delta = TimeDelta::nanoseconds(v);194unix_epoch().checked_add_signed(delta)195}196197/// Converts a timestamp in `time_unit` and `timezone` into [`chrono::DateTime`].198#[inline]199pub(crate) fn timestamp_to_naive_datetime(200timestamp: i64,201time_unit: TimeUnit,202) -> chrono::NaiveDateTime {203match time_unit {204TimeUnit::Second => timestamp_s_to_datetime(timestamp),205TimeUnit::Millisecond => timestamp_ms_to_datetime(timestamp),206TimeUnit::Microsecond => timestamp_us_to_datetime(timestamp),207TimeUnit::Nanosecond => timestamp_ns_to_datetime(timestamp),208}209}210211/// Converts a timestamp in `time_unit` and `timezone` into [`chrono::DateTime`].212#[inline]213pub fn timestamp_to_datetime<T: chrono::TimeZone>(214timestamp: i64,215time_unit: TimeUnit,216timezone: &T,217) -> chrono::DateTime<T> {218timezone.from_utc_datetime(×tamp_to_naive_datetime(timestamp, time_unit))219}220221/// Calculates the scale factor between two TimeUnits. The function returns the222/// scale that should multiply the TimeUnit "b" to have the same time scale as223/// the TimeUnit "a".224pub fn timeunit_scale(a: TimeUnit, b: TimeUnit) -> f64 {225match (a, b) {226(TimeUnit::Second, TimeUnit::Second) => 1.0,227(TimeUnit::Second, TimeUnit::Millisecond) => 0.001,228(TimeUnit::Second, TimeUnit::Microsecond) => 0.000_001,229(TimeUnit::Second, TimeUnit::Nanosecond) => 0.000_000_001,230(TimeUnit::Millisecond, TimeUnit::Second) => 1_000.0,231(TimeUnit::Millisecond, TimeUnit::Millisecond) => 1.0,232(TimeUnit::Millisecond, TimeUnit::Microsecond) => 0.001,233(TimeUnit::Millisecond, TimeUnit::Nanosecond) => 0.000_001,234(TimeUnit::Microsecond, TimeUnit::Second) => 1_000_000.0,235(TimeUnit::Microsecond, TimeUnit::Millisecond) => 1_000.0,236(TimeUnit::Microsecond, TimeUnit::Microsecond) => 1.0,237(TimeUnit::Microsecond, TimeUnit::Nanosecond) => 0.001,238(TimeUnit::Nanosecond, TimeUnit::Second) => 1_000_000_000.0,239(TimeUnit::Nanosecond, TimeUnit::Millisecond) => 1_000_000.0,240(TimeUnit::Nanosecond, TimeUnit::Microsecond) => 1_000.0,241(TimeUnit::Nanosecond, TimeUnit::Nanosecond) => 1.0,242}243}244245/// Parses `value` to `Option<i64>` consistent with the Arrow's definition of timestamp with timezone.246///247/// `tz` must be built from `timezone` (either via [`parse_offset`] or `chrono-tz`).248/// Returns in scale `tz` of `TimeUnit`.249#[inline]250pub fn utf8_to_timestamp_scalar<T: chrono::TimeZone>(251value: &str,252fmt: &str,253tz: &T,254tu: &TimeUnit,255) -> Option<i64> {256let mut parsed = Parsed::new();257let fmt = StrftimeItems::new(fmt);258let r = parse(&mut parsed, value, fmt).ok();259if r.is_some() {260parsed261.to_datetime()262.map(|x| x.naive_utc())263.map(|x| tz.from_utc_datetime(&x))264.map(|x| match tu {265TimeUnit::Second => x.timestamp(),266TimeUnit::Millisecond => x.timestamp_millis(),267TimeUnit::Microsecond => x.timestamp_micros(),268TimeUnit::Nanosecond => x.timestamp_nanos_opt().unwrap(),269})270.ok()271} else {272None273}274}275276/// Parses an offset of the form `"+WX:YZ"` or `"UTC"` into [`FixedOffset`].277/// # Errors278/// If the offset is not in any of the allowed forms.279pub fn parse_offset(offset: &str) -> PolarsResult<FixedOffset> {280if offset == "UTC" {281return Ok(FixedOffset::east_opt(0).expect("FixedOffset::east out of bounds"));282}283let error = "timezone offset must be of the form [-]00:00";284285let mut a = offset.split(':');286let first: &str = a287.next()288.ok_or_else(|| polars_err!(InvalidOperation: error))?;289let last = a290.next()291.ok_or_else(|| polars_err!(InvalidOperation: error))?;292let hours: i32 = first293.parse()294.map_err(|_| polars_err!(InvalidOperation: error))?;295let minutes: i32 = last296.parse()297.map_err(|_| polars_err!(InvalidOperation: error))?;298299Ok(FixedOffset::east_opt(hours * 60 * 60 + minutes * 60)300.expect("FixedOffset::east out of bounds"))301}302303/// Parses `value` to a [`chrono_tz::Tz`] with the Arrow's definition of timestamp with a timezone.304#[cfg(feature = "chrono-tz")]305#[cfg_attr(docsrs, doc(cfg(feature = "chrono-tz")))]306pub fn parse_offset_tz(timezone: &str) -> PolarsResult<chrono_tz::Tz> {307timezone308.parse::<chrono_tz::Tz>()309.map_err(|_| polars_err!(InvalidOperation: "timezone \"{timezone}\" cannot be parsed"))310}311312313