Skip to content

Commit

Permalink
add duration methods (#2372)
Browse files Browse the repository at this point in the history
  • Loading branch information
ritchie46 committed Jan 14, 2022
1 parent b4874c8 commit 8dd4574
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 0 deletions.
44 changes: 44 additions & 0 deletions polars/polars-core/src/chunked_array/temporal/duration.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use crate::export::chrono::Duration as ChronoDuration;
use crate::prelude::DataType::Duration;
use crate::prelude::*;
use arrow::temporal_conversions::{MILLISECONDS, MILLISECONDS_IN_DAY, NANOSECONDS};

const NANOSECONDS_IN_MILLISECOND: i64 = 1_000_000;
const SECONDS_IN_HOUR: i64 = 3600;

impl DurationChunked {
pub fn time_unit(&self) -> TimeUnit {
Expand All @@ -22,4 +26,44 @@ impl DurationChunked {
let vals = v.iter().map(func).collect_trusted::<Vec<_>>();
Int64Chunked::new_from_aligned_vec(name, vals).into_duration(tu)
}

/// Extract the hours from a `Duration`
pub fn hours(&self) -> Int64Chunked {
match self.time_unit() {
TimeUnit::Milliseconds => &self.0 / (MILLISECONDS * SECONDS_IN_HOUR),
TimeUnit::Nanoseconds => &self.0 / (NANOSECONDS * SECONDS_IN_HOUR),
}
}

/// Extract the days from a `Duration`
pub fn days(&self) -> Int64Chunked {
match self.time_unit() {
TimeUnit::Milliseconds => &self.0 / MILLISECONDS_IN_DAY,
TimeUnit::Nanoseconds => &self.0 / (NANOSECONDS * SECONDS_IN_DAY),
}
}

/// Extract the milliseconds from a `Duration`
pub fn milliseconds(&self) -> Int64Chunked {
match self.time_unit() {
TimeUnit::Milliseconds => self.0.clone(),
TimeUnit::Nanoseconds => &self.0 / 1_000_000,
}
}

/// Extract the nanoseconds from a `Duration`
pub fn nanoseconds(&self) -> Int64Chunked {
match self.time_unit() {
TimeUnit::Milliseconds => &self.0 * NANOSECONDS_IN_MILLISECOND,
TimeUnit::Nanoseconds => self.0.clone(),
}
}

/// Extract the seconds from a `Duration`
pub fn seconds(&self) -> Int64Chunked {
match self.time_unit() {
TimeUnit::Milliseconds => &self.0 / MILLISECONDS,
TimeUnit::Nanoseconds => &self.0 / NANOSECONDS,
}
}
}
5 changes: 5 additions & 0 deletions py-polars/docs/source/reference/expression.rst
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ The following methods are available under the `expr.dt` attribute.
ExprDateTimeNameSpace.epoch_seconds
ExprDateTimeNameSpace.and_time_unit
ExprDateTimeNameSpace.and_time_zone
ExprDateTimeNameSpace.days
ExprDateTimeNameSpace.hours
ExprDateTimeNameSpace.seconds
ExprDateTimeNameSpace.milliseconds
ExprDateTimeNameSpace.nanoseconds

Strings
-------
Expand Down
5 changes: 5 additions & 0 deletions py-polars/docs/source/reference/series.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ The following methods are available under the `Series.dt` attribute.
DateTimeNameSpace.epoch_seconds
DateTimeNameSpace.and_time_unit
DateTimeNameSpace.and_time_zone
DateTimeNameSpace.days
DateTimeNameSpace.hours
DateTimeNameSpace.seconds
DateTimeNameSpace.milliseconds
DateTimeNameSpace.nanoseconds


Strings
Expand Down
50 changes: 50 additions & 0 deletions py-polars/polars/internals/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3123,6 +3123,56 @@ def and_time_zone(self, tz: Optional[str]) -> Expr:
lambda s: s.dt.and_time_zone(tz), return_dtype=Datetime
)

def days(self) -> Expr:
"""
Extract the days from a Duration type.
Returns
-------
A series of dtype Int64
"""
return wrap_expr(self._pyexpr.duration_days())

def hours(self) -> Expr:
"""
Extract the hours from a Duration type.
Returns
-------
A series of dtype Int64
"""
return wrap_expr(self._pyexpr.duration_hours())

def seconds(self) -> Expr:
"""
Extract the seconds from a Duration type.
Returns
-------
A series of dtype Int64
"""
return wrap_expr(self._pyexpr.duration_seconds())

def milliseconds(self) -> Expr:
"""
Extract the milliseconds from a Duration type.
Returns
-------
A series of dtype Int64
"""
return wrap_expr(self._pyexpr.duration_milliseconds())

def nanoseconds(self) -> Expr:
"""
Extract the nanoseconds from a Duration type.
Returns
-------
A series of dtype Int64
"""
return wrap_expr(self._pyexpr.duration_nanoseconds())


def expr_to_lit_or_expr(
expr: Union[Expr, bool, int, float, str, "pli.Series"],
Expand Down
50 changes: 50 additions & 0 deletions py-polars/polars/internals/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -4216,6 +4216,56 @@ def and_time_zone(self, tz: Optional[str]) -> "Series":
"""
return wrap_s(self._s.and_time_zone(tz))

def days(self) -> Series:
"""
Extract the days from a Duration type.
Returns
-------
A series of dtype Int64
"""
return pli.select(pli.lit(wrap_s(self._s)).dt.days()).to_series()

def hours(self) -> Series:
"""
Extract the hours from a Duration type.
Returns
-------
A series of dtype Int64
"""
return pli.select(pli.lit(wrap_s(self._s)).dt.hours()).to_series()

def seconds(self) -> Series:
"""
Extract the seconds from a Duration type.
Returns
-------
A series of dtype Int64
"""
return pli.select(pli.lit(wrap_s(self._s)).dt.seconds()).to_series()

def milliseconds(self) -> Series:
"""
Extract the milliseconds from a Duration type.
Returns
-------
A series of dtype Int64
"""
return pli.select(pli.lit(wrap_s(self._s)).dt.milliseconds()).to_series()

def nanoseconds(self) -> Series:
"""
Extract the nanoseconds from a Duration type.
Returns
-------
A series of dtype Int64
"""
return pli.select(pli.lit(wrap_s(self._s)).dt.nanoseconds()).to_series()


class SeriesIter:
"""
Expand Down
45 changes: 45 additions & 0 deletions py-polars/src/lazy/dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,51 @@ impl PyExpr {
pub fn nanosecond(&self) -> PyExpr {
self.clone().inner.nanosecond().into()
}
pub fn duration_days(&self) -> PyExpr {
self.inner
.clone()
.map(
|s| Ok(s.duration()?.days().into_series()),
GetOutput::from_type(DataType::Int64),
)
.into()
}
pub fn duration_hours(&self) -> PyExpr {
self.inner
.clone()
.map(
|s| Ok(s.duration()?.hours().into_series()),
GetOutput::from_type(DataType::Int64),
)
.into()
}
pub fn duration_seconds(&self) -> PyExpr {
self.inner
.clone()
.map(
|s| Ok(s.duration()?.seconds().into_series()),
GetOutput::from_type(DataType::Int64),
)
.into()
}
pub fn duration_nanoseconds(&self) -> PyExpr {
self.inner
.clone()
.map(
|s| Ok(s.duration()?.nanoseconds().into_series()),
GetOutput::from_type(DataType::Int64),
)
.into()
}
pub fn duration_milliseconds(&self) -> PyExpr {
self.inner
.clone()
.map(
|s| Ok(s.duration()?.milliseconds().into_series()),
GetOutput::from_type(DataType::Int64),
)
.into()
}
pub fn timestamp(&self) -> PyExpr {
self.clone()
.inner
Expand Down
21 changes: 21 additions & 0 deletions py-polars/tests/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -1432,3 +1432,24 @@ def test_ceil() -> None:
a = pl.Series("a", [1.8, 1.2, 3.0])
expected = pl.Series("a", [2.0, 2.0, 3.0])
verify_series_and_expr_api(a, expected, "ceil")


def test_duration_extract_times() -> None:
a = pl.Series("a", [datetime(2021, 1, 1)])
b = pl.Series("b", [datetime(2021, 1, 2)])

duration = b - a
expected = pl.Series("b", [1])
verify_series_and_expr_api(duration, expected, "dt.days")

expected = pl.Series("b", [24])
verify_series_and_expr_api(duration, expected, "dt.hours")

expected = pl.Series("b", [3600 * 24])
verify_series_and_expr_api(duration, expected, "dt.seconds")

expected = pl.Series("b", [3600 * 24 * 1000])
verify_series_and_expr_api(duration, expected, "dt.milliseconds")

expected = pl.Series("b", [3600 * 24 * int(1e9)])
verify_series_and_expr_api(duration, expected, "dt.nanoseconds")

0 comments on commit 8dd4574

Please sign in to comment.