Path: blob/main/py-polars/tests/unit/operations/namespaces/temporal/test_round.py
6940 views
from __future__ import annotations12from datetime import date, datetime, timedelta3from typing import TYPE_CHECKING4from zoneinfo import ZoneInfo56import hypothesis.strategies as st7import pytest8from hypothesis import given910import polars as pl11from polars._utils.convert import parse_as_duration_string12from polars.testing import assert_series_equal1314if TYPE_CHECKING:15from polars.type_aliases import TimeUnit161718@pytest.mark.parametrize("time_zone", [None, "Asia/Kathmandu"])19def test_round_by_day_datetime(time_zone: str | None) -> None:20ser = pl.Series([datetime(2021, 11, 7, 3)]).dt.replace_time_zone(time_zone)21result = ser.dt.round("1d")22expected = pl.Series([datetime(2021, 11, 7)]).dt.replace_time_zone(time_zone)23assert_series_equal(result, expected)242526def test_round_ambiguous() -> None:27t = (28pl.datetime_range(29date(2020, 10, 25),30datetime(2020, 10, 25, 2),31"30m",32eager=True,33time_zone="Europe/London",34)35.alias("datetime")36.dt.offset_by("15m")37)38result = t.dt.round("30m")39expected = (40pl.Series(41[42"2020-10-25T00:30:00+0100",43"2020-10-25T01:00:00+0100",44"2020-10-25T01:30:00+0100",45"2020-10-25T01:00:00+0000",46"2020-10-25T01:30:00+0000",47"2020-10-25T02:00:00+0000",48"2020-10-25T02:30:00+0000",49]50)51.str.to_datetime()52.dt.convert_time_zone("Europe/London")53.rename("datetime")54)55assert_series_equal(result, expected)5657df = pl.DataFrame(58{59"date": pl.datetime_range(60date(2020, 10, 25),61datetime(2020, 10, 25, 2),62"30m",63eager=True,64time_zone="Europe/London",65).dt.offset_by("15m")66}67)6869df = df.select(pl.col("date").dt.round("30m"))70assert df.to_dict(as_series=False) == {71"date": [72datetime(2020, 10, 25, 0, 30, tzinfo=ZoneInfo("Europe/London")),73datetime(2020, 10, 25, 1, tzinfo=ZoneInfo("Europe/London")),74datetime(2020, 10, 25, 1, 30, tzinfo=ZoneInfo("Europe/London")),75datetime(2020, 10, 25, 1, tzinfo=ZoneInfo("Europe/London")),76datetime(2020, 10, 25, 1, 30, tzinfo=ZoneInfo("Europe/London")),77datetime(2020, 10, 25, 2, tzinfo=ZoneInfo("Europe/London")),78datetime(2020, 10, 25, 2, 30, tzinfo=ZoneInfo("Europe/London")),79]80}818283def test_round_by_week() -> None:84df = pl.DataFrame(85{86"date": pl.Series(87[88# Sunday and Monday89"1998-04-12",90"2022-11-28",91]92).str.strptime(pl.Date, "%Y-%m-%d")93}94)9596assert (97df.select(98pl.col("date").dt.round("7d").alias("7d"),99pl.col("date").dt.round("1w").alias("1w"),100)101).to_dict(as_series=False) == {102"7d": [date(1998, 4, 9), date(2022, 12, 1)],103"1w": [date(1998, 4, 13), date(2022, 11, 28)],104}105106107@given(108datetimes=st.lists(109st.datetimes(min_value=datetime(1960, 1, 1), max_value=datetime(1980, 1, 1)),110min_size=1,111max_size=3,112),113every=st.timedeltas(114min_value=timedelta(microseconds=1), max_value=timedelta(days=1)115).map(parse_as_duration_string),116)117def test_dt_round_fast_path_vs_slow_path(datetimes: list[datetime], every: str) -> None:118s = pl.Series(datetimes)119# Might use fastpath:120result = s.dt.round(every)121# Definitely uses slowpath:122expected = s.dt.round(pl.Series([every] * len(datetimes)))123assert_series_equal(result, expected)124125126def test_round_date() -> None:127# n vs n128df = pl.DataFrame(129{"a": [date(2020, 1, 1), None, date(2020, 1, 19)], "b": [None, "1mo", "1mo"]}130)131result = df.select(pl.col("a").dt.round(pl.col("b")))["a"]132expected = pl.Series("a", [None, None, date(2020, 2, 1)])133assert_series_equal(result, expected)134135# n vs 1136df = pl.DataFrame(137{"a": [date(2020, 1, 1), None, date(2020, 1, 3)], "b": [None, "1mo", "1mo"]}138)139result = df.select(pl.col("a").dt.round("1mo"))["a"]140expected = pl.Series("a", [date(2020, 1, 1), None, date(2020, 1, 1)])141assert_series_equal(result, expected)142143# n vs missing144df = pl.DataFrame(145{"a": [date(2020, 1, 1), None, date(2020, 1, 3)], "b": [None, "1mo", "1mo"]}146)147result = df.select(pl.col("a").dt.round(pl.lit(None, dtype=pl.String)))["a"]148expected = pl.Series("a", [None, None, None], dtype=pl.Date)149assert_series_equal(result, expected)150151# 1 vs n152df = pl.DataFrame(153{"a": [date(2020, 1, 1), None, date(2020, 1, 3)], "b": [None, "1mo", "1mo"]}154)155result = df.select(a=pl.date(2020, 1, 1).dt.round(pl.col("b")))["a"]156expected = pl.Series("a", [None, date(2020, 1, 1), date(2020, 1, 1)])157assert_series_equal(result, expected)158159# missing vs n160df = pl.DataFrame(161{"a": [date(2020, 1, 1), None, date(2020, 1, 3)], "b": [None, "1mo", "1mo"]}162)163result = df.select(a=pl.lit(None, dtype=pl.Date).dt.round(pl.col("b")))["a"]164expected = pl.Series("a", [None, None, None], dtype=pl.Date)165assert_series_equal(result, expected)166167168@pytest.mark.parametrize("time_unit", ["ms", "us", "ns"])169def test_round_datetime_simple(time_unit: TimeUnit) -> None:170s = pl.Series([datetime(2020, 1, 2, 6)], dtype=pl.Datetime(time_unit))171result = s.dt.round("1mo").item()172assert result == datetime(2020, 1, 1)173result = s.dt.round("1d").item()174assert result == datetime(2020, 1, 2)175176177@pytest.mark.parametrize("time_unit", ["ms", "us", "ns"])178def test_round_datetime_w_expression(time_unit: TimeUnit) -> None:179df = pl.DataFrame(180{"a": [datetime(2020, 1, 2, 6), datetime(2020, 1, 20, 21)], "b": ["1mo", "1d"]},181schema_overrides={"a": pl.Datetime(time_unit)},182)183result = df.select(pl.col("a").dt.round(pl.col("b")))["a"]184assert result[0] == datetime(2020, 1, 1)185assert result[1] == datetime(2020, 1, 21)186187188@pytest.mark.parametrize(189("time_unit", "expected"),190[191("ms", 0),192("us", 0),193("ns", 0),194],195)196def test_round_negative_towards_epoch_18239(time_unit: TimeUnit, expected: int) -> None:197s = pl.Series([datetime(1970, 1, 1)], dtype=pl.Datetime(time_unit))198s = s.dt.offset_by(f"-1{time_unit}")199result = s.dt.round(f"2{time_unit}").dt.timestamp(time_unit="ns").item()200assert result == expected201result = (202s.dt.replace_time_zone("Europe/London")203.dt.round(f"2{time_unit}")204.dt.replace_time_zone(None)205.dt.timestamp(time_unit="ns")206.item()207)208assert result == expected209210211@pytest.mark.parametrize(212("time_unit", "expected"),213[214("ms", 2_000_000),215("us", 2_000),216("ns", 2),217],218)219def test_round_positive_away_from_epoch_18239(220time_unit: TimeUnit, expected: int221) -> None:222s = pl.Series([datetime(1970, 1, 1)], dtype=pl.Datetime(time_unit))223s = s.dt.offset_by(f"1{time_unit}")224result = s.dt.round(f"2{time_unit}").dt.timestamp(time_unit="ns").item()225assert result == expected226result = (227s.dt.replace_time_zone("Europe/London")228.dt.round(f"2{time_unit}")229.dt.replace_time_zone(None)230.dt.timestamp(time_unit="ns")231.item()232)233assert result == expected234235236@pytest.mark.parametrize("as_date", [False, True])237def test_round_unequal_length_22018(as_date: bool) -> None:238start = datetime(2001, 1, 1)239stop = datetime(2001, 1, 1, 1)240s = pl.datetime_range(start, stop, "10m", eager=True).alias("datetime")241if as_date:242s = s.dt.date()243244with pytest.raises(pl.exceptions.ShapeError):245s.dt.round(pl.Series(["30m", "20m"]))246247248def test_round_small() -> None:249small = 1.234e-320250small_s = pl.Series([small])251assert small_s.round().item() == 0.0252assert small_s.round(320).item() == 1e-320253assert small_s.round(321).item() == 1.2e-320254assert small_s.round(322).item() == 1.23e-320255assert small_s.round(323).item() == 1.234e-320256assert small_s.round(324).item() == small257assert small_s.round(1000).item() == small258259assert small_s.round_sig_figs(1).item() == 1e-320260assert small_s.round_sig_figs(2).item() == 1.2e-320261assert small_s.round_sig_figs(3).item() == 1.23e-320262assert small_s.round_sig_figs(4).item() == 1.234e-320263assert small_s.round_sig_figs(5).item() == small264assert small_s.round_sig_figs(1000).item() == small265266267def test_round_big() -> None:268big = 1.234e308269max_err = big / 10**10270big_s = pl.Series([big])271assert big_s.round().item() == big272assert big_s.round(1).item() == big273assert big_s.round(100).item() == big274275assert abs(big_s.round_sig_figs(1).item() - 1e308) <= max_err276assert abs(big_s.round_sig_figs(2).item() - 1.2e308) <= max_err277assert abs(big_s.round_sig_figs(3).item() - 1.23e308) <= max_err278assert abs(big_s.round_sig_figs(4).item() - 1.234e308) <= max_err279assert abs(big_s.round_sig_figs(4).item() - big) <= max_err280assert big_s.round_sig_figs(100).item() == big281282283