Skip to content

Commit

Permalink
python better datetime.time support (#3243)
Browse files Browse the repository at this point in the history
  • Loading branch information
ritchie46 committed Apr 27, 2022
1 parent 5f4c91b commit d7751a8
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 2 deletions.
4 changes: 4 additions & 0 deletions polars/polars-core/src/series/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ impl Series {
}
#[cfg(feature = "dtype-time")]
ArrowDataType::Time64(tu) | ArrowDataType::Time32(tu) => {
let mut chunks = chunks;
if matches!(dtype, ArrowDataType::Time32(_)) {
chunks = cast_chunks(&chunks, &DataType::Int32).unwrap();
}
let chunks = cast_chunks(&chunks, &DataType::Int64).unwrap();
let s = Int64Chunked::from_chunks(name, chunks)
.into_time()
Expand Down
14 changes: 13 additions & 1 deletion py-polars/polars/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ctypes
import os
import sys
from datetime import date, datetime, timedelta, timezone
from datetime import date, datetime, time, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union

Expand Down Expand Up @@ -142,6 +142,18 @@ def handle_projection_columns(
return projection, columns # type: ignore


def _to_python_time(value: int) -> time:
value = value // 1_000
microsecond = value
seconds = (microsecond // 1000_000) % 60
minutes = (microsecond // (1000_000 * 60)) % 60
hours = (microsecond // (1000_000 * 60 * 60)) % 24

microsecond = microsecond % seconds * 1000_000

return time(hour=hours, minute=minutes, second=seconds, microsecond=microsecond)


def _to_python_timedelta(
value: Union[int, float], tu: Optional[str] = "ns"
) -> timedelta:
Expand Down
13 changes: 13 additions & 0 deletions py-polars/src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,19 @@ impl ToPyObject for Wrap<&DatetimeChunked> {
}
}

impl ToPyObject for Wrap<&TimeChunked> {
fn to_object(&self, py: Python) -> PyObject {
let pl = PyModule::import(py, "polars").unwrap();
let pl_utils = pl.getattr("utils").unwrap();
let convert = pl_utils.getattr("_to_python_time").unwrap();
let iter = self
.0
.into_iter()
.map(|opt_v| opt_v.map(|v| convert.call1((v,)).unwrap()));
PyList::new(py, iter).into_py(py)
}
}

impl ToPyObject for Wrap<&DateChunked> {
fn to_object(&self, py: Python) -> PyObject {
let pl = PyModule::import(py, "polars").unwrap();
Expand Down
4 changes: 4 additions & 0 deletions py-polars/src/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,10 @@ impl PySeries {
let ca = series.date().unwrap();
return Wrap(ca).to_object(python);
}
DataType::Time => {
let ca = series.time().unwrap();
return Wrap(ca).to_object(python);
}
DataType::Datetime(_, _) => {
let ca = series.datetime().unwrap();
return Wrap(ca).to_object(python);
Expand Down
15 changes: 14 additions & 1 deletion py-polars/tests/test_datelike.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io
from datetime import date, datetime, timedelta
import typing
from datetime import date, datetime, time, timedelta

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -834,3 +835,15 @@ def test_agg_logical() -> None:
s = pl.Series(dates)
assert s.max() == dates[1]
assert s.min() == dates[0]


@typing.no_type_check
def test_from_time_arrow() -> None:
times = pa.array([10, 20, 30], type=pa.time32("s"))
times_table = pa.table([times], names=["times"])

assert pl.from_arrow(times_table).to_series().to_list() == [
time(0, 0, 10),
time(0, 0, 20),
time(0, 0, 30),
]

0 comments on commit d7751a8

Please sign in to comment.