Spaces:
Configuration error
Configuration error
Upload folder using huggingface_hub (part 14)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_asfreq.py +815 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_period.py +1208 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/test_na_scalar.py +334 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/test_nat.py +684 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_as_unit.py +80 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_round.py +195 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_arithmetic.py +1251 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_constructors.py +795 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_formats.py +109 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_timedelta.py +740 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_as_unit.py +86 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_normalize.py +29 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_replace.py +211 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_round.py +372 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_timestamp_method.py +34 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_julian_date.py +28 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_pydatetime.py +83 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_convert.py +51 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_localize.py +364 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_arithmetic.py +346 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_comparisons.py +313 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_constructors.py +1107 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_formats.py +206 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timestamp.py +938 -0
- .venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timezones.py +25 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/accessors/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_cat_accessor.py +260 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_dt_accessor.py +832 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_list_accessor.py +146 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py +9 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_str_accessor.py +26 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_struct_accessor.py +188 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_datetime.py +493 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_delitem.py +69 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_get.py +228 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_getitem.py +721 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_indexing.py +510 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_mask.py +69 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_set_value.py +49 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_setitem.py +1843 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_take.py +50 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_where.py +446 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_xs.py +82 -0
- .venv/lib/python3.13/site-packages/pandas/tests/series/methods/__init__.py +7 -0
.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_asfreq.py
ADDED
|
@@ -0,0 +1,815 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
|
| 4 |
+
|
| 5 |
+
from pandas import (
|
| 6 |
+
Period,
|
| 7 |
+
Timestamp,
|
| 8 |
+
offsets,
|
| 9 |
+
)
|
| 10 |
+
import pandas._testing as tm
|
| 11 |
+
|
| 12 |
+
bday_msg = "Period with BDay freq is deprecated"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class TestFreqConversion:
|
| 16 |
+
"""Test frequency conversion of date objects"""
|
| 17 |
+
|
| 18 |
+
@pytest.mark.filterwarnings("ignore:Period with BDay:FutureWarning")
|
| 19 |
+
@pytest.mark.parametrize("freq", ["Y", "Q", "M", "W", "B", "D"])
|
| 20 |
+
def test_asfreq_near_zero(self, freq):
|
| 21 |
+
# GH#19643, GH#19650
|
| 22 |
+
per = Period("0001-01-01", freq=freq)
|
| 23 |
+
tup1 = (per.year, per.hour, per.day)
|
| 24 |
+
|
| 25 |
+
prev = per - 1
|
| 26 |
+
assert prev.ordinal == per.ordinal - 1
|
| 27 |
+
tup2 = (prev.year, prev.month, prev.day)
|
| 28 |
+
assert tup2 < tup1
|
| 29 |
+
|
| 30 |
+
def test_asfreq_near_zero_weekly(self):
|
| 31 |
+
# GH#19834
|
| 32 |
+
per1 = Period("0001-01-01", "D") + 6
|
| 33 |
+
per2 = Period("0001-01-01", "D") - 6
|
| 34 |
+
week1 = per1.asfreq("W")
|
| 35 |
+
week2 = per2.asfreq("W")
|
| 36 |
+
assert week1 != week2
|
| 37 |
+
assert week1.asfreq("D", "E") >= per1
|
| 38 |
+
assert week2.asfreq("D", "S") <= per2
|
| 39 |
+
|
| 40 |
+
def test_to_timestamp_out_of_bounds(self):
|
| 41 |
+
# GH#19643, used to incorrectly give Timestamp in 1754
|
| 42 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 43 |
+
per = Period("0001-01-01", freq="B")
|
| 44 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 45 |
+
per.to_timestamp()
|
| 46 |
+
|
| 47 |
+
def test_asfreq_corner(self):
|
| 48 |
+
val = Period(freq="Y", year=2007)
|
| 49 |
+
result1 = val.asfreq("5min")
|
| 50 |
+
result2 = val.asfreq("min")
|
| 51 |
+
expected = Period("2007-12-31 23:59", freq="min")
|
| 52 |
+
assert result1.ordinal == expected.ordinal
|
| 53 |
+
assert result1.freqstr == "5min"
|
| 54 |
+
assert result2.ordinal == expected.ordinal
|
| 55 |
+
assert result2.freqstr == "min"
|
| 56 |
+
|
| 57 |
+
def test_conv_annual(self):
|
| 58 |
+
# frequency conversion tests: from Annual Frequency
|
| 59 |
+
msg = INVALID_FREQ_ERR_MSG
|
| 60 |
+
|
| 61 |
+
ival_A = Period(freq="Y", year=2007)
|
| 62 |
+
|
| 63 |
+
ival_AJAN = Period(freq="Y-JAN", year=2007)
|
| 64 |
+
ival_AJUN = Period(freq="Y-JUN", year=2007)
|
| 65 |
+
ival_ANOV = Period(freq="Y-NOV", year=2007)
|
| 66 |
+
|
| 67 |
+
ival_A_to_Q_start = Period(freq="Q", year=2007, quarter=1)
|
| 68 |
+
ival_A_to_Q_end = Period(freq="Q", year=2007, quarter=4)
|
| 69 |
+
ival_A_to_M_start = Period(freq="M", year=2007, month=1)
|
| 70 |
+
ival_A_to_M_end = Period(freq="M", year=2007, month=12)
|
| 71 |
+
ival_A_to_W_start = Period(freq="W", year=2007, month=1, day=1)
|
| 72 |
+
ival_A_to_W_end = Period(freq="W", year=2007, month=12, day=31)
|
| 73 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 74 |
+
ival_A_to_B_start = Period(freq="B", year=2007, month=1, day=1)
|
| 75 |
+
ival_A_to_B_end = Period(freq="B", year=2007, month=12, day=31)
|
| 76 |
+
ival_A_to_D_start = Period(freq="D", year=2007, month=1, day=1)
|
| 77 |
+
ival_A_to_D_end = Period(freq="D", year=2007, month=12, day=31)
|
| 78 |
+
ival_A_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 79 |
+
ival_A_to_H_end = Period(freq="h", year=2007, month=12, day=31, hour=23)
|
| 80 |
+
ival_A_to_T_start = Period(
|
| 81 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0
|
| 82 |
+
)
|
| 83 |
+
ival_A_to_T_end = Period(
|
| 84 |
+
freq="Min", year=2007, month=12, day=31, hour=23, minute=59
|
| 85 |
+
)
|
| 86 |
+
ival_A_to_S_start = Period(
|
| 87 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 88 |
+
)
|
| 89 |
+
ival_A_to_S_end = Period(
|
| 90 |
+
freq="s", year=2007, month=12, day=31, hour=23, minute=59, second=59
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
ival_AJAN_to_D_end = Period(freq="D", year=2007, month=1, day=31)
|
| 94 |
+
ival_AJAN_to_D_start = Period(freq="D", year=2006, month=2, day=1)
|
| 95 |
+
ival_AJUN_to_D_end = Period(freq="D", year=2007, month=6, day=30)
|
| 96 |
+
ival_AJUN_to_D_start = Period(freq="D", year=2006, month=7, day=1)
|
| 97 |
+
ival_ANOV_to_D_end = Period(freq="D", year=2007, month=11, day=30)
|
| 98 |
+
ival_ANOV_to_D_start = Period(freq="D", year=2006, month=12, day=1)
|
| 99 |
+
|
| 100 |
+
assert ival_A.asfreq("Q", "s") == ival_A_to_Q_start
|
| 101 |
+
assert ival_A.asfreq("Q", "e") == ival_A_to_Q_end
|
| 102 |
+
assert ival_A.asfreq("M", "s") == ival_A_to_M_start
|
| 103 |
+
assert ival_A.asfreq("M", "E") == ival_A_to_M_end
|
| 104 |
+
assert ival_A.asfreq("W", "s") == ival_A_to_W_start
|
| 105 |
+
assert ival_A.asfreq("W", "E") == ival_A_to_W_end
|
| 106 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 107 |
+
assert ival_A.asfreq("B", "s") == ival_A_to_B_start
|
| 108 |
+
assert ival_A.asfreq("B", "E") == ival_A_to_B_end
|
| 109 |
+
assert ival_A.asfreq("D", "s") == ival_A_to_D_start
|
| 110 |
+
assert ival_A.asfreq("D", "E") == ival_A_to_D_end
|
| 111 |
+
with pytest.raises(ValueError, match=msg):
|
| 112 |
+
assert ival_A.asfreq("H", "s") == ival_A_to_H_start
|
| 113 |
+
assert ival_A.asfreq("H", "E") == ival_A_to_H_end
|
| 114 |
+
assert ival_A.asfreq("min", "s") == ival_A_to_T_start
|
| 115 |
+
assert ival_A.asfreq("min", "E") == ival_A_to_T_end
|
| 116 |
+
with pytest.raises(ValueError, match=msg):
|
| 117 |
+
assert ival_A.asfreq("T", "s") == ival_A_to_T_start
|
| 118 |
+
assert ival_A.asfreq("T", "E") == ival_A_to_T_end
|
| 119 |
+
assert ival_A.asfreq("S", "S") == ival_A_to_S_start
|
| 120 |
+
assert ival_A.asfreq("S", "E") == ival_A_to_S_end
|
| 121 |
+
|
| 122 |
+
assert ival_AJAN.asfreq("D", "s") == ival_AJAN_to_D_start
|
| 123 |
+
assert ival_AJAN.asfreq("D", "E") == ival_AJAN_to_D_end
|
| 124 |
+
|
| 125 |
+
assert ival_AJUN.asfreq("D", "s") == ival_AJUN_to_D_start
|
| 126 |
+
assert ival_AJUN.asfreq("D", "E") == ival_AJUN_to_D_end
|
| 127 |
+
|
| 128 |
+
assert ival_ANOV.asfreq("D", "s") == ival_ANOV_to_D_start
|
| 129 |
+
assert ival_ANOV.asfreq("D", "E") == ival_ANOV_to_D_end
|
| 130 |
+
|
| 131 |
+
assert ival_A.asfreq("Y") == ival_A
|
| 132 |
+
|
| 133 |
+
def test_conv_quarterly(self):
|
| 134 |
+
# frequency conversion tests: from Quarterly Frequency
|
| 135 |
+
|
| 136 |
+
ival_Q = Period(freq="Q", year=2007, quarter=1)
|
| 137 |
+
ival_Q_end_of_year = Period(freq="Q", year=2007, quarter=4)
|
| 138 |
+
|
| 139 |
+
ival_QEJAN = Period(freq="Q-JAN", year=2007, quarter=1)
|
| 140 |
+
ival_QEJUN = Period(freq="Q-JUN", year=2007, quarter=1)
|
| 141 |
+
|
| 142 |
+
ival_Q_to_A = Period(freq="Y", year=2007)
|
| 143 |
+
ival_Q_to_M_start = Period(freq="M", year=2007, month=1)
|
| 144 |
+
ival_Q_to_M_end = Period(freq="M", year=2007, month=3)
|
| 145 |
+
ival_Q_to_W_start = Period(freq="W", year=2007, month=1, day=1)
|
| 146 |
+
ival_Q_to_W_end = Period(freq="W", year=2007, month=3, day=31)
|
| 147 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 148 |
+
ival_Q_to_B_start = Period(freq="B", year=2007, month=1, day=1)
|
| 149 |
+
ival_Q_to_B_end = Period(freq="B", year=2007, month=3, day=30)
|
| 150 |
+
ival_Q_to_D_start = Period(freq="D", year=2007, month=1, day=1)
|
| 151 |
+
ival_Q_to_D_end = Period(freq="D", year=2007, month=3, day=31)
|
| 152 |
+
ival_Q_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 153 |
+
ival_Q_to_H_end = Period(freq="h", year=2007, month=3, day=31, hour=23)
|
| 154 |
+
ival_Q_to_T_start = Period(
|
| 155 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0
|
| 156 |
+
)
|
| 157 |
+
ival_Q_to_T_end = Period(
|
| 158 |
+
freq="Min", year=2007, month=3, day=31, hour=23, minute=59
|
| 159 |
+
)
|
| 160 |
+
ival_Q_to_S_start = Period(
|
| 161 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 162 |
+
)
|
| 163 |
+
ival_Q_to_S_end = Period(
|
| 164 |
+
freq="s", year=2007, month=3, day=31, hour=23, minute=59, second=59
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
ival_QEJAN_to_D_start = Period(freq="D", year=2006, month=2, day=1)
|
| 168 |
+
ival_QEJAN_to_D_end = Period(freq="D", year=2006, month=4, day=30)
|
| 169 |
+
|
| 170 |
+
ival_QEJUN_to_D_start = Period(freq="D", year=2006, month=7, day=1)
|
| 171 |
+
ival_QEJUN_to_D_end = Period(freq="D", year=2006, month=9, day=30)
|
| 172 |
+
|
| 173 |
+
assert ival_Q.asfreq("Y") == ival_Q_to_A
|
| 174 |
+
assert ival_Q_end_of_year.asfreq("Y") == ival_Q_to_A
|
| 175 |
+
|
| 176 |
+
assert ival_Q.asfreq("M", "s") == ival_Q_to_M_start
|
| 177 |
+
assert ival_Q.asfreq("M", "E") == ival_Q_to_M_end
|
| 178 |
+
assert ival_Q.asfreq("W", "s") == ival_Q_to_W_start
|
| 179 |
+
assert ival_Q.asfreq("W", "E") == ival_Q_to_W_end
|
| 180 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 181 |
+
assert ival_Q.asfreq("B", "s") == ival_Q_to_B_start
|
| 182 |
+
assert ival_Q.asfreq("B", "E") == ival_Q_to_B_end
|
| 183 |
+
assert ival_Q.asfreq("D", "s") == ival_Q_to_D_start
|
| 184 |
+
assert ival_Q.asfreq("D", "E") == ival_Q_to_D_end
|
| 185 |
+
assert ival_Q.asfreq("h", "s") == ival_Q_to_H_start
|
| 186 |
+
assert ival_Q.asfreq("h", "E") == ival_Q_to_H_end
|
| 187 |
+
assert ival_Q.asfreq("Min", "s") == ival_Q_to_T_start
|
| 188 |
+
assert ival_Q.asfreq("Min", "E") == ival_Q_to_T_end
|
| 189 |
+
assert ival_Q.asfreq("s", "s") == ival_Q_to_S_start
|
| 190 |
+
assert ival_Q.asfreq("s", "E") == ival_Q_to_S_end
|
| 191 |
+
|
| 192 |
+
assert ival_QEJAN.asfreq("D", "s") == ival_QEJAN_to_D_start
|
| 193 |
+
assert ival_QEJAN.asfreq("D", "E") == ival_QEJAN_to_D_end
|
| 194 |
+
assert ival_QEJUN.asfreq("D", "s") == ival_QEJUN_to_D_start
|
| 195 |
+
assert ival_QEJUN.asfreq("D", "E") == ival_QEJUN_to_D_end
|
| 196 |
+
|
| 197 |
+
assert ival_Q.asfreq("Q") == ival_Q
|
| 198 |
+
|
| 199 |
+
def test_conv_monthly(self):
|
| 200 |
+
# frequency conversion tests: from Monthly Frequency
|
| 201 |
+
|
| 202 |
+
ival_M = Period(freq="M", year=2007, month=1)
|
| 203 |
+
ival_M_end_of_year = Period(freq="M", year=2007, month=12)
|
| 204 |
+
ival_M_end_of_quarter = Period(freq="M", year=2007, month=3)
|
| 205 |
+
ival_M_to_A = Period(freq="Y", year=2007)
|
| 206 |
+
ival_M_to_Q = Period(freq="Q", year=2007, quarter=1)
|
| 207 |
+
ival_M_to_W_start = Period(freq="W", year=2007, month=1, day=1)
|
| 208 |
+
ival_M_to_W_end = Period(freq="W", year=2007, month=1, day=31)
|
| 209 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 210 |
+
ival_M_to_B_start = Period(freq="B", year=2007, month=1, day=1)
|
| 211 |
+
ival_M_to_B_end = Period(freq="B", year=2007, month=1, day=31)
|
| 212 |
+
ival_M_to_D_start = Period(freq="D", year=2007, month=1, day=1)
|
| 213 |
+
ival_M_to_D_end = Period(freq="D", year=2007, month=1, day=31)
|
| 214 |
+
ival_M_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 215 |
+
ival_M_to_H_end = Period(freq="h", year=2007, month=1, day=31, hour=23)
|
| 216 |
+
ival_M_to_T_start = Period(
|
| 217 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0
|
| 218 |
+
)
|
| 219 |
+
ival_M_to_T_end = Period(
|
| 220 |
+
freq="Min", year=2007, month=1, day=31, hour=23, minute=59
|
| 221 |
+
)
|
| 222 |
+
ival_M_to_S_start = Period(
|
| 223 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 224 |
+
)
|
| 225 |
+
ival_M_to_S_end = Period(
|
| 226 |
+
freq="s", year=2007, month=1, day=31, hour=23, minute=59, second=59
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
assert ival_M.asfreq("Y") == ival_M_to_A
|
| 230 |
+
assert ival_M_end_of_year.asfreq("Y") == ival_M_to_A
|
| 231 |
+
assert ival_M.asfreq("Q") == ival_M_to_Q
|
| 232 |
+
assert ival_M_end_of_quarter.asfreq("Q") == ival_M_to_Q
|
| 233 |
+
|
| 234 |
+
assert ival_M.asfreq("W", "s") == ival_M_to_W_start
|
| 235 |
+
assert ival_M.asfreq("W", "E") == ival_M_to_W_end
|
| 236 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 237 |
+
assert ival_M.asfreq("B", "s") == ival_M_to_B_start
|
| 238 |
+
assert ival_M.asfreq("B", "E") == ival_M_to_B_end
|
| 239 |
+
assert ival_M.asfreq("D", "s") == ival_M_to_D_start
|
| 240 |
+
assert ival_M.asfreq("D", "E") == ival_M_to_D_end
|
| 241 |
+
assert ival_M.asfreq("h", "s") == ival_M_to_H_start
|
| 242 |
+
assert ival_M.asfreq("h", "E") == ival_M_to_H_end
|
| 243 |
+
assert ival_M.asfreq("Min", "s") == ival_M_to_T_start
|
| 244 |
+
assert ival_M.asfreq("Min", "E") == ival_M_to_T_end
|
| 245 |
+
assert ival_M.asfreq("s", "s") == ival_M_to_S_start
|
| 246 |
+
assert ival_M.asfreq("s", "E") == ival_M_to_S_end
|
| 247 |
+
|
| 248 |
+
assert ival_M.asfreq("M") == ival_M
|
| 249 |
+
|
| 250 |
+
def test_conv_weekly(self):
|
| 251 |
+
# frequency conversion tests: from Weekly Frequency
|
| 252 |
+
ival_W = Period(freq="W", year=2007, month=1, day=1)
|
| 253 |
+
|
| 254 |
+
ival_WSUN = Period(freq="W", year=2007, month=1, day=7)
|
| 255 |
+
ival_WSAT = Period(freq="W-SAT", year=2007, month=1, day=6)
|
| 256 |
+
ival_WFRI = Period(freq="W-FRI", year=2007, month=1, day=5)
|
| 257 |
+
ival_WTHU = Period(freq="W-THU", year=2007, month=1, day=4)
|
| 258 |
+
ival_WWED = Period(freq="W-WED", year=2007, month=1, day=3)
|
| 259 |
+
ival_WTUE = Period(freq="W-TUE", year=2007, month=1, day=2)
|
| 260 |
+
ival_WMON = Period(freq="W-MON", year=2007, month=1, day=1)
|
| 261 |
+
|
| 262 |
+
ival_WSUN_to_D_start = Period(freq="D", year=2007, month=1, day=1)
|
| 263 |
+
ival_WSUN_to_D_end = Period(freq="D", year=2007, month=1, day=7)
|
| 264 |
+
ival_WSAT_to_D_start = Period(freq="D", year=2006, month=12, day=31)
|
| 265 |
+
ival_WSAT_to_D_end = Period(freq="D", year=2007, month=1, day=6)
|
| 266 |
+
ival_WFRI_to_D_start = Period(freq="D", year=2006, month=12, day=30)
|
| 267 |
+
ival_WFRI_to_D_end = Period(freq="D", year=2007, month=1, day=5)
|
| 268 |
+
ival_WTHU_to_D_start = Period(freq="D", year=2006, month=12, day=29)
|
| 269 |
+
ival_WTHU_to_D_end = Period(freq="D", year=2007, month=1, day=4)
|
| 270 |
+
ival_WWED_to_D_start = Period(freq="D", year=2006, month=12, day=28)
|
| 271 |
+
ival_WWED_to_D_end = Period(freq="D", year=2007, month=1, day=3)
|
| 272 |
+
ival_WTUE_to_D_start = Period(freq="D", year=2006, month=12, day=27)
|
| 273 |
+
ival_WTUE_to_D_end = Period(freq="D", year=2007, month=1, day=2)
|
| 274 |
+
ival_WMON_to_D_start = Period(freq="D", year=2006, month=12, day=26)
|
| 275 |
+
ival_WMON_to_D_end = Period(freq="D", year=2007, month=1, day=1)
|
| 276 |
+
|
| 277 |
+
ival_W_end_of_year = Period(freq="W", year=2007, month=12, day=31)
|
| 278 |
+
ival_W_end_of_quarter = Period(freq="W", year=2007, month=3, day=31)
|
| 279 |
+
ival_W_end_of_month = Period(freq="W", year=2007, month=1, day=31)
|
| 280 |
+
ival_W_to_A = Period(freq="Y", year=2007)
|
| 281 |
+
ival_W_to_Q = Period(freq="Q", year=2007, quarter=1)
|
| 282 |
+
ival_W_to_M = Period(freq="M", year=2007, month=1)
|
| 283 |
+
|
| 284 |
+
if Period(freq="D", year=2007, month=12, day=31).weekday == 6:
|
| 285 |
+
ival_W_to_A_end_of_year = Period(freq="Y", year=2007)
|
| 286 |
+
else:
|
| 287 |
+
ival_W_to_A_end_of_year = Period(freq="Y", year=2008)
|
| 288 |
+
|
| 289 |
+
if Period(freq="D", year=2007, month=3, day=31).weekday == 6:
|
| 290 |
+
ival_W_to_Q_end_of_quarter = Period(freq="Q", year=2007, quarter=1)
|
| 291 |
+
else:
|
| 292 |
+
ival_W_to_Q_end_of_quarter = Period(freq="Q", year=2007, quarter=2)
|
| 293 |
+
|
| 294 |
+
if Period(freq="D", year=2007, month=1, day=31).weekday == 6:
|
| 295 |
+
ival_W_to_M_end_of_month = Period(freq="M", year=2007, month=1)
|
| 296 |
+
else:
|
| 297 |
+
ival_W_to_M_end_of_month = Period(freq="M", year=2007, month=2)
|
| 298 |
+
|
| 299 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 300 |
+
ival_W_to_B_start = Period(freq="B", year=2007, month=1, day=1)
|
| 301 |
+
ival_W_to_B_end = Period(freq="B", year=2007, month=1, day=5)
|
| 302 |
+
ival_W_to_D_start = Period(freq="D", year=2007, month=1, day=1)
|
| 303 |
+
ival_W_to_D_end = Period(freq="D", year=2007, month=1, day=7)
|
| 304 |
+
ival_W_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 305 |
+
ival_W_to_H_end = Period(freq="h", year=2007, month=1, day=7, hour=23)
|
| 306 |
+
ival_W_to_T_start = Period(
|
| 307 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0
|
| 308 |
+
)
|
| 309 |
+
ival_W_to_T_end = Period(
|
| 310 |
+
freq="Min", year=2007, month=1, day=7, hour=23, minute=59
|
| 311 |
+
)
|
| 312 |
+
ival_W_to_S_start = Period(
|
| 313 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 314 |
+
)
|
| 315 |
+
ival_W_to_S_end = Period(
|
| 316 |
+
freq="s", year=2007, month=1, day=7, hour=23, minute=59, second=59
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
assert ival_W.asfreq("Y") == ival_W_to_A
|
| 320 |
+
assert ival_W_end_of_year.asfreq("Y") == ival_W_to_A_end_of_year
|
| 321 |
+
|
| 322 |
+
assert ival_W.asfreq("Q") == ival_W_to_Q
|
| 323 |
+
assert ival_W_end_of_quarter.asfreq("Q") == ival_W_to_Q_end_of_quarter
|
| 324 |
+
|
| 325 |
+
assert ival_W.asfreq("M") == ival_W_to_M
|
| 326 |
+
assert ival_W_end_of_month.asfreq("M") == ival_W_to_M_end_of_month
|
| 327 |
+
|
| 328 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 329 |
+
assert ival_W.asfreq("B", "s") == ival_W_to_B_start
|
| 330 |
+
assert ival_W.asfreq("B", "E") == ival_W_to_B_end
|
| 331 |
+
|
| 332 |
+
assert ival_W.asfreq("D", "s") == ival_W_to_D_start
|
| 333 |
+
assert ival_W.asfreq("D", "E") == ival_W_to_D_end
|
| 334 |
+
|
| 335 |
+
assert ival_WSUN.asfreq("D", "s") == ival_WSUN_to_D_start
|
| 336 |
+
assert ival_WSUN.asfreq("D", "E") == ival_WSUN_to_D_end
|
| 337 |
+
assert ival_WSAT.asfreq("D", "s") == ival_WSAT_to_D_start
|
| 338 |
+
assert ival_WSAT.asfreq("D", "E") == ival_WSAT_to_D_end
|
| 339 |
+
assert ival_WFRI.asfreq("D", "s") == ival_WFRI_to_D_start
|
| 340 |
+
assert ival_WFRI.asfreq("D", "E") == ival_WFRI_to_D_end
|
| 341 |
+
assert ival_WTHU.asfreq("D", "s") == ival_WTHU_to_D_start
|
| 342 |
+
assert ival_WTHU.asfreq("D", "E") == ival_WTHU_to_D_end
|
| 343 |
+
assert ival_WWED.asfreq("D", "s") == ival_WWED_to_D_start
|
| 344 |
+
assert ival_WWED.asfreq("D", "E") == ival_WWED_to_D_end
|
| 345 |
+
assert ival_WTUE.asfreq("D", "s") == ival_WTUE_to_D_start
|
| 346 |
+
assert ival_WTUE.asfreq("D", "E") == ival_WTUE_to_D_end
|
| 347 |
+
assert ival_WMON.asfreq("D", "s") == ival_WMON_to_D_start
|
| 348 |
+
assert ival_WMON.asfreq("D", "E") == ival_WMON_to_D_end
|
| 349 |
+
|
| 350 |
+
assert ival_W.asfreq("h", "s") == ival_W_to_H_start
|
| 351 |
+
assert ival_W.asfreq("h", "E") == ival_W_to_H_end
|
| 352 |
+
assert ival_W.asfreq("Min", "s") == ival_W_to_T_start
|
| 353 |
+
assert ival_W.asfreq("Min", "E") == ival_W_to_T_end
|
| 354 |
+
assert ival_W.asfreq("s", "s") == ival_W_to_S_start
|
| 355 |
+
assert ival_W.asfreq("s", "E") == ival_W_to_S_end
|
| 356 |
+
|
| 357 |
+
assert ival_W.asfreq("W") == ival_W
|
| 358 |
+
|
| 359 |
+
msg = INVALID_FREQ_ERR_MSG
|
| 360 |
+
with pytest.raises(ValueError, match=msg):
|
| 361 |
+
ival_W.asfreq("WK")
|
| 362 |
+
|
| 363 |
+
def test_conv_weekly_legacy(self):
|
| 364 |
+
# frequency conversion tests: from Weekly Frequency
|
| 365 |
+
msg = INVALID_FREQ_ERR_MSG
|
| 366 |
+
with pytest.raises(ValueError, match=msg):
|
| 367 |
+
Period(freq="WK", year=2007, month=1, day=1)
|
| 368 |
+
|
| 369 |
+
with pytest.raises(ValueError, match=msg):
|
| 370 |
+
Period(freq="WK-SAT", year=2007, month=1, day=6)
|
| 371 |
+
with pytest.raises(ValueError, match=msg):
|
| 372 |
+
Period(freq="WK-FRI", year=2007, month=1, day=5)
|
| 373 |
+
with pytest.raises(ValueError, match=msg):
|
| 374 |
+
Period(freq="WK-THU", year=2007, month=1, day=4)
|
| 375 |
+
with pytest.raises(ValueError, match=msg):
|
| 376 |
+
Period(freq="WK-WED", year=2007, month=1, day=3)
|
| 377 |
+
with pytest.raises(ValueError, match=msg):
|
| 378 |
+
Period(freq="WK-TUE", year=2007, month=1, day=2)
|
| 379 |
+
with pytest.raises(ValueError, match=msg):
|
| 380 |
+
Period(freq="WK-MON", year=2007, month=1, day=1)
|
| 381 |
+
|
| 382 |
+
def test_conv_business(self):
|
| 383 |
+
# frequency conversion tests: from Business Frequency"
|
| 384 |
+
|
| 385 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 386 |
+
ival_B = Period(freq="B", year=2007, month=1, day=1)
|
| 387 |
+
ival_B_end_of_year = Period(freq="B", year=2007, month=12, day=31)
|
| 388 |
+
ival_B_end_of_quarter = Period(freq="B", year=2007, month=3, day=30)
|
| 389 |
+
ival_B_end_of_month = Period(freq="B", year=2007, month=1, day=31)
|
| 390 |
+
ival_B_end_of_week = Period(freq="B", year=2007, month=1, day=5)
|
| 391 |
+
|
| 392 |
+
ival_B_to_A = Period(freq="Y", year=2007)
|
| 393 |
+
ival_B_to_Q = Period(freq="Q", year=2007, quarter=1)
|
| 394 |
+
ival_B_to_M = Period(freq="M", year=2007, month=1)
|
| 395 |
+
ival_B_to_W = Period(freq="W", year=2007, month=1, day=7)
|
| 396 |
+
ival_B_to_D = Period(freq="D", year=2007, month=1, day=1)
|
| 397 |
+
ival_B_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 398 |
+
ival_B_to_H_end = Period(freq="h", year=2007, month=1, day=1, hour=23)
|
| 399 |
+
ival_B_to_T_start = Period(
|
| 400 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0
|
| 401 |
+
)
|
| 402 |
+
ival_B_to_T_end = Period(
|
| 403 |
+
freq="Min", year=2007, month=1, day=1, hour=23, minute=59
|
| 404 |
+
)
|
| 405 |
+
ival_B_to_S_start = Period(
|
| 406 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 407 |
+
)
|
| 408 |
+
ival_B_to_S_end = Period(
|
| 409 |
+
freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
assert ival_B.asfreq("Y") == ival_B_to_A
|
| 413 |
+
assert ival_B_end_of_year.asfreq("Y") == ival_B_to_A
|
| 414 |
+
assert ival_B.asfreq("Q") == ival_B_to_Q
|
| 415 |
+
assert ival_B_end_of_quarter.asfreq("Q") == ival_B_to_Q
|
| 416 |
+
assert ival_B.asfreq("M") == ival_B_to_M
|
| 417 |
+
assert ival_B_end_of_month.asfreq("M") == ival_B_to_M
|
| 418 |
+
assert ival_B.asfreq("W") == ival_B_to_W
|
| 419 |
+
assert ival_B_end_of_week.asfreq("W") == ival_B_to_W
|
| 420 |
+
|
| 421 |
+
assert ival_B.asfreq("D") == ival_B_to_D
|
| 422 |
+
|
| 423 |
+
assert ival_B.asfreq("h", "s") == ival_B_to_H_start
|
| 424 |
+
assert ival_B.asfreq("h", "E") == ival_B_to_H_end
|
| 425 |
+
assert ival_B.asfreq("Min", "s") == ival_B_to_T_start
|
| 426 |
+
assert ival_B.asfreq("Min", "E") == ival_B_to_T_end
|
| 427 |
+
assert ival_B.asfreq("s", "s") == ival_B_to_S_start
|
| 428 |
+
assert ival_B.asfreq("s", "E") == ival_B_to_S_end
|
| 429 |
+
|
| 430 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 431 |
+
assert ival_B.asfreq("B") == ival_B
|
| 432 |
+
|
| 433 |
+
def test_conv_daily(self):
|
| 434 |
+
# frequency conversion tests: from Business Frequency"
|
| 435 |
+
|
| 436 |
+
ival_D = Period(freq="D", year=2007, month=1, day=1)
|
| 437 |
+
ival_D_end_of_year = Period(freq="D", year=2007, month=12, day=31)
|
| 438 |
+
ival_D_end_of_quarter = Period(freq="D", year=2007, month=3, day=31)
|
| 439 |
+
ival_D_end_of_month = Period(freq="D", year=2007, month=1, day=31)
|
| 440 |
+
ival_D_end_of_week = Period(freq="D", year=2007, month=1, day=7)
|
| 441 |
+
|
| 442 |
+
ival_D_friday = Period(freq="D", year=2007, month=1, day=5)
|
| 443 |
+
ival_D_saturday = Period(freq="D", year=2007, month=1, day=6)
|
| 444 |
+
ival_D_sunday = Period(freq="D", year=2007, month=1, day=7)
|
| 445 |
+
|
| 446 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 447 |
+
ival_B_friday = Period(freq="B", year=2007, month=1, day=5)
|
| 448 |
+
ival_B_monday = Period(freq="B", year=2007, month=1, day=8)
|
| 449 |
+
|
| 450 |
+
ival_D_to_A = Period(freq="Y", year=2007)
|
| 451 |
+
|
| 452 |
+
ival_Deoq_to_AJAN = Period(freq="Y-JAN", year=2008)
|
| 453 |
+
ival_Deoq_to_AJUN = Period(freq="Y-JUN", year=2007)
|
| 454 |
+
ival_Deoq_to_ADEC = Period(freq="Y-DEC", year=2007)
|
| 455 |
+
|
| 456 |
+
ival_D_to_QEJAN = Period(freq="Q-JAN", year=2007, quarter=4)
|
| 457 |
+
ival_D_to_QEJUN = Period(freq="Q-JUN", year=2007, quarter=3)
|
| 458 |
+
ival_D_to_QEDEC = Period(freq="Q-DEC", year=2007, quarter=1)
|
| 459 |
+
|
| 460 |
+
ival_D_to_M = Period(freq="M", year=2007, month=1)
|
| 461 |
+
ival_D_to_W = Period(freq="W", year=2007, month=1, day=7)
|
| 462 |
+
|
| 463 |
+
ival_D_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 464 |
+
ival_D_to_H_end = Period(freq="h", year=2007, month=1, day=1, hour=23)
|
| 465 |
+
ival_D_to_T_start = Period(
|
| 466 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0
|
| 467 |
+
)
|
| 468 |
+
ival_D_to_T_end = Period(
|
| 469 |
+
freq="Min", year=2007, month=1, day=1, hour=23, minute=59
|
| 470 |
+
)
|
| 471 |
+
ival_D_to_S_start = Period(
|
| 472 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 473 |
+
)
|
| 474 |
+
ival_D_to_S_end = Period(
|
| 475 |
+
freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
assert ival_D.asfreq("Y") == ival_D_to_A
|
| 479 |
+
|
| 480 |
+
assert ival_D_end_of_quarter.asfreq("Y-JAN") == ival_Deoq_to_AJAN
|
| 481 |
+
assert ival_D_end_of_quarter.asfreq("Y-JUN") == ival_Deoq_to_AJUN
|
| 482 |
+
assert ival_D_end_of_quarter.asfreq("Y-DEC") == ival_Deoq_to_ADEC
|
| 483 |
+
|
| 484 |
+
assert ival_D_end_of_year.asfreq("Y") == ival_D_to_A
|
| 485 |
+
assert ival_D_end_of_quarter.asfreq("Q") == ival_D_to_QEDEC
|
| 486 |
+
assert ival_D.asfreq("Q-JAN") == ival_D_to_QEJAN
|
| 487 |
+
assert ival_D.asfreq("Q-JUN") == ival_D_to_QEJUN
|
| 488 |
+
assert ival_D.asfreq("Q-DEC") == ival_D_to_QEDEC
|
| 489 |
+
assert ival_D.asfreq("M") == ival_D_to_M
|
| 490 |
+
assert ival_D_end_of_month.asfreq("M") == ival_D_to_M
|
| 491 |
+
assert ival_D.asfreq("W") == ival_D_to_W
|
| 492 |
+
assert ival_D_end_of_week.asfreq("W") == ival_D_to_W
|
| 493 |
+
|
| 494 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 495 |
+
assert ival_D_friday.asfreq("B") == ival_B_friday
|
| 496 |
+
assert ival_D_saturday.asfreq("B", "s") == ival_B_friday
|
| 497 |
+
assert ival_D_saturday.asfreq("B", "E") == ival_B_monday
|
| 498 |
+
assert ival_D_sunday.asfreq("B", "s") == ival_B_friday
|
| 499 |
+
assert ival_D_sunday.asfreq("B", "E") == ival_B_monday
|
| 500 |
+
|
| 501 |
+
assert ival_D.asfreq("h", "s") == ival_D_to_H_start
|
| 502 |
+
assert ival_D.asfreq("h", "E") == ival_D_to_H_end
|
| 503 |
+
assert ival_D.asfreq("Min", "s") == ival_D_to_T_start
|
| 504 |
+
assert ival_D.asfreq("Min", "E") == ival_D_to_T_end
|
| 505 |
+
assert ival_D.asfreq("s", "s") == ival_D_to_S_start
|
| 506 |
+
assert ival_D.asfreq("s", "E") == ival_D_to_S_end
|
| 507 |
+
|
| 508 |
+
assert ival_D.asfreq("D") == ival_D
|
| 509 |
+
|
| 510 |
+
def test_conv_hourly(self):
|
| 511 |
+
# frequency conversion tests: from Hourly Frequency"
|
| 512 |
+
|
| 513 |
+
ival_H = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 514 |
+
ival_H_end_of_year = Period(freq="h", year=2007, month=12, day=31, hour=23)
|
| 515 |
+
ival_H_end_of_quarter = Period(freq="h", year=2007, month=3, day=31, hour=23)
|
| 516 |
+
ival_H_end_of_month = Period(freq="h", year=2007, month=1, day=31, hour=23)
|
| 517 |
+
ival_H_end_of_week = Period(freq="h", year=2007, month=1, day=7, hour=23)
|
| 518 |
+
ival_H_end_of_day = Period(freq="h", year=2007, month=1, day=1, hour=23)
|
| 519 |
+
ival_H_end_of_bus = Period(freq="h", year=2007, month=1, day=1, hour=23)
|
| 520 |
+
|
| 521 |
+
ival_H_to_A = Period(freq="Y", year=2007)
|
| 522 |
+
ival_H_to_Q = Period(freq="Q", year=2007, quarter=1)
|
| 523 |
+
ival_H_to_M = Period(freq="M", year=2007, month=1)
|
| 524 |
+
ival_H_to_W = Period(freq="W", year=2007, month=1, day=7)
|
| 525 |
+
ival_H_to_D = Period(freq="D", year=2007, month=1, day=1)
|
| 526 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 527 |
+
ival_H_to_B = Period(freq="B", year=2007, month=1, day=1)
|
| 528 |
+
|
| 529 |
+
ival_H_to_T_start = Period(
|
| 530 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0
|
| 531 |
+
)
|
| 532 |
+
ival_H_to_T_end = Period(
|
| 533 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=59
|
| 534 |
+
)
|
| 535 |
+
ival_H_to_S_start = Period(
|
| 536 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 537 |
+
)
|
| 538 |
+
ival_H_to_S_end = Period(
|
| 539 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=59, second=59
|
| 540 |
+
)
|
| 541 |
+
|
| 542 |
+
assert ival_H.asfreq("Y") == ival_H_to_A
|
| 543 |
+
assert ival_H_end_of_year.asfreq("Y") == ival_H_to_A
|
| 544 |
+
assert ival_H.asfreq("Q") == ival_H_to_Q
|
| 545 |
+
assert ival_H_end_of_quarter.asfreq("Q") == ival_H_to_Q
|
| 546 |
+
assert ival_H.asfreq("M") == ival_H_to_M
|
| 547 |
+
assert ival_H_end_of_month.asfreq("M") == ival_H_to_M
|
| 548 |
+
assert ival_H.asfreq("W") == ival_H_to_W
|
| 549 |
+
assert ival_H_end_of_week.asfreq("W") == ival_H_to_W
|
| 550 |
+
assert ival_H.asfreq("D") == ival_H_to_D
|
| 551 |
+
assert ival_H_end_of_day.asfreq("D") == ival_H_to_D
|
| 552 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 553 |
+
assert ival_H.asfreq("B") == ival_H_to_B
|
| 554 |
+
assert ival_H_end_of_bus.asfreq("B") == ival_H_to_B
|
| 555 |
+
|
| 556 |
+
assert ival_H.asfreq("Min", "s") == ival_H_to_T_start
|
| 557 |
+
assert ival_H.asfreq("Min", "E") == ival_H_to_T_end
|
| 558 |
+
assert ival_H.asfreq("s", "s") == ival_H_to_S_start
|
| 559 |
+
assert ival_H.asfreq("s", "E") == ival_H_to_S_end
|
| 560 |
+
|
| 561 |
+
assert ival_H.asfreq("h") == ival_H
|
| 562 |
+
|
| 563 |
+
def test_conv_minutely(self):
|
| 564 |
+
# frequency conversion tests: from Minutely Frequency"
|
| 565 |
+
|
| 566 |
+
ival_T = Period(freq="Min", year=2007, month=1, day=1, hour=0, minute=0)
|
| 567 |
+
ival_T_end_of_year = Period(
|
| 568 |
+
freq="Min", year=2007, month=12, day=31, hour=23, minute=59
|
| 569 |
+
)
|
| 570 |
+
ival_T_end_of_quarter = Period(
|
| 571 |
+
freq="Min", year=2007, month=3, day=31, hour=23, minute=59
|
| 572 |
+
)
|
| 573 |
+
ival_T_end_of_month = Period(
|
| 574 |
+
freq="Min", year=2007, month=1, day=31, hour=23, minute=59
|
| 575 |
+
)
|
| 576 |
+
ival_T_end_of_week = Period(
|
| 577 |
+
freq="Min", year=2007, month=1, day=7, hour=23, minute=59
|
| 578 |
+
)
|
| 579 |
+
ival_T_end_of_day = Period(
|
| 580 |
+
freq="Min", year=2007, month=1, day=1, hour=23, minute=59
|
| 581 |
+
)
|
| 582 |
+
ival_T_end_of_bus = Period(
|
| 583 |
+
freq="Min", year=2007, month=1, day=1, hour=23, minute=59
|
| 584 |
+
)
|
| 585 |
+
ival_T_end_of_hour = Period(
|
| 586 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=59
|
| 587 |
+
)
|
| 588 |
+
|
| 589 |
+
ival_T_to_A = Period(freq="Y", year=2007)
|
| 590 |
+
ival_T_to_Q = Period(freq="Q", year=2007, quarter=1)
|
| 591 |
+
ival_T_to_M = Period(freq="M", year=2007, month=1)
|
| 592 |
+
ival_T_to_W = Period(freq="W", year=2007, month=1, day=7)
|
| 593 |
+
ival_T_to_D = Period(freq="D", year=2007, month=1, day=1)
|
| 594 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 595 |
+
ival_T_to_B = Period(freq="B", year=2007, month=1, day=1)
|
| 596 |
+
ival_T_to_H = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 597 |
+
|
| 598 |
+
ival_T_to_S_start = Period(
|
| 599 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 600 |
+
)
|
| 601 |
+
ival_T_to_S_end = Period(
|
| 602 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=59
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
assert ival_T.asfreq("Y") == ival_T_to_A
|
| 606 |
+
assert ival_T_end_of_year.asfreq("Y") == ival_T_to_A
|
| 607 |
+
assert ival_T.asfreq("Q") == ival_T_to_Q
|
| 608 |
+
assert ival_T_end_of_quarter.asfreq("Q") == ival_T_to_Q
|
| 609 |
+
assert ival_T.asfreq("M") == ival_T_to_M
|
| 610 |
+
assert ival_T_end_of_month.asfreq("M") == ival_T_to_M
|
| 611 |
+
assert ival_T.asfreq("W") == ival_T_to_W
|
| 612 |
+
assert ival_T_end_of_week.asfreq("W") == ival_T_to_W
|
| 613 |
+
assert ival_T.asfreq("D") == ival_T_to_D
|
| 614 |
+
assert ival_T_end_of_day.asfreq("D") == ival_T_to_D
|
| 615 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 616 |
+
assert ival_T.asfreq("B") == ival_T_to_B
|
| 617 |
+
assert ival_T_end_of_bus.asfreq("B") == ival_T_to_B
|
| 618 |
+
assert ival_T.asfreq("h") == ival_T_to_H
|
| 619 |
+
assert ival_T_end_of_hour.asfreq("h") == ival_T_to_H
|
| 620 |
+
|
| 621 |
+
assert ival_T.asfreq("s", "s") == ival_T_to_S_start
|
| 622 |
+
assert ival_T.asfreq("s", "E") == ival_T_to_S_end
|
| 623 |
+
|
| 624 |
+
assert ival_T.asfreq("Min") == ival_T
|
| 625 |
+
|
| 626 |
+
def test_conv_secondly(self):
|
| 627 |
+
# frequency conversion tests: from Secondly Frequency"
|
| 628 |
+
|
| 629 |
+
ival_S = Period(freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0)
|
| 630 |
+
ival_S_end_of_year = Period(
|
| 631 |
+
freq="s", year=2007, month=12, day=31, hour=23, minute=59, second=59
|
| 632 |
+
)
|
| 633 |
+
ival_S_end_of_quarter = Period(
|
| 634 |
+
freq="s", year=2007, month=3, day=31, hour=23, minute=59, second=59
|
| 635 |
+
)
|
| 636 |
+
ival_S_end_of_month = Period(
|
| 637 |
+
freq="s", year=2007, month=1, day=31, hour=23, minute=59, second=59
|
| 638 |
+
)
|
| 639 |
+
ival_S_end_of_week = Period(
|
| 640 |
+
freq="s", year=2007, month=1, day=7, hour=23, minute=59, second=59
|
| 641 |
+
)
|
| 642 |
+
ival_S_end_of_day = Period(
|
| 643 |
+
freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59
|
| 644 |
+
)
|
| 645 |
+
ival_S_end_of_bus = Period(
|
| 646 |
+
freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59
|
| 647 |
+
)
|
| 648 |
+
ival_S_end_of_hour = Period(
|
| 649 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=59, second=59
|
| 650 |
+
)
|
| 651 |
+
ival_S_end_of_minute = Period(
|
| 652 |
+
freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=59
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
ival_S_to_A = Period(freq="Y", year=2007)
|
| 656 |
+
ival_S_to_Q = Period(freq="Q", year=2007, quarter=1)
|
| 657 |
+
ival_S_to_M = Period(freq="M", year=2007, month=1)
|
| 658 |
+
ival_S_to_W = Period(freq="W", year=2007, month=1, day=7)
|
| 659 |
+
ival_S_to_D = Period(freq="D", year=2007, month=1, day=1)
|
| 660 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 661 |
+
ival_S_to_B = Period(freq="B", year=2007, month=1, day=1)
|
| 662 |
+
ival_S_to_H = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 663 |
+
ival_S_to_T = Period(freq="Min", year=2007, month=1, day=1, hour=0, minute=0)
|
| 664 |
+
|
| 665 |
+
assert ival_S.asfreq("Y") == ival_S_to_A
|
| 666 |
+
assert ival_S_end_of_year.asfreq("Y") == ival_S_to_A
|
| 667 |
+
assert ival_S.asfreq("Q") == ival_S_to_Q
|
| 668 |
+
assert ival_S_end_of_quarter.asfreq("Q") == ival_S_to_Q
|
| 669 |
+
assert ival_S.asfreq("M") == ival_S_to_M
|
| 670 |
+
assert ival_S_end_of_month.asfreq("M") == ival_S_to_M
|
| 671 |
+
assert ival_S.asfreq("W") == ival_S_to_W
|
| 672 |
+
assert ival_S_end_of_week.asfreq("W") == ival_S_to_W
|
| 673 |
+
assert ival_S.asfreq("D") == ival_S_to_D
|
| 674 |
+
assert ival_S_end_of_day.asfreq("D") == ival_S_to_D
|
| 675 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 676 |
+
assert ival_S.asfreq("B") == ival_S_to_B
|
| 677 |
+
assert ival_S_end_of_bus.asfreq("B") == ival_S_to_B
|
| 678 |
+
assert ival_S.asfreq("h") == ival_S_to_H
|
| 679 |
+
assert ival_S_end_of_hour.asfreq("h") == ival_S_to_H
|
| 680 |
+
assert ival_S.asfreq("Min") == ival_S_to_T
|
| 681 |
+
assert ival_S_end_of_minute.asfreq("Min") == ival_S_to_T
|
| 682 |
+
|
| 683 |
+
assert ival_S.asfreq("s") == ival_S
|
| 684 |
+
|
| 685 |
+
def test_conv_microsecond(self):
|
| 686 |
+
# GH#31475 Avoid floating point errors dropping the start_time to
|
| 687 |
+
# before the beginning of the Period
|
| 688 |
+
per = Period("2020-01-30 15:57:27.576166", freq="us")
|
| 689 |
+
assert per.ordinal == 1580399847576166
|
| 690 |
+
|
| 691 |
+
start = per.start_time
|
| 692 |
+
expected = Timestamp("2020-01-30 15:57:27.576166")
|
| 693 |
+
assert start == expected
|
| 694 |
+
assert start._value == per.ordinal
|
| 695 |
+
|
| 696 |
+
def test_asfreq_mult(self):
|
| 697 |
+
# normal freq to mult freq
|
| 698 |
+
p = Period(freq="Y", year=2007)
|
| 699 |
+
# ordinal will not change
|
| 700 |
+
for freq in ["3Y", offsets.YearEnd(3)]:
|
| 701 |
+
result = p.asfreq(freq)
|
| 702 |
+
expected = Period("2007", freq="3Y")
|
| 703 |
+
|
| 704 |
+
assert result == expected
|
| 705 |
+
assert result.ordinal == expected.ordinal
|
| 706 |
+
assert result.freq == expected.freq
|
| 707 |
+
# ordinal will not change
|
| 708 |
+
for freq in ["3Y", offsets.YearEnd(3)]:
|
| 709 |
+
result = p.asfreq(freq, how="S")
|
| 710 |
+
expected = Period("2007", freq="3Y")
|
| 711 |
+
|
| 712 |
+
assert result == expected
|
| 713 |
+
assert result.ordinal == expected.ordinal
|
| 714 |
+
assert result.freq == expected.freq
|
| 715 |
+
|
| 716 |
+
# mult freq to normal freq
|
| 717 |
+
p = Period(freq="3Y", year=2007)
|
| 718 |
+
# ordinal will change because how=E is the default
|
| 719 |
+
for freq in ["Y", offsets.YearEnd()]:
|
| 720 |
+
result = p.asfreq(freq)
|
| 721 |
+
expected = Period("2009", freq="Y")
|
| 722 |
+
|
| 723 |
+
assert result == expected
|
| 724 |
+
assert result.ordinal == expected.ordinal
|
| 725 |
+
assert result.freq == expected.freq
|
| 726 |
+
# ordinal will not change
|
| 727 |
+
for freq in ["Y", offsets.YearEnd()]:
|
| 728 |
+
result = p.asfreq(freq, how="s")
|
| 729 |
+
expected = Period("2007", freq="Y")
|
| 730 |
+
|
| 731 |
+
assert result == expected
|
| 732 |
+
assert result.ordinal == expected.ordinal
|
| 733 |
+
assert result.freq == expected.freq
|
| 734 |
+
|
| 735 |
+
p = Period(freq="Y", year=2007)
|
| 736 |
+
for freq in ["2M", offsets.MonthEnd(2)]:
|
| 737 |
+
result = p.asfreq(freq)
|
| 738 |
+
expected = Period("2007-12", freq="2M")
|
| 739 |
+
|
| 740 |
+
assert result == expected
|
| 741 |
+
assert result.ordinal == expected.ordinal
|
| 742 |
+
assert result.freq == expected.freq
|
| 743 |
+
for freq in ["2M", offsets.MonthEnd(2)]:
|
| 744 |
+
result = p.asfreq(freq, how="s")
|
| 745 |
+
expected = Period("2007-01", freq="2M")
|
| 746 |
+
|
| 747 |
+
assert result == expected
|
| 748 |
+
assert result.ordinal == expected.ordinal
|
| 749 |
+
assert result.freq == expected.freq
|
| 750 |
+
|
| 751 |
+
p = Period(freq="3Y", year=2007)
|
| 752 |
+
for freq in ["2M", offsets.MonthEnd(2)]:
|
| 753 |
+
result = p.asfreq(freq)
|
| 754 |
+
expected = Period("2009-12", freq="2M")
|
| 755 |
+
|
| 756 |
+
assert result == expected
|
| 757 |
+
assert result.ordinal == expected.ordinal
|
| 758 |
+
assert result.freq == expected.freq
|
| 759 |
+
for freq in ["2M", offsets.MonthEnd(2)]:
|
| 760 |
+
result = p.asfreq(freq, how="s")
|
| 761 |
+
expected = Period("2007-01", freq="2M")
|
| 762 |
+
|
| 763 |
+
assert result == expected
|
| 764 |
+
assert result.ordinal == expected.ordinal
|
| 765 |
+
assert result.freq == expected.freq
|
| 766 |
+
|
| 767 |
+
def test_asfreq_combined(self):
|
| 768 |
+
# normal freq to combined freq
|
| 769 |
+
p = Period("2007", freq="h")
|
| 770 |
+
|
| 771 |
+
# ordinal will not change
|
| 772 |
+
expected = Period("2007", freq="25h")
|
| 773 |
+
for freq, how in zip(["1D1h", "1h1D"], ["E", "S"]):
|
| 774 |
+
result = p.asfreq(freq, how=how)
|
| 775 |
+
assert result == expected
|
| 776 |
+
assert result.ordinal == expected.ordinal
|
| 777 |
+
assert result.freq == expected.freq
|
| 778 |
+
|
| 779 |
+
# combined freq to normal freq
|
| 780 |
+
p1 = Period(freq="1D1h", year=2007)
|
| 781 |
+
p2 = Period(freq="1h1D", year=2007)
|
| 782 |
+
|
| 783 |
+
# ordinal will change because how=E is the default
|
| 784 |
+
result1 = p1.asfreq("h")
|
| 785 |
+
result2 = p2.asfreq("h")
|
| 786 |
+
expected = Period("2007-01-02", freq="h")
|
| 787 |
+
assert result1 == expected
|
| 788 |
+
assert result1.ordinal == expected.ordinal
|
| 789 |
+
assert result1.freq == expected.freq
|
| 790 |
+
assert result2 == expected
|
| 791 |
+
assert result2.ordinal == expected.ordinal
|
| 792 |
+
assert result2.freq == expected.freq
|
| 793 |
+
|
| 794 |
+
# ordinal will not change
|
| 795 |
+
result1 = p1.asfreq("h", how="S")
|
| 796 |
+
result2 = p2.asfreq("h", how="S")
|
| 797 |
+
expected = Period("2007-01-01", freq="h")
|
| 798 |
+
assert result1 == expected
|
| 799 |
+
assert result1.ordinal == expected.ordinal
|
| 800 |
+
assert result1.freq == expected.freq
|
| 801 |
+
assert result2 == expected
|
| 802 |
+
assert result2.ordinal == expected.ordinal
|
| 803 |
+
assert result2.freq == expected.freq
|
| 804 |
+
|
| 805 |
+
def test_asfreq_MS(self):
|
| 806 |
+
initial = Period("2013")
|
| 807 |
+
|
| 808 |
+
assert initial.asfreq(freq="M", how="S") == Period("2013-01", "M")
|
| 809 |
+
|
| 810 |
+
msg = INVALID_FREQ_ERR_MSG
|
| 811 |
+
with pytest.raises(ValueError, match=msg):
|
| 812 |
+
initial.asfreq(freq="MS", how="S")
|
| 813 |
+
|
| 814 |
+
with pytest.raises(ValueError, match=msg):
|
| 815 |
+
Period("2013-01", "MS")
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_period.py
ADDED
|
@@ -0,0 +1,1208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import (
|
| 2 |
+
date,
|
| 3 |
+
datetime,
|
| 4 |
+
timedelta,
|
| 5 |
+
)
|
| 6 |
+
import re
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
from pandas._libs.tslibs import (
|
| 12 |
+
iNaT,
|
| 13 |
+
to_offset,
|
| 14 |
+
)
|
| 15 |
+
from pandas._libs.tslibs.ccalendar import (
|
| 16 |
+
DAYS,
|
| 17 |
+
MONTHS,
|
| 18 |
+
)
|
| 19 |
+
from pandas._libs.tslibs.parsing import DateParseError
|
| 20 |
+
from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
|
| 21 |
+
from pandas.compat import PY314
|
| 22 |
+
from pandas.errors import (
|
| 23 |
+
OutOfBoundsDatetime,
|
| 24 |
+
Pandas4Warning,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
from pandas import (
|
| 28 |
+
NaT,
|
| 29 |
+
Period,
|
| 30 |
+
Timedelta,
|
| 31 |
+
Timestamp,
|
| 32 |
+
offsets,
|
| 33 |
+
)
|
| 34 |
+
import pandas._testing as tm
|
| 35 |
+
|
| 36 |
+
bday_msg = "Period with BDay freq is deprecated"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class TestPeriodDisallowedFreqs:
|
| 40 |
+
@pytest.mark.parametrize(
|
| 41 |
+
"freq, freq_msg",
|
| 42 |
+
[
|
| 43 |
+
(offsets.BYearBegin(), "BYearBegin"),
|
| 44 |
+
(offsets.YearBegin(2), "YearBegin"),
|
| 45 |
+
(offsets.QuarterBegin(startingMonth=12), "QuarterBegin"),
|
| 46 |
+
(offsets.BusinessMonthEnd(2), "BusinessMonthEnd"),
|
| 47 |
+
],
|
| 48 |
+
)
|
| 49 |
+
def test_offsets_not_supported(self, freq, freq_msg):
|
| 50 |
+
# GH#55785
|
| 51 |
+
msg = re.escape(f"{freq} is not supported as period frequency")
|
| 52 |
+
with pytest.raises(ValueError, match=msg):
|
| 53 |
+
Period(year=2014, freq=freq)
|
| 54 |
+
|
| 55 |
+
def test_custom_business_day_freq_raises(self):
|
| 56 |
+
# GH#52534
|
| 57 |
+
msg = "C is not supported as period frequency"
|
| 58 |
+
with pytest.raises(ValueError, match=msg):
|
| 59 |
+
Period("2023-04-10", freq="C")
|
| 60 |
+
msg = f"{offsets.CustomBusinessDay().base} is not supported as period frequency"
|
| 61 |
+
with pytest.raises(ValueError, match=msg):
|
| 62 |
+
Period("2023-04-10", freq=offsets.CustomBusinessDay())
|
| 63 |
+
|
| 64 |
+
def test_invalid_frequency_error_message(self):
|
| 65 |
+
msg = "WOM-1MON is not supported as period frequency"
|
| 66 |
+
with pytest.raises(ValueError, match=msg):
|
| 67 |
+
Period("2012-01-02", freq="WOM-1MON")
|
| 68 |
+
|
| 69 |
+
def test_invalid_frequency_period_error_message(self):
|
| 70 |
+
msg = "Invalid frequency: ME"
|
| 71 |
+
with pytest.raises(ValueError, match=msg):
|
| 72 |
+
Period("2012-01-02", freq="ME")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class TestPeriodConstruction:
|
| 76 |
+
def test_from_td64nat_raises(self):
|
| 77 |
+
# GH#44507
|
| 78 |
+
td = NaT.to_numpy("m8[ns]")
|
| 79 |
+
|
| 80 |
+
msg = "Value must be Period, string, integer, or datetime"
|
| 81 |
+
with pytest.raises(ValueError, match=msg):
|
| 82 |
+
Period(td)
|
| 83 |
+
|
| 84 |
+
with pytest.raises(ValueError, match=msg):
|
| 85 |
+
Period(td, freq="D")
|
| 86 |
+
|
| 87 |
+
def test_construction(self):
|
| 88 |
+
i1 = Period("1/1/2005", freq="M")
|
| 89 |
+
i2 = Period("Jan 2005")
|
| 90 |
+
|
| 91 |
+
assert i1 == i2
|
| 92 |
+
|
| 93 |
+
# GH#54105 - Period can be confusingly instantiated with lowercase freq
|
| 94 |
+
# TODO: raise in the future an error when passing lowercase freq
|
| 95 |
+
i1 = Period("2005", freq="Y")
|
| 96 |
+
i2 = Period("2005")
|
| 97 |
+
|
| 98 |
+
assert i1 == i2
|
| 99 |
+
|
| 100 |
+
i4 = Period("2005", freq="M")
|
| 101 |
+
assert i1 != i4
|
| 102 |
+
|
| 103 |
+
i1 = Period.now(freq="Q")
|
| 104 |
+
i2 = Period(datetime.now(), freq="Q")
|
| 105 |
+
|
| 106 |
+
assert i1 == i2
|
| 107 |
+
|
| 108 |
+
# Pass in freq as a keyword argument sometimes as a test for
|
| 109 |
+
# https://github.com/pandas-dev/pandas/issues/53369
|
| 110 |
+
i1 = Period.now(freq="D")
|
| 111 |
+
i2 = Period(datetime.now(), freq="D")
|
| 112 |
+
i3 = Period.now(offsets.Day())
|
| 113 |
+
|
| 114 |
+
assert i1 == i2
|
| 115 |
+
assert i1 == i3
|
| 116 |
+
|
| 117 |
+
i1 = Period("1982", freq="min")
|
| 118 |
+
msg = "'MIN' is deprecated and will be removed in a future version."
|
| 119 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 120 |
+
i2 = Period("1982", freq="MIN")
|
| 121 |
+
assert i1 == i2
|
| 122 |
+
|
| 123 |
+
i1 = Period(year=2005, month=3, day=1, freq="D")
|
| 124 |
+
i2 = Period("3/1/2005", freq="D")
|
| 125 |
+
assert i1 == i2
|
| 126 |
+
|
| 127 |
+
msg = "'d' is deprecated and will be removed in a future version."
|
| 128 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 129 |
+
i3 = Period(year=2005, month=3, day=1, freq="d")
|
| 130 |
+
assert i1 == i3
|
| 131 |
+
|
| 132 |
+
i1 = Period("2007-01-01 09:00:00.001")
|
| 133 |
+
expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1000), freq="ms")
|
| 134 |
+
assert i1 == expected
|
| 135 |
+
|
| 136 |
+
expected = Period("2007-01-01 09:00:00.001", freq="ms")
|
| 137 |
+
assert i1 == expected
|
| 138 |
+
|
| 139 |
+
i1 = Period("2007-01-01 09:00:00.00101")
|
| 140 |
+
expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1010), freq="us")
|
| 141 |
+
assert i1 == expected
|
| 142 |
+
|
| 143 |
+
expected = Period("2007-01-01 09:00:00.00101", freq="us")
|
| 144 |
+
assert i1 == expected
|
| 145 |
+
|
| 146 |
+
msg = "Must supply freq for ordinal value"
|
| 147 |
+
with pytest.raises(ValueError, match=msg):
|
| 148 |
+
Period(ordinal=200701)
|
| 149 |
+
|
| 150 |
+
msg = "Invalid frequency: X"
|
| 151 |
+
with pytest.raises(ValueError, match=msg):
|
| 152 |
+
Period("2007-1-1", freq="X")
|
| 153 |
+
|
| 154 |
+
def test_tuple_freq_disallowed(self):
|
| 155 |
+
# GH#34703 tuple freq disallowed
|
| 156 |
+
with pytest.raises(TypeError, match="pass as a string instead"):
|
| 157 |
+
Period("1982", freq=("Min", 1))
|
| 158 |
+
|
| 159 |
+
with pytest.raises(TypeError, match="pass as a string instead"):
|
| 160 |
+
Period("2006-12-31", ("w", 1))
|
| 161 |
+
|
| 162 |
+
def test_construction_from_timestamp_nanos(self):
|
| 163 |
+
# GH#46811 don't drop nanos from Timestamp
|
| 164 |
+
ts = Timestamp("2022-04-20 09:23:24.123456789")
|
| 165 |
+
per = Period(ts, freq="ns")
|
| 166 |
+
|
| 167 |
+
# should losslessly round-trip, not lose the 789
|
| 168 |
+
rt = per.to_timestamp()
|
| 169 |
+
assert rt == ts
|
| 170 |
+
|
| 171 |
+
# same thing but from a datetime64 object
|
| 172 |
+
dt64 = ts.asm8
|
| 173 |
+
per2 = Period(dt64, freq="ns")
|
| 174 |
+
rt2 = per2.to_timestamp()
|
| 175 |
+
assert rt2.asm8 == dt64
|
| 176 |
+
|
| 177 |
+
@pytest.mark.parametrize("freq", ["ms", "us", "ns"])
|
| 178 |
+
def test_construction_from_min_timestamp(self, freq):
|
| 179 |
+
# GH-63278
|
| 180 |
+
ts = Timestamp(Timestamp.min.value, unit=freq)
|
| 181 |
+
per = Period(ts, freq=freq)
|
| 182 |
+
|
| 183 |
+
# pandas.errors.OutOfBoundsDatetime: Out of bounds nanosecond timestamp:
|
| 184 |
+
# -290308-12-21 19:59:05
|
| 185 |
+
# rt = per.to_timestamp()
|
| 186 |
+
|
| 187 |
+
assert per.year == ts.year
|
| 188 |
+
assert per.month == ts.month
|
| 189 |
+
assert per.day == ts.day
|
| 190 |
+
assert per.hour == ts.hour
|
| 191 |
+
assert per.minute == ts.minute
|
| 192 |
+
assert per.second == ts.second
|
| 193 |
+
|
| 194 |
+
def test_construction_bday(self):
|
| 195 |
+
# Biz day construction, roll forward if non-weekday
|
| 196 |
+
with tm.assert_produces_warning(
|
| 197 |
+
Pandas4Warning, match="'b' is deprecated", raise_on_extra_warnings=False
|
| 198 |
+
):
|
| 199 |
+
i1 = Period("3/10/12", freq="B")
|
| 200 |
+
i2 = Period("3/10/12", freq="D")
|
| 201 |
+
assert i1 == i2.asfreq("B")
|
| 202 |
+
i2 = Period("3/11/12", freq="D")
|
| 203 |
+
assert i1 == i2.asfreq("B")
|
| 204 |
+
i2 = Period("3/12/12", freq="D")
|
| 205 |
+
assert i1 == i2.asfreq("B")
|
| 206 |
+
|
| 207 |
+
i3 = Period("3/10/12", freq="b")
|
| 208 |
+
assert i1 == i3
|
| 209 |
+
|
| 210 |
+
i1 = Period(year=2012, month=3, day=10, freq="B")
|
| 211 |
+
i2 = Period("3/12/12", freq="B")
|
| 212 |
+
assert i1 == i2
|
| 213 |
+
|
| 214 |
+
def test_construction_quarter(self):
|
| 215 |
+
i1 = Period(year=2005, quarter=1, freq="Q")
|
| 216 |
+
i2 = Period("1/1/2005", freq="Q")
|
| 217 |
+
assert i1 == i2
|
| 218 |
+
|
| 219 |
+
i1 = Period(year=2005, quarter=3, freq="Q")
|
| 220 |
+
i2 = Period("9/1/2005", freq="Q")
|
| 221 |
+
assert i1 == i2
|
| 222 |
+
|
| 223 |
+
i1 = Period("2005Q1")
|
| 224 |
+
i2 = Period(year=2005, quarter=1, freq="Q")
|
| 225 |
+
i3 = Period("2005q1")
|
| 226 |
+
assert i1 == i2
|
| 227 |
+
assert i1 == i3
|
| 228 |
+
|
| 229 |
+
i1 = Period("05Q1")
|
| 230 |
+
assert i1 == i2
|
| 231 |
+
lower = Period("05q1")
|
| 232 |
+
assert i1 == lower
|
| 233 |
+
|
| 234 |
+
i1 = Period("1Q2005")
|
| 235 |
+
assert i1 == i2
|
| 236 |
+
lower = Period("1q2005")
|
| 237 |
+
assert i1 == lower
|
| 238 |
+
|
| 239 |
+
i1 = Period("1Q05")
|
| 240 |
+
assert i1 == i2
|
| 241 |
+
lower = Period("1q05")
|
| 242 |
+
assert i1 == lower
|
| 243 |
+
|
| 244 |
+
i1 = Period("4Q1984")
|
| 245 |
+
assert i1.year == 1984
|
| 246 |
+
lower = Period("4q1984")
|
| 247 |
+
assert i1 == lower
|
| 248 |
+
|
| 249 |
+
def test_construction_month(self):
|
| 250 |
+
expected = Period("2007-01", freq="M")
|
| 251 |
+
i1 = Period("200701", freq="M")
|
| 252 |
+
assert i1 == expected
|
| 253 |
+
|
| 254 |
+
i1 = Period("200701", freq="M")
|
| 255 |
+
assert i1 == expected
|
| 256 |
+
|
| 257 |
+
i1 = Period(200701, freq="M")
|
| 258 |
+
assert i1 == expected
|
| 259 |
+
|
| 260 |
+
i1 = Period(ordinal=200701, freq="M")
|
| 261 |
+
assert i1.year == 18695
|
| 262 |
+
|
| 263 |
+
i1 = Period(datetime(2007, 1, 1), freq="M")
|
| 264 |
+
i2 = Period("200701", freq="M")
|
| 265 |
+
assert i1 == i2
|
| 266 |
+
|
| 267 |
+
i1 = Period(date(2007, 1, 1), freq="M")
|
| 268 |
+
i2 = Period(datetime(2007, 1, 1), freq="M")
|
| 269 |
+
i3 = Period(np.datetime64("2007-01-01"), freq="M")
|
| 270 |
+
i4 = Period("2007-01-01 00:00:00", freq="M")
|
| 271 |
+
i5 = Period("2007-01-01 00:00:00.000", freq="M")
|
| 272 |
+
assert i1 == i2
|
| 273 |
+
assert i1 == i3
|
| 274 |
+
assert i1 == i4
|
| 275 |
+
assert i1 == i5
|
| 276 |
+
|
| 277 |
+
def test_period_constructor_offsets(self):
|
| 278 |
+
assert Period("1/1/2005", freq=offsets.MonthEnd()) == Period(
|
| 279 |
+
"1/1/2005", freq="M"
|
| 280 |
+
)
|
| 281 |
+
assert Period("2005", freq=offsets.YearEnd()) == Period("2005", freq="Y")
|
| 282 |
+
assert Period("2005", freq=offsets.MonthEnd()) == Period("2005", freq="M")
|
| 283 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 284 |
+
assert Period("3/10/12", freq=offsets.BusinessDay()) == Period(
|
| 285 |
+
"3/10/12", freq="B"
|
| 286 |
+
)
|
| 287 |
+
assert Period("3/10/12", freq=offsets.Day()) == Period("3/10/12", freq="D")
|
| 288 |
+
|
| 289 |
+
assert Period(
|
| 290 |
+
year=2005, quarter=1, freq=offsets.QuarterEnd(startingMonth=12)
|
| 291 |
+
) == Period(year=2005, quarter=1, freq="Q")
|
| 292 |
+
assert Period(
|
| 293 |
+
year=2005, quarter=2, freq=offsets.QuarterEnd(startingMonth=12)
|
| 294 |
+
) == Period(year=2005, quarter=2, freq="Q")
|
| 295 |
+
|
| 296 |
+
assert Period(year=2005, month=3, day=1, freq=offsets.Day()) == Period(
|
| 297 |
+
year=2005, month=3, day=1, freq="D"
|
| 298 |
+
)
|
| 299 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 300 |
+
assert Period(year=2012, month=3, day=10, freq=offsets.BDay()) == Period(
|
| 301 |
+
year=2012, month=3, day=10, freq="B"
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
expected = Period("2005-03-01", freq="3D")
|
| 305 |
+
assert Period(year=2005, month=3, day=1, freq=offsets.Day(3)) == expected
|
| 306 |
+
assert Period(year=2005, month=3, day=1, freq="3D") == expected
|
| 307 |
+
|
| 308 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 309 |
+
assert Period(year=2012, month=3, day=10, freq=offsets.BDay(3)) == Period(
|
| 310 |
+
year=2012, month=3, day=10, freq="3B"
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
assert Period(200701, freq=offsets.MonthEnd()) == Period(200701, freq="M")
|
| 314 |
+
|
| 315 |
+
i1 = Period(ordinal=200701, freq=offsets.MonthEnd())
|
| 316 |
+
i2 = Period(ordinal=200701, freq="M")
|
| 317 |
+
assert i1 == i2
|
| 318 |
+
assert i1.year == 18695
|
| 319 |
+
assert i2.year == 18695
|
| 320 |
+
|
| 321 |
+
i1 = Period(datetime(2007, 1, 1), freq="M")
|
| 322 |
+
i2 = Period("200701", freq="M")
|
| 323 |
+
assert i1 == i2
|
| 324 |
+
|
| 325 |
+
i1 = Period(date(2007, 1, 1), freq="M")
|
| 326 |
+
i2 = Period(datetime(2007, 1, 1), freq="M")
|
| 327 |
+
i3 = Period(np.datetime64("2007-01-01"), freq="M")
|
| 328 |
+
i4 = Period("2007-01-01 00:00:00", freq="M")
|
| 329 |
+
i5 = Period("2007-01-01 00:00:00.000", freq="M")
|
| 330 |
+
assert i1 == i2
|
| 331 |
+
assert i1 == i3
|
| 332 |
+
assert i1 == i4
|
| 333 |
+
assert i1 == i5
|
| 334 |
+
|
| 335 |
+
i1 = Period("2007-01-01 09:00:00.001")
|
| 336 |
+
expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1000), freq="ms")
|
| 337 |
+
assert i1 == expected
|
| 338 |
+
|
| 339 |
+
expected = Period("2007-01-01 09:00:00.001", freq="ms")
|
| 340 |
+
assert i1 == expected
|
| 341 |
+
|
| 342 |
+
i1 = Period("2007-01-01 09:00:00.00101")
|
| 343 |
+
expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1010), freq="us")
|
| 344 |
+
assert i1 == expected
|
| 345 |
+
|
| 346 |
+
expected = Period("2007-01-01 09:00:00.00101", freq="us")
|
| 347 |
+
assert i1 == expected
|
| 348 |
+
|
| 349 |
+
def test_invalid_arguments(self):
|
| 350 |
+
msg = "Must supply freq for datetime value"
|
| 351 |
+
with pytest.raises(ValueError, match=msg):
|
| 352 |
+
Period(datetime.now())
|
| 353 |
+
with pytest.raises(ValueError, match=msg):
|
| 354 |
+
Period(datetime.now().date())
|
| 355 |
+
|
| 356 |
+
msg = "Value must be Period, string, integer, or datetime"
|
| 357 |
+
with pytest.raises(ValueError, match=msg):
|
| 358 |
+
Period(1.6, freq="D")
|
| 359 |
+
msg = "Ordinal must be an integer"
|
| 360 |
+
with pytest.raises(ValueError, match=msg):
|
| 361 |
+
Period(ordinal=1.6, freq="D")
|
| 362 |
+
msg = "Only value or ordinal but not both should be given but not both"
|
| 363 |
+
with pytest.raises(ValueError, match=msg):
|
| 364 |
+
Period(ordinal=2, value=1, freq="D")
|
| 365 |
+
|
| 366 |
+
msg = "If value is None, freq cannot be None"
|
| 367 |
+
with pytest.raises(ValueError, match=msg):
|
| 368 |
+
Period(month=1)
|
| 369 |
+
|
| 370 |
+
msg = '^Given date string "-2000" not likely a datetime$'
|
| 371 |
+
with pytest.raises(ValueError, match=msg):
|
| 372 |
+
Period("-2000", "Y")
|
| 373 |
+
if PY314:
|
| 374 |
+
msg = "day 0 must be in range 1..31 for month 1 in year 1: 0"
|
| 375 |
+
else:
|
| 376 |
+
msg = "day is out of range for month"
|
| 377 |
+
with pytest.raises(DateParseError, match=msg):
|
| 378 |
+
Period("0", "Y")
|
| 379 |
+
msg = "Unknown datetime string format, unable to parse"
|
| 380 |
+
with pytest.raises(DateParseError, match=msg):
|
| 381 |
+
Period("1/1/-2000", "Y")
|
| 382 |
+
|
| 383 |
+
def test_constructor_corner(self):
|
| 384 |
+
expected = Period("2007-01", freq="2M")
|
| 385 |
+
assert Period(year=2007, month=1, freq="2M") == expected
|
| 386 |
+
|
| 387 |
+
assert Period(None) is NaT
|
| 388 |
+
|
| 389 |
+
p = Period("2007-01-01", freq="D")
|
| 390 |
+
|
| 391 |
+
result = Period(p, freq="Y")
|
| 392 |
+
exp = Period("2007", freq="Y")
|
| 393 |
+
assert result == exp
|
| 394 |
+
|
| 395 |
+
def test_constructor_infer_freq(self):
|
| 396 |
+
p = Period("2007-01-01")
|
| 397 |
+
assert p.freq == "D"
|
| 398 |
+
|
| 399 |
+
p = Period("2007-01-01 07")
|
| 400 |
+
assert p.freq == "h"
|
| 401 |
+
|
| 402 |
+
p = Period("2007-01-01 07:10")
|
| 403 |
+
assert p.freq == "min"
|
| 404 |
+
|
| 405 |
+
p = Period("2007-01-01 07:10:15")
|
| 406 |
+
assert p.freq == "s"
|
| 407 |
+
|
| 408 |
+
p = Period("2007-01-01 07:10:15.123")
|
| 409 |
+
assert p.freq == "ms"
|
| 410 |
+
|
| 411 |
+
# We see that there are 6 digits after the decimal, so get microsecond
|
| 412 |
+
# even though they are all zeros.
|
| 413 |
+
p = Period("2007-01-01 07:10:15.123000")
|
| 414 |
+
assert p.freq == "us"
|
| 415 |
+
|
| 416 |
+
p = Period("2007-01-01 07:10:15.123400")
|
| 417 |
+
assert p.freq == "us"
|
| 418 |
+
|
| 419 |
+
def test_multiples(self):
|
| 420 |
+
result1 = Period("1989", freq="2Y")
|
| 421 |
+
result2 = Period("1989", freq="Y")
|
| 422 |
+
assert result1.ordinal == result2.ordinal
|
| 423 |
+
assert result1.freqstr == "2Y-DEC"
|
| 424 |
+
assert result2.freqstr == "Y-DEC"
|
| 425 |
+
assert result1.freq == offsets.YearEnd(2)
|
| 426 |
+
assert result2.freq == offsets.YearEnd()
|
| 427 |
+
|
| 428 |
+
assert (result1 + 1).ordinal == result1.ordinal + 2
|
| 429 |
+
assert (1 + result1).ordinal == result1.ordinal + 2
|
| 430 |
+
assert (result1 - 1).ordinal == result2.ordinal - 2
|
| 431 |
+
assert (-1 + result1).ordinal == result2.ordinal - 2
|
| 432 |
+
|
| 433 |
+
@pytest.mark.parametrize("month", MONTHS)
|
| 434 |
+
def test_period_cons_quarterly(self, month):
|
| 435 |
+
# bugs in scikits.timeseries
|
| 436 |
+
freq = f"Q-{month}"
|
| 437 |
+
exp = Period("1989Q3", freq=freq)
|
| 438 |
+
assert "1989Q3" in str(exp)
|
| 439 |
+
stamp = exp.to_timestamp("D", how="end")
|
| 440 |
+
p = Period(stamp, freq=freq)
|
| 441 |
+
assert p == exp
|
| 442 |
+
|
| 443 |
+
stamp = exp.to_timestamp("3D", how="end")
|
| 444 |
+
p = Period(stamp, freq=freq)
|
| 445 |
+
assert p == exp
|
| 446 |
+
|
| 447 |
+
@pytest.mark.parametrize("month", MONTHS)
|
| 448 |
+
def test_period_cons_annual(self, month):
|
| 449 |
+
# bugs in scikits.timeseries
|
| 450 |
+
freq = f"Y-{month}"
|
| 451 |
+
exp = Period("1989", freq=freq)
|
| 452 |
+
stamp = exp.to_timestamp("D", how="end") + timedelta(days=30)
|
| 453 |
+
p = Period(stamp, freq=freq)
|
| 454 |
+
|
| 455 |
+
assert p == exp + 1
|
| 456 |
+
assert isinstance(p, Period)
|
| 457 |
+
|
| 458 |
+
@pytest.mark.parametrize("day", DAYS)
|
| 459 |
+
@pytest.mark.parametrize("num", range(10, 17))
|
| 460 |
+
def test_period_cons_weekly(self, num, day):
|
| 461 |
+
daystr = f"2011-02-{num}"
|
| 462 |
+
freq = f"W-{day}"
|
| 463 |
+
|
| 464 |
+
result = Period(daystr, freq=freq)
|
| 465 |
+
expected = Period(daystr, freq="D").asfreq(freq)
|
| 466 |
+
assert result == expected
|
| 467 |
+
assert isinstance(result, Period)
|
| 468 |
+
|
| 469 |
+
def test_parse_week_str_roundstrip(self):
|
| 470 |
+
# GH#50803
|
| 471 |
+
per = Period("2017-01-23/2017-01-29")
|
| 472 |
+
assert per.freq.freqstr == "W-SUN"
|
| 473 |
+
|
| 474 |
+
per = Period("2017-01-24/2017-01-30")
|
| 475 |
+
assert per.freq.freqstr == "W-MON"
|
| 476 |
+
|
| 477 |
+
msg = "Could not parse as weekly-freq Period"
|
| 478 |
+
with pytest.raises(ValueError, match=msg):
|
| 479 |
+
# not 6 days apart
|
| 480 |
+
Period("2016-01-23/2017-01-29")
|
| 481 |
+
|
| 482 |
+
def test_period_from_ordinal(self):
|
| 483 |
+
p = Period("2011-01", freq="M")
|
| 484 |
+
res = Period._from_ordinal(p.ordinal, freq=p.freq)
|
| 485 |
+
assert p == res
|
| 486 |
+
assert isinstance(res, Period)
|
| 487 |
+
|
| 488 |
+
@pytest.mark.parametrize("freq", ["Y", "M", "D", "h"])
|
| 489 |
+
def test_construct_from_nat_string_and_freq(self, freq):
|
| 490 |
+
per = Period("NaT", freq=freq)
|
| 491 |
+
assert per is NaT
|
| 492 |
+
|
| 493 |
+
per = Period("NaT", freq="2" + freq)
|
| 494 |
+
assert per is NaT
|
| 495 |
+
|
| 496 |
+
per = Period("NaT", freq="3" + freq)
|
| 497 |
+
assert per is NaT
|
| 498 |
+
|
| 499 |
+
def test_period_cons_nat(self):
|
| 500 |
+
p = Period("nat", freq="W-SUN")
|
| 501 |
+
assert p is NaT
|
| 502 |
+
|
| 503 |
+
p = Period(iNaT, freq="D")
|
| 504 |
+
assert p is NaT
|
| 505 |
+
|
| 506 |
+
p = Period(iNaT, freq="3D")
|
| 507 |
+
assert p is NaT
|
| 508 |
+
|
| 509 |
+
p = Period(iNaT, freq="1D1h")
|
| 510 |
+
assert p is NaT
|
| 511 |
+
|
| 512 |
+
p = Period("NaT")
|
| 513 |
+
assert p is NaT
|
| 514 |
+
|
| 515 |
+
p = Period(iNaT)
|
| 516 |
+
assert p is NaT
|
| 517 |
+
|
| 518 |
+
def test_period_cons_mult(self):
|
| 519 |
+
p1 = Period("2011-01", freq="3M")
|
| 520 |
+
p2 = Period("2011-01", freq="M")
|
| 521 |
+
assert p1.ordinal == p2.ordinal
|
| 522 |
+
|
| 523 |
+
assert p1.freq == offsets.MonthEnd(3)
|
| 524 |
+
assert p1.freqstr == "3M"
|
| 525 |
+
|
| 526 |
+
assert p2.freq == offsets.MonthEnd()
|
| 527 |
+
assert p2.freqstr == "M"
|
| 528 |
+
|
| 529 |
+
result = p1 + 1
|
| 530 |
+
assert result.ordinal == (p2 + 3).ordinal
|
| 531 |
+
|
| 532 |
+
assert result.freq == p1.freq
|
| 533 |
+
assert result.freqstr == "3M"
|
| 534 |
+
|
| 535 |
+
result = p1 - 1
|
| 536 |
+
assert result.ordinal == (p2 - 3).ordinal
|
| 537 |
+
assert result.freq == p1.freq
|
| 538 |
+
assert result.freqstr == "3M"
|
| 539 |
+
|
| 540 |
+
msg = "Frequency must be positive, because it represents span: -3M"
|
| 541 |
+
with pytest.raises(ValueError, match=msg):
|
| 542 |
+
Period("2011-01", freq="-3M")
|
| 543 |
+
|
| 544 |
+
msg = "Frequency must be positive, because it represents span: 0M"
|
| 545 |
+
with pytest.raises(ValueError, match=msg):
|
| 546 |
+
Period("2011-01", freq="0M")
|
| 547 |
+
|
| 548 |
+
def test_period_cons_combined(self):
|
| 549 |
+
p = [
|
| 550 |
+
(
|
| 551 |
+
Period("2011-01", freq="1D1h"),
|
| 552 |
+
Period("2011-01", freq="1h1D"),
|
| 553 |
+
Period("2011-01", freq="h"),
|
| 554 |
+
),
|
| 555 |
+
(
|
| 556 |
+
Period(ordinal=1, freq="1D1h"),
|
| 557 |
+
Period(ordinal=1, freq="1h1D"),
|
| 558 |
+
Period(ordinal=1, freq="h"),
|
| 559 |
+
),
|
| 560 |
+
]
|
| 561 |
+
|
| 562 |
+
for p1, p2, p3 in p:
|
| 563 |
+
assert p1.ordinal == p3.ordinal
|
| 564 |
+
assert p2.ordinal == p3.ordinal
|
| 565 |
+
|
| 566 |
+
assert p1.freq == offsets.Hour(25)
|
| 567 |
+
assert p1.freqstr == "25h"
|
| 568 |
+
|
| 569 |
+
assert p2.freq == offsets.Hour(25)
|
| 570 |
+
assert p2.freqstr == "25h"
|
| 571 |
+
|
| 572 |
+
assert p3.freq == offsets.Hour()
|
| 573 |
+
assert p3.freqstr == "h"
|
| 574 |
+
|
| 575 |
+
result = p1 + 1
|
| 576 |
+
assert result.ordinal == (p3 + 25).ordinal
|
| 577 |
+
assert result.freq == p1.freq
|
| 578 |
+
assert result.freqstr == "25h"
|
| 579 |
+
|
| 580 |
+
result = p2 + 1
|
| 581 |
+
assert result.ordinal == (p3 + 25).ordinal
|
| 582 |
+
assert result.freq == p2.freq
|
| 583 |
+
assert result.freqstr == "25h"
|
| 584 |
+
|
| 585 |
+
result = p1 - 1
|
| 586 |
+
assert result.ordinal == (p3 - 25).ordinal
|
| 587 |
+
assert result.freq == p1.freq
|
| 588 |
+
assert result.freqstr == "25h"
|
| 589 |
+
|
| 590 |
+
result = p2 - 1
|
| 591 |
+
assert result.ordinal == (p3 - 25).ordinal
|
| 592 |
+
assert result.freq == p2.freq
|
| 593 |
+
assert result.freqstr == "25h"
|
| 594 |
+
|
| 595 |
+
msg = "Frequency must be positive, because it represents span: -25h"
|
| 596 |
+
with pytest.raises(ValueError, match=msg):
|
| 597 |
+
Period("2011-01", freq="-1D1h")
|
| 598 |
+
with pytest.raises(ValueError, match=msg):
|
| 599 |
+
Period("2011-01", freq="-1h1D")
|
| 600 |
+
with pytest.raises(ValueError, match=msg):
|
| 601 |
+
Period(ordinal=1, freq="-1D1h")
|
| 602 |
+
with pytest.raises(ValueError, match=msg):
|
| 603 |
+
Period(ordinal=1, freq="-1h1D")
|
| 604 |
+
|
| 605 |
+
msg = "Frequency must be positive, because it represents span: 0D"
|
| 606 |
+
with pytest.raises(ValueError, match=msg):
|
| 607 |
+
Period("2011-01", freq="0D0h")
|
| 608 |
+
with pytest.raises(ValueError, match=msg):
|
| 609 |
+
Period(ordinal=1, freq="0D0h")
|
| 610 |
+
|
| 611 |
+
# You can only combine together day and intraday offsets
|
| 612 |
+
msg = "Invalid frequency: 1W1D"
|
| 613 |
+
with pytest.raises(ValueError, match=msg):
|
| 614 |
+
Period("2011-01", freq="1W1D")
|
| 615 |
+
msg = "Invalid frequency: 1D1W"
|
| 616 |
+
with pytest.raises(ValueError, match=msg):
|
| 617 |
+
Period("2011-01", freq="1D1W")
|
| 618 |
+
|
| 619 |
+
@pytest.mark.parametrize("day", ["1970/01/01 ", "2020-12-31 ", "1981/09/13 "])
|
| 620 |
+
@pytest.mark.parametrize("hour", ["00:00:00", "00:00:01", "23:59:59", "12:00:59"])
|
| 621 |
+
@pytest.mark.parametrize(
|
| 622 |
+
"sec_float, expected",
|
| 623 |
+
[
|
| 624 |
+
(".000000001", 1),
|
| 625 |
+
(".000000999", 999),
|
| 626 |
+
(".123456789", 789),
|
| 627 |
+
(".999999999", 999),
|
| 628 |
+
(".999999000", 0),
|
| 629 |
+
# Test femtoseconds, attoseconds, picoseconds are dropped like Timestamp
|
| 630 |
+
(".999999001123", 1),
|
| 631 |
+
(".999999001123456", 1),
|
| 632 |
+
(".999999001123456789", 1),
|
| 633 |
+
],
|
| 634 |
+
)
|
| 635 |
+
def test_period_constructor_nanosecond(self, day, hour, sec_float, expected):
|
| 636 |
+
# GH 34621
|
| 637 |
+
|
| 638 |
+
assert Period(day + hour + sec_float).start_time.nanosecond == expected
|
| 639 |
+
|
| 640 |
+
@pytest.mark.parametrize("hour", range(24))
|
| 641 |
+
def test_period_large_ordinal(self, hour):
|
| 642 |
+
# Issue #36430
|
| 643 |
+
# Integer overflow for Period over the maximum timestamp
|
| 644 |
+
p = Period(ordinal=2562048 + hour, freq="1h")
|
| 645 |
+
assert p.hour == hour
|
| 646 |
+
|
| 647 |
+
@pytest.mark.filterwarnings(
|
| 648 |
+
"ignore:Period with BDay freq is deprecated:FutureWarning"
|
| 649 |
+
)
|
| 650 |
+
@pytest.mark.parametrize(
|
| 651 |
+
"freq,freq_depr",
|
| 652 |
+
[("2W", "2w"), ("2W-FRI", "2w-fri"), ("2D", "2d"), ("2B", "2b")],
|
| 653 |
+
)
|
| 654 |
+
def test_period_deprecated_lowercase_freq(self, freq, freq_depr):
|
| 655 |
+
# GH#58998
|
| 656 |
+
msg = (
|
| 657 |
+
f"'{freq_depr[1:]}' is deprecated and will be removed in a future version."
|
| 658 |
+
)
|
| 659 |
+
|
| 660 |
+
with tm.assert_produces_warning(
|
| 661 |
+
Pandas4Warning, match=msg, raise_on_extra_warnings=False
|
| 662 |
+
):
|
| 663 |
+
result = Period("2016-03-01 09:00", freq=freq_depr)
|
| 664 |
+
|
| 665 |
+
expected = Period("2016-03-01 09:00", freq=freq)
|
| 666 |
+
assert result == expected
|
| 667 |
+
|
| 668 |
+
|
| 669 |
+
class TestPeriodMethods:
|
| 670 |
+
def test_round_trip(self, temp_file):
|
| 671 |
+
p = Period("2000Q1")
|
| 672 |
+
new_p = tm.round_trip_pickle(p, temp_file)
|
| 673 |
+
assert new_p == p
|
| 674 |
+
|
| 675 |
+
def test_hash(self):
|
| 676 |
+
assert hash(Period("2011-01", freq="M")) == hash(Period("2011-01", freq="M"))
|
| 677 |
+
|
| 678 |
+
assert hash(Period("2011-01-01", freq="D")) != hash(Period("2011-01", freq="M"))
|
| 679 |
+
|
| 680 |
+
assert hash(Period("2011-01", freq="3M")) != hash(Period("2011-01", freq="2M"))
|
| 681 |
+
|
| 682 |
+
assert hash(Period("2011-01", freq="M")) != hash(Period("2011-02", freq="M"))
|
| 683 |
+
|
| 684 |
+
# --------------------------------------------------------------
|
| 685 |
+
# to_timestamp
|
| 686 |
+
|
| 687 |
+
def test_to_timestamp_mult(self):
|
| 688 |
+
p = Period("2011-01", freq="M")
|
| 689 |
+
assert p.to_timestamp(how="S") == Timestamp("2011-01-01")
|
| 690 |
+
expected = Timestamp("2011-02-01") - Timedelta(1, unit="us")
|
| 691 |
+
assert p.to_timestamp(how="E") == expected
|
| 692 |
+
|
| 693 |
+
p = Period("2011-01", freq="3M")
|
| 694 |
+
assert p.to_timestamp(how="S") == Timestamp("2011-01-01")
|
| 695 |
+
expected = Timestamp("2011-04-01") - Timedelta(1, unit="us")
|
| 696 |
+
assert p.to_timestamp(how="E") == expected
|
| 697 |
+
|
| 698 |
+
@pytest.mark.filterwarnings(
|
| 699 |
+
"ignore:Period with BDay freq is deprecated:FutureWarning"
|
| 700 |
+
)
|
| 701 |
+
def test_to_timestamp(self):
|
| 702 |
+
p = Period("1982", freq="Y")
|
| 703 |
+
start_ts = p.to_timestamp(how="S")
|
| 704 |
+
aliases = ["s", "StarT", "BEGIn"]
|
| 705 |
+
for a in aliases:
|
| 706 |
+
assert start_ts == p.to_timestamp("D", how=a)
|
| 707 |
+
# freq with mult should not affect to the result
|
| 708 |
+
assert start_ts == p.to_timestamp("3D", how=a)
|
| 709 |
+
|
| 710 |
+
end_ts = p.to_timestamp(how="E")
|
| 711 |
+
aliases = ["e", "end", "FINIsH"]
|
| 712 |
+
for a in aliases:
|
| 713 |
+
assert end_ts == p.to_timestamp("D", how=a)
|
| 714 |
+
assert end_ts == p.to_timestamp("3D", how=a)
|
| 715 |
+
|
| 716 |
+
from_lst = ["Y", "Q", "M", "W", "B", "D", "h", "Min", "s"]
|
| 717 |
+
|
| 718 |
+
def _ex(p):
|
| 719 |
+
if p.freq == "B":
|
| 720 |
+
return p.start_time + Timedelta(days=1) - Timedelta(microseconds=1)
|
| 721 |
+
return Timestamp((p + p.freq).start_time._value - 1, unit="us")
|
| 722 |
+
|
| 723 |
+
for fcode in from_lst:
|
| 724 |
+
p = Period("1982", freq=fcode)
|
| 725 |
+
result = p.to_timestamp().to_period(fcode)
|
| 726 |
+
assert result == p
|
| 727 |
+
|
| 728 |
+
assert p.start_time == p.to_timestamp(how="S")
|
| 729 |
+
|
| 730 |
+
assert p.end_time == _ex(p)
|
| 731 |
+
|
| 732 |
+
# Frequency other than daily
|
| 733 |
+
|
| 734 |
+
p = Period("1985", freq="Y")
|
| 735 |
+
|
| 736 |
+
result = p.to_timestamp("h", how="end")
|
| 737 |
+
expected = Timestamp(1986, 1, 1) - Timedelta(1, unit="us")
|
| 738 |
+
assert result == expected
|
| 739 |
+
result = p.to_timestamp("3h", how="end")
|
| 740 |
+
assert result == expected
|
| 741 |
+
|
| 742 |
+
result = p.to_timestamp("min", how="end")
|
| 743 |
+
expected = Timestamp(1986, 1, 1) - Timedelta(1, unit="us")
|
| 744 |
+
assert result == expected
|
| 745 |
+
result = p.to_timestamp("2min", how="end")
|
| 746 |
+
assert result == expected
|
| 747 |
+
|
| 748 |
+
result = p.to_timestamp(how="end")
|
| 749 |
+
expected = Timestamp(1986, 1, 1) - Timedelta(1, unit="us")
|
| 750 |
+
assert result == expected
|
| 751 |
+
|
| 752 |
+
expected = datetime(1985, 1, 1)
|
| 753 |
+
result = p.to_timestamp("h", how="start")
|
| 754 |
+
assert result == expected
|
| 755 |
+
result = p.to_timestamp("min", how="start")
|
| 756 |
+
assert result == expected
|
| 757 |
+
result = p.to_timestamp("s", how="start")
|
| 758 |
+
assert result == expected
|
| 759 |
+
result = p.to_timestamp("3h", how="start")
|
| 760 |
+
assert result == expected
|
| 761 |
+
result = p.to_timestamp("5s", how="start")
|
| 762 |
+
assert result == expected
|
| 763 |
+
|
| 764 |
+
def test_to_timestamp_business_end(self):
|
| 765 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 766 |
+
per = Period("1990-01-05", "B") # Friday
|
| 767 |
+
result = per.to_timestamp("B", how="E")
|
| 768 |
+
|
| 769 |
+
expected = Timestamp("1990-01-06") - Timedelta(1, unit="us")
|
| 770 |
+
assert result == expected
|
| 771 |
+
|
| 772 |
+
@pytest.mark.parametrize(
|
| 773 |
+
"ts, expected",
|
| 774 |
+
[
|
| 775 |
+
("1970-01-01 00:00:00", 0),
|
| 776 |
+
("1970-01-01 00:00:00.000001", 1),
|
| 777 |
+
("1970-01-01 00:00:00.00001", 10),
|
| 778 |
+
("1970-01-01 00:00:00.499", 499000),
|
| 779 |
+
("1999-12-31 23:59:59.999", 999000),
|
| 780 |
+
("1999-12-31 23:59:59.999999", 999999),
|
| 781 |
+
("2050-12-31 23:59:59.5", 500000),
|
| 782 |
+
("2050-12-31 23:59:59.500001", 500001),
|
| 783 |
+
("2050-12-31 23:59:59.123456", 123456),
|
| 784 |
+
],
|
| 785 |
+
)
|
| 786 |
+
@pytest.mark.parametrize("freq", [None, "us", "ns"])
|
| 787 |
+
def test_to_timestamp_microsecond(self, ts, expected, freq):
|
| 788 |
+
# GH 24444
|
| 789 |
+
result = Period(ts).to_timestamp(freq=freq).microsecond
|
| 790 |
+
assert result == expected
|
| 791 |
+
|
| 792 |
+
# --------------------------------------------------------------
|
| 793 |
+
# Rendering: __repr__, strftime, etc
|
| 794 |
+
|
| 795 |
+
@pytest.mark.parametrize(
|
| 796 |
+
"str_ts,freq,str_res,str_freq",
|
| 797 |
+
(
|
| 798 |
+
("Jan-2000", None, "2000-01", "M"),
|
| 799 |
+
("2000-12-15", None, "2000-12-15", "D"),
|
| 800 |
+
(
|
| 801 |
+
"2000-12-15 13:45:26.123456789",
|
| 802 |
+
"ns",
|
| 803 |
+
"2000-12-15 13:45:26.123456789",
|
| 804 |
+
"ns",
|
| 805 |
+
),
|
| 806 |
+
("2000-12-15 13:45:26.123456789", "us", "2000-12-15 13:45:26.123456", "us"),
|
| 807 |
+
("2000-12-15 13:45:26.123456", None, "2000-12-15 13:45:26.123456", "us"),
|
| 808 |
+
("2000-12-15 13:45:26.123456789", "ms", "2000-12-15 13:45:26.123", "ms"),
|
| 809 |
+
("2000-12-15 13:45:26.123", None, "2000-12-15 13:45:26.123", "ms"),
|
| 810 |
+
("2000-12-15 13:45:26", "s", "2000-12-15 13:45:26", "s"),
|
| 811 |
+
("2000-12-15 13:45:26", "min", "2000-12-15 13:45", "min"),
|
| 812 |
+
("2000-12-15 13:45:26", "h", "2000-12-15 13:00", "h"),
|
| 813 |
+
("2000-12-15", "Y", "2000", "Y-DEC"),
|
| 814 |
+
("2000-12-15", "Q", "2000Q4", "Q-DEC"),
|
| 815 |
+
("2000-12-15", "M", "2000-12", "M"),
|
| 816 |
+
("2000-12-15", "W", "2000-12-11/2000-12-17", "W-SUN"),
|
| 817 |
+
("2000-12-15", "D", "2000-12-15", "D"),
|
| 818 |
+
("2000-12-15", "B", "2000-12-15", "B"),
|
| 819 |
+
),
|
| 820 |
+
)
|
| 821 |
+
@pytest.mark.filterwarnings(
|
| 822 |
+
"ignore:Period with BDay freq is deprecated:FutureWarning"
|
| 823 |
+
)
|
| 824 |
+
def test_repr(self, str_ts, freq, str_res, str_freq):
|
| 825 |
+
p = Period(str_ts, freq=freq)
|
| 826 |
+
assert str(p) == str_res
|
| 827 |
+
assert repr(p) == f"Period('{str_res}', '{str_freq}')"
|
| 828 |
+
|
| 829 |
+
def test_repr_nat(self):
|
| 830 |
+
p = Period("nat", freq="M")
|
| 831 |
+
assert repr(NaT) in repr(p)
|
| 832 |
+
|
| 833 |
+
def test_strftime(self):
|
| 834 |
+
# GH#3363
|
| 835 |
+
p = Period("2000-1-1 12:34:12", freq="s")
|
| 836 |
+
res = p.strftime("%Y-%m-%d %H:%M:%S")
|
| 837 |
+
assert res == "2000-01-01 12:34:12"
|
| 838 |
+
assert isinstance(res, str)
|
| 839 |
+
|
| 840 |
+
|
| 841 |
+
class TestPeriodProperties:
|
| 842 |
+
"""Test properties such as year, month, weekday, etc...."""
|
| 843 |
+
|
| 844 |
+
@pytest.mark.parametrize("freq", ["Y", "M", "D", "h"])
|
| 845 |
+
def test_is_leap_year(self, freq):
|
| 846 |
+
# GH 13727
|
| 847 |
+
p = Period("2000-01-01 00:00:00", freq=freq)
|
| 848 |
+
assert p.is_leap_year
|
| 849 |
+
assert isinstance(p.is_leap_year, bool)
|
| 850 |
+
|
| 851 |
+
p = Period("1999-01-01 00:00:00", freq=freq)
|
| 852 |
+
assert not p.is_leap_year
|
| 853 |
+
|
| 854 |
+
p = Period("2004-01-01 00:00:00", freq=freq)
|
| 855 |
+
assert p.is_leap_year
|
| 856 |
+
|
| 857 |
+
p = Period("2100-01-01 00:00:00", freq=freq)
|
| 858 |
+
assert not p.is_leap_year
|
| 859 |
+
|
| 860 |
+
def test_quarterly_negative_ordinals(self):
|
| 861 |
+
p = Period(ordinal=-1, freq="Q-DEC")
|
| 862 |
+
assert p.year == 1969
|
| 863 |
+
assert p.quarter == 4
|
| 864 |
+
assert isinstance(p, Period)
|
| 865 |
+
|
| 866 |
+
p = Period(ordinal=-2, freq="Q-DEC")
|
| 867 |
+
assert p.year == 1969
|
| 868 |
+
assert p.quarter == 3
|
| 869 |
+
assert isinstance(p, Period)
|
| 870 |
+
|
| 871 |
+
p = Period(ordinal=-2, freq="M")
|
| 872 |
+
assert p.year == 1969
|
| 873 |
+
assert p.month == 11
|
| 874 |
+
assert isinstance(p, Period)
|
| 875 |
+
|
| 876 |
+
def test_freq_str(self):
|
| 877 |
+
i1 = Period("1982", freq="Min")
|
| 878 |
+
assert i1.freq == offsets.Minute()
|
| 879 |
+
assert i1.freqstr == "min"
|
| 880 |
+
|
| 881 |
+
@pytest.mark.filterwarnings(
|
| 882 |
+
"ignore:Period with BDay freq is deprecated:FutureWarning"
|
| 883 |
+
)
|
| 884 |
+
def test_period_deprecated_freq(self):
|
| 885 |
+
cases = {
|
| 886 |
+
"M": ["MTH", "MONTH", "MONTHLY", "Mth", "month", "monthly"],
|
| 887 |
+
"B": ["BUS", "BUSINESS", "BUSINESSLY", "WEEKDAY", "bus"],
|
| 888 |
+
"D": ["DAY", "DLY", "DAILY", "Day", "Dly", "Daily"],
|
| 889 |
+
"h": ["HR", "HOUR", "HRLY", "HOURLY", "hr", "Hour", "HRly"],
|
| 890 |
+
"min": ["minute", "MINUTE", "MINUTELY", "minutely"],
|
| 891 |
+
"s": ["sec", "SEC", "SECOND", "SECONDLY", "second"],
|
| 892 |
+
"ms": ["MILLISECOND", "MILLISECONDLY", "millisecond"],
|
| 893 |
+
"us": ["MICROSECOND", "MICROSECONDLY", "microsecond"],
|
| 894 |
+
"ns": ["NANOSECOND", "NANOSECONDLY", "nanosecond"],
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
msg = INVALID_FREQ_ERR_MSG
|
| 898 |
+
for exp, freqs in cases.items():
|
| 899 |
+
for freq in freqs:
|
| 900 |
+
with pytest.raises(ValueError, match=msg):
|
| 901 |
+
Period("2016-03-01 09:00", freq=freq)
|
| 902 |
+
with pytest.raises(ValueError, match=msg):
|
| 903 |
+
Period(ordinal=1, freq=freq)
|
| 904 |
+
|
| 905 |
+
# check supported freq-aliases still works
|
| 906 |
+
p1 = Period("2016-03-01 09:00", freq=exp)
|
| 907 |
+
p2 = Period(ordinal=1, freq=exp)
|
| 908 |
+
assert isinstance(p1, Period)
|
| 909 |
+
assert isinstance(p2, Period)
|
| 910 |
+
|
| 911 |
+
@staticmethod
|
| 912 |
+
def _period_constructor(bound, offset):
|
| 913 |
+
return Period(
|
| 914 |
+
year=bound.year,
|
| 915 |
+
month=bound.month,
|
| 916 |
+
day=bound.day,
|
| 917 |
+
hour=bound.hour,
|
| 918 |
+
minute=bound.minute,
|
| 919 |
+
second=bound.second + offset,
|
| 920 |
+
freq="us",
|
| 921 |
+
)
|
| 922 |
+
|
| 923 |
+
@pytest.mark.parametrize("bound, offset", [(Timestamp.min, -1), (Timestamp.max, 1)])
|
| 924 |
+
@pytest.mark.parametrize("period_property", ["start_time", "end_time"])
|
| 925 |
+
def test_outer_bounds_start_and_end_time(self, bound, offset, period_property):
|
| 926 |
+
# GH #13346
|
| 927 |
+
period = TestPeriodProperties._period_constructor(bound, offset)
|
| 928 |
+
# post-GH#63760 this no longer raises OutOfBoundsDatetime
|
| 929 |
+
getattr(period, period_property)
|
| 930 |
+
|
| 931 |
+
per = Period._from_ordinal(bound.value, freq=to_offset("ms"))
|
| 932 |
+
|
| 933 |
+
if period_property == "end_time" and offset == 1:
|
| 934 |
+
err = OverflowError
|
| 935 |
+
msg = "value too large"
|
| 936 |
+
else:
|
| 937 |
+
err = OutOfBoundsDatetime
|
| 938 |
+
msg = "Out of bounds microsecond"
|
| 939 |
+
# But if we do the same thing with a much-more-remote date, we still
|
| 940 |
+
# raise OutOfBoundsDatetime (as opposed to silently giving a wrong result)
|
| 941 |
+
with pytest.raises(err, match=msg):
|
| 942 |
+
getattr(per, period_property)
|
| 943 |
+
|
| 944 |
+
@pytest.mark.parametrize("bound, offset", [(Timestamp.min, -1), (Timestamp.max, 1)])
|
| 945 |
+
@pytest.mark.parametrize("period_property", ["start_time", "end_time"])
|
| 946 |
+
def test_inner_bounds_start_and_end_time(self, bound, offset, period_property):
|
| 947 |
+
# GH #13346
|
| 948 |
+
period = TestPeriodProperties._period_constructor(bound, -offset)
|
| 949 |
+
expected = period.to_timestamp().round(freq="s")
|
| 950 |
+
assert getattr(period, period_property).round(freq="s") == expected
|
| 951 |
+
expected = (bound - offset * Timedelta(1, unit="s")).floor("s")
|
| 952 |
+
assert getattr(period, period_property).floor("s") == expected
|
| 953 |
+
|
| 954 |
+
def test_start_time(self):
|
| 955 |
+
freq_lst = ["Y", "Q", "M", "D", "h", "min", "s"]
|
| 956 |
+
xp = datetime(2012, 1, 1)
|
| 957 |
+
for f in freq_lst:
|
| 958 |
+
p = Period("2012", freq=f)
|
| 959 |
+
assert p.start_time == xp
|
| 960 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 961 |
+
assert Period("2012", freq="B").start_time == datetime(2012, 1, 2)
|
| 962 |
+
assert Period("2012", freq="W").start_time == datetime(2011, 12, 26)
|
| 963 |
+
|
| 964 |
+
def test_end_time(self):
|
| 965 |
+
p = Period("2012", freq="Y")
|
| 966 |
+
|
| 967 |
+
exp = Timestamp(2012, 12, 31, 23, 59, 59, 999999)
|
| 968 |
+
assert p.end_time == exp
|
| 969 |
+
|
| 970 |
+
p = Period("2012", freq="Q")
|
| 971 |
+
exp = Timestamp(2012, 3, 31, 23, 59, 59, 999999)
|
| 972 |
+
assert p.end_time == exp
|
| 973 |
+
|
| 974 |
+
p = Period("2012", freq="M")
|
| 975 |
+
exp = Timestamp(2012, 1, 31, 23, 59, 59, 999999)
|
| 976 |
+
assert p.end_time == exp
|
| 977 |
+
|
| 978 |
+
p = Period("2012", freq="D")
|
| 979 |
+
exp = Timestamp(2012, 1, 1, 23, 59, 59, 999999)
|
| 980 |
+
assert p.end_time == exp
|
| 981 |
+
|
| 982 |
+
p = Period("2012", freq="h")
|
| 983 |
+
exp = Timestamp(2012, 1, 1, 0, 59, 59, 999999)
|
| 984 |
+
assert p.end_time == exp
|
| 985 |
+
|
| 986 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 987 |
+
p = Period("2012", freq="B")
|
| 988 |
+
exp = Timestamp(2012, 1, 2, 23, 59, 59, 999999)
|
| 989 |
+
assert p.end_time == exp
|
| 990 |
+
|
| 991 |
+
p = Period("2012", freq="W")
|
| 992 |
+
exp = Timestamp(2012, 1, 1, 23, 59, 59, 999999)
|
| 993 |
+
assert p.end_time == exp
|
| 994 |
+
|
| 995 |
+
# Test for GH 11738
|
| 996 |
+
p = Period("2012", freq="15D")
|
| 997 |
+
exp = Timestamp(2012, 1, 15, 23, 59, 59, 999999)
|
| 998 |
+
assert p.end_time == exp
|
| 999 |
+
|
| 1000 |
+
p = Period("2012", freq="1D1h")
|
| 1001 |
+
exp = Timestamp(2012, 1, 2, 0, 59, 59, 999999)
|
| 1002 |
+
assert p.end_time == exp
|
| 1003 |
+
|
| 1004 |
+
p = Period("2012", freq="1h1D")
|
| 1005 |
+
exp = Timestamp(2012, 1, 2, 0, 59, 59, 999999)
|
| 1006 |
+
assert p.end_time == exp
|
| 1007 |
+
|
| 1008 |
+
def test_end_time_business_friday(self):
|
| 1009 |
+
# GH#34449
|
| 1010 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 1011 |
+
per = Period("1990-01-05", "B")
|
| 1012 |
+
result = per.end_time
|
| 1013 |
+
|
| 1014 |
+
expected = Timestamp("1990-01-06") - Timedelta(1, unit="us")
|
| 1015 |
+
assert result == expected
|
| 1016 |
+
|
| 1017 |
+
def test_anchor_week_end_time(self):
|
| 1018 |
+
p = Period("2013-1-1", "W-SAT")
|
| 1019 |
+
exp = Timestamp(2013, 1, 5, 23, 59, 59, 999999)
|
| 1020 |
+
assert p.end_time == exp
|
| 1021 |
+
|
| 1022 |
+
def test_properties_annually(self):
|
| 1023 |
+
# Test properties on Periods with annually frequency.
|
| 1024 |
+
a_date = Period(freq="Y", year=2007)
|
| 1025 |
+
assert a_date.year == 2007
|
| 1026 |
+
|
| 1027 |
+
def test_properties_quarterly(self):
|
| 1028 |
+
# Test properties on Periods with daily frequency.
|
| 1029 |
+
qedec_date = Period(freq="Q-DEC", year=2007, quarter=1)
|
| 1030 |
+
qejan_date = Period(freq="Q-JAN", year=2007, quarter=1)
|
| 1031 |
+
qejun_date = Period(freq="Q-JUN", year=2007, quarter=1)
|
| 1032 |
+
for x in range(3):
|
| 1033 |
+
for qd in (qedec_date, qejan_date, qejun_date):
|
| 1034 |
+
assert (qd + x).qyear == 2007
|
| 1035 |
+
assert (qd + x).quarter == x + 1
|
| 1036 |
+
|
| 1037 |
+
def test_properties_monthly(self):
|
| 1038 |
+
# Test properties on Periods with daily frequency.
|
| 1039 |
+
m_date = Period(freq="M", year=2007, month=1)
|
| 1040 |
+
for x in range(11):
|
| 1041 |
+
m_ival_x = m_date + x
|
| 1042 |
+
assert m_ival_x.year == 2007
|
| 1043 |
+
if 1 <= x + 1 <= 3:
|
| 1044 |
+
assert m_ival_x.quarter == 1
|
| 1045 |
+
elif 4 <= x + 1 <= 6:
|
| 1046 |
+
assert m_ival_x.quarter == 2
|
| 1047 |
+
elif 7 <= x + 1 <= 9:
|
| 1048 |
+
assert m_ival_x.quarter == 3
|
| 1049 |
+
elif 10 <= x + 1 <= 12:
|
| 1050 |
+
assert m_ival_x.quarter == 4
|
| 1051 |
+
assert m_ival_x.month == x + 1
|
| 1052 |
+
|
| 1053 |
+
def test_properties_weekly(self):
|
| 1054 |
+
# Test properties on Periods with daily frequency.
|
| 1055 |
+
w_date = Period(freq="W", year=2007, month=1, day=7)
|
| 1056 |
+
assert w_date.year == 2007
|
| 1057 |
+
assert w_date.quarter == 1
|
| 1058 |
+
assert w_date.month == 1
|
| 1059 |
+
assert w_date.week == 1
|
| 1060 |
+
assert (w_date - 1).week == 52
|
| 1061 |
+
assert w_date.days_in_month == 31
|
| 1062 |
+
assert Period(freq="W", year=2012, month=2, day=1).days_in_month == 29
|
| 1063 |
+
|
| 1064 |
+
def test_properties_weekly_legacy(self):
|
| 1065 |
+
# Test properties on Periods with daily frequency.
|
| 1066 |
+
w_date = Period(freq="W", year=2007, month=1, day=7)
|
| 1067 |
+
assert w_date.year == 2007
|
| 1068 |
+
assert w_date.quarter == 1
|
| 1069 |
+
assert w_date.month == 1
|
| 1070 |
+
assert w_date.week == 1
|
| 1071 |
+
assert (w_date - 1).week == 52
|
| 1072 |
+
assert w_date.days_in_month == 31
|
| 1073 |
+
|
| 1074 |
+
exp = Period(freq="W", year=2012, month=2, day=1)
|
| 1075 |
+
assert exp.days_in_month == 29
|
| 1076 |
+
|
| 1077 |
+
msg = INVALID_FREQ_ERR_MSG
|
| 1078 |
+
with pytest.raises(ValueError, match=msg):
|
| 1079 |
+
Period(freq="WK", year=2007, month=1, day=7)
|
| 1080 |
+
|
| 1081 |
+
def test_properties_daily(self):
|
| 1082 |
+
# Test properties on Periods with daily frequency.
|
| 1083 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 1084 |
+
b_date = Period(freq="B", year=2007, month=1, day=1)
|
| 1085 |
+
assert b_date.year == 2007
|
| 1086 |
+
assert b_date.quarter == 1
|
| 1087 |
+
assert b_date.month == 1
|
| 1088 |
+
assert b_date.day == 1
|
| 1089 |
+
assert b_date.weekday == 0
|
| 1090 |
+
assert b_date.dayofyear == 1
|
| 1091 |
+
assert b_date.days_in_month == 31
|
| 1092 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 1093 |
+
assert Period(freq="B", year=2012, month=2, day=1).days_in_month == 29
|
| 1094 |
+
|
| 1095 |
+
d_date = Period(freq="D", year=2007, month=1, day=1)
|
| 1096 |
+
|
| 1097 |
+
assert d_date.year == 2007
|
| 1098 |
+
assert d_date.quarter == 1
|
| 1099 |
+
assert d_date.month == 1
|
| 1100 |
+
assert d_date.day == 1
|
| 1101 |
+
assert d_date.weekday == 0
|
| 1102 |
+
assert d_date.dayofyear == 1
|
| 1103 |
+
assert d_date.days_in_month == 31
|
| 1104 |
+
assert Period(freq="D", year=2012, month=2, day=1).days_in_month == 29
|
| 1105 |
+
|
| 1106 |
+
def test_properties_hourly(self):
|
| 1107 |
+
# Test properties on Periods with hourly frequency.
|
| 1108 |
+
h_date1 = Period(freq="h", year=2007, month=1, day=1, hour=0)
|
| 1109 |
+
h_date2 = Period(freq="2h", year=2007, month=1, day=1, hour=0)
|
| 1110 |
+
|
| 1111 |
+
for h_date in [h_date1, h_date2]:
|
| 1112 |
+
assert h_date.year == 2007
|
| 1113 |
+
assert h_date.quarter == 1
|
| 1114 |
+
assert h_date.month == 1
|
| 1115 |
+
assert h_date.day == 1
|
| 1116 |
+
assert h_date.weekday == 0
|
| 1117 |
+
assert h_date.dayofyear == 1
|
| 1118 |
+
assert h_date.hour == 0
|
| 1119 |
+
assert h_date.days_in_month == 31
|
| 1120 |
+
assert (
|
| 1121 |
+
Period(freq="h", year=2012, month=2, day=1, hour=0).days_in_month == 29
|
| 1122 |
+
)
|
| 1123 |
+
|
| 1124 |
+
def test_properties_minutely(self):
|
| 1125 |
+
# Test properties on Periods with minutely frequency.
|
| 1126 |
+
t_date = Period(freq="Min", year=2007, month=1, day=1, hour=0, minute=0)
|
| 1127 |
+
assert t_date.quarter == 1
|
| 1128 |
+
assert t_date.month == 1
|
| 1129 |
+
assert t_date.day == 1
|
| 1130 |
+
assert t_date.weekday == 0
|
| 1131 |
+
assert t_date.dayofyear == 1
|
| 1132 |
+
assert t_date.hour == 0
|
| 1133 |
+
assert t_date.minute == 0
|
| 1134 |
+
assert t_date.days_in_month == 31
|
| 1135 |
+
assert (
|
| 1136 |
+
Period(freq="D", year=2012, month=2, day=1, hour=0, minute=0).days_in_month
|
| 1137 |
+
== 29
|
| 1138 |
+
)
|
| 1139 |
+
|
| 1140 |
+
def test_properties_secondly(self):
|
| 1141 |
+
# Test properties on Periods with secondly frequency.
|
| 1142 |
+
s_date = Period(
|
| 1143 |
+
freq="Min", year=2007, month=1, day=1, hour=0, minute=0, second=0
|
| 1144 |
+
)
|
| 1145 |
+
assert s_date.year == 2007
|
| 1146 |
+
assert s_date.quarter == 1
|
| 1147 |
+
assert s_date.month == 1
|
| 1148 |
+
assert s_date.day == 1
|
| 1149 |
+
assert s_date.weekday == 0
|
| 1150 |
+
assert s_date.dayofyear == 1
|
| 1151 |
+
assert s_date.hour == 0
|
| 1152 |
+
assert s_date.minute == 0
|
| 1153 |
+
assert s_date.second == 0
|
| 1154 |
+
assert s_date.days_in_month == 31
|
| 1155 |
+
assert (
|
| 1156 |
+
Period(
|
| 1157 |
+
freq="Min", year=2012, month=2, day=1, hour=0, minute=0, second=0
|
| 1158 |
+
).days_in_month
|
| 1159 |
+
== 29
|
| 1160 |
+
)
|
| 1161 |
+
|
| 1162 |
+
|
| 1163 |
+
class TestPeriodComparisons:
|
| 1164 |
+
def test_sort_periods(self):
|
| 1165 |
+
jan = Period("2000-01", "M")
|
| 1166 |
+
feb = Period("2000-02", "M")
|
| 1167 |
+
mar = Period("2000-03", "M")
|
| 1168 |
+
periods = [mar, jan, feb]
|
| 1169 |
+
correctPeriods = [jan, feb, mar]
|
| 1170 |
+
assert sorted(periods) == correctPeriods
|
| 1171 |
+
|
| 1172 |
+
|
| 1173 |
+
def test_period_immutable():
|
| 1174 |
+
# see gh-17116
|
| 1175 |
+
msg = "not writable"
|
| 1176 |
+
|
| 1177 |
+
per = Period("2014Q1")
|
| 1178 |
+
with pytest.raises(AttributeError, match=msg):
|
| 1179 |
+
per.ordinal = 14
|
| 1180 |
+
|
| 1181 |
+
freq = per.freq
|
| 1182 |
+
with pytest.raises(AttributeError, match=msg):
|
| 1183 |
+
per.freq = 2 * freq
|
| 1184 |
+
|
| 1185 |
+
|
| 1186 |
+
def test_small_year_parsing():
|
| 1187 |
+
per1 = Period("0001-01-07", "D")
|
| 1188 |
+
assert per1.year == 1
|
| 1189 |
+
assert per1.day == 7
|
| 1190 |
+
|
| 1191 |
+
|
| 1192 |
+
def test_negone_ordinals():
|
| 1193 |
+
freqs = ["Y", "M", "Q", "D", "h", "min", "s"]
|
| 1194 |
+
|
| 1195 |
+
period = Period(ordinal=-1, freq="D")
|
| 1196 |
+
for freq in freqs:
|
| 1197 |
+
repr(period.asfreq(freq))
|
| 1198 |
+
|
| 1199 |
+
for freq in freqs:
|
| 1200 |
+
period = Period(ordinal=-1, freq=freq)
|
| 1201 |
+
repr(period)
|
| 1202 |
+
assert period.year == 1969
|
| 1203 |
+
|
| 1204 |
+
with tm.assert_produces_warning(FutureWarning, match=bday_msg):
|
| 1205 |
+
period = Period(ordinal=-1, freq="B")
|
| 1206 |
+
repr(period)
|
| 1207 |
+
period = Period(ordinal=-1, freq="W")
|
| 1208 |
+
repr(period)
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_na_scalar.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import (
|
| 2 |
+
date,
|
| 3 |
+
time,
|
| 4 |
+
timedelta,
|
| 5 |
+
)
|
| 6 |
+
import pickle
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
from pandas._libs.missing import NA
|
| 12 |
+
|
| 13 |
+
from pandas.core.dtypes.common import is_scalar
|
| 14 |
+
|
| 15 |
+
import pandas as pd
|
| 16 |
+
import pandas._testing as tm
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_singleton():
|
| 20 |
+
assert NA is NA
|
| 21 |
+
new_NA = type(NA)()
|
| 22 |
+
assert new_NA is NA
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_repr():
|
| 26 |
+
assert repr(NA) == "<NA>"
|
| 27 |
+
assert str(NA) == "<NA>"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_format():
|
| 31 |
+
# GH-34740
|
| 32 |
+
assert format(NA) == "<NA>"
|
| 33 |
+
assert format(NA, ">10") == " <NA>"
|
| 34 |
+
assert format(NA, "xxx") == "<NA>" # NA is flexible, accept any format spec
|
| 35 |
+
|
| 36 |
+
assert f"{NA}" == "<NA>"
|
| 37 |
+
assert f"{NA:>10}" == " <NA>"
|
| 38 |
+
assert f"{NA:xxx}" == "<NA>"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def test_truthiness():
|
| 42 |
+
msg = "boolean value of NA is ambiguous"
|
| 43 |
+
|
| 44 |
+
with pytest.raises(TypeError, match=msg):
|
| 45 |
+
bool(NA)
|
| 46 |
+
|
| 47 |
+
with pytest.raises(TypeError, match=msg):
|
| 48 |
+
not NA
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_hashable():
|
| 52 |
+
assert hash(NA) == hash(NA)
|
| 53 |
+
d = {NA: "test"}
|
| 54 |
+
assert d[NA] == "test"
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
@pytest.mark.parametrize(
|
| 58 |
+
"other", [NA, 1, 1.0, "a", b"a", np.int64(1), np.nan], ids=repr
|
| 59 |
+
)
|
| 60 |
+
def test_arithmetic_ops(all_arithmetic_functions, other):
|
| 61 |
+
op = all_arithmetic_functions
|
| 62 |
+
|
| 63 |
+
if op.__name__ in ("pow", "rpow", "rmod") and isinstance(other, (str, bytes)):
|
| 64 |
+
pytest.skip(reason=f"{op.__name__} with NA and {other} not defined.")
|
| 65 |
+
if op.__name__ in ("divmod", "rdivmod"):
|
| 66 |
+
assert op(NA, other) is (NA, NA)
|
| 67 |
+
else:
|
| 68 |
+
if op.__name__ == "rpow":
|
| 69 |
+
# avoid special case
|
| 70 |
+
other += 1
|
| 71 |
+
assert op(NA, other) is NA
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
@pytest.mark.parametrize(
|
| 75 |
+
"other",
|
| 76 |
+
[
|
| 77 |
+
NA,
|
| 78 |
+
1,
|
| 79 |
+
1.0,
|
| 80 |
+
"a",
|
| 81 |
+
b"a",
|
| 82 |
+
np.int64(1),
|
| 83 |
+
np.nan,
|
| 84 |
+
np.bool_(True),
|
| 85 |
+
time(0),
|
| 86 |
+
date(1, 2, 3),
|
| 87 |
+
timedelta(1),
|
| 88 |
+
pd.NaT,
|
| 89 |
+
],
|
| 90 |
+
)
|
| 91 |
+
def test_comparison_ops(comparison_op, other):
|
| 92 |
+
assert comparison_op(NA, other) is NA
|
| 93 |
+
assert comparison_op(other, NA) is NA
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
@pytest.mark.parametrize(
|
| 97 |
+
"value",
|
| 98 |
+
[
|
| 99 |
+
0,
|
| 100 |
+
0.0,
|
| 101 |
+
-0,
|
| 102 |
+
-0.0,
|
| 103 |
+
False,
|
| 104 |
+
np.bool_(False),
|
| 105 |
+
np.int_(0),
|
| 106 |
+
np.float64(0),
|
| 107 |
+
np.int_(-0),
|
| 108 |
+
np.float64(-0),
|
| 109 |
+
],
|
| 110 |
+
)
|
| 111 |
+
@pytest.mark.parametrize("asarray", [True, False])
|
| 112 |
+
def test_pow_special(value, asarray):
|
| 113 |
+
if asarray:
|
| 114 |
+
value = np.array([value])
|
| 115 |
+
result = NA**value
|
| 116 |
+
|
| 117 |
+
if asarray:
|
| 118 |
+
result = result[0]
|
| 119 |
+
else:
|
| 120 |
+
# this assertion isn't possible for ndarray.
|
| 121 |
+
assert isinstance(result, type(value))
|
| 122 |
+
assert result == 1
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@pytest.mark.parametrize(
|
| 126 |
+
"value", [1, 1.0, True, np.bool_(True), np.int_(1), np.float64(1)]
|
| 127 |
+
)
|
| 128 |
+
@pytest.mark.parametrize("asarray", [True, False])
|
| 129 |
+
def test_rpow_special(value, asarray):
|
| 130 |
+
if asarray:
|
| 131 |
+
value = np.array([value])
|
| 132 |
+
result = value**NA
|
| 133 |
+
|
| 134 |
+
if asarray:
|
| 135 |
+
result = result[0]
|
| 136 |
+
elif not isinstance(value, (np.float64, np.bool_, np.int_)):
|
| 137 |
+
# this assertion isn't possible with asarray=True
|
| 138 |
+
assert isinstance(result, type(value))
|
| 139 |
+
|
| 140 |
+
assert result == value
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
@pytest.mark.parametrize("value", [-1, -1.0, np.int_(-1), np.float64(-1)])
|
| 144 |
+
@pytest.mark.parametrize("asarray", [True, False])
|
| 145 |
+
def test_rpow_minus_one(value, asarray):
|
| 146 |
+
if asarray:
|
| 147 |
+
value = np.array([value])
|
| 148 |
+
result = value**NA
|
| 149 |
+
|
| 150 |
+
if asarray:
|
| 151 |
+
result = result[0]
|
| 152 |
+
|
| 153 |
+
assert pd.isna(result)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def test_unary_ops():
|
| 157 |
+
assert +NA is NA
|
| 158 |
+
assert -NA is NA
|
| 159 |
+
assert abs(NA) is NA
|
| 160 |
+
assert ~NA is NA
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def test_logical_and():
|
| 164 |
+
assert NA & True is NA
|
| 165 |
+
assert True & NA is NA
|
| 166 |
+
assert NA & False is False
|
| 167 |
+
assert False & NA is False
|
| 168 |
+
assert NA & NA is NA
|
| 169 |
+
|
| 170 |
+
# GH#58427
|
| 171 |
+
assert NA & np.bool_(True) is NA
|
| 172 |
+
assert np.bool_(True) & NA is NA
|
| 173 |
+
assert NA & np.bool_(False) is False
|
| 174 |
+
assert np.bool_(False) & NA is False
|
| 175 |
+
|
| 176 |
+
msg = "unsupported operand type"
|
| 177 |
+
with pytest.raises(TypeError, match=msg):
|
| 178 |
+
NA & 5
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def test_logical_or():
|
| 182 |
+
assert NA | True is True
|
| 183 |
+
assert True | NA is True
|
| 184 |
+
assert NA | False is NA
|
| 185 |
+
assert False | NA is NA
|
| 186 |
+
assert NA | NA is NA
|
| 187 |
+
|
| 188 |
+
# GH#58427
|
| 189 |
+
assert NA | np.bool_(True) is True
|
| 190 |
+
assert np.bool_(True) | NA is True
|
| 191 |
+
assert NA | np.bool_(False) is NA
|
| 192 |
+
assert np.bool_(False) | NA is NA
|
| 193 |
+
|
| 194 |
+
msg = "unsupported operand type"
|
| 195 |
+
with pytest.raises(TypeError, match=msg):
|
| 196 |
+
NA | 5
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def test_logical_xor():
|
| 200 |
+
assert NA ^ True is NA
|
| 201 |
+
assert True ^ NA is NA
|
| 202 |
+
assert NA ^ False is NA
|
| 203 |
+
assert False ^ NA is NA
|
| 204 |
+
assert NA ^ NA is NA
|
| 205 |
+
|
| 206 |
+
# GH#58427
|
| 207 |
+
assert NA ^ np.bool_(True) is NA
|
| 208 |
+
assert np.bool_(True) ^ NA is NA
|
| 209 |
+
assert NA ^ np.bool_(False) is NA
|
| 210 |
+
assert np.bool_(False) ^ NA is NA
|
| 211 |
+
|
| 212 |
+
msg = "unsupported operand type"
|
| 213 |
+
with pytest.raises(TypeError, match=msg):
|
| 214 |
+
NA ^ 5
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def test_logical_not():
|
| 218 |
+
assert ~NA is NA
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
@pytest.mark.parametrize("shape", [(3,), (3, 3), (1, 2, 3)])
|
| 222 |
+
def test_arithmetic_ndarray(shape, all_arithmetic_functions):
|
| 223 |
+
op = all_arithmetic_functions
|
| 224 |
+
a = np.zeros(shape)
|
| 225 |
+
if op.__name__ == "pow":
|
| 226 |
+
a += 5
|
| 227 |
+
result = op(NA, a)
|
| 228 |
+
expected = np.full(a.shape, NA, dtype=object)
|
| 229 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def test_is_scalar():
|
| 233 |
+
assert is_scalar(NA) is True
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def test_isna():
|
| 237 |
+
assert pd.isna(NA) is True
|
| 238 |
+
assert pd.notna(NA) is False
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def test_series_isna():
|
| 242 |
+
s = pd.Series([1, NA], dtype=object)
|
| 243 |
+
expected = pd.Series([False, True])
|
| 244 |
+
tm.assert_series_equal(s.isna(), expected)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def test_ufunc():
|
| 248 |
+
assert np.log(NA) is NA
|
| 249 |
+
assert np.add(NA, 1) is NA
|
| 250 |
+
result = np.divmod(NA, 1)
|
| 251 |
+
assert result[0] is NA and result[1] is NA
|
| 252 |
+
|
| 253 |
+
result = np.frexp(NA)
|
| 254 |
+
assert result[0] is NA and result[1] is NA
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def test_ufunc_raises():
|
| 258 |
+
msg = "ufunc method 'at'"
|
| 259 |
+
with pytest.raises(ValueError, match=msg):
|
| 260 |
+
np.log.at(NA, 0)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def test_binary_input_not_dunder():
|
| 264 |
+
a = np.array([1, 2, 3])
|
| 265 |
+
expected = np.array([NA, NA, NA], dtype=object)
|
| 266 |
+
result = np.logaddexp(a, NA)
|
| 267 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 268 |
+
|
| 269 |
+
result = np.logaddexp(NA, a)
|
| 270 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 271 |
+
|
| 272 |
+
# all NA, multiple inputs
|
| 273 |
+
assert np.logaddexp(NA, NA) is NA
|
| 274 |
+
|
| 275 |
+
result = np.modf(NA, NA)
|
| 276 |
+
assert len(result) == 2
|
| 277 |
+
assert all(x is NA for x in result)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def test_divmod_ufunc():
|
| 281 |
+
# binary in, binary out.
|
| 282 |
+
a = np.array([1, 2, 3])
|
| 283 |
+
expected = np.array([NA, NA, NA], dtype=object)
|
| 284 |
+
|
| 285 |
+
result = np.divmod(a, NA)
|
| 286 |
+
assert isinstance(result, tuple)
|
| 287 |
+
for arr in result:
|
| 288 |
+
tm.assert_numpy_array_equal(arr, expected)
|
| 289 |
+
tm.assert_numpy_array_equal(arr, expected)
|
| 290 |
+
|
| 291 |
+
result = np.divmod(NA, a)
|
| 292 |
+
for arr in result:
|
| 293 |
+
tm.assert_numpy_array_equal(arr, expected)
|
| 294 |
+
tm.assert_numpy_array_equal(arr, expected)
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def test_integer_hash_collision_dict():
|
| 298 |
+
# GH 30013
|
| 299 |
+
result = {NA: "foo", hash(NA): "bar"}
|
| 300 |
+
|
| 301 |
+
assert result[NA] == "foo"
|
| 302 |
+
assert result[hash(NA)] == "bar"
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def test_integer_hash_collision_set():
|
| 306 |
+
# GH 30013
|
| 307 |
+
result = {NA, hash(NA)}
|
| 308 |
+
|
| 309 |
+
assert len(result) == 2
|
| 310 |
+
assert NA in result
|
| 311 |
+
assert hash(NA) in result
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def test_pickle_roundtrip():
|
| 315 |
+
# https://github.com/pandas-dev/pandas/issues/31847
|
| 316 |
+
result = pickle.loads(pickle.dumps(NA))
|
| 317 |
+
assert result is NA
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def test_pickle_roundtrip_pandas(temp_file):
|
| 321 |
+
result = tm.round_trip_pickle(NA, temp_file)
|
| 322 |
+
assert result is NA
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
@pytest.mark.parametrize(
|
| 326 |
+
"values, dtype", [([1, 2, NA], "Int64"), (["A", "B", NA], "string")]
|
| 327 |
+
)
|
| 328 |
+
@pytest.mark.parametrize("as_frame", [True, False])
|
| 329 |
+
def test_pickle_roundtrip_containers(as_frame, values, dtype, temp_file):
|
| 330 |
+
s = pd.Series(pd.array(values, dtype=dtype))
|
| 331 |
+
if as_frame:
|
| 332 |
+
s = s.to_frame(name="A")
|
| 333 |
+
result = tm.round_trip_pickle(s, temp_file)
|
| 334 |
+
tm.assert_equal(result, s)
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_nat.py
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import (
|
| 2 |
+
datetime,
|
| 3 |
+
timedelta,
|
| 4 |
+
)
|
| 5 |
+
import operator
|
| 6 |
+
import zoneinfo
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
from pandas._libs.tslibs import iNaT
|
| 12 |
+
|
| 13 |
+
from pandas import (
|
| 14 |
+
DatetimeIndex,
|
| 15 |
+
DatetimeTZDtype,
|
| 16 |
+
Index,
|
| 17 |
+
NaT,
|
| 18 |
+
Period,
|
| 19 |
+
Series,
|
| 20 |
+
Timedelta,
|
| 21 |
+
TimedeltaIndex,
|
| 22 |
+
Timestamp,
|
| 23 |
+
isna,
|
| 24 |
+
offsets,
|
| 25 |
+
)
|
| 26 |
+
import pandas._testing as tm
|
| 27 |
+
from pandas.core import roperator
|
| 28 |
+
from pandas.core.arrays import (
|
| 29 |
+
DatetimeArray,
|
| 30 |
+
PeriodArray,
|
| 31 |
+
TimedeltaArray,
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class TestNaTFormatting:
|
| 36 |
+
def test_repr(self):
|
| 37 |
+
assert repr(NaT) == "NaT"
|
| 38 |
+
|
| 39 |
+
def test_str(self):
|
| 40 |
+
assert str(NaT) == "NaT"
|
| 41 |
+
|
| 42 |
+
def test_isoformat(self):
|
| 43 |
+
assert NaT.isoformat() == "NaT"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@pytest.mark.parametrize(
|
| 47 |
+
"nat,idx",
|
| 48 |
+
[
|
| 49 |
+
(Timestamp("NaT"), DatetimeArray),
|
| 50 |
+
(Timedelta("NaT"), TimedeltaArray),
|
| 51 |
+
(Period("NaT", freq="M"), PeriodArray),
|
| 52 |
+
],
|
| 53 |
+
)
|
| 54 |
+
def test_nat_fields(nat, idx):
|
| 55 |
+
for field in idx._field_ops:
|
| 56 |
+
# weekday is a property of DTI, but a method
|
| 57 |
+
# on NaT/Timestamp for compat with datetime
|
| 58 |
+
if field == "weekday":
|
| 59 |
+
continue
|
| 60 |
+
|
| 61 |
+
result = getattr(NaT, field)
|
| 62 |
+
assert np.isnan(result)
|
| 63 |
+
|
| 64 |
+
result = getattr(nat, field)
|
| 65 |
+
assert np.isnan(result)
|
| 66 |
+
|
| 67 |
+
for field in idx._bool_ops:
|
| 68 |
+
result = getattr(NaT, field)
|
| 69 |
+
assert result is False
|
| 70 |
+
|
| 71 |
+
result = getattr(nat, field)
|
| 72 |
+
assert result is False
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def test_nat_vector_field_access():
|
| 76 |
+
idx = DatetimeIndex(["1/1/2000", None, None, "1/4/2000"])
|
| 77 |
+
|
| 78 |
+
for field in DatetimeArray._field_ops:
|
| 79 |
+
# weekday is a property of DTI, but a method
|
| 80 |
+
# on NaT/Timestamp for compat with datetime
|
| 81 |
+
if field == "weekday":
|
| 82 |
+
continue
|
| 83 |
+
|
| 84 |
+
result = getattr(idx, field)
|
| 85 |
+
expected = Index([getattr(x, field) for x in idx])
|
| 86 |
+
tm.assert_index_equal(result, expected)
|
| 87 |
+
|
| 88 |
+
ser = Series(idx)
|
| 89 |
+
|
| 90 |
+
for field in DatetimeArray._field_ops:
|
| 91 |
+
# weekday is a property of DTI, but a method
|
| 92 |
+
# on NaT/Timestamp for compat with datetime
|
| 93 |
+
if field == "weekday":
|
| 94 |
+
continue
|
| 95 |
+
|
| 96 |
+
result = getattr(ser.dt, field)
|
| 97 |
+
expected = [getattr(x, field) for x in idx]
|
| 98 |
+
tm.assert_series_equal(result, Series(expected))
|
| 99 |
+
|
| 100 |
+
for field in DatetimeArray._bool_ops:
|
| 101 |
+
result = getattr(ser.dt, field)
|
| 102 |
+
expected = [getattr(x, field) for x in idx]
|
| 103 |
+
tm.assert_series_equal(result, Series(expected))
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@pytest.mark.parametrize("klass", [Timestamp, Timedelta, Period])
|
| 107 |
+
@pytest.mark.parametrize(
|
| 108 |
+
"value", [None, np.nan, iNaT, float("nan"), NaT, "NaT", "nat", "", "NAT"]
|
| 109 |
+
)
|
| 110 |
+
def test_identity(klass, value):
|
| 111 |
+
assert klass(value) is NaT
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@pytest.mark.parametrize("klass", [Timestamp, Timedelta])
|
| 115 |
+
@pytest.mark.parametrize("method", ["round", "floor", "ceil"])
|
| 116 |
+
@pytest.mark.parametrize("freq", ["s", "5s", "min", "5min", "h", "5h"])
|
| 117 |
+
def test_round_nat(klass, method, freq):
|
| 118 |
+
# see gh-14940
|
| 119 |
+
ts = klass("nat")
|
| 120 |
+
|
| 121 |
+
round_method = getattr(ts, method)
|
| 122 |
+
assert round_method(freq) is ts
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@pytest.mark.parametrize(
|
| 126 |
+
"method",
|
| 127 |
+
[
|
| 128 |
+
"astimezone",
|
| 129 |
+
"combine",
|
| 130 |
+
"ctime",
|
| 131 |
+
"dst",
|
| 132 |
+
"fromordinal",
|
| 133 |
+
"fromtimestamp",
|
| 134 |
+
"fromisocalendar",
|
| 135 |
+
"isocalendar",
|
| 136 |
+
"strftime",
|
| 137 |
+
"strptime",
|
| 138 |
+
"time",
|
| 139 |
+
"timestamp",
|
| 140 |
+
"timetuple",
|
| 141 |
+
"timetz",
|
| 142 |
+
"toordinal",
|
| 143 |
+
"tzname",
|
| 144 |
+
"utcfromtimestamp",
|
| 145 |
+
"utcnow",
|
| 146 |
+
"utcoffset",
|
| 147 |
+
"utctimetuple",
|
| 148 |
+
],
|
| 149 |
+
)
|
| 150 |
+
def test_nat_methods_raise(method):
|
| 151 |
+
# see gh-9513, gh-17329
|
| 152 |
+
msg = f"NaTType does not support {method}"
|
| 153 |
+
|
| 154 |
+
with pytest.raises(ValueError, match=msg):
|
| 155 |
+
getattr(NaT, method)()
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
@pytest.mark.parametrize("method", ["weekday", "isoweekday"])
|
| 159 |
+
def test_nat_methods_nan(method):
|
| 160 |
+
# see gh-9513, gh-17329
|
| 161 |
+
assert np.isnan(getattr(NaT, method)())
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@pytest.mark.parametrize(
|
| 165 |
+
"method", ["date", "now", "replace", "today", "tz_convert", "tz_localize"]
|
| 166 |
+
)
|
| 167 |
+
def test_nat_methods_nat(method):
|
| 168 |
+
# see gh-8254, gh-9513, gh-17329
|
| 169 |
+
assert getattr(NaT, method)() is NaT
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
@pytest.mark.parametrize(
|
| 173 |
+
"get_nat", [lambda x: NaT, lambda x: Timedelta(x), lambda x: Timestamp(x)]
|
| 174 |
+
)
|
| 175 |
+
def test_nat_iso_format(get_nat):
|
| 176 |
+
# see gh-12300
|
| 177 |
+
assert get_nat("NaT").isoformat() == "NaT"
|
| 178 |
+
assert get_nat("NaT").isoformat(timespec="nanoseconds") == "NaT"
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
@pytest.mark.parametrize(
|
| 182 |
+
"klass,expected",
|
| 183 |
+
[
|
| 184 |
+
(Timestamp, ["normalize", "to_julian_date", "to_period", "unit"]),
|
| 185 |
+
(
|
| 186 |
+
Timedelta,
|
| 187 |
+
[
|
| 188 |
+
"components",
|
| 189 |
+
"resolution_string",
|
| 190 |
+
"to_pytimedelta",
|
| 191 |
+
"to_timedelta64",
|
| 192 |
+
"unit",
|
| 193 |
+
"view",
|
| 194 |
+
],
|
| 195 |
+
),
|
| 196 |
+
],
|
| 197 |
+
)
|
| 198 |
+
def test_missing_public_nat_methods(klass, expected):
|
| 199 |
+
# see gh-17327
|
| 200 |
+
#
|
| 201 |
+
# NaT should have *most* of the Timestamp and Timedelta methods.
|
| 202 |
+
# Here, we check which public methods NaT does not have. We
|
| 203 |
+
# ignore any missing private methods.
|
| 204 |
+
nat_names = dir(NaT)
|
| 205 |
+
klass_names = dir(klass)
|
| 206 |
+
|
| 207 |
+
missing = [x for x in klass_names if x not in nat_names and not x.startswith("_")]
|
| 208 |
+
missing.sort()
|
| 209 |
+
|
| 210 |
+
assert missing == expected
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def _get_overlap_public_nat_methods(klass, as_tuple=False):
|
| 214 |
+
"""
|
| 215 |
+
Get overlapping public methods between NaT and another class.
|
| 216 |
+
|
| 217 |
+
Parameters
|
| 218 |
+
----------
|
| 219 |
+
klass : type
|
| 220 |
+
The class to compare with NaT
|
| 221 |
+
as_tuple : bool, default False
|
| 222 |
+
Whether to return a list of tuples of the form (klass, method).
|
| 223 |
+
|
| 224 |
+
Returns
|
| 225 |
+
-------
|
| 226 |
+
overlap : list
|
| 227 |
+
"""
|
| 228 |
+
nat_names = dir(NaT)
|
| 229 |
+
klass_names = dir(klass)
|
| 230 |
+
|
| 231 |
+
overlap = [
|
| 232 |
+
x
|
| 233 |
+
for x in nat_names
|
| 234 |
+
if x in klass_names and not x.startswith("_") and callable(getattr(klass, x))
|
| 235 |
+
]
|
| 236 |
+
|
| 237 |
+
# Timestamp takes precedence over Timedelta in terms of overlap.
|
| 238 |
+
if klass is Timedelta:
|
| 239 |
+
ts_names = dir(Timestamp)
|
| 240 |
+
overlap = [x for x in overlap if x not in ts_names]
|
| 241 |
+
|
| 242 |
+
if as_tuple:
|
| 243 |
+
overlap = [(klass, method) for method in overlap]
|
| 244 |
+
|
| 245 |
+
overlap.sort()
|
| 246 |
+
return overlap
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
@pytest.mark.parametrize(
|
| 250 |
+
"klass,expected",
|
| 251 |
+
[
|
| 252 |
+
(
|
| 253 |
+
Timestamp,
|
| 254 |
+
[
|
| 255 |
+
"as_unit",
|
| 256 |
+
"astimezone",
|
| 257 |
+
"ceil",
|
| 258 |
+
"combine",
|
| 259 |
+
"ctime",
|
| 260 |
+
"date",
|
| 261 |
+
"day_name",
|
| 262 |
+
"dst",
|
| 263 |
+
"floor",
|
| 264 |
+
"fromisocalendar",
|
| 265 |
+
"fromisoformat",
|
| 266 |
+
"fromordinal",
|
| 267 |
+
"fromtimestamp",
|
| 268 |
+
"isocalendar",
|
| 269 |
+
"isoformat",
|
| 270 |
+
"isoweekday",
|
| 271 |
+
"month_name",
|
| 272 |
+
"now",
|
| 273 |
+
"replace",
|
| 274 |
+
"round",
|
| 275 |
+
"strftime",
|
| 276 |
+
"strptime",
|
| 277 |
+
"time",
|
| 278 |
+
"timestamp",
|
| 279 |
+
"timetuple",
|
| 280 |
+
"timetz",
|
| 281 |
+
"to_datetime64",
|
| 282 |
+
"to_numpy",
|
| 283 |
+
"to_pydatetime",
|
| 284 |
+
"today",
|
| 285 |
+
"toordinal",
|
| 286 |
+
"tz_convert",
|
| 287 |
+
"tz_localize",
|
| 288 |
+
"tzname",
|
| 289 |
+
"utcfromtimestamp",
|
| 290 |
+
"utcnow",
|
| 291 |
+
"utcoffset",
|
| 292 |
+
"utctimetuple",
|
| 293 |
+
"weekday",
|
| 294 |
+
],
|
| 295 |
+
),
|
| 296 |
+
(Timedelta, ["total_seconds"]),
|
| 297 |
+
],
|
| 298 |
+
)
|
| 299 |
+
def test_overlap_public_nat_methods(klass, expected):
|
| 300 |
+
# see gh-17327
|
| 301 |
+
#
|
| 302 |
+
# NaT should have *most* of the Timestamp and Timedelta methods.
|
| 303 |
+
# In case when Timestamp, Timedelta, and NaT are overlap, the overlap
|
| 304 |
+
# is considered to be with Timestamp and NaT, not Timedelta.
|
| 305 |
+
assert _get_overlap_public_nat_methods(klass) == expected
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
@pytest.mark.parametrize(
|
| 309 |
+
"compare",
|
| 310 |
+
(
|
| 311 |
+
_get_overlap_public_nat_methods(Timestamp, True)
|
| 312 |
+
+ _get_overlap_public_nat_methods(Timedelta, True)
|
| 313 |
+
),
|
| 314 |
+
ids=lambda x: f"{x[0].__name__}.{x[1]}",
|
| 315 |
+
)
|
| 316 |
+
def test_nat_doc_strings(compare):
|
| 317 |
+
# see gh-17327
|
| 318 |
+
#
|
| 319 |
+
# The docstrings for overlapping methods should match.
|
| 320 |
+
klass, method = compare
|
| 321 |
+
klass_doc = getattr(klass, method).__doc__
|
| 322 |
+
|
| 323 |
+
if klass == Timestamp and method == "isoformat":
|
| 324 |
+
pytest.skip(
|
| 325 |
+
"Ignore differences with Timestamp.isoformat() as they're intentional"
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
if method == "to_numpy":
|
| 329 |
+
# GH#44460 can return either dt64 or td64 depending on dtype,
|
| 330 |
+
# different docstring is intentional
|
| 331 |
+
pytest.skip(f"different docstring for {method} is intentional")
|
| 332 |
+
|
| 333 |
+
nat_doc = getattr(NaT, method).__doc__
|
| 334 |
+
assert klass_doc == nat_doc
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
_ops = {
|
| 338 |
+
"left_plus_right": lambda a, b: a + b,
|
| 339 |
+
"right_plus_left": lambda a, b: b + a,
|
| 340 |
+
"left_minus_right": lambda a, b: a - b,
|
| 341 |
+
"right_minus_left": lambda a, b: b - a,
|
| 342 |
+
"left_times_right": lambda a, b: a * b,
|
| 343 |
+
"right_times_left": lambda a, b: b * a,
|
| 344 |
+
"left_div_right": lambda a, b: a / b,
|
| 345 |
+
"right_div_left": lambda a, b: b / a,
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
@pytest.mark.parametrize("op_name", list(_ops.keys()))
|
| 350 |
+
@pytest.mark.parametrize(
|
| 351 |
+
"value,val_type",
|
| 352 |
+
[
|
| 353 |
+
(2, "scalar"),
|
| 354 |
+
(1.5, "floating"),
|
| 355 |
+
(np.nan, "floating"),
|
| 356 |
+
("foo", "str"),
|
| 357 |
+
(timedelta(3600), "timedelta"),
|
| 358 |
+
(Timedelta("5s"), "timedelta"),
|
| 359 |
+
(datetime(2014, 1, 1), "timestamp"),
|
| 360 |
+
(Timestamp("2014-01-01"), "timestamp"),
|
| 361 |
+
(Timestamp("2014-01-01", tz="UTC"), "timestamp"),
|
| 362 |
+
(Timestamp("2014-01-01", tz="US/Eastern"), "timestamp"),
|
| 363 |
+
(datetime(2014, 1, 1).astimezone(zoneinfo.ZoneInfo("Asia/Tokyo")), "timestamp"),
|
| 364 |
+
],
|
| 365 |
+
)
|
| 366 |
+
def test_nat_arithmetic_scalar(op_name, value, val_type):
|
| 367 |
+
# see gh-6873
|
| 368 |
+
invalid_ops = {
|
| 369 |
+
"scalar": {"right_div_left"},
|
| 370 |
+
"floating": {
|
| 371 |
+
"right_div_left",
|
| 372 |
+
"left_minus_right",
|
| 373 |
+
"right_minus_left",
|
| 374 |
+
"left_plus_right",
|
| 375 |
+
"right_plus_left",
|
| 376 |
+
},
|
| 377 |
+
"str": set(_ops.keys()),
|
| 378 |
+
"timedelta": {"left_times_right", "right_times_left"},
|
| 379 |
+
"timestamp": {
|
| 380 |
+
"left_times_right",
|
| 381 |
+
"right_times_left",
|
| 382 |
+
"left_div_right",
|
| 383 |
+
"right_div_left",
|
| 384 |
+
},
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
op = _ops[op_name]
|
| 388 |
+
|
| 389 |
+
if op_name in invalid_ops.get(val_type, set()):
|
| 390 |
+
if (
|
| 391 |
+
val_type == "timedelta"
|
| 392 |
+
and "times" in op_name
|
| 393 |
+
and isinstance(value, Timedelta)
|
| 394 |
+
):
|
| 395 |
+
typs = "(Timedelta|NaTType)"
|
| 396 |
+
msg = rf"unsupported operand type\(s\) for \*: '{typs}' and '{typs}'"
|
| 397 |
+
elif val_type == "str":
|
| 398 |
+
# un-specific check here because the message comes from str
|
| 399 |
+
# and varies by method
|
| 400 |
+
msg = "|".join(
|
| 401 |
+
[
|
| 402 |
+
"can only concatenate str",
|
| 403 |
+
"unsupported operand type",
|
| 404 |
+
"can't multiply sequence",
|
| 405 |
+
"Can't convert 'NaTType'",
|
| 406 |
+
"must be str, not NaTType",
|
| 407 |
+
]
|
| 408 |
+
)
|
| 409 |
+
else:
|
| 410 |
+
msg = "unsupported operand type"
|
| 411 |
+
|
| 412 |
+
with pytest.raises(TypeError, match=msg):
|
| 413 |
+
op(NaT, value)
|
| 414 |
+
else:
|
| 415 |
+
if val_type == "timedelta" and "div" in op_name:
|
| 416 |
+
expected = np.nan
|
| 417 |
+
else:
|
| 418 |
+
expected = NaT
|
| 419 |
+
|
| 420 |
+
assert op(NaT, value) is expected
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
@pytest.mark.parametrize(
|
| 424 |
+
"val,expected",
|
| 425 |
+
[(np.nan, NaT), (NaT, np.nan), (np.timedelta64("NaT", "ns"), np.nan)],
|
| 426 |
+
)
|
| 427 |
+
def test_nat_rfloordiv_timedelta(val, expected):
|
| 428 |
+
# see gh-#18846
|
| 429 |
+
#
|
| 430 |
+
# See also test_timedelta.TestTimedeltaArithmetic.test_floordiv
|
| 431 |
+
td = Timedelta(hours=3, minutes=4)
|
| 432 |
+
assert td // val is expected
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
@pytest.mark.parametrize(
|
| 436 |
+
"op_name",
|
| 437 |
+
["left_plus_right", "right_plus_left", "left_minus_right", "right_minus_left"],
|
| 438 |
+
)
|
| 439 |
+
@pytest.mark.parametrize(
|
| 440 |
+
"value",
|
| 441 |
+
[
|
| 442 |
+
DatetimeIndex(["2011-01-01", "2011-01-02"], dtype="M8[ns]", name="x"),
|
| 443 |
+
DatetimeIndex(
|
| 444 |
+
["2011-01-01", "2011-01-02"], dtype="M8[ns, US/Eastern]", name="x"
|
| 445 |
+
),
|
| 446 |
+
DatetimeArray._from_sequence(["2011-01-01", "2011-01-02"], dtype="M8[ns]"),
|
| 447 |
+
DatetimeArray._from_sequence(
|
| 448 |
+
["2011-01-01", "2011-01-02"], dtype=DatetimeTZDtype(tz="US/Pacific")
|
| 449 |
+
),
|
| 450 |
+
TimedeltaIndex(["1 day", "2 day"], name="x"),
|
| 451 |
+
],
|
| 452 |
+
)
|
| 453 |
+
def test_nat_arithmetic_index(op_name, value):
|
| 454 |
+
# see gh-11718
|
| 455 |
+
exp_name = "x"
|
| 456 |
+
exp_data = [NaT] * 2
|
| 457 |
+
|
| 458 |
+
if value.dtype.kind == "M" and "plus" in op_name:
|
| 459 |
+
expected = DatetimeIndex(exp_data, tz=value.tz, name=exp_name)
|
| 460 |
+
else:
|
| 461 |
+
expected = TimedeltaIndex(exp_data, name=exp_name)
|
| 462 |
+
expected = expected.as_unit(value.unit)
|
| 463 |
+
|
| 464 |
+
if not isinstance(value, Index):
|
| 465 |
+
expected = expected.array
|
| 466 |
+
|
| 467 |
+
op = _ops[op_name]
|
| 468 |
+
result = op(NaT, value)
|
| 469 |
+
tm.assert_equal(result, expected)
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
@pytest.mark.parametrize(
|
| 473 |
+
"op_name",
|
| 474 |
+
["left_plus_right", "right_plus_left", "left_minus_right", "right_minus_left"],
|
| 475 |
+
)
|
| 476 |
+
@pytest.mark.parametrize("box", [TimedeltaIndex, Series, TimedeltaArray._from_sequence])
|
| 477 |
+
def test_nat_arithmetic_td64_vector(op_name, box):
|
| 478 |
+
# see gh-19124
|
| 479 |
+
vec = box(["1 day", "2 day"], dtype="timedelta64[ns]")
|
| 480 |
+
box_nat = box([NaT, NaT], dtype="timedelta64[ns]")
|
| 481 |
+
tm.assert_equal(_ops[op_name](vec, NaT), box_nat)
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
@pytest.mark.parametrize(
|
| 485 |
+
"dtype,op,out_dtype",
|
| 486 |
+
[
|
| 487 |
+
("datetime64[ns]", operator.add, "datetime64[ns]"),
|
| 488 |
+
("datetime64[ns]", roperator.radd, "datetime64[ns]"),
|
| 489 |
+
("datetime64[ns]", operator.sub, "timedelta64[ns]"),
|
| 490 |
+
("datetime64[ns]", roperator.rsub, "timedelta64[ns]"),
|
| 491 |
+
("timedelta64[ns]", operator.add, "datetime64[ns]"),
|
| 492 |
+
("timedelta64[ns]", roperator.radd, "datetime64[ns]"),
|
| 493 |
+
("timedelta64[ns]", operator.sub, "datetime64[ns]"),
|
| 494 |
+
("timedelta64[ns]", roperator.rsub, "timedelta64[ns]"),
|
| 495 |
+
],
|
| 496 |
+
)
|
| 497 |
+
def test_nat_arithmetic_ndarray(dtype, op, out_dtype):
|
| 498 |
+
other = np.arange(10).astype(dtype)
|
| 499 |
+
result = op(NaT, other)
|
| 500 |
+
|
| 501 |
+
expected = np.empty(other.shape, dtype=out_dtype)
|
| 502 |
+
expected.fill("NaT")
|
| 503 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
def test_nat_pinned_docstrings():
|
| 507 |
+
# see gh-17327
|
| 508 |
+
assert NaT.ctime.__doc__ == Timestamp.ctime.__doc__
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def test_to_numpy_alias():
|
| 512 |
+
# GH 24653: alias .to_numpy() for scalars
|
| 513 |
+
expected = NaT.to_datetime64()
|
| 514 |
+
result = NaT.to_numpy()
|
| 515 |
+
|
| 516 |
+
assert isna(expected) and isna(result)
|
| 517 |
+
|
| 518 |
+
# GH#44460
|
| 519 |
+
result = NaT.to_numpy("M8[s]")
|
| 520 |
+
assert isinstance(result, np.datetime64)
|
| 521 |
+
assert result.dtype == "M8[s]"
|
| 522 |
+
|
| 523 |
+
result = NaT.to_numpy("m8[ns]")
|
| 524 |
+
assert isinstance(result, np.timedelta64)
|
| 525 |
+
assert result.dtype == "m8[ns]"
|
| 526 |
+
|
| 527 |
+
result = NaT.to_numpy("m8[s]")
|
| 528 |
+
assert isinstance(result, np.timedelta64)
|
| 529 |
+
assert result.dtype == "m8[s]"
|
| 530 |
+
|
| 531 |
+
with pytest.raises(ValueError, match="NaT.to_numpy dtype must be a "):
|
| 532 |
+
NaT.to_numpy(np.int64)
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
@pytest.mark.parametrize(
|
| 536 |
+
"other",
|
| 537 |
+
[
|
| 538 |
+
Timedelta(0),
|
| 539 |
+
Timedelta(0).to_pytimedelta(),
|
| 540 |
+
Timedelta(0).to_timedelta64(),
|
| 541 |
+
Timestamp(0),
|
| 542 |
+
Timestamp(0).to_pydatetime(),
|
| 543 |
+
Timestamp(0).to_datetime64(),
|
| 544 |
+
Timestamp(0).tz_localize("UTC"),
|
| 545 |
+
NaT,
|
| 546 |
+
],
|
| 547 |
+
)
|
| 548 |
+
def test_nat_comparisons(compare_operators_no_eq_ne, other):
|
| 549 |
+
# GH 26039
|
| 550 |
+
opname = compare_operators_no_eq_ne
|
| 551 |
+
|
| 552 |
+
assert getattr(NaT, opname)(other) is False
|
| 553 |
+
|
| 554 |
+
op = getattr(operator, opname.strip("_"))
|
| 555 |
+
assert op(NaT, other) is False
|
| 556 |
+
assert op(other, NaT) is False
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
@pytest.mark.parametrize("other_and_type", [("foo", "str"), (2, "int"), (2.0, "float")])
|
| 560 |
+
@pytest.mark.parametrize(
|
| 561 |
+
"symbol_and_op",
|
| 562 |
+
[("<=", operator.le), ("<", operator.lt), (">=", operator.ge), (">", operator.gt)],
|
| 563 |
+
)
|
| 564 |
+
def test_nat_comparisons_invalid(other_and_type, symbol_and_op):
|
| 565 |
+
# GH#35585
|
| 566 |
+
other, other_type = other_and_type
|
| 567 |
+
symbol, op = symbol_and_op
|
| 568 |
+
|
| 569 |
+
assert not NaT == other
|
| 570 |
+
assert not other == NaT
|
| 571 |
+
|
| 572 |
+
assert NaT != other
|
| 573 |
+
assert other != NaT
|
| 574 |
+
|
| 575 |
+
msg = f"'{symbol}' not supported between instances of 'NaTType' and '{other_type}'"
|
| 576 |
+
with pytest.raises(TypeError, match=msg):
|
| 577 |
+
op(NaT, other)
|
| 578 |
+
|
| 579 |
+
msg = f"'{symbol}' not supported between instances of '{other_type}' and 'NaTType'"
|
| 580 |
+
with pytest.raises(TypeError, match=msg):
|
| 581 |
+
op(other, NaT)
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
@pytest.mark.parametrize(
|
| 585 |
+
"other",
|
| 586 |
+
[
|
| 587 |
+
np.array(["foo"] * 2, dtype=object),
|
| 588 |
+
np.array([2, 3], dtype="int64"),
|
| 589 |
+
np.array([2.0, 3.5], dtype="float64"),
|
| 590 |
+
],
|
| 591 |
+
ids=["str", "int", "float"],
|
| 592 |
+
)
|
| 593 |
+
def test_nat_comparisons_invalid_ndarray(other):
|
| 594 |
+
# GH#40722
|
| 595 |
+
expected = np.array([False, False])
|
| 596 |
+
result = NaT == other
|
| 597 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 598 |
+
result = other == NaT
|
| 599 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 600 |
+
|
| 601 |
+
expected = np.array([True, True])
|
| 602 |
+
result = NaT != other
|
| 603 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 604 |
+
result = other != NaT
|
| 605 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 606 |
+
|
| 607 |
+
for symbol, op in [
|
| 608 |
+
("<=", operator.le),
|
| 609 |
+
("<", operator.lt),
|
| 610 |
+
(">=", operator.ge),
|
| 611 |
+
(">", operator.gt),
|
| 612 |
+
]:
|
| 613 |
+
msg = f"'{symbol}' not supported between"
|
| 614 |
+
|
| 615 |
+
with pytest.raises(TypeError, match=msg):
|
| 616 |
+
op(NaT, other)
|
| 617 |
+
|
| 618 |
+
if other.dtype == np.dtype("object"):
|
| 619 |
+
# uses the reverse operator, so symbol changes
|
| 620 |
+
msg = None
|
| 621 |
+
with pytest.raises(TypeError, match=msg):
|
| 622 |
+
op(other, NaT)
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
def test_compare_date(fixed_now_ts):
|
| 626 |
+
# GH#39151 comparing NaT with date object is deprecated
|
| 627 |
+
# See also: tests.scalar.timestamps.test_comparisons::test_compare_date
|
| 628 |
+
|
| 629 |
+
dt = fixed_now_ts.to_pydatetime().date()
|
| 630 |
+
|
| 631 |
+
msg = "Cannot compare NaT with datetime.date object"
|
| 632 |
+
for left, right in [(NaT, dt), (dt, NaT)]:
|
| 633 |
+
assert not left == right
|
| 634 |
+
assert left != right
|
| 635 |
+
|
| 636 |
+
with pytest.raises(TypeError, match=msg):
|
| 637 |
+
left < right
|
| 638 |
+
with pytest.raises(TypeError, match=msg):
|
| 639 |
+
left <= right
|
| 640 |
+
with pytest.raises(TypeError, match=msg):
|
| 641 |
+
left > right
|
| 642 |
+
with pytest.raises(TypeError, match=msg):
|
| 643 |
+
left >= right
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
@pytest.mark.parametrize(
|
| 647 |
+
"obj",
|
| 648 |
+
[
|
| 649 |
+
offsets.YearEnd(2),
|
| 650 |
+
offsets.YearBegin(2),
|
| 651 |
+
offsets.MonthBegin(1),
|
| 652 |
+
offsets.MonthEnd(2),
|
| 653 |
+
offsets.MonthEnd(12),
|
| 654 |
+
offsets.Day(2),
|
| 655 |
+
offsets.Day(5),
|
| 656 |
+
offsets.Hour(24),
|
| 657 |
+
offsets.Hour(3),
|
| 658 |
+
offsets.Minute(),
|
| 659 |
+
np.timedelta64(3, "h"),
|
| 660 |
+
np.timedelta64(4, "h"),
|
| 661 |
+
np.timedelta64(3200, "s"),
|
| 662 |
+
np.timedelta64(3600, "s"),
|
| 663 |
+
np.timedelta64(3600 * 24, "s"),
|
| 664 |
+
np.timedelta64(2, "D"),
|
| 665 |
+
np.timedelta64(365, "D"),
|
| 666 |
+
timedelta(-2),
|
| 667 |
+
timedelta(365),
|
| 668 |
+
timedelta(minutes=120),
|
| 669 |
+
timedelta(days=4, minutes=180),
|
| 670 |
+
timedelta(hours=23),
|
| 671 |
+
timedelta(hours=23, minutes=30),
|
| 672 |
+
timedelta(hours=48),
|
| 673 |
+
],
|
| 674 |
+
)
|
| 675 |
+
def test_nat_addsub_tdlike_scalar(obj):
|
| 676 |
+
assert NaT + obj is NaT
|
| 677 |
+
assert obj + NaT is NaT
|
| 678 |
+
assert NaT - obj is NaT
|
| 679 |
+
|
| 680 |
+
|
| 681 |
+
def test_pickle(temp_file):
|
| 682 |
+
# GH#4606
|
| 683 |
+
p = tm.round_trip_pickle(NaT, temp_file)
|
| 684 |
+
assert p is NaT
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_as_unit.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 4 |
+
from pandas.errors import OutOfBoundsTimedelta
|
| 5 |
+
|
| 6 |
+
from pandas import Timedelta
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestAsUnit:
|
| 10 |
+
def test_as_unit(self):
|
| 11 |
+
td = Timedelta(days=1)
|
| 12 |
+
|
| 13 |
+
assert td.as_unit("us") is td
|
| 14 |
+
|
| 15 |
+
res = td.as_unit("ns")
|
| 16 |
+
assert res._value == td._value * 1000
|
| 17 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 18 |
+
|
| 19 |
+
rt = res.as_unit("us")
|
| 20 |
+
assert rt._value == td._value
|
| 21 |
+
assert rt._creso == td._creso
|
| 22 |
+
|
| 23 |
+
res = td.as_unit("ms")
|
| 24 |
+
assert res._value == td._value // 1_000
|
| 25 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 26 |
+
|
| 27 |
+
rt = res.as_unit("us")
|
| 28 |
+
assert rt._value == td._value
|
| 29 |
+
assert rt._creso == td._creso
|
| 30 |
+
|
| 31 |
+
res = td.as_unit("s")
|
| 32 |
+
assert res._value == td._value // 1_000_000
|
| 33 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 34 |
+
|
| 35 |
+
rt = res.as_unit("us")
|
| 36 |
+
assert rt._value == td._value
|
| 37 |
+
assert rt._creso == td._creso
|
| 38 |
+
|
| 39 |
+
def test_as_unit_overflows(self):
|
| 40 |
+
# microsecond that would be just out of bounds for nano
|
| 41 |
+
us = 9223372800000000
|
| 42 |
+
td = Timedelta._from_value_and_reso(us, NpyDatetimeUnit.NPY_FR_us.value)
|
| 43 |
+
|
| 44 |
+
msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow"
|
| 45 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 46 |
+
td.as_unit("ns")
|
| 47 |
+
|
| 48 |
+
res = td.as_unit("ms")
|
| 49 |
+
assert res._value == us // 1000
|
| 50 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 51 |
+
|
| 52 |
+
def test_as_unit_rounding(self):
|
| 53 |
+
td = Timedelta(microseconds=1500)
|
| 54 |
+
res = td.as_unit("ms")
|
| 55 |
+
|
| 56 |
+
expected = Timedelta(milliseconds=1)
|
| 57 |
+
assert res == expected
|
| 58 |
+
|
| 59 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 60 |
+
assert res._value == 1
|
| 61 |
+
|
| 62 |
+
with pytest.raises(ValueError, match="Cannot losslessly convert units"):
|
| 63 |
+
td.as_unit("ms", round_ok=False)
|
| 64 |
+
|
| 65 |
+
def test_as_unit_non_nano(self):
|
| 66 |
+
# case where we are going neither to nor from nano
|
| 67 |
+
td = Timedelta(days=1).as_unit("ms")
|
| 68 |
+
assert td.days == 1
|
| 69 |
+
assert td._value == 86_400_000
|
| 70 |
+
assert td.components.days == 1
|
| 71 |
+
assert td._d == 1
|
| 72 |
+
assert td.total_seconds() == 86400
|
| 73 |
+
|
| 74 |
+
res = td.as_unit("us")
|
| 75 |
+
assert res._value == 86_400_000_000
|
| 76 |
+
assert res.components.days == 1
|
| 77 |
+
assert res.components.hours == 0
|
| 78 |
+
assert res._d == 1
|
| 79 |
+
assert res._h == 0
|
| 80 |
+
assert res.total_seconds() == 86400
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_round.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from hypothesis import (
|
| 2 |
+
given,
|
| 3 |
+
strategies as st,
|
| 4 |
+
)
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
from pandas._libs import lib
|
| 9 |
+
from pandas._libs.tslibs import iNaT
|
| 10 |
+
from pandas.errors import OutOfBoundsTimedelta
|
| 11 |
+
|
| 12 |
+
from pandas import Timedelta
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class TestTimedeltaRound:
|
| 16 |
+
@pytest.mark.parametrize(
|
| 17 |
+
"freq,s1,s2",
|
| 18 |
+
[
|
| 19 |
+
# This first case has s1, s2 being the same as t1,t2 below
|
| 20 |
+
(
|
| 21 |
+
"ns",
|
| 22 |
+
"1 days 02:34:56.789123456",
|
| 23 |
+
"-1 days 02:34:56.789123456",
|
| 24 |
+
),
|
| 25 |
+
(
|
| 26 |
+
"us",
|
| 27 |
+
"1 days 02:34:56.789123000",
|
| 28 |
+
"-1 days 02:34:56.789123000",
|
| 29 |
+
),
|
| 30 |
+
(
|
| 31 |
+
"ms",
|
| 32 |
+
"1 days 02:34:56.789000000",
|
| 33 |
+
"-1 days 02:34:56.789000000",
|
| 34 |
+
),
|
| 35 |
+
("s", "1 days 02:34:57", "-1 days 02:34:57"),
|
| 36 |
+
("2s", "1 days 02:34:56", "-1 days 02:34:56"),
|
| 37 |
+
("5s", "1 days 02:34:55", "-1 days 02:34:55"),
|
| 38 |
+
("min", "1 days 02:35:00", "-1 days 02:35:00"),
|
| 39 |
+
("12min", "1 days 02:36:00", "-1 days 02:36:00"),
|
| 40 |
+
("h", "1 days 03:00:00", "-1 days 03:00:00"),
|
| 41 |
+
("D", "1 days", "-1 days"),
|
| 42 |
+
],
|
| 43 |
+
)
|
| 44 |
+
def test_round(self, freq, s1, s2):
|
| 45 |
+
s1 = Timedelta(s1)
|
| 46 |
+
s2 = Timedelta(s2)
|
| 47 |
+
t1 = Timedelta("1 days 02:34:56.789123456")
|
| 48 |
+
t2 = Timedelta("-1 days 02:34:56.789123456")
|
| 49 |
+
|
| 50 |
+
r1 = t1.round(freq)
|
| 51 |
+
assert r1 == s1
|
| 52 |
+
r2 = t2.round(freq)
|
| 53 |
+
assert r2 == s2
|
| 54 |
+
|
| 55 |
+
def test_round_invalid(self):
|
| 56 |
+
t1 = Timedelta("1 days 02:34:56.789123456")
|
| 57 |
+
|
| 58 |
+
for freq, msg in [
|
| 59 |
+
("YE", "<YearEnd: month=12> is a non-fixed frequency"),
|
| 60 |
+
("ME", "<MonthEnd> is a non-fixed frequency"),
|
| 61 |
+
("foobar", "Invalid frequency: foobar"),
|
| 62 |
+
]:
|
| 63 |
+
with pytest.raises(ValueError, match=msg):
|
| 64 |
+
t1.round(freq)
|
| 65 |
+
|
| 66 |
+
def test_round_implementation_bounds(self):
|
| 67 |
+
# See also: analogous test for Timestamp
|
| 68 |
+
# GH#38964
|
| 69 |
+
result = Timedelta.min.ceil("s")
|
| 70 |
+
expected = Timedelta.min + Timedelta(seconds=1) - Timedelta(145224193)
|
| 71 |
+
assert result == expected
|
| 72 |
+
|
| 73 |
+
result = Timedelta.max.floor("s")
|
| 74 |
+
expected = Timedelta.max - Timedelta(854775807)
|
| 75 |
+
assert result == expected
|
| 76 |
+
|
| 77 |
+
msg = (
|
| 78 |
+
r"Cannot round -106752 days \+00:12:43.145224193 to freq=s without overflow"
|
| 79 |
+
)
|
| 80 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 81 |
+
Timedelta.min.floor("s")
|
| 82 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 83 |
+
Timedelta.min.round("s")
|
| 84 |
+
|
| 85 |
+
msg = "Cannot round 106751 days 23:47:16.854775807 to freq=s without overflow"
|
| 86 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 87 |
+
Timedelta.max.ceil("s")
|
| 88 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 89 |
+
Timedelta.max.round("s")
|
| 90 |
+
|
| 91 |
+
@pytest.mark.slow
|
| 92 |
+
@given(val=st.integers(min_value=iNaT + 1, max_value=lib.i8max))
|
| 93 |
+
@pytest.mark.parametrize(
|
| 94 |
+
"method", [Timedelta.round, Timedelta.floor, Timedelta.ceil]
|
| 95 |
+
)
|
| 96 |
+
def test_round_sanity(self, val, method):
|
| 97 |
+
cls = Timedelta
|
| 98 |
+
err_cls = OutOfBoundsTimedelta
|
| 99 |
+
|
| 100 |
+
val = np.int64(val)
|
| 101 |
+
td = cls(val)
|
| 102 |
+
|
| 103 |
+
def checker(ts, nanos, unit):
|
| 104 |
+
# First check that we do raise in cases where we should
|
| 105 |
+
if nanos == 1:
|
| 106 |
+
pass
|
| 107 |
+
else:
|
| 108 |
+
div, mod = divmod(ts._value, nanos)
|
| 109 |
+
diff = int(nanos - mod)
|
| 110 |
+
lb = ts._value - mod
|
| 111 |
+
assert lb <= ts._value # i.e. no overflows with python ints
|
| 112 |
+
ub = ts._value + diff
|
| 113 |
+
assert ub > ts._value # i.e. no overflows with python ints
|
| 114 |
+
|
| 115 |
+
msg = "without overflow"
|
| 116 |
+
if mod == 0:
|
| 117 |
+
# We should never be raising in this
|
| 118 |
+
pass
|
| 119 |
+
elif method is cls.ceil:
|
| 120 |
+
if ub > cls.max._value:
|
| 121 |
+
with pytest.raises(err_cls, match=msg):
|
| 122 |
+
method(ts, unit)
|
| 123 |
+
return
|
| 124 |
+
elif method is cls.floor:
|
| 125 |
+
if lb < cls.min._value:
|
| 126 |
+
with pytest.raises(err_cls, match=msg):
|
| 127 |
+
method(ts, unit)
|
| 128 |
+
return
|
| 129 |
+
elif mod >= diff:
|
| 130 |
+
if ub > cls.max._value:
|
| 131 |
+
with pytest.raises(err_cls, match=msg):
|
| 132 |
+
method(ts, unit)
|
| 133 |
+
return
|
| 134 |
+
elif lb < cls.min._value:
|
| 135 |
+
with pytest.raises(err_cls, match=msg):
|
| 136 |
+
method(ts, unit)
|
| 137 |
+
return
|
| 138 |
+
|
| 139 |
+
res = method(ts, unit)
|
| 140 |
+
|
| 141 |
+
td = res - ts
|
| 142 |
+
diff = abs(td._value)
|
| 143 |
+
assert diff < nanos
|
| 144 |
+
assert res._value % nanos == 0
|
| 145 |
+
|
| 146 |
+
if method is cls.round:
|
| 147 |
+
assert diff <= nanos / 2
|
| 148 |
+
elif method is cls.floor:
|
| 149 |
+
assert res <= ts
|
| 150 |
+
elif method is cls.ceil:
|
| 151 |
+
assert res >= ts
|
| 152 |
+
|
| 153 |
+
nanos = 1
|
| 154 |
+
checker(td, nanos, "ns")
|
| 155 |
+
|
| 156 |
+
nanos = 1000
|
| 157 |
+
checker(td, nanos, "us")
|
| 158 |
+
|
| 159 |
+
nanos = 1_000_000
|
| 160 |
+
checker(td, nanos, "ms")
|
| 161 |
+
|
| 162 |
+
nanos = 1_000_000_000
|
| 163 |
+
checker(td, nanos, "s")
|
| 164 |
+
|
| 165 |
+
nanos = 60 * 1_000_000_000
|
| 166 |
+
checker(td, nanos, "min")
|
| 167 |
+
|
| 168 |
+
nanos = 60 * 60 * 1_000_000_000
|
| 169 |
+
checker(td, nanos, "h")
|
| 170 |
+
|
| 171 |
+
nanos = 24 * 60 * 60 * 1_000_000_000
|
| 172 |
+
checker(td, nanos, "D")
|
| 173 |
+
|
| 174 |
+
def test_round_non_nano(self, unit):
|
| 175 |
+
td = Timedelta("1 days 02:34:57").as_unit(unit)
|
| 176 |
+
|
| 177 |
+
res = td.round("min")
|
| 178 |
+
assert res == Timedelta("1 days 02:35:00")
|
| 179 |
+
assert res._creso == td._creso
|
| 180 |
+
|
| 181 |
+
res = td.floor("min")
|
| 182 |
+
assert res == Timedelta("1 days 02:34:00")
|
| 183 |
+
assert res._creso == td._creso
|
| 184 |
+
|
| 185 |
+
res = td.ceil("min")
|
| 186 |
+
assert res == Timedelta("1 days 02:35:00")
|
| 187 |
+
assert res._creso == td._creso
|
| 188 |
+
|
| 189 |
+
def test_round_freq_finer_than_resolution(self):
|
| 190 |
+
# GH#64828
|
| 191 |
+
td = Timedelta(1.0, unit="days").as_unit("s")
|
| 192 |
+
assert td.unit == "s"
|
| 193 |
+
assert td.round("100ms") == Timedelta("1 days 00:00:00")
|
| 194 |
+
assert td.floor("100ms") == Timedelta("1 days 00:00:00")
|
| 195 |
+
assert td.ceil("100ms") == Timedelta("1 days 00:00:00")
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_arithmetic.py
ADDED
|
@@ -0,0 +1,1251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Tests for scalar Timedelta arithmetic ops
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from datetime import (
|
| 6 |
+
datetime,
|
| 7 |
+
timedelta,
|
| 8 |
+
)
|
| 9 |
+
import operator
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
import pytest
|
| 13 |
+
|
| 14 |
+
from pandas.errors import (
|
| 15 |
+
OutOfBoundsTimedelta,
|
| 16 |
+
Pandas4Warning,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
import pandas as pd
|
| 20 |
+
from pandas import (
|
| 21 |
+
NaT,
|
| 22 |
+
Timedelta,
|
| 23 |
+
Timestamp,
|
| 24 |
+
offsets,
|
| 25 |
+
)
|
| 26 |
+
import pandas._testing as tm
|
| 27 |
+
from pandas.core import ops
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class TestTimedeltaAdditionSubtraction:
|
| 31 |
+
"""
|
| 32 |
+
Tests for Timedelta methods:
|
| 33 |
+
|
| 34 |
+
__add__, __radd__,
|
| 35 |
+
__sub__, __rsub__
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def test_td_add_sub_pydatetime(self, unit):
|
| 39 |
+
# GH#53643
|
| 40 |
+
td = Timedelta(hours=23).as_unit(unit)
|
| 41 |
+
dt = datetime(2016, 1, 1)
|
| 42 |
+
|
| 43 |
+
expected = datetime(2016, 1, 1, 23)
|
| 44 |
+
result = dt + td
|
| 45 |
+
assert result == expected
|
| 46 |
+
result = td + dt
|
| 47 |
+
assert result == expected
|
| 48 |
+
|
| 49 |
+
expected = datetime(2015, 12, 31, 1)
|
| 50 |
+
result = dt - td
|
| 51 |
+
assert result == expected
|
| 52 |
+
|
| 53 |
+
@pytest.mark.parametrize(
|
| 54 |
+
"ten_seconds",
|
| 55 |
+
[
|
| 56 |
+
Timedelta(10, unit="s"),
|
| 57 |
+
timedelta(seconds=10),
|
| 58 |
+
np.timedelta64(10, "s"),
|
| 59 |
+
np.timedelta64(10000000000, "ns"),
|
| 60 |
+
offsets.Second(10),
|
| 61 |
+
],
|
| 62 |
+
)
|
| 63 |
+
def test_td_add_sub_ten_seconds(self, ten_seconds):
|
| 64 |
+
# GH#6808
|
| 65 |
+
base = Timestamp("20130101 09:01:12.123456")
|
| 66 |
+
expected_add = Timestamp("20130101 09:01:22.123456")
|
| 67 |
+
expected_sub = Timestamp("20130101 09:01:02.123456")
|
| 68 |
+
|
| 69 |
+
result = base + ten_seconds
|
| 70 |
+
assert result == expected_add
|
| 71 |
+
|
| 72 |
+
result = base - ten_seconds
|
| 73 |
+
assert result == expected_sub
|
| 74 |
+
|
| 75 |
+
@pytest.mark.parametrize(
|
| 76 |
+
"one_day_ten_secs",
|
| 77 |
+
[
|
| 78 |
+
Timedelta("1 day, 00:00:10"),
|
| 79 |
+
Timedelta("1 days, 00:00:10"),
|
| 80 |
+
timedelta(days=1, seconds=10),
|
| 81 |
+
np.timedelta64(1, "D") + np.timedelta64(10, "s"),
|
| 82 |
+
offsets.Day() + offsets.Second(10),
|
| 83 |
+
],
|
| 84 |
+
)
|
| 85 |
+
def test_td_add_sub_one_day_ten_seconds(self, one_day_ten_secs):
|
| 86 |
+
# GH#6808
|
| 87 |
+
base = Timestamp("20130102 09:01:12.123456")
|
| 88 |
+
expected_add = Timestamp("20130103 09:01:22.123456")
|
| 89 |
+
expected_sub = Timestamp("20130101 09:01:02.123456")
|
| 90 |
+
|
| 91 |
+
result = base + one_day_ten_secs
|
| 92 |
+
assert result == expected_add
|
| 93 |
+
|
| 94 |
+
result = base - one_day_ten_secs
|
| 95 |
+
assert result == expected_sub
|
| 96 |
+
|
| 97 |
+
@pytest.mark.parametrize("op", [operator.add, ops.radd])
|
| 98 |
+
def test_td_add_datetimelike_scalar(self, op):
|
| 99 |
+
# GH#19738
|
| 100 |
+
td = Timedelta(10, unit="D")
|
| 101 |
+
|
| 102 |
+
result = op(td, datetime(2016, 1, 1))
|
| 103 |
+
if op is operator.add:
|
| 104 |
+
# datetime + Timedelta does _not_ call Timedelta.__radd__,
|
| 105 |
+
# so we get a datetime back instead of a Timestamp
|
| 106 |
+
assert isinstance(result, Timestamp)
|
| 107 |
+
assert result == Timestamp(2016, 1, 11)
|
| 108 |
+
|
| 109 |
+
result = op(td, Timestamp("2018-01-12 18:09"))
|
| 110 |
+
assert isinstance(result, Timestamp)
|
| 111 |
+
assert result == Timestamp("2018-01-22 18:09")
|
| 112 |
+
|
| 113 |
+
result = op(td, np.datetime64("2018-01-12"))
|
| 114 |
+
assert isinstance(result, Timestamp)
|
| 115 |
+
assert result == Timestamp("2018-01-22")
|
| 116 |
+
|
| 117 |
+
result = op(td, NaT)
|
| 118 |
+
assert result is NaT
|
| 119 |
+
|
| 120 |
+
def test_td_add_timestamp_overflow(self):
|
| 121 |
+
ts = Timestamp("1700-01-01").as_unit("ns")
|
| 122 |
+
msg = "Cannot cast 259987 days 00:00:00 to unit='ns' without overflow."
|
| 123 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 124 |
+
ts + Timedelta(13 * 19999, unit="D")
|
| 125 |
+
|
| 126 |
+
msg = "Cannot cast 259987 days 00:00:00 to unit='ns' without overflow"
|
| 127 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 128 |
+
ts + timedelta(days=13 * 19999)
|
| 129 |
+
|
| 130 |
+
@pytest.mark.parametrize("op", [operator.add, ops.radd])
|
| 131 |
+
def test_td_add_td(self, op):
|
| 132 |
+
td = Timedelta(10, unit="D")
|
| 133 |
+
|
| 134 |
+
result = op(td, Timedelta(days=10))
|
| 135 |
+
assert isinstance(result, Timedelta)
|
| 136 |
+
assert result == Timedelta(days=20)
|
| 137 |
+
|
| 138 |
+
@pytest.mark.parametrize("op", [operator.add, ops.radd])
|
| 139 |
+
def test_td_add_pytimedelta(self, op):
|
| 140 |
+
td = Timedelta(10, unit="D")
|
| 141 |
+
result = op(td, timedelta(days=9))
|
| 142 |
+
assert isinstance(result, Timedelta)
|
| 143 |
+
assert result == Timedelta(days=19)
|
| 144 |
+
|
| 145 |
+
@pytest.mark.parametrize("op", [operator.add, ops.radd])
|
| 146 |
+
def test_td_add_timedelta64(self, op):
|
| 147 |
+
td = Timedelta(10, unit="D")
|
| 148 |
+
result = op(td, np.timedelta64(-4, "D"))
|
| 149 |
+
assert isinstance(result, Timedelta)
|
| 150 |
+
assert result == Timedelta(days=6)
|
| 151 |
+
|
| 152 |
+
@pytest.mark.parametrize("op", [operator.add, ops.radd])
|
| 153 |
+
def test_td_add_offset(self, op):
|
| 154 |
+
td = Timedelta(10, unit="D")
|
| 155 |
+
|
| 156 |
+
result = op(td, offsets.Hour(6))
|
| 157 |
+
assert isinstance(result, Timedelta)
|
| 158 |
+
assert result == Timedelta(days=10, hours=6)
|
| 159 |
+
|
| 160 |
+
def test_td_sub_td(self):
|
| 161 |
+
td = Timedelta(10, unit="D")
|
| 162 |
+
expected = Timedelta(0, unit="ns")
|
| 163 |
+
result = td - td
|
| 164 |
+
assert isinstance(result, Timedelta)
|
| 165 |
+
assert result == expected
|
| 166 |
+
|
| 167 |
+
def test_td_sub_pytimedelta(self):
|
| 168 |
+
td = Timedelta(10, unit="D")
|
| 169 |
+
expected = Timedelta(0, unit="ns")
|
| 170 |
+
|
| 171 |
+
result = td - td.to_pytimedelta()
|
| 172 |
+
assert isinstance(result, Timedelta)
|
| 173 |
+
assert result == expected
|
| 174 |
+
|
| 175 |
+
result = td.to_pytimedelta() - td
|
| 176 |
+
assert isinstance(result, Timedelta)
|
| 177 |
+
assert result == expected
|
| 178 |
+
|
| 179 |
+
def test_td_sub_timedelta64(self):
|
| 180 |
+
td = Timedelta(10, unit="D")
|
| 181 |
+
expected = Timedelta(0, unit="ns")
|
| 182 |
+
|
| 183 |
+
result = td - td.to_timedelta64()
|
| 184 |
+
assert isinstance(result, Timedelta)
|
| 185 |
+
assert result == expected
|
| 186 |
+
|
| 187 |
+
result = td.to_timedelta64() - td
|
| 188 |
+
assert isinstance(result, Timedelta)
|
| 189 |
+
assert result == expected
|
| 190 |
+
|
| 191 |
+
def test_td_sub_nat(self):
|
| 192 |
+
# In this context pd.NaT is treated as timedelta-like
|
| 193 |
+
td = Timedelta(10, unit="D")
|
| 194 |
+
result = td - NaT
|
| 195 |
+
assert result is NaT
|
| 196 |
+
|
| 197 |
+
def test_td_sub_td64_nat(self):
|
| 198 |
+
td = Timedelta(10, unit="D")
|
| 199 |
+
td_nat = np.timedelta64("NaT", "ns")
|
| 200 |
+
|
| 201 |
+
result = td - td_nat
|
| 202 |
+
assert result is NaT
|
| 203 |
+
|
| 204 |
+
result = td_nat - td
|
| 205 |
+
assert result is NaT
|
| 206 |
+
|
| 207 |
+
def test_td_sub_offset(self):
|
| 208 |
+
td = Timedelta(10, unit="D")
|
| 209 |
+
result = td - offsets.Hour(1)
|
| 210 |
+
assert isinstance(result, Timedelta)
|
| 211 |
+
assert result == Timedelta(239, unit="h")
|
| 212 |
+
|
| 213 |
+
def test_td_add_sub_numeric_raises(self):
|
| 214 |
+
td = Timedelta(10, unit="D")
|
| 215 |
+
msg = "unsupported operand type"
|
| 216 |
+
for other in [2, 2.0, np.int64(2), np.float64(2)]:
|
| 217 |
+
with pytest.raises(TypeError, match=msg):
|
| 218 |
+
td + other
|
| 219 |
+
with pytest.raises(TypeError, match=msg):
|
| 220 |
+
other + td
|
| 221 |
+
with pytest.raises(TypeError, match=msg):
|
| 222 |
+
td - other
|
| 223 |
+
with pytest.raises(TypeError, match=msg):
|
| 224 |
+
other - td
|
| 225 |
+
|
| 226 |
+
def test_td_add_sub_int_ndarray(self):
|
| 227 |
+
td = Timedelta("1 day")
|
| 228 |
+
other = np.array([1])
|
| 229 |
+
|
| 230 |
+
msg = r"unsupported operand type\(s\) for \+: 'Timedelta' and 'int'"
|
| 231 |
+
with pytest.raises(TypeError, match=msg):
|
| 232 |
+
td + np.array([1])
|
| 233 |
+
|
| 234 |
+
msg = "|".join(
|
| 235 |
+
[
|
| 236 |
+
(
|
| 237 |
+
r"unsupported operand type\(s\) for \+: 'numpy.ndarray' "
|
| 238 |
+
"and 'Timedelta'"
|
| 239 |
+
),
|
| 240 |
+
# This message goes on to say "Please do not rely on this error;
|
| 241 |
+
# it may not be given on all Python implementations"
|
| 242 |
+
"Concatenation operation is not implemented for NumPy arrays",
|
| 243 |
+
]
|
| 244 |
+
)
|
| 245 |
+
with pytest.raises(TypeError, match=msg):
|
| 246 |
+
other + td
|
| 247 |
+
msg = r"unsupported operand type\(s\) for -: 'Timedelta' and 'int'"
|
| 248 |
+
with pytest.raises(TypeError, match=msg):
|
| 249 |
+
td - other
|
| 250 |
+
msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timedelta'"
|
| 251 |
+
with pytest.raises(TypeError, match=msg):
|
| 252 |
+
other - td
|
| 253 |
+
|
| 254 |
+
def test_td_rsub_nat(self):
|
| 255 |
+
td = Timedelta(10, unit="D")
|
| 256 |
+
result = NaT - td
|
| 257 |
+
assert result is NaT
|
| 258 |
+
|
| 259 |
+
result = np.datetime64("NaT", "ns") - td
|
| 260 |
+
assert result is NaT
|
| 261 |
+
|
| 262 |
+
def test_td_rsub_offset(self):
|
| 263 |
+
result = offsets.Hour(1) - Timedelta(10, unit="D")
|
| 264 |
+
assert isinstance(result, Timedelta)
|
| 265 |
+
assert result == Timedelta(-239, unit="h")
|
| 266 |
+
|
| 267 |
+
def test_td_sub_timedeltalike_object_dtype_array(self):
|
| 268 |
+
# GH#21980
|
| 269 |
+
arr = np.array([Timestamp("20130101 9:01"), Timestamp("20121230 9:02")])
|
| 270 |
+
exp = np.array([Timestamp("20121231 9:01"), Timestamp("20121229 9:02")])
|
| 271 |
+
res = arr - Timedelta("1D")
|
| 272 |
+
tm.assert_numpy_array_equal(res, exp)
|
| 273 |
+
|
| 274 |
+
def test_td_sub_mixed_most_timedeltalike_object_dtype_array(self):
|
| 275 |
+
# GH#21980
|
| 276 |
+
now = Timestamp("2021-11-09 09:54:00")
|
| 277 |
+
arr = np.array([now, Timedelta("1D"), np.timedelta64(2, "h")])
|
| 278 |
+
exp = np.array(
|
| 279 |
+
[
|
| 280 |
+
now - Timedelta("1D"),
|
| 281 |
+
Timedelta("0D"),
|
| 282 |
+
np.timedelta64(2, "h") - Timedelta("1D"),
|
| 283 |
+
]
|
| 284 |
+
)
|
| 285 |
+
res = arr - Timedelta("1D")
|
| 286 |
+
tm.assert_numpy_array_equal(res, exp)
|
| 287 |
+
|
| 288 |
+
def test_td_rsub_mixed_most_timedeltalike_object_dtype_array(self):
|
| 289 |
+
# GH#21980
|
| 290 |
+
now = Timestamp("2021-11-09 09:54:00")
|
| 291 |
+
arr = np.array([now, Timedelta("1D"), np.timedelta64(2, "h")])
|
| 292 |
+
msg = r"unsupported operand type\(s\) for \-: 'Timedelta' and 'Timestamp'"
|
| 293 |
+
with pytest.raises(TypeError, match=msg):
|
| 294 |
+
Timedelta("1D") - arr
|
| 295 |
+
|
| 296 |
+
@pytest.mark.parametrize("op", [operator.add, ops.radd])
|
| 297 |
+
def test_td_add_timedeltalike_object_dtype_array(self, op):
|
| 298 |
+
# GH#21980
|
| 299 |
+
arr = np.array([Timestamp("20130101 9:01"), Timestamp("20121230 9:02")])
|
| 300 |
+
exp = np.array([Timestamp("20130102 9:01"), Timestamp("20121231 9:02")])
|
| 301 |
+
res = op(arr, Timedelta("1D"))
|
| 302 |
+
tm.assert_numpy_array_equal(res, exp)
|
| 303 |
+
|
| 304 |
+
@pytest.mark.parametrize("op", [operator.add, ops.radd])
|
| 305 |
+
def test_td_add_mixed_timedeltalike_object_dtype_array(self, op):
|
| 306 |
+
# GH#21980
|
| 307 |
+
now = Timestamp("2021-11-09 09:54:00")
|
| 308 |
+
arr = np.array([now, Timedelta("1D")])
|
| 309 |
+
exp = np.array([now + Timedelta("1D"), Timedelta("2D")])
|
| 310 |
+
res = op(arr, Timedelta("1D"))
|
| 311 |
+
tm.assert_numpy_array_equal(res, exp)
|
| 312 |
+
|
| 313 |
+
def test_td_add_sub_td64_ndarray(self):
|
| 314 |
+
td = Timedelta("1 day")
|
| 315 |
+
|
| 316 |
+
other = np.array([td.to_timedelta64()])
|
| 317 |
+
expected = np.array([Timedelta("2 Days").to_timedelta64()])
|
| 318 |
+
|
| 319 |
+
result = td + other
|
| 320 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 321 |
+
result = other + td
|
| 322 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 323 |
+
|
| 324 |
+
result = td - other
|
| 325 |
+
tm.assert_numpy_array_equal(result, expected * 0)
|
| 326 |
+
result = other - td
|
| 327 |
+
tm.assert_numpy_array_equal(result, expected * 0)
|
| 328 |
+
|
| 329 |
+
def test_td_add_sub_dt64_ndarray(self):
|
| 330 |
+
td = Timedelta("1 day")
|
| 331 |
+
other = np.array(["2000-01-01"], dtype="M8[ns]")
|
| 332 |
+
|
| 333 |
+
expected = np.array(["2000-01-02"], dtype="M8[ns]")
|
| 334 |
+
tm.assert_numpy_array_equal(td + other, expected)
|
| 335 |
+
tm.assert_numpy_array_equal(other + td, expected)
|
| 336 |
+
|
| 337 |
+
expected = np.array(["1999-12-31"], dtype="M8[ns]")
|
| 338 |
+
tm.assert_numpy_array_equal(-td + other, expected)
|
| 339 |
+
tm.assert_numpy_array_equal(other - td, expected)
|
| 340 |
+
|
| 341 |
+
def test_td_add_sub_ndarray_0d(self):
|
| 342 |
+
td = Timedelta("1 day")
|
| 343 |
+
other = np.array(td.asm8)
|
| 344 |
+
|
| 345 |
+
result = td + other
|
| 346 |
+
assert isinstance(result, Timedelta)
|
| 347 |
+
assert result == 2 * td
|
| 348 |
+
|
| 349 |
+
result = other + td
|
| 350 |
+
assert isinstance(result, Timedelta)
|
| 351 |
+
assert result == 2 * td
|
| 352 |
+
|
| 353 |
+
result = other - td
|
| 354 |
+
assert isinstance(result, Timedelta)
|
| 355 |
+
assert result == 0 * td
|
| 356 |
+
|
| 357 |
+
result = td - other
|
| 358 |
+
assert isinstance(result, Timedelta)
|
| 359 |
+
assert result == 0 * td
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
class TestTimedeltaMultiplicationDivision:
|
| 363 |
+
"""
|
| 364 |
+
Tests for Timedelta methods:
|
| 365 |
+
|
| 366 |
+
__mul__, __rmul__,
|
| 367 |
+
__div__, __rdiv__,
|
| 368 |
+
__truediv__, __rtruediv__,
|
| 369 |
+
__floordiv__, __rfloordiv__,
|
| 370 |
+
__mod__, __rmod__,
|
| 371 |
+
__divmod__, __rdivmod__
|
| 372 |
+
"""
|
| 373 |
+
|
| 374 |
+
# ---------------------------------------------------------------
|
| 375 |
+
# Timedelta.__mul__, __rmul__
|
| 376 |
+
|
| 377 |
+
@pytest.mark.parametrize("td_nat", [NaT, np.timedelta64("NaT", "ns")])
|
| 378 |
+
@pytest.mark.parametrize("op", [operator.mul, ops.rmul])
|
| 379 |
+
def test_td_mul_nat(self, op, td_nat):
|
| 380 |
+
# GH#19819
|
| 381 |
+
td = Timedelta(10, unit="D")
|
| 382 |
+
typs = "|".join(["numpy.timedelta64", "NaTType", "Timedelta"])
|
| 383 |
+
msg = "|".join(
|
| 384 |
+
[
|
| 385 |
+
rf"unsupported operand type\(s\) for \*: '{typs}' and '{typs}'",
|
| 386 |
+
r"ufunc '?multiply'? cannot use operands with types",
|
| 387 |
+
]
|
| 388 |
+
)
|
| 389 |
+
with pytest.raises(TypeError, match=msg):
|
| 390 |
+
op(td, td_nat)
|
| 391 |
+
|
| 392 |
+
@pytest.mark.parametrize("nan", [np.nan, np.float64("NaN"), float("nan")])
|
| 393 |
+
@pytest.mark.parametrize("op", [operator.mul, ops.rmul])
|
| 394 |
+
def test_td_mul_nan(self, op, nan):
|
| 395 |
+
# np.float64('NaN') has a 'dtype' attr, avoid treating as array
|
| 396 |
+
td = Timedelta(10, unit="D")
|
| 397 |
+
result = op(td, nan)
|
| 398 |
+
assert result is NaT
|
| 399 |
+
|
| 400 |
+
@pytest.mark.parametrize("op", [operator.mul, ops.rmul])
|
| 401 |
+
def test_td_mul_scalar(self, op):
|
| 402 |
+
# GH#19738
|
| 403 |
+
td = Timedelta(minutes=3)
|
| 404 |
+
|
| 405 |
+
result = op(td, 2)
|
| 406 |
+
assert result == Timedelta(minutes=6)
|
| 407 |
+
|
| 408 |
+
result = op(td, 1.5)
|
| 409 |
+
assert result == Timedelta(minutes=4, seconds=30)
|
| 410 |
+
|
| 411 |
+
assert op(td, np.nan) is NaT
|
| 412 |
+
|
| 413 |
+
assert op(-1, td)._value == -1 * td._value
|
| 414 |
+
assert op(-1.0, td)._value == -1.0 * td._value
|
| 415 |
+
|
| 416 |
+
msg = "unsupported operand type"
|
| 417 |
+
with pytest.raises(TypeError, match=msg):
|
| 418 |
+
# timedelta * datetime is gibberish
|
| 419 |
+
op(td, Timestamp(2016, 1, 2))
|
| 420 |
+
|
| 421 |
+
with pytest.raises(TypeError, match=msg):
|
| 422 |
+
# invalid multiply with another timedelta
|
| 423 |
+
op(td, td)
|
| 424 |
+
|
| 425 |
+
def test_td_mul_numeric_ndarray(self):
|
| 426 |
+
td = Timedelta("1 day")
|
| 427 |
+
other = np.array([2])
|
| 428 |
+
expected = np.array([Timedelta("2 Days").to_timedelta64()])
|
| 429 |
+
|
| 430 |
+
result = td * other
|
| 431 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 432 |
+
|
| 433 |
+
result = other * td
|
| 434 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 435 |
+
|
| 436 |
+
def test_td_mul_numeric_ndarray_0d(self):
|
| 437 |
+
td = Timedelta("1 day")
|
| 438 |
+
other = np.array(2, dtype=np.int64)
|
| 439 |
+
assert other.ndim == 0
|
| 440 |
+
expected = Timedelta("2 days")
|
| 441 |
+
|
| 442 |
+
res = td * other
|
| 443 |
+
assert type(res) is Timedelta
|
| 444 |
+
assert res == expected
|
| 445 |
+
|
| 446 |
+
res = other * td
|
| 447 |
+
assert type(res) is Timedelta
|
| 448 |
+
assert res == expected
|
| 449 |
+
|
| 450 |
+
def test_td_mul_td64_ndarray_invalid(self):
|
| 451 |
+
td = Timedelta("1 day")
|
| 452 |
+
other = np.array([Timedelta("2 Days").to_timedelta64()])
|
| 453 |
+
|
| 454 |
+
msg = (
|
| 455 |
+
"ufunc '?multiply'? cannot use operands with types "
|
| 456 |
+
rf"dtype\('{tm.ENDIAN}m8\[us\]'\) and dtype\('{tm.ENDIAN}m8\[us\]'\)"
|
| 457 |
+
)
|
| 458 |
+
with pytest.raises(TypeError, match=msg):
|
| 459 |
+
td * other
|
| 460 |
+
with pytest.raises(TypeError, match=msg):
|
| 461 |
+
other * td
|
| 462 |
+
|
| 463 |
+
# ---------------------------------------------------------------
|
| 464 |
+
# Timedelta.__div__, __truediv__
|
| 465 |
+
|
| 466 |
+
def test_td_div_timedeltalike_scalar(self):
|
| 467 |
+
# GH#19738
|
| 468 |
+
td = Timedelta(10, unit="D")
|
| 469 |
+
|
| 470 |
+
result = td / offsets.Hour(1)
|
| 471 |
+
assert result == 240
|
| 472 |
+
|
| 473 |
+
assert td / td == 1
|
| 474 |
+
assert td / np.timedelta64(60, "h") == 4
|
| 475 |
+
|
| 476 |
+
assert np.isnan(td / NaT)
|
| 477 |
+
|
| 478 |
+
def test_td_div_td64_non_nano(self):
|
| 479 |
+
# truediv
|
| 480 |
+
td = Timedelta("1 days 2 hours 3 ns")
|
| 481 |
+
result = td / np.timedelta64(1, "D")
|
| 482 |
+
assert result == td._value / (86400 * 10**9)
|
| 483 |
+
result = td / np.timedelta64(1, "s")
|
| 484 |
+
assert result == td._value / 10**9
|
| 485 |
+
result = td / np.timedelta64(1, "ns")
|
| 486 |
+
assert result == td._value
|
| 487 |
+
|
| 488 |
+
# floordiv
|
| 489 |
+
td = Timedelta("1 days 2 hours 3 ns")
|
| 490 |
+
result = td // np.timedelta64(1, "D")
|
| 491 |
+
assert result == 1
|
| 492 |
+
result = td // np.timedelta64(1, "s")
|
| 493 |
+
assert result == 93600
|
| 494 |
+
result = td // np.timedelta64(1, "ns")
|
| 495 |
+
assert result == td._value
|
| 496 |
+
|
| 497 |
+
def test_td_div_numeric_scalar(self):
|
| 498 |
+
# GH#19738
|
| 499 |
+
td = Timedelta(10, unit="D")
|
| 500 |
+
|
| 501 |
+
result = td / 2
|
| 502 |
+
assert isinstance(result, Timedelta)
|
| 503 |
+
assert result == Timedelta(days=5)
|
| 504 |
+
|
| 505 |
+
result = td / 5
|
| 506 |
+
assert isinstance(result, Timedelta)
|
| 507 |
+
assert result == Timedelta(days=2)
|
| 508 |
+
|
| 509 |
+
@pytest.mark.parametrize(
|
| 510 |
+
"nan",
|
| 511 |
+
[
|
| 512 |
+
np.nan,
|
| 513 |
+
np.float64("NaN"),
|
| 514 |
+
float("nan"),
|
| 515 |
+
],
|
| 516 |
+
)
|
| 517 |
+
def test_td_div_nan(self, nan):
|
| 518 |
+
# np.float64('NaN') has a 'dtype' attr, avoid treating as array
|
| 519 |
+
td = Timedelta(10, unit="D")
|
| 520 |
+
result = td / nan
|
| 521 |
+
assert result is NaT
|
| 522 |
+
|
| 523 |
+
result = td // nan
|
| 524 |
+
assert result is NaT
|
| 525 |
+
|
| 526 |
+
def test_td_div_td64_ndarray(self):
|
| 527 |
+
td = Timedelta("1 day")
|
| 528 |
+
|
| 529 |
+
other = np.array([Timedelta("2 Days").to_timedelta64()])
|
| 530 |
+
expected = np.array([0.5])
|
| 531 |
+
|
| 532 |
+
result = td / other
|
| 533 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 534 |
+
|
| 535 |
+
result = other / td
|
| 536 |
+
tm.assert_numpy_array_equal(result, expected * 4)
|
| 537 |
+
|
| 538 |
+
def test_td_div_ndarray_0d(self):
|
| 539 |
+
td = Timedelta("1 day")
|
| 540 |
+
|
| 541 |
+
other = np.array(1)
|
| 542 |
+
res = td / other
|
| 543 |
+
assert isinstance(res, Timedelta)
|
| 544 |
+
assert res == td
|
| 545 |
+
|
| 546 |
+
# ---------------------------------------------------------------
|
| 547 |
+
# Timedelta.__rdiv__
|
| 548 |
+
|
| 549 |
+
def test_td_rdiv_timedeltalike_scalar(self):
|
| 550 |
+
# GH#19738
|
| 551 |
+
td = Timedelta(10, unit="D")
|
| 552 |
+
result = offsets.Hour(1) / td
|
| 553 |
+
assert result == 1 / 240.0
|
| 554 |
+
|
| 555 |
+
assert np.timedelta64(60, "h") / td == 0.25
|
| 556 |
+
|
| 557 |
+
def test_td_rdiv_na_scalar(self):
|
| 558 |
+
# GH#31869 None gets cast to NaT
|
| 559 |
+
td = Timedelta(10, unit="D")
|
| 560 |
+
|
| 561 |
+
result = NaT / td
|
| 562 |
+
assert np.isnan(result)
|
| 563 |
+
|
| 564 |
+
result = None / td
|
| 565 |
+
assert np.isnan(result)
|
| 566 |
+
|
| 567 |
+
result = np.timedelta64("NaT", "ns") / td
|
| 568 |
+
assert np.isnan(result)
|
| 569 |
+
|
| 570 |
+
msg = r"unsupported operand type\(s\) for /: 'numpy.datetime64' and 'Timedelta'"
|
| 571 |
+
with pytest.raises(TypeError, match=msg):
|
| 572 |
+
np.datetime64("NaT", "ns") / td
|
| 573 |
+
|
| 574 |
+
msg = r"unsupported operand type\(s\) for /: 'float' and 'Timedelta'"
|
| 575 |
+
with pytest.raises(TypeError, match=msg):
|
| 576 |
+
np.nan / td
|
| 577 |
+
|
| 578 |
+
def test_td_rdiv_ndarray(self):
|
| 579 |
+
td = Timedelta(10, unit="D")
|
| 580 |
+
|
| 581 |
+
arr = np.array([td], dtype=object)
|
| 582 |
+
result = arr / td
|
| 583 |
+
expected = np.array([1], dtype=np.float64)
|
| 584 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 585 |
+
|
| 586 |
+
arr = np.array([None])
|
| 587 |
+
result = arr / td
|
| 588 |
+
expected = np.array([np.nan])
|
| 589 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 590 |
+
|
| 591 |
+
arr = np.array([np.nan], dtype=object)
|
| 592 |
+
msg = r"unsupported operand type\(s\) for /: 'float' and 'Timedelta'"
|
| 593 |
+
with pytest.raises(TypeError, match=msg):
|
| 594 |
+
arr / td
|
| 595 |
+
|
| 596 |
+
arr = np.array([np.nan], dtype=np.float64)
|
| 597 |
+
msg = "cannot use operands with types dtype"
|
| 598 |
+
with pytest.raises(TypeError, match=msg):
|
| 599 |
+
arr / td
|
| 600 |
+
|
| 601 |
+
def test_td_rdiv_ndarray_0d(self):
|
| 602 |
+
td = Timedelta(10, unit="D")
|
| 603 |
+
|
| 604 |
+
arr = np.array(td.asm8)
|
| 605 |
+
|
| 606 |
+
assert arr / td == 1
|
| 607 |
+
|
| 608 |
+
# ---------------------------------------------------------------
|
| 609 |
+
# Timedelta.__floordiv__
|
| 610 |
+
|
| 611 |
+
def test_td_floordiv_timedeltalike_scalar(self):
|
| 612 |
+
# GH#18846
|
| 613 |
+
td = Timedelta(hours=3, minutes=4)
|
| 614 |
+
scalar = Timedelta(hours=3, minutes=3)
|
| 615 |
+
|
| 616 |
+
assert td // scalar == 1
|
| 617 |
+
assert -td // scalar.to_pytimedelta() == -2
|
| 618 |
+
assert (2 * td) // scalar.to_timedelta64() == 2
|
| 619 |
+
|
| 620 |
+
def test_td_floordiv_null_scalar(self):
|
| 621 |
+
# GH#18846
|
| 622 |
+
td = Timedelta(hours=3, minutes=4)
|
| 623 |
+
|
| 624 |
+
assert td // np.nan is NaT
|
| 625 |
+
assert np.isnan(td // NaT)
|
| 626 |
+
assert np.isnan(td // np.timedelta64("NaT", "ns"))
|
| 627 |
+
|
| 628 |
+
def test_td_floordiv_offsets(self):
|
| 629 |
+
# GH#19738
|
| 630 |
+
td = Timedelta(hours=3, minutes=4)
|
| 631 |
+
assert td // offsets.Hour(1) == 3
|
| 632 |
+
assert td // offsets.Minute(2) == 92
|
| 633 |
+
|
| 634 |
+
def test_td_floordiv_invalid_scalar(self):
|
| 635 |
+
# GH#18846
|
| 636 |
+
td = Timedelta(hours=3, minutes=4)
|
| 637 |
+
|
| 638 |
+
msg = "|".join(
|
| 639 |
+
[
|
| 640 |
+
r"Invalid dtype datetime64\[D\] for __floordiv__",
|
| 641 |
+
"'dtype' is an invalid keyword argument for this function",
|
| 642 |
+
"this function got an unexpected keyword argument 'dtype'",
|
| 643 |
+
r"ufunc '?floor_divide'? cannot use operands with types",
|
| 644 |
+
]
|
| 645 |
+
)
|
| 646 |
+
with pytest.raises(TypeError, match=msg):
|
| 647 |
+
td // np.datetime64("2016-01-01", dtype="datetime64[us]")
|
| 648 |
+
|
| 649 |
+
def test_td_floordiv_numeric_scalar(self):
|
| 650 |
+
# GH#18846
|
| 651 |
+
td = Timedelta(hours=3, minutes=4)
|
| 652 |
+
|
| 653 |
+
expected = Timedelta(hours=1, minutes=32)
|
| 654 |
+
assert td // 2 == expected
|
| 655 |
+
assert td // 2.0 == expected
|
| 656 |
+
assert td // np.float64(2.0) == expected
|
| 657 |
+
assert td // np.int32(2.0) == expected
|
| 658 |
+
assert td // np.uint8(2.0) == expected
|
| 659 |
+
|
| 660 |
+
def test_td_floordiv_timedeltalike_array(self):
|
| 661 |
+
# GH#18846
|
| 662 |
+
td = Timedelta(hours=3, minutes=4)
|
| 663 |
+
scalar = Timedelta(hours=3, minutes=3)
|
| 664 |
+
|
| 665 |
+
# Array-like others
|
| 666 |
+
assert td // np.array(scalar.to_timedelta64()) == 1
|
| 667 |
+
|
| 668 |
+
res = (3 * td) // np.array([scalar.to_timedelta64()])
|
| 669 |
+
expected = np.array([3], dtype=np.int64)
|
| 670 |
+
tm.assert_numpy_array_equal(res, expected)
|
| 671 |
+
|
| 672 |
+
res = (10 * td) // np.array(
|
| 673 |
+
[scalar.to_timedelta64(), np.timedelta64("NaT", "ns")]
|
| 674 |
+
)
|
| 675 |
+
expected = np.array([10, np.nan])
|
| 676 |
+
tm.assert_numpy_array_equal(res, expected)
|
| 677 |
+
|
| 678 |
+
def test_td_floordiv_numeric_series(self):
|
| 679 |
+
# GH#18846
|
| 680 |
+
td = Timedelta(hours=3, minutes=4)
|
| 681 |
+
ser = pd.Series([1], dtype=np.int64)
|
| 682 |
+
res = td // ser
|
| 683 |
+
assert res.dtype.kind == "m"
|
| 684 |
+
|
| 685 |
+
# ---------------------------------------------------------------
|
| 686 |
+
# Timedelta.__rfloordiv__
|
| 687 |
+
|
| 688 |
+
def test_td_rfloordiv_timedeltalike_scalar(self):
|
| 689 |
+
# GH#18846
|
| 690 |
+
td = Timedelta(hours=3, minutes=3)
|
| 691 |
+
scalar = Timedelta(hours=3, minutes=4)
|
| 692 |
+
|
| 693 |
+
# scalar others
|
| 694 |
+
# x // Timedelta is defined only for timedelta-like x. int-like,
|
| 695 |
+
# float-like, and date-like, in particular, should all either
|
| 696 |
+
# a) raise TypeError directly or
|
| 697 |
+
# b) return NotImplemented, following which the reversed
|
| 698 |
+
# operation will raise TypeError.
|
| 699 |
+
assert td.__rfloordiv__(scalar) == 1
|
| 700 |
+
assert (-td).__rfloordiv__(scalar.to_pytimedelta()) == -2
|
| 701 |
+
assert (2 * td).__rfloordiv__(scalar.to_timedelta64()) == 0
|
| 702 |
+
|
| 703 |
+
def test_td_rfloordiv_null_scalar(self):
|
| 704 |
+
# GH#18846
|
| 705 |
+
td = Timedelta(hours=3, minutes=3)
|
| 706 |
+
|
| 707 |
+
assert np.isnan(td.__rfloordiv__(NaT))
|
| 708 |
+
assert np.isnan(td.__rfloordiv__(np.timedelta64("NaT", "ns")))
|
| 709 |
+
|
| 710 |
+
def test_td_rfloordiv_offsets(self):
|
| 711 |
+
# GH#19738
|
| 712 |
+
assert offsets.Hour(1) // Timedelta(minutes=25) == 2
|
| 713 |
+
|
| 714 |
+
def test_td_rfloordiv_invalid_scalar(self):
|
| 715 |
+
# GH#18846
|
| 716 |
+
td = Timedelta(hours=3, minutes=3)
|
| 717 |
+
|
| 718 |
+
dt64 = np.datetime64("2016-01-01", "us")
|
| 719 |
+
|
| 720 |
+
assert td.__rfloordiv__(dt64) is NotImplemented
|
| 721 |
+
|
| 722 |
+
msg = (
|
| 723 |
+
r"unsupported operand type\(s\) for //: 'numpy.datetime64' and 'Timedelta'"
|
| 724 |
+
)
|
| 725 |
+
with pytest.raises(TypeError, match=msg):
|
| 726 |
+
dt64 // td
|
| 727 |
+
|
| 728 |
+
def test_td_rfloordiv_numeric_scalar(self):
|
| 729 |
+
# GH#18846
|
| 730 |
+
td = Timedelta(hours=3, minutes=3)
|
| 731 |
+
|
| 732 |
+
assert td.__rfloordiv__(np.nan) is NotImplemented
|
| 733 |
+
assert td.__rfloordiv__(3.5) is NotImplemented
|
| 734 |
+
assert td.__rfloordiv__(2) is NotImplemented
|
| 735 |
+
assert td.__rfloordiv__(np.float64(2.0)) is NotImplemented
|
| 736 |
+
assert td.__rfloordiv__(np.uint8(9)) is NotImplemented
|
| 737 |
+
assert td.__rfloordiv__(np.int32(2.0)) is NotImplemented
|
| 738 |
+
|
| 739 |
+
msg = r"unsupported operand type\(s\) for //: '.*' and 'Timedelta"
|
| 740 |
+
with pytest.raises(TypeError, match=msg):
|
| 741 |
+
np.float64(2.0) // td
|
| 742 |
+
with pytest.raises(TypeError, match=msg):
|
| 743 |
+
np.uint8(9) // td
|
| 744 |
+
with pytest.raises(TypeError, match=msg):
|
| 745 |
+
# deprecated GH#19761, enforced GH#29797
|
| 746 |
+
np.int32(2.0) // td
|
| 747 |
+
|
| 748 |
+
def test_td_rfloordiv_timedeltalike_array(self):
|
| 749 |
+
# GH#18846
|
| 750 |
+
td = Timedelta(hours=3, minutes=3)
|
| 751 |
+
scalar = Timedelta(hours=3, minutes=4)
|
| 752 |
+
|
| 753 |
+
# Array-like others
|
| 754 |
+
assert td.__rfloordiv__(np.array(scalar.to_timedelta64())) == 1
|
| 755 |
+
|
| 756 |
+
res = td.__rfloordiv__(np.array([(3 * scalar).to_timedelta64()]))
|
| 757 |
+
expected = np.array([3], dtype=np.int64)
|
| 758 |
+
tm.assert_numpy_array_equal(res, expected)
|
| 759 |
+
|
| 760 |
+
arr = np.array([(10 * scalar).to_timedelta64(), np.timedelta64("NaT", "ns")])
|
| 761 |
+
res = td.__rfloordiv__(arr)
|
| 762 |
+
expected = np.array([10, np.nan])
|
| 763 |
+
tm.assert_numpy_array_equal(res, expected)
|
| 764 |
+
|
| 765 |
+
def test_td_rfloordiv_intarray(self):
|
| 766 |
+
# deprecated GH#19761, enforced GH#29797
|
| 767 |
+
ints = np.array([1349654400, 1349740800, 1349827200, 1349913600]) * 10**9
|
| 768 |
+
|
| 769 |
+
msg = "Invalid dtype"
|
| 770 |
+
with pytest.raises(TypeError, match=msg):
|
| 771 |
+
ints // Timedelta(1, unit="s")
|
| 772 |
+
|
| 773 |
+
def test_td_rfloordiv_numeric_series(self):
|
| 774 |
+
# GH#18846
|
| 775 |
+
td = Timedelta(hours=3, minutes=3)
|
| 776 |
+
ser = pd.Series([1], dtype=np.int64)
|
| 777 |
+
res = td.__rfloordiv__(ser)
|
| 778 |
+
assert res is NotImplemented
|
| 779 |
+
|
| 780 |
+
msg = "Invalid dtype"
|
| 781 |
+
with pytest.raises(TypeError, match=msg):
|
| 782 |
+
# Deprecated GH#19761, enforced GH#29797
|
| 783 |
+
ser // td
|
| 784 |
+
|
| 785 |
+
# ----------------------------------------------------------------
|
| 786 |
+
# Timedelta.__mod__, __rmod__
|
| 787 |
+
|
| 788 |
+
def test_mod_timedeltalike(self):
|
| 789 |
+
# GH#19365
|
| 790 |
+
td = Timedelta(hours=37)
|
| 791 |
+
|
| 792 |
+
# Timedelta-like others
|
| 793 |
+
result = td % Timedelta(hours=6)
|
| 794 |
+
assert isinstance(result, Timedelta)
|
| 795 |
+
assert result == Timedelta(hours=1)
|
| 796 |
+
|
| 797 |
+
result = td % timedelta(minutes=60)
|
| 798 |
+
assert isinstance(result, Timedelta)
|
| 799 |
+
assert result == Timedelta(0)
|
| 800 |
+
|
| 801 |
+
result = td % NaT
|
| 802 |
+
assert result is NaT
|
| 803 |
+
|
| 804 |
+
def test_mod_timedelta64_nat(self):
|
| 805 |
+
# GH#19365
|
| 806 |
+
td = Timedelta(hours=37)
|
| 807 |
+
|
| 808 |
+
result = td % np.timedelta64("NaT", "ns")
|
| 809 |
+
assert result is NaT
|
| 810 |
+
|
| 811 |
+
def test_mod_timedelta64(self):
|
| 812 |
+
# GH#19365
|
| 813 |
+
td = Timedelta(hours=37)
|
| 814 |
+
|
| 815 |
+
result = td % np.timedelta64(2, "h")
|
| 816 |
+
assert isinstance(result, Timedelta)
|
| 817 |
+
assert result == Timedelta(hours=1)
|
| 818 |
+
|
| 819 |
+
def test_mod_offset(self):
|
| 820 |
+
# GH#19365
|
| 821 |
+
td = Timedelta(hours=37)
|
| 822 |
+
|
| 823 |
+
result = td % offsets.Hour(5)
|
| 824 |
+
assert isinstance(result, Timedelta)
|
| 825 |
+
assert result == Timedelta(hours=2)
|
| 826 |
+
|
| 827 |
+
def test_mod_numeric(self):
|
| 828 |
+
# GH#19365
|
| 829 |
+
td = Timedelta(hours=37)
|
| 830 |
+
|
| 831 |
+
# Numeric Others
|
| 832 |
+
result = td % 2
|
| 833 |
+
assert isinstance(result, Timedelta)
|
| 834 |
+
assert result == Timedelta(0)
|
| 835 |
+
|
| 836 |
+
result = td % 1e9
|
| 837 |
+
assert isinstance(result, Timedelta)
|
| 838 |
+
assert result == Timedelta(minutes=3, seconds=20)
|
| 839 |
+
|
| 840 |
+
result = td % int(1e9)
|
| 841 |
+
assert isinstance(result, Timedelta)
|
| 842 |
+
assert result == Timedelta(minutes=3, seconds=20)
|
| 843 |
+
|
| 844 |
+
def test_mod_invalid(self):
|
| 845 |
+
# GH#19365
|
| 846 |
+
td = Timedelta(hours=37)
|
| 847 |
+
msg = "unsupported operand type"
|
| 848 |
+
with pytest.raises(TypeError, match=msg):
|
| 849 |
+
td % Timestamp("2018-01-22")
|
| 850 |
+
|
| 851 |
+
with pytest.raises(TypeError, match=msg):
|
| 852 |
+
td % []
|
| 853 |
+
|
| 854 |
+
def test_rmod_pytimedelta(self):
|
| 855 |
+
# GH#19365
|
| 856 |
+
td = Timedelta(minutes=3)
|
| 857 |
+
|
| 858 |
+
result = timedelta(minutes=4) % td
|
| 859 |
+
assert isinstance(result, Timedelta)
|
| 860 |
+
assert result == Timedelta(minutes=1)
|
| 861 |
+
|
| 862 |
+
def test_rmod_timedelta64(self):
|
| 863 |
+
# GH#19365
|
| 864 |
+
td = Timedelta(minutes=3)
|
| 865 |
+
result = np.timedelta64(5, "m") % td
|
| 866 |
+
assert isinstance(result, Timedelta)
|
| 867 |
+
assert result == Timedelta(minutes=2)
|
| 868 |
+
|
| 869 |
+
def test_rmod_invalid(self):
|
| 870 |
+
# GH#19365
|
| 871 |
+
td = Timedelta(minutes=3)
|
| 872 |
+
|
| 873 |
+
msg = "unsupported operand"
|
| 874 |
+
with pytest.raises(TypeError, match=msg):
|
| 875 |
+
Timestamp("2018-01-22") % td
|
| 876 |
+
|
| 877 |
+
with pytest.raises(TypeError, match=msg):
|
| 878 |
+
15 % td
|
| 879 |
+
|
| 880 |
+
with pytest.raises(TypeError, match=msg):
|
| 881 |
+
16.0 % td
|
| 882 |
+
|
| 883 |
+
msg = "Invalid dtype int"
|
| 884 |
+
with pytest.raises(TypeError, match=msg):
|
| 885 |
+
np.array([22, 24]) % td
|
| 886 |
+
|
| 887 |
+
# ----------------------------------------------------------------
|
| 888 |
+
# Timedelta.__divmod__, __rdivmod__
|
| 889 |
+
|
| 890 |
+
def test_divmod_numeric(self):
|
| 891 |
+
# GH#19365
|
| 892 |
+
td = Timedelta(days=2, hours=6)
|
| 893 |
+
|
| 894 |
+
result = divmod(td, 53 * 3600 * 1e6)
|
| 895 |
+
assert result[0] == Timedelta(1, unit="us").as_unit("us")
|
| 896 |
+
assert isinstance(result[1], Timedelta)
|
| 897 |
+
assert result[1] == Timedelta(hours=1)
|
| 898 |
+
|
| 899 |
+
assert result
|
| 900 |
+
result = divmod(td, np.nan)
|
| 901 |
+
assert result[0] is NaT
|
| 902 |
+
assert result[1] is NaT
|
| 903 |
+
|
| 904 |
+
def test_divmod(self):
|
| 905 |
+
# GH#19365
|
| 906 |
+
td = Timedelta(days=2, hours=6)
|
| 907 |
+
|
| 908 |
+
result = divmod(td, timedelta(days=1))
|
| 909 |
+
assert result[0] == 2
|
| 910 |
+
assert isinstance(result[1], Timedelta)
|
| 911 |
+
assert result[1] == Timedelta(hours=6)
|
| 912 |
+
|
| 913 |
+
result = divmod(td, 54)
|
| 914 |
+
assert result[0] == Timedelta(hours=1)
|
| 915 |
+
assert isinstance(result[1], Timedelta)
|
| 916 |
+
assert result[1] == Timedelta(0)
|
| 917 |
+
|
| 918 |
+
result = divmod(td, NaT)
|
| 919 |
+
assert np.isnan(result[0])
|
| 920 |
+
assert result[1] is NaT
|
| 921 |
+
|
| 922 |
+
def test_divmod_offset(self):
|
| 923 |
+
# GH#19365
|
| 924 |
+
td = Timedelta(days=2, hours=6)
|
| 925 |
+
|
| 926 |
+
result = divmod(td, offsets.Hour(-4))
|
| 927 |
+
assert result[0] == -14
|
| 928 |
+
assert isinstance(result[1], Timedelta)
|
| 929 |
+
assert result[1] == Timedelta(hours=-2)
|
| 930 |
+
|
| 931 |
+
def test_divmod_invalid(self):
|
| 932 |
+
# GH#19365
|
| 933 |
+
td = Timedelta(days=2, hours=6)
|
| 934 |
+
|
| 935 |
+
msg = r"unsupported operand type\(s\) for //: 'Timedelta' and 'Timestamp'"
|
| 936 |
+
with pytest.raises(TypeError, match=msg):
|
| 937 |
+
divmod(td, Timestamp("2018-01-22"))
|
| 938 |
+
|
| 939 |
+
def test_rdivmod_pytimedelta(self):
|
| 940 |
+
# GH#19365
|
| 941 |
+
result = divmod(timedelta(days=2, hours=6), Timedelta(days=1))
|
| 942 |
+
assert result[0] == 2
|
| 943 |
+
assert isinstance(result[1], Timedelta)
|
| 944 |
+
assert result[1] == Timedelta(hours=6)
|
| 945 |
+
|
| 946 |
+
def test_rdivmod_offset(self):
|
| 947 |
+
result = divmod(offsets.Hour(54), Timedelta(hours=-4))
|
| 948 |
+
assert result[0] == -14
|
| 949 |
+
assert isinstance(result[1], Timedelta)
|
| 950 |
+
assert result[1] == Timedelta(hours=-2)
|
| 951 |
+
|
| 952 |
+
def test_rdivmod_invalid(self):
|
| 953 |
+
# GH#19365
|
| 954 |
+
td = Timedelta(minutes=3)
|
| 955 |
+
msg = "unsupported operand type"
|
| 956 |
+
|
| 957 |
+
with pytest.raises(TypeError, match=msg):
|
| 958 |
+
divmod(Timestamp("2018-01-22"), td)
|
| 959 |
+
|
| 960 |
+
with pytest.raises(TypeError, match=msg):
|
| 961 |
+
divmod(15, td)
|
| 962 |
+
|
| 963 |
+
with pytest.raises(TypeError, match=msg):
|
| 964 |
+
divmod(16.0, td)
|
| 965 |
+
|
| 966 |
+
msg = "Invalid dtype int"
|
| 967 |
+
with pytest.raises(TypeError, match=msg):
|
| 968 |
+
divmod(np.array([22, 24]), td)
|
| 969 |
+
|
| 970 |
+
# ----------------------------------------------------------------
|
| 971 |
+
|
| 972 |
+
@pytest.mark.parametrize(
|
| 973 |
+
"op", [operator.mul, ops.rmul, operator.truediv, ops.rdiv, ops.rsub]
|
| 974 |
+
)
|
| 975 |
+
@pytest.mark.parametrize(
|
| 976 |
+
"arr",
|
| 977 |
+
[
|
| 978 |
+
[Timestamp("20130101 9:01"), Timestamp("20121230 9:02")],
|
| 979 |
+
[Timestamp("2021-11-09 09:54:00"), Timedelta("1D")],
|
| 980 |
+
],
|
| 981 |
+
)
|
| 982 |
+
def test_td_op_timedelta_timedeltalike_array(self, op, arr):
|
| 983 |
+
arr = np.array(arr)
|
| 984 |
+
msg = "unsupported operand type|cannot use operands with types"
|
| 985 |
+
with pytest.raises(TypeError, match=msg):
|
| 986 |
+
op(arr, Timedelta("1D"))
|
| 987 |
+
|
| 988 |
+
def test_mul_bool_invalid(self):
|
| 989 |
+
# GH#62316
|
| 990 |
+
msg = "Cannot multiply Timedelta by bool. Explicitly cast to integer"
|
| 991 |
+
with pytest.raises(TypeError, match=msg):
|
| 992 |
+
Timedelta("1 day") * True
|
| 993 |
+
|
| 994 |
+
|
| 995 |
+
class TestTimedeltaComparison:
|
| 996 |
+
def test_compare_pytimedelta_bounds(self):
|
| 997 |
+
# GH#49021 don't overflow on comparison with very large pytimedeltas
|
| 998 |
+
|
| 999 |
+
for unit in ["ns", "us"]:
|
| 1000 |
+
tdmax = Timedelta.max.as_unit(unit).max
|
| 1001 |
+
tdmin = Timedelta.min.as_unit(unit).min
|
| 1002 |
+
|
| 1003 |
+
assert tdmax < timedelta.max
|
| 1004 |
+
assert tdmax <= timedelta.max
|
| 1005 |
+
assert not tdmax > timedelta.max
|
| 1006 |
+
assert not tdmax >= timedelta.max
|
| 1007 |
+
assert tdmax != timedelta.max
|
| 1008 |
+
assert not tdmax == timedelta.max
|
| 1009 |
+
|
| 1010 |
+
assert tdmin > timedelta.min
|
| 1011 |
+
assert tdmin >= timedelta.min
|
| 1012 |
+
assert not tdmin < timedelta.min
|
| 1013 |
+
assert not tdmin <= timedelta.min
|
| 1014 |
+
assert tdmin != timedelta.min
|
| 1015 |
+
assert not tdmin == timedelta.min
|
| 1016 |
+
|
| 1017 |
+
# But the "ms" and "s"-reso bounds extend pass pytimedelta
|
| 1018 |
+
for unit in ["ms", "s"]:
|
| 1019 |
+
tdmax = Timedelta.max.as_unit(unit).max
|
| 1020 |
+
tdmin = Timedelta.min.as_unit(unit).min
|
| 1021 |
+
|
| 1022 |
+
assert tdmax > timedelta.max
|
| 1023 |
+
assert tdmax >= timedelta.max
|
| 1024 |
+
assert not tdmax < timedelta.max
|
| 1025 |
+
assert not tdmax <= timedelta.max
|
| 1026 |
+
assert tdmax != timedelta.max
|
| 1027 |
+
assert not tdmax == timedelta.max
|
| 1028 |
+
|
| 1029 |
+
assert tdmin < timedelta.min
|
| 1030 |
+
assert tdmin <= timedelta.min
|
| 1031 |
+
assert not tdmin > timedelta.min
|
| 1032 |
+
assert not tdmin >= timedelta.min
|
| 1033 |
+
assert tdmin != timedelta.min
|
| 1034 |
+
assert not tdmin == timedelta.min
|
| 1035 |
+
|
| 1036 |
+
def test_compare_pytimedelta_bounds2(self):
|
| 1037 |
+
# a pytimedelta outside the microsecond bounds
|
| 1038 |
+
pytd = timedelta(days=999999999, seconds=86399)
|
| 1039 |
+
# NB: np.timedelta64(td, "s"") incorrectly overflows
|
| 1040 |
+
td64 = np.timedelta64(pytd.days, "D") + np.timedelta64(pytd.seconds, "s")
|
| 1041 |
+
td = Timedelta(td64)
|
| 1042 |
+
assert td.days == pytd.days
|
| 1043 |
+
assert td.seconds == pytd.seconds
|
| 1044 |
+
|
| 1045 |
+
assert td == pytd
|
| 1046 |
+
assert not td != pytd
|
| 1047 |
+
assert not td < pytd
|
| 1048 |
+
assert not td > pytd
|
| 1049 |
+
assert td <= pytd
|
| 1050 |
+
assert td >= pytd
|
| 1051 |
+
|
| 1052 |
+
td2 = td - Timedelta(seconds=1).as_unit("s")
|
| 1053 |
+
assert td2 != pytd
|
| 1054 |
+
assert not td2 == pytd
|
| 1055 |
+
assert td2 < pytd
|
| 1056 |
+
assert td2 <= pytd
|
| 1057 |
+
assert not td2 > pytd
|
| 1058 |
+
assert not td2 >= pytd
|
| 1059 |
+
|
| 1060 |
+
def test_compare_tick(self, tick_classes):
|
| 1061 |
+
cls = tick_classes
|
| 1062 |
+
|
| 1063 |
+
off = cls(4)
|
| 1064 |
+
td = off._as_pd_timedelta
|
| 1065 |
+
assert isinstance(td, Timedelta)
|
| 1066 |
+
|
| 1067 |
+
assert td == off
|
| 1068 |
+
assert not td != off
|
| 1069 |
+
assert td <= off
|
| 1070 |
+
assert td >= off
|
| 1071 |
+
assert not td < off
|
| 1072 |
+
assert not td > off
|
| 1073 |
+
|
| 1074 |
+
assert not td == 2 * off
|
| 1075 |
+
assert td != 2 * off
|
| 1076 |
+
assert td <= 2 * off
|
| 1077 |
+
assert td < 2 * off
|
| 1078 |
+
assert not td >= 2 * off
|
| 1079 |
+
assert not td > 2 * off
|
| 1080 |
+
|
| 1081 |
+
def test_comparison_object_array(self):
|
| 1082 |
+
# analogous to GH#15183
|
| 1083 |
+
td = Timedelta("2 days")
|
| 1084 |
+
other = Timedelta("3 hours")
|
| 1085 |
+
|
| 1086 |
+
arr = np.array([other, td], dtype=object)
|
| 1087 |
+
res = arr == td
|
| 1088 |
+
expected = np.array([False, True], dtype=bool)
|
| 1089 |
+
assert (res == expected).all()
|
| 1090 |
+
|
| 1091 |
+
# 2D case
|
| 1092 |
+
arr = np.array([[other, td], [td, other]], dtype=object)
|
| 1093 |
+
res = arr != td
|
| 1094 |
+
expected = np.array([[True, False], [False, True]], dtype=bool)
|
| 1095 |
+
assert res.shape == expected.shape
|
| 1096 |
+
assert (res == expected).all()
|
| 1097 |
+
|
| 1098 |
+
def test_compare_timedelta_ndarray(self):
|
| 1099 |
+
# GH#11835
|
| 1100 |
+
periods = [Timedelta("0 days 01:00:00"), Timedelta("0 days 01:00:00")]
|
| 1101 |
+
arr = np.array(periods)
|
| 1102 |
+
result = arr[0] > arr
|
| 1103 |
+
expected = np.array([False, False])
|
| 1104 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 1105 |
+
|
| 1106 |
+
def test_compare_td64_ndarray(self):
|
| 1107 |
+
# GG#33441
|
| 1108 |
+
arr = np.arange(5).astype("timedelta64[ns]")
|
| 1109 |
+
td = Timedelta(arr[1])
|
| 1110 |
+
|
| 1111 |
+
expected = np.array([False, True, False, False, False], dtype=bool)
|
| 1112 |
+
|
| 1113 |
+
result = td == arr
|
| 1114 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 1115 |
+
|
| 1116 |
+
result = arr == td
|
| 1117 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 1118 |
+
|
| 1119 |
+
result = td != arr
|
| 1120 |
+
tm.assert_numpy_array_equal(result, ~expected)
|
| 1121 |
+
|
| 1122 |
+
result = arr != td
|
| 1123 |
+
tm.assert_numpy_array_equal(result, ~expected)
|
| 1124 |
+
|
| 1125 |
+
def test_compare_custom_object(self):
|
| 1126 |
+
"""
|
| 1127 |
+
Make sure non supported operations on Timedelta returns NonImplemented
|
| 1128 |
+
and yields to other operand (GH#20829).
|
| 1129 |
+
"""
|
| 1130 |
+
|
| 1131 |
+
class CustomClass:
|
| 1132 |
+
def __init__(self, cmp_result=None) -> None:
|
| 1133 |
+
self.cmp_result = cmp_result
|
| 1134 |
+
|
| 1135 |
+
def generic_result(self):
|
| 1136 |
+
if self.cmp_result is None:
|
| 1137 |
+
return NotImplemented
|
| 1138 |
+
else:
|
| 1139 |
+
return self.cmp_result
|
| 1140 |
+
|
| 1141 |
+
def __eq__(self, other):
|
| 1142 |
+
return self.generic_result()
|
| 1143 |
+
|
| 1144 |
+
def __gt__(self, other):
|
| 1145 |
+
return self.generic_result()
|
| 1146 |
+
|
| 1147 |
+
t = Timedelta("1s")
|
| 1148 |
+
|
| 1149 |
+
assert t != "string"
|
| 1150 |
+
assert t != 1
|
| 1151 |
+
assert t != CustomClass()
|
| 1152 |
+
assert t != CustomClass(cmp_result=False)
|
| 1153 |
+
|
| 1154 |
+
assert t < CustomClass(cmp_result=True)
|
| 1155 |
+
assert not t < CustomClass(cmp_result=False)
|
| 1156 |
+
|
| 1157 |
+
assert t == CustomClass(cmp_result=True)
|
| 1158 |
+
|
| 1159 |
+
@pytest.mark.parametrize("val", ["string", 1])
|
| 1160 |
+
def test_compare_unknown_type(self, val):
|
| 1161 |
+
# GH#20829
|
| 1162 |
+
t = Timedelta("1s")
|
| 1163 |
+
msg = "not supported between instances of 'Timedelta' and '(int|str)'"
|
| 1164 |
+
with pytest.raises(TypeError, match=msg):
|
| 1165 |
+
t >= val
|
| 1166 |
+
with pytest.raises(TypeError, match=msg):
|
| 1167 |
+
t > val
|
| 1168 |
+
with pytest.raises(TypeError, match=msg):
|
| 1169 |
+
t <= val
|
| 1170 |
+
with pytest.raises(TypeError, match=msg):
|
| 1171 |
+
t < val
|
| 1172 |
+
|
| 1173 |
+
|
| 1174 |
+
def test_ops_notimplemented():
|
| 1175 |
+
class Other:
|
| 1176 |
+
pass
|
| 1177 |
+
|
| 1178 |
+
other = Other()
|
| 1179 |
+
|
| 1180 |
+
td = Timedelta("1 day")
|
| 1181 |
+
assert td.__add__(other) is NotImplemented
|
| 1182 |
+
assert td.__sub__(other) is NotImplemented
|
| 1183 |
+
assert td.__truediv__(other) is NotImplemented
|
| 1184 |
+
assert td.__mul__(other) is NotImplemented
|
| 1185 |
+
assert td.__floordiv__(other) is NotImplemented
|
| 1186 |
+
|
| 1187 |
+
|
| 1188 |
+
def test_ops_error_str():
|
| 1189 |
+
# GH#13624
|
| 1190 |
+
td = Timedelta("1 day")
|
| 1191 |
+
|
| 1192 |
+
for left, right in [(td, "a"), ("a", td)]:
|
| 1193 |
+
msg = "|".join(
|
| 1194 |
+
[
|
| 1195 |
+
"unsupported operand type",
|
| 1196 |
+
r'can only concatenate str \(not "Timedelta"\) to str',
|
| 1197 |
+
"must be str, not Timedelta",
|
| 1198 |
+
]
|
| 1199 |
+
)
|
| 1200 |
+
with pytest.raises(TypeError, match=msg):
|
| 1201 |
+
left + right
|
| 1202 |
+
|
| 1203 |
+
msg = "not supported between instances of"
|
| 1204 |
+
with pytest.raises(TypeError, match=msg):
|
| 1205 |
+
left > right
|
| 1206 |
+
|
| 1207 |
+
assert not left == right
|
| 1208 |
+
assert left != right
|
| 1209 |
+
|
| 1210 |
+
|
| 1211 |
+
@pytest.mark.parametrize("box", [True, False])
|
| 1212 |
+
def test_ops_str_deprecated(box):
|
| 1213 |
+
# GH#59653
|
| 1214 |
+
td = Timedelta("1 day")
|
| 1215 |
+
item = "1"
|
| 1216 |
+
if box:
|
| 1217 |
+
item = np.array([item], dtype=object)
|
| 1218 |
+
|
| 1219 |
+
msg = "Scalar operations between Timedelta and string are deprecated"
|
| 1220 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1221 |
+
td + item
|
| 1222 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1223 |
+
item + td
|
| 1224 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1225 |
+
td - item
|
| 1226 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1227 |
+
item - td
|
| 1228 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1229 |
+
item / td
|
| 1230 |
+
if not box:
|
| 1231 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1232 |
+
td / item
|
| 1233 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1234 |
+
item // td
|
| 1235 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 1236 |
+
td // item
|
| 1237 |
+
else:
|
| 1238 |
+
msg = "|".join(
|
| 1239 |
+
[
|
| 1240 |
+
"ufunc 'divide' cannot use operands",
|
| 1241 |
+
"Invalid dtype object for __floordiv__",
|
| 1242 |
+
r"unsupported operand type\(s\) for /: 'int' and 'str'",
|
| 1243 |
+
r"unsupported operand type\(s\) for /: 'datetime.timedelta' and 'str'",
|
| 1244 |
+
]
|
| 1245 |
+
)
|
| 1246 |
+
with pytest.raises(TypeError, match=msg):
|
| 1247 |
+
td / item
|
| 1248 |
+
with pytest.raises(TypeError, match=msg):
|
| 1249 |
+
item // td
|
| 1250 |
+
with pytest.raises(TypeError, match=msg):
|
| 1251 |
+
td // item
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_constructors.py
ADDED
|
@@ -0,0 +1,795 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import timedelta
|
| 2 |
+
from itertools import product
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pytest
|
| 6 |
+
|
| 7 |
+
from pandas._libs.tslibs import OutOfBoundsTimedelta
|
| 8 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 9 |
+
from pandas.compat.numpy import (
|
| 10 |
+
is_numpy_dev,
|
| 11 |
+
np_version_gt2_5,
|
| 12 |
+
)
|
| 13 |
+
from pandas.errors import Pandas4Warning
|
| 14 |
+
|
| 15 |
+
from pandas import (
|
| 16 |
+
Index,
|
| 17 |
+
NaT,
|
| 18 |
+
Timedelta,
|
| 19 |
+
TimedeltaIndex,
|
| 20 |
+
offsets,
|
| 21 |
+
to_timedelta,
|
| 22 |
+
)
|
| 23 |
+
import pandas._testing as tm
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TestTimedeltaConstructorKeywordBased:
|
| 27 |
+
# Tests for constructing a Timedelta from keywords like the pytimedelta
|
| 28 |
+
# base class
|
| 29 |
+
def test_nanosecond_keyword(self):
|
| 30 |
+
# GH#63216
|
| 31 |
+
td = Timedelta(nanoseconds=1000)
|
| 32 |
+
assert td.unit == "ns"
|
| 33 |
+
|
| 34 |
+
def test_noninteger_microseconds(self):
|
| 35 |
+
# GH#63216
|
| 36 |
+
td = Timedelta(microseconds=1.5)
|
| 37 |
+
assert td.unit == "ns"
|
| 38 |
+
assert td == Timedelta(nanoseconds=1500)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class TestTimedeltaConstructorUnitKeyword:
|
| 42 |
+
def test_result_unit(self):
|
| 43 |
+
# For supported units, we get result.unit == unit
|
| 44 |
+
for unit in ["s", "ms", "us", "ns"]:
|
| 45 |
+
td = Timedelta(1, unit=unit)
|
| 46 |
+
assert td.unit == unit
|
| 47 |
+
|
| 48 |
+
td = to_timedelta(1, unit=unit)
|
| 49 |
+
assert td.unit == unit
|
| 50 |
+
|
| 51 |
+
# For non-supported units we get the closest-supported unit
|
| 52 |
+
for unit in ["W", "D", "h", "m"]:
|
| 53 |
+
td = Timedelta(1, unit=unit)
|
| 54 |
+
assert td.unit == "s"
|
| 55 |
+
|
| 56 |
+
td = to_timedelta(1, unit=unit)
|
| 57 |
+
assert td.unit == "s"
|
| 58 |
+
|
| 59 |
+
@pytest.mark.parametrize("unit", ["Y", "y", "M"])
|
| 60 |
+
def test_unit_m_y_raises(self, unit):
|
| 61 |
+
msg = "Units 'M', 'Y', and 'y' are no longer supported"
|
| 62 |
+
|
| 63 |
+
with pytest.raises(ValueError, match=msg):
|
| 64 |
+
Timedelta(10, unit)
|
| 65 |
+
|
| 66 |
+
with pytest.raises(ValueError, match=msg):
|
| 67 |
+
to_timedelta(10, unit)
|
| 68 |
+
|
| 69 |
+
with pytest.raises(ValueError, match=msg):
|
| 70 |
+
to_timedelta([1, 2], unit)
|
| 71 |
+
|
| 72 |
+
@pytest.mark.parametrize(
|
| 73 |
+
"unit,unit_depr",
|
| 74 |
+
[
|
| 75 |
+
("W", "w"),
|
| 76 |
+
("D", "d"),
|
| 77 |
+
("min", "MIN"),
|
| 78 |
+
("s", "S"),
|
| 79 |
+
("h", "H"),
|
| 80 |
+
("ms", "MS"),
|
| 81 |
+
("us", "US"),
|
| 82 |
+
],
|
| 83 |
+
)
|
| 84 |
+
def test_unit_deprecated(self, unit, unit_depr):
|
| 85 |
+
# GH#52536
|
| 86 |
+
msg = f"'{unit_depr}' is deprecated and will be removed in a future version."
|
| 87 |
+
|
| 88 |
+
expected = Timedelta(1, unit=unit)
|
| 89 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 90 |
+
result = Timedelta(1, unit=unit_depr)
|
| 91 |
+
tm.assert_equal(result, expected)
|
| 92 |
+
|
| 93 |
+
@pytest.mark.parametrize(
|
| 94 |
+
"unit, np_unit",
|
| 95 |
+
[("W", "W")]
|
| 96 |
+
+ [(value, "D") for value in ["D", "days", "day", "Days", "Day"]]
|
| 97 |
+
+ [
|
| 98 |
+
(value, "m")
|
| 99 |
+
for value in [
|
| 100 |
+
"m",
|
| 101 |
+
"minute",
|
| 102 |
+
"min",
|
| 103 |
+
"minutes",
|
| 104 |
+
"Minute",
|
| 105 |
+
"Min",
|
| 106 |
+
"Minutes",
|
| 107 |
+
]
|
| 108 |
+
]
|
| 109 |
+
+ [
|
| 110 |
+
(value, "s")
|
| 111 |
+
for value in [
|
| 112 |
+
"s",
|
| 113 |
+
"seconds",
|
| 114 |
+
"sec",
|
| 115 |
+
"second",
|
| 116 |
+
"Seconds",
|
| 117 |
+
"Sec",
|
| 118 |
+
"Second",
|
| 119 |
+
]
|
| 120 |
+
]
|
| 121 |
+
+ [
|
| 122 |
+
(value, "ms")
|
| 123 |
+
for value in [
|
| 124 |
+
"ms",
|
| 125 |
+
"milliseconds",
|
| 126 |
+
"millisecond",
|
| 127 |
+
"milli",
|
| 128 |
+
"millis",
|
| 129 |
+
"Milliseconds",
|
| 130 |
+
"Millisecond",
|
| 131 |
+
"Milli",
|
| 132 |
+
"Millis",
|
| 133 |
+
]
|
| 134 |
+
]
|
| 135 |
+
+ [
|
| 136 |
+
(value, "us")
|
| 137 |
+
for value in [
|
| 138 |
+
"us",
|
| 139 |
+
"microseconds",
|
| 140 |
+
"microsecond",
|
| 141 |
+
"micro",
|
| 142 |
+
"micros",
|
| 143 |
+
"Microseconds",
|
| 144 |
+
"Microsecond",
|
| 145 |
+
"Micro",
|
| 146 |
+
"Micros",
|
| 147 |
+
]
|
| 148 |
+
]
|
| 149 |
+
+ [
|
| 150 |
+
(value, "ns")
|
| 151 |
+
for value in [
|
| 152 |
+
"ns",
|
| 153 |
+
"nanoseconds",
|
| 154 |
+
"nanosecond",
|
| 155 |
+
"nano",
|
| 156 |
+
"nanos",
|
| 157 |
+
"Nanoseconds",
|
| 158 |
+
"Nanosecond",
|
| 159 |
+
"Nano",
|
| 160 |
+
"Nanos",
|
| 161 |
+
]
|
| 162 |
+
],
|
| 163 |
+
)
|
| 164 |
+
@pytest.mark.parametrize("wrapper", [np.array, list, Index])
|
| 165 |
+
def test_unit_parser(self, unit, np_unit, wrapper):
|
| 166 |
+
# validate all units, GH 6855, GH 21762
|
| 167 |
+
# array-likes
|
| 168 |
+
exp_unit = np_unit if np_unit not in ["W", "D", "m"] else "s"
|
| 169 |
+
expected = TimedeltaIndex(
|
| 170 |
+
[np.timedelta64(i, np_unit) for i in np.arange(5).tolist()],
|
| 171 |
+
dtype=f"m8[{exp_unit}]",
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
result = to_timedelta(wrapper(range(5)), unit=unit)
|
| 175 |
+
tm.assert_index_equal(result, expected)
|
| 176 |
+
|
| 177 |
+
str_repr = [f"{x}{unit}" for x in np.arange(5)]
|
| 178 |
+
exp_unit = "us" if np_unit != "ns" else "ns"
|
| 179 |
+
result = to_timedelta(wrapper(str_repr))
|
| 180 |
+
tm.assert_index_equal(result, expected.as_unit(exp_unit))
|
| 181 |
+
result = to_timedelta(wrapper(str_repr))
|
| 182 |
+
tm.assert_index_equal(result, expected.as_unit(exp_unit))
|
| 183 |
+
|
| 184 |
+
# scalar
|
| 185 |
+
expected = Timedelta(np.timedelta64(2, np_unit)).as_unit(exp_unit)
|
| 186 |
+
result = to_timedelta(2, unit=unit)
|
| 187 |
+
assert result == expected
|
| 188 |
+
result = Timedelta(2, unit=unit)
|
| 189 |
+
assert result == expected
|
| 190 |
+
|
| 191 |
+
result = to_timedelta(f"2{unit}")
|
| 192 |
+
assert result == expected
|
| 193 |
+
result = Timedelta(f"2{unit}")
|
| 194 |
+
assert result == expected
|
| 195 |
+
|
| 196 |
+
@pytest.mark.parametrize("unit", ["T", "t", "L", "l", "U", "u", "N", "n"])
|
| 197 |
+
def test_unit_T_L_N_U_raises(self, unit):
|
| 198 |
+
msg = f"invalid unit abbreviation: {unit}"
|
| 199 |
+
with pytest.raises(ValueError, match=msg):
|
| 200 |
+
Timedelta(1, unit=unit)
|
| 201 |
+
|
| 202 |
+
with pytest.raises(ValueError, match=msg):
|
| 203 |
+
to_timedelta(10, unit)
|
| 204 |
+
|
| 205 |
+
with pytest.raises(ValueError, match=msg):
|
| 206 |
+
to_timedelta([1, 2], unit)
|
| 207 |
+
|
| 208 |
+
def test_unit_round_float(self):
|
| 209 |
+
# When the float is round, we give the requested unit
|
| 210 |
+
# (or nearest-supported) like we do with integers
|
| 211 |
+
td = Timedelta(45.0, unit="s")
|
| 212 |
+
assert td.unit == "s"
|
| 213 |
+
assert td == Timedelta(45, unit="s")
|
| 214 |
+
|
| 215 |
+
td = to_timedelta(45.0, unit="s")
|
| 216 |
+
assert td.unit == "s"
|
| 217 |
+
assert td == Timedelta(45, unit="s")
|
| 218 |
+
|
| 219 |
+
def test_unit_non_round_float(self):
|
| 220 |
+
# With non-round floats, we have to give nanosecond
|
| 221 |
+
td = Timedelta(45.5, unit="s")
|
| 222 |
+
assert td.unit == "ns"
|
| 223 |
+
assert td == Timedelta(45_500, unit="ms")
|
| 224 |
+
|
| 225 |
+
td = to_timedelta(45.5, unit="s")
|
| 226 |
+
assert td.unit == "ns"
|
| 227 |
+
assert td == Timedelta(45_500, unit="ms")
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def test_construct_from_kwargs_overflow():
|
| 231 |
+
# GH#55503
|
| 232 |
+
msg = "seconds=86400000000000000000, milliseconds=0, microseconds=0, nanoseconds=0"
|
| 233 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 234 |
+
Timedelta(days=10**6)
|
| 235 |
+
msg = "seconds=60000000000000000000, milliseconds=0, microseconds=0, nanoseconds=0"
|
| 236 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 237 |
+
Timedelta(minutes=10**9)
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def test_construct_with_weeks_unit_overflow():
|
| 241 |
+
# GH#47268 don't silently wrap around
|
| 242 |
+
msg = "1000000000000000000 weeks"
|
| 243 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 244 |
+
Timedelta(1000000000000000000, unit="W")
|
| 245 |
+
|
| 246 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 247 |
+
Timedelta(1000000000000000000.0, unit="W")
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def test_construct_from_td64_with_unit():
|
| 251 |
+
# ignore the unit, as it may cause silently overflows leading to incorrect
|
| 252 |
+
# results, and in non-overflow cases is irrelevant GH#46827
|
| 253 |
+
obj = np.timedelta64(123456789000000000, "h")
|
| 254 |
+
|
| 255 |
+
msg = "The 'unit' keyword is only used when the Timedelta input is"
|
| 256 |
+
|
| 257 |
+
with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
|
| 258 |
+
with tm.assert_produces_warning(UserWarning, match=msg):
|
| 259 |
+
Timedelta(obj, unit="ps")
|
| 260 |
+
|
| 261 |
+
with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
|
| 262 |
+
with tm.assert_produces_warning(UserWarning, match=msg):
|
| 263 |
+
Timedelta(obj, unit="ns")
|
| 264 |
+
|
| 265 |
+
with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
|
| 266 |
+
Timedelta(obj)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def test_from_td64_retain_resolution():
|
| 270 |
+
# case where we retain millisecond resolution
|
| 271 |
+
obj = np.timedelta64(12345, "ms")
|
| 272 |
+
|
| 273 |
+
td = Timedelta(obj)
|
| 274 |
+
assert td._value == obj.view("i8")
|
| 275 |
+
assert td._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 276 |
+
|
| 277 |
+
# Case where we cast to nearest-supported reso
|
| 278 |
+
obj2 = np.timedelta64(1234, "D")
|
| 279 |
+
td2 = Timedelta(obj2)
|
| 280 |
+
assert td2._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 281 |
+
assert td2 == obj2
|
| 282 |
+
assert td2.days == 1234
|
| 283 |
+
|
| 284 |
+
# Case that _would_ overflow if we didn't support non-nano
|
| 285 |
+
obj3 = np.timedelta64(1000000000000000000, "us")
|
| 286 |
+
td3 = Timedelta(obj3)
|
| 287 |
+
assert td3.total_seconds() == 1000000000000
|
| 288 |
+
assert td3._creso == NpyDatetimeUnit.NPY_FR_us.value
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def test_from_pytimedelta_us_reso():
|
| 292 |
+
# pytimedelta has microsecond resolution, so Timedelta(pytd) inherits that
|
| 293 |
+
td = timedelta(days=4, minutes=3)
|
| 294 |
+
result = Timedelta(td)
|
| 295 |
+
assert result.to_pytimedelta() == td
|
| 296 |
+
assert result._creso == NpyDatetimeUnit.NPY_FR_us.value
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def test_from_tick_reso():
|
| 300 |
+
tick = offsets.Nano()
|
| 301 |
+
assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 302 |
+
|
| 303 |
+
tick = offsets.Micro()
|
| 304 |
+
assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_us.value
|
| 305 |
+
|
| 306 |
+
tick = offsets.Milli()
|
| 307 |
+
assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 308 |
+
|
| 309 |
+
tick = offsets.Second()
|
| 310 |
+
assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 311 |
+
|
| 312 |
+
# everything above Second gets cast to the closest supported reso: second
|
| 313 |
+
tick = offsets.Minute()
|
| 314 |
+
assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 315 |
+
|
| 316 |
+
tick = offsets.Hour()
|
| 317 |
+
assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 318 |
+
|
| 319 |
+
tick = offsets.Day()
|
| 320 |
+
msg = (
|
| 321 |
+
"Value must be Timedelta, string, integer, float, timedelta "
|
| 322 |
+
"or convertible, not Day"
|
| 323 |
+
)
|
| 324 |
+
with pytest.raises(ValueError, match=msg):
|
| 325 |
+
# GH#41943 Day is no longer a Tick
|
| 326 |
+
Timedelta(tick)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def test_construction():
|
| 330 |
+
expected = np.timedelta64(10, "D").astype("m8[ns]").view("i8")
|
| 331 |
+
assert Timedelta(10, unit="D")._value == expected // 10**9
|
| 332 |
+
assert Timedelta(10.0, unit="D")._value == expected // 10**9
|
| 333 |
+
assert Timedelta("10 days")._value == expected // 1000
|
| 334 |
+
assert Timedelta(days=10)._value == expected // 1000
|
| 335 |
+
assert Timedelta(days=10.0)._value == expected // 1000
|
| 336 |
+
|
| 337 |
+
expected += np.timedelta64(10, "s").astype("m8[ns]").view("i8")
|
| 338 |
+
assert Timedelta("10 days 00:00:10")._value == expected // 1000
|
| 339 |
+
assert Timedelta(days=10, seconds=10)._value == expected // 1000
|
| 340 |
+
assert Timedelta(days=10, milliseconds=10 * 1000)._value == expected // 1000
|
| 341 |
+
assert Timedelta(days=10, microseconds=10 * 1000 * 1000)._value == expected // 1000
|
| 342 |
+
|
| 343 |
+
# rounding cases
|
| 344 |
+
assert Timedelta(82739999850000)._value == 82739999850000
|
| 345 |
+
assert "0 days 22:58:59.999850" in str(Timedelta(82739999850000))
|
| 346 |
+
assert Timedelta(123072001000000)._value == 123072001000000
|
| 347 |
+
assert "1 days 10:11:12.001" in str(Timedelta(123072001000000))
|
| 348 |
+
|
| 349 |
+
# string conversion with/without leading zero
|
| 350 |
+
# GH#9570
|
| 351 |
+
assert Timedelta("0:00:00") == timedelta(hours=0)
|
| 352 |
+
assert Timedelta("00:00:00") == timedelta(hours=0)
|
| 353 |
+
assert Timedelta("-1:00:00") == -timedelta(hours=1)
|
| 354 |
+
assert Timedelta("-01:00:00") == -timedelta(hours=1)
|
| 355 |
+
|
| 356 |
+
# more strings & abbrevs
|
| 357 |
+
# GH#8190
|
| 358 |
+
assert Timedelta("1 h") == timedelta(hours=1)
|
| 359 |
+
assert Timedelta("1 hour") == timedelta(hours=1)
|
| 360 |
+
assert Timedelta("1 hr") == timedelta(hours=1)
|
| 361 |
+
assert Timedelta("1 hours") == timedelta(hours=1)
|
| 362 |
+
assert Timedelta("-1 hours") == -timedelta(hours=1)
|
| 363 |
+
assert Timedelta("1 m") == timedelta(minutes=1)
|
| 364 |
+
assert Timedelta("1.5 m") == timedelta(seconds=90)
|
| 365 |
+
assert Timedelta("1 minute") == timedelta(minutes=1)
|
| 366 |
+
assert Timedelta("1 minutes") == timedelta(minutes=1)
|
| 367 |
+
assert Timedelta("1 s") == timedelta(seconds=1)
|
| 368 |
+
assert Timedelta("1 second") == timedelta(seconds=1)
|
| 369 |
+
assert Timedelta("1 seconds") == timedelta(seconds=1)
|
| 370 |
+
assert Timedelta("1 ms") == timedelta(milliseconds=1)
|
| 371 |
+
assert Timedelta("1 milli") == timedelta(milliseconds=1)
|
| 372 |
+
assert Timedelta("1 millisecond") == timedelta(milliseconds=1)
|
| 373 |
+
assert Timedelta("1 us") == timedelta(microseconds=1)
|
| 374 |
+
assert Timedelta("1 µs") == timedelta(microseconds=1)
|
| 375 |
+
assert Timedelta("1 micros") == timedelta(microseconds=1)
|
| 376 |
+
assert Timedelta("1 microsecond") == timedelta(microseconds=1)
|
| 377 |
+
assert Timedelta("1.5 microsecond") == Timedelta("00:00:00.000001500")
|
| 378 |
+
assert Timedelta("1 ns") == Timedelta("00:00:00.000000001")
|
| 379 |
+
assert Timedelta("1 nano") == Timedelta("00:00:00.000000001")
|
| 380 |
+
assert Timedelta("1 nanosecond") == Timedelta("00:00:00.000000001")
|
| 381 |
+
|
| 382 |
+
# combos
|
| 383 |
+
assert Timedelta("10 days 1 hour") == timedelta(days=10, hours=1)
|
| 384 |
+
assert Timedelta("10 days 1 h") == timedelta(days=10, hours=1)
|
| 385 |
+
assert Timedelta("10 days 1 h 1m 1s") == timedelta(
|
| 386 |
+
days=10, hours=1, minutes=1, seconds=1
|
| 387 |
+
)
|
| 388 |
+
assert Timedelta("-10 days 1 h 1m 1s") == -timedelta(
|
| 389 |
+
days=10, hours=1, minutes=1, seconds=1
|
| 390 |
+
)
|
| 391 |
+
assert Timedelta("-10 days 1 h 1m 1s") == -timedelta(
|
| 392 |
+
days=10, hours=1, minutes=1, seconds=1
|
| 393 |
+
)
|
| 394 |
+
assert Timedelta("-10 days 1 h 1m 1s 3us") == -timedelta(
|
| 395 |
+
days=10, hours=1, minutes=1, seconds=1, microseconds=3
|
| 396 |
+
)
|
| 397 |
+
assert Timedelta("-10 days 1 h 1.5m 1s 3us") == -timedelta(
|
| 398 |
+
days=10, hours=1, minutes=1, seconds=31, microseconds=3
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
# Currently invalid as it has a - on the hh:mm:dd part
|
| 402 |
+
# (only allowed on the days)
|
| 403 |
+
msg = "only leading negative signs are allowed"
|
| 404 |
+
with pytest.raises(ValueError, match=msg):
|
| 405 |
+
Timedelta("-10 days -1 h 1.5m 1s 3us")
|
| 406 |
+
|
| 407 |
+
# only leading neg signs are allowed
|
| 408 |
+
with pytest.raises(ValueError, match=msg):
|
| 409 |
+
Timedelta("10 days -1 h 1.5m 1s 3us")
|
| 410 |
+
|
| 411 |
+
# no units specified
|
| 412 |
+
msg = "no units specified"
|
| 413 |
+
with pytest.raises(ValueError, match=msg):
|
| 414 |
+
Timedelta("3.1415")
|
| 415 |
+
|
| 416 |
+
# invalid construction
|
| 417 |
+
msg = "cannot construct a Timedelta"
|
| 418 |
+
with pytest.raises(ValueError, match=msg):
|
| 419 |
+
Timedelta()
|
| 420 |
+
|
| 421 |
+
msg = "unit abbreviation w/o a number"
|
| 422 |
+
with pytest.raises(ValueError, match=msg):
|
| 423 |
+
Timedelta("foo")
|
| 424 |
+
|
| 425 |
+
msg = (
|
| 426 |
+
"cannot construct a Timedelta from the passed arguments, allowed keywords are "
|
| 427 |
+
)
|
| 428 |
+
with pytest.raises(ValueError, match=msg):
|
| 429 |
+
Timedelta(day=10)
|
| 430 |
+
|
| 431 |
+
# floats
|
| 432 |
+
expected = np.timedelta64(10, "s").astype("m8[ns]").view("i8") + np.timedelta64(
|
| 433 |
+
500, "ms"
|
| 434 |
+
).astype("m8[ns]").view("i8")
|
| 435 |
+
assert Timedelta(10.5, unit="s")._value == expected
|
| 436 |
+
|
| 437 |
+
# offset
|
| 438 |
+
assert to_timedelta(offsets.Hour(2)) == Timedelta(hours=2)
|
| 439 |
+
assert Timedelta(offsets.Hour(2)) == Timedelta(hours=2)
|
| 440 |
+
assert Timedelta(offsets.Second(2)) == Timedelta(seconds=2)
|
| 441 |
+
|
| 442 |
+
# GH#11995: unicode
|
| 443 |
+
expected = Timedelta("1h")
|
| 444 |
+
result = Timedelta("1h")
|
| 445 |
+
assert result == expected
|
| 446 |
+
assert to_timedelta(offsets.Hour(2)) == Timedelta("0 days, 02:00:00")
|
| 447 |
+
|
| 448 |
+
msg = "unit abbreviation w/o a number"
|
| 449 |
+
with pytest.raises(ValueError, match=msg):
|
| 450 |
+
Timedelta("foo bar")
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
@pytest.mark.parametrize(
|
| 454 |
+
"item",
|
| 455 |
+
list(
|
| 456 |
+
{
|
| 457 |
+
"days": "D",
|
| 458 |
+
"seconds": "s",
|
| 459 |
+
"microseconds": "us",
|
| 460 |
+
"milliseconds": "ms",
|
| 461 |
+
"minutes": "m",
|
| 462 |
+
"hours": "h",
|
| 463 |
+
"weeks": "W",
|
| 464 |
+
}.items()
|
| 465 |
+
),
|
| 466 |
+
)
|
| 467 |
+
@pytest.mark.parametrize(
|
| 468 |
+
"npdtype", [np.int64, np.int32, np.int16, np.float64, np.float32, np.float16]
|
| 469 |
+
)
|
| 470 |
+
def test_td_construction_with_np_dtypes(npdtype, item):
|
| 471 |
+
# GH#8757: test construction with np dtypes
|
| 472 |
+
pykwarg, npkwarg = item
|
| 473 |
+
expected = np.timedelta64(1, npkwarg).astype("m8[us]").view("i8")
|
| 474 |
+
assert Timedelta(**{pykwarg: npdtype(1)})._value == expected
|
| 475 |
+
|
| 476 |
+
|
| 477 |
+
@pytest.mark.parametrize(
|
| 478 |
+
"val",
|
| 479 |
+
[
|
| 480 |
+
"1s",
|
| 481 |
+
"-1s",
|
| 482 |
+
"1us",
|
| 483 |
+
"-1us",
|
| 484 |
+
"1 day",
|
| 485 |
+
"-1 day",
|
| 486 |
+
"-23:59:59.999999",
|
| 487 |
+
"-1 days +23:59:59.999999",
|
| 488 |
+
"-1ns",
|
| 489 |
+
"1ns",
|
| 490 |
+
"-23:59:59.999999999",
|
| 491 |
+
],
|
| 492 |
+
)
|
| 493 |
+
def test_td_from_repr_roundtrip(val):
|
| 494 |
+
# round-trip both for string and value
|
| 495 |
+
td = Timedelta(val)
|
| 496 |
+
assert Timedelta(td.value) == td
|
| 497 |
+
|
| 498 |
+
assert Timedelta(str(td)) == td
|
| 499 |
+
assert Timedelta(td._repr_base(format="all")) == td
|
| 500 |
+
assert Timedelta(td._repr_base()) == td
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
def test_overflow_on_construction():
|
| 504 |
+
# GH#3374
|
| 505 |
+
value = Timedelta("1day").as_unit("ns")._value * 20169940
|
| 506 |
+
msg = "Cannot cast 1742682816000000000000 from ns to 'ns' without overflow"
|
| 507 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 508 |
+
Timedelta(value)
|
| 509 |
+
|
| 510 |
+
# xref GH#17637
|
| 511 |
+
# used to overflows before we changed output unit to "s"
|
| 512 |
+
td = Timedelta(7 * 19999, unit="D")
|
| 513 |
+
assert td.unit == "s"
|
| 514 |
+
|
| 515 |
+
# used to overflow before non-ns support
|
| 516 |
+
td = Timedelta(timedelta(days=13 * 19999))
|
| 517 |
+
assert td._creso == NpyDatetimeUnit.NPY_FR_us.value
|
| 518 |
+
assert td.days == 13 * 19999
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
@pytest.mark.parametrize(
|
| 522 |
+
"val, unit",
|
| 523 |
+
[
|
| 524 |
+
(15251, "W"), # 1
|
| 525 |
+
(106752, "D"), # change from previous:
|
| 526 |
+
(2562048, "h"), # 0 hours
|
| 527 |
+
(153722868, "m"), # 13 minutes
|
| 528 |
+
(9223372037, "s"), # 44 seconds
|
| 529 |
+
],
|
| 530 |
+
)
|
| 531 |
+
def test_construction_out_of_bounds_td64ns(val, unit):
|
| 532 |
+
# TODO: parametrize over units just above/below the implementation bounds
|
| 533 |
+
# once GH#38964 is resolved
|
| 534 |
+
|
| 535 |
+
# Timedelta.max is just under 106752 days
|
| 536 |
+
td64 = np.timedelta64(val, unit)
|
| 537 |
+
if is_numpy_dev or np_version_gt2_5:
|
| 538 |
+
# numpy now raises instead of silently overflowing
|
| 539 |
+
with pytest.raises(OverflowError, match="Overflow"):
|
| 540 |
+
td64.astype("m8[ns]")
|
| 541 |
+
else:
|
| 542 |
+
assert td64.astype("m8[ns]").view("i8") < 0 # i.e. naive astype will be wrong
|
| 543 |
+
|
| 544 |
+
td = Timedelta(td64)
|
| 545 |
+
if unit != "M":
|
| 546 |
+
# with unit="M" the conversion to "s" is poorly defined
|
| 547 |
+
# (and numpy issues DeprecationWarning)
|
| 548 |
+
assert td.asm8 == td64
|
| 549 |
+
assert td.asm8.dtype == "m8[s]"
|
| 550 |
+
msg = r"Cannot cast 1067\d\d days .* to unit='ns' without overflow"
|
| 551 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 552 |
+
td.as_unit("ns")
|
| 553 |
+
|
| 554 |
+
# But just back in bounds and we are OK
|
| 555 |
+
one = np.timedelta64(1, unit)
|
| 556 |
+
assert Timedelta(td64 - one) == td64 - one
|
| 557 |
+
|
| 558 |
+
td64 = np.timedelta64(-val, unit)
|
| 559 |
+
if is_numpy_dev or np_version_gt2_5:
|
| 560 |
+
with pytest.raises(OverflowError, match="Overflow"):
|
| 561 |
+
td64.astype("m8[ns]")
|
| 562 |
+
else:
|
| 563 |
+
assert td64.astype("m8[ns]").view("i8") > 0 # i.e. naive astype will be wrong
|
| 564 |
+
|
| 565 |
+
td2 = Timedelta(td64)
|
| 566 |
+
msg = r"Cannot cast -1067\d\d days .* to unit='ns' without overflow"
|
| 567 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 568 |
+
td2.as_unit("ns")
|
| 569 |
+
|
| 570 |
+
# But just back in bounds and we are OK
|
| 571 |
+
assert Timedelta(td64 + one) == td64 + one
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
@pytest.mark.parametrize(
|
| 575 |
+
"val, unit",
|
| 576 |
+
[
|
| 577 |
+
(15251 * 10**9, "W"),
|
| 578 |
+
(106752 * 10**9, "D"),
|
| 579 |
+
(2562048 * 10**9, "h"),
|
| 580 |
+
(153722868 * 10**9, "m"),
|
| 581 |
+
],
|
| 582 |
+
)
|
| 583 |
+
def test_construction_out_of_bounds_td64s(val, unit):
|
| 584 |
+
td64 = np.timedelta64(val, unit)
|
| 585 |
+
with pytest.raises(OutOfBoundsTimedelta, match=str(td64)):
|
| 586 |
+
Timedelta(td64)
|
| 587 |
+
|
| 588 |
+
# But just back in bounds and we are OK
|
| 589 |
+
offset = np.timedelta64(10**9, unit)
|
| 590 |
+
assert Timedelta(td64 - offset) == td64 - offset
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
@pytest.mark.parametrize(
|
| 594 |
+
"fmt,exp",
|
| 595 |
+
[
|
| 596 |
+
(
|
| 597 |
+
"P6DT0H50M3.010010012S",
|
| 598 |
+
Timedelta(
|
| 599 |
+
days=6,
|
| 600 |
+
minutes=50,
|
| 601 |
+
seconds=3,
|
| 602 |
+
milliseconds=10,
|
| 603 |
+
microseconds=10,
|
| 604 |
+
nanoseconds=12,
|
| 605 |
+
),
|
| 606 |
+
),
|
| 607 |
+
(
|
| 608 |
+
"P-6DT0H50M3.010010012S",
|
| 609 |
+
Timedelta(
|
| 610 |
+
days=-6,
|
| 611 |
+
minutes=50,
|
| 612 |
+
seconds=3,
|
| 613 |
+
milliseconds=10,
|
| 614 |
+
microseconds=10,
|
| 615 |
+
nanoseconds=12,
|
| 616 |
+
),
|
| 617 |
+
),
|
| 618 |
+
("P4DT12H30M5S", Timedelta(days=4, hours=12, minutes=30, seconds=5)),
|
| 619 |
+
("P0DT0H0M0.000000123S", Timedelta(nanoseconds=123)),
|
| 620 |
+
("P0DT0H0M0.00001S", Timedelta(microseconds=10)),
|
| 621 |
+
("P0DT0H0M0.001S", Timedelta(milliseconds=1)),
|
| 622 |
+
("P0DT0H1M0S", Timedelta(minutes=1)),
|
| 623 |
+
("P1DT25H61M61S", Timedelta(days=1, hours=25, minutes=61, seconds=61)),
|
| 624 |
+
("PT1S", Timedelta(seconds=1)),
|
| 625 |
+
("PT0S", Timedelta(seconds=0)),
|
| 626 |
+
("P1WT0S", Timedelta(days=7, seconds=0)),
|
| 627 |
+
("P1D", Timedelta(days=1)),
|
| 628 |
+
("P1DT1H", Timedelta(days=1, hours=1)),
|
| 629 |
+
("P1W", Timedelta(days=7)),
|
| 630 |
+
("PT300S", Timedelta(seconds=300)),
|
| 631 |
+
("P1DT0H0M00000000000S", Timedelta(days=1)),
|
| 632 |
+
("PT-6H3M", Timedelta(hours=-6, minutes=3)),
|
| 633 |
+
("-PT6H3M", Timedelta(hours=-6, minutes=-3)),
|
| 634 |
+
("-PT-6H+3M", Timedelta(hours=6, minutes=-3)),
|
| 635 |
+
],
|
| 636 |
+
)
|
| 637 |
+
def test_iso_constructor(fmt, exp):
|
| 638 |
+
assert Timedelta(fmt) == exp
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
@pytest.mark.parametrize(
|
| 642 |
+
"fmt",
|
| 643 |
+
[
|
| 644 |
+
"PPPPPPPPPPPP",
|
| 645 |
+
"PDTHMS",
|
| 646 |
+
"P0DT999H999M999S",
|
| 647 |
+
"P1DT0H0M0.0000000000000S",
|
| 648 |
+
"P1DT0H0M0.S",
|
| 649 |
+
"P",
|
| 650 |
+
"-P",
|
| 651 |
+
],
|
| 652 |
+
)
|
| 653 |
+
def test_iso_constructor_raises(fmt):
|
| 654 |
+
msg = f"Invalid ISO 8601 Duration format - {fmt}"
|
| 655 |
+
with pytest.raises(ValueError, match=msg):
|
| 656 |
+
Timedelta(fmt)
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
@pytest.mark.parametrize(
|
| 660 |
+
"constructed_td, conversion",
|
| 661 |
+
[
|
| 662 |
+
(Timedelta(nanoseconds=100), "100ns"),
|
| 663 |
+
(
|
| 664 |
+
Timedelta(
|
| 665 |
+
days=1,
|
| 666 |
+
hours=1,
|
| 667 |
+
minutes=1,
|
| 668 |
+
weeks=1,
|
| 669 |
+
seconds=1,
|
| 670 |
+
milliseconds=1,
|
| 671 |
+
microseconds=1,
|
| 672 |
+
nanoseconds=1,
|
| 673 |
+
),
|
| 674 |
+
694861001001001,
|
| 675 |
+
),
|
| 676 |
+
(Timedelta(microseconds=1) + Timedelta(nanoseconds=1), "1us1ns"),
|
| 677 |
+
(Timedelta(microseconds=1) - Timedelta(nanoseconds=1), "999ns"),
|
| 678 |
+
(Timedelta(microseconds=1) + 5 * Timedelta(nanoseconds=-2), "990ns"),
|
| 679 |
+
],
|
| 680 |
+
)
|
| 681 |
+
def test_td_constructor_on_nanoseconds(constructed_td, conversion):
|
| 682 |
+
# GH#9273
|
| 683 |
+
assert constructed_td == Timedelta(conversion)
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
def test_td_constructor_value_error():
|
| 687 |
+
msg = "Invalid type <class 'str'>. Must be int or float."
|
| 688 |
+
with pytest.raises(TypeError, match=msg):
|
| 689 |
+
Timedelta(nanoseconds="abc")
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
def test_timedelta_constructor_identity():
|
| 693 |
+
# Test for #30543
|
| 694 |
+
expected = Timedelta(np.timedelta64(1, "s"))
|
| 695 |
+
result = Timedelta(expected)
|
| 696 |
+
assert result is expected
|
| 697 |
+
|
| 698 |
+
|
| 699 |
+
def test_timedelta_pass_td_and_kwargs_raises():
|
| 700 |
+
# don't silently ignore the kwargs GH#48898
|
| 701 |
+
td = Timedelta(days=1)
|
| 702 |
+
msg = (
|
| 703 |
+
"Cannot pass both a Timedelta input and timedelta keyword arguments, "
|
| 704 |
+
r"got \['days'\]"
|
| 705 |
+
)
|
| 706 |
+
with pytest.raises(ValueError, match=msg):
|
| 707 |
+
Timedelta(td, days=2)
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
@pytest.mark.filterwarnings(
|
| 711 |
+
"ignore:The 'unit' keyword is only used when the Timedelta input:UserWarning"
|
| 712 |
+
)
|
| 713 |
+
@pytest.mark.parametrize(
|
| 714 |
+
"constructor, value, unit",
|
| 715 |
+
[
|
| 716 |
+
(Timedelta, "10s", "ms"),
|
| 717 |
+
(to_timedelta, "10s", "ms"),
|
| 718 |
+
(to_timedelta, ["1", 2, 3], "s"),
|
| 719 |
+
],
|
| 720 |
+
)
|
| 721 |
+
def test_string_with_unit(constructor, value, unit):
|
| 722 |
+
with pytest.raises(ValueError, match="unit must not be specified"):
|
| 723 |
+
constructor(value, unit=unit)
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
@pytest.mark.parametrize(
|
| 727 |
+
"value",
|
| 728 |
+
[
|
| 729 |
+
"".join(elements)
|
| 730 |
+
for repetition in (1, 2)
|
| 731 |
+
for elements in product("+-, ", repeat=repetition)
|
| 732 |
+
],
|
| 733 |
+
)
|
| 734 |
+
def test_string_without_numbers(value):
|
| 735 |
+
# GH39710 Timedelta input string with only symbols and no digits raises an error
|
| 736 |
+
msg = (
|
| 737 |
+
"symbols w/o a number"
|
| 738 |
+
if value != "--"
|
| 739 |
+
else "only leading negative signs are allowed"
|
| 740 |
+
)
|
| 741 |
+
with pytest.raises(ValueError, match=msg):
|
| 742 |
+
Timedelta(value)
|
| 743 |
+
|
| 744 |
+
|
| 745 |
+
def test_timedelta_new_npnat():
|
| 746 |
+
# GH#48898
|
| 747 |
+
nat = np.timedelta64("NaT", "h")
|
| 748 |
+
assert Timedelta(nat) is NaT
|
| 749 |
+
|
| 750 |
+
|
| 751 |
+
def test_subclass_respected():
|
| 752 |
+
# GH#49579
|
| 753 |
+
class MyCustomTimedelta(Timedelta):
|
| 754 |
+
pass
|
| 755 |
+
|
| 756 |
+
td = MyCustomTimedelta("1 minute")
|
| 757 |
+
assert isinstance(td, MyCustomTimedelta)
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
def test_non_nano_value():
|
| 761 |
+
# https://github.com/pandas-dev/pandas/issues/49076
|
| 762 |
+
result = Timedelta(10, unit="D").as_unit("s").value
|
| 763 |
+
# `.value` shows nanoseconds, even though unit is 's'
|
| 764 |
+
assert result == 864000000000000
|
| 765 |
+
|
| 766 |
+
# out-of-nanoseconds-bounds `.value` raises informative message
|
| 767 |
+
msg = (
|
| 768 |
+
r"Cannot convert Timedelta to nanoseconds without overflow. "
|
| 769 |
+
r"Use `.asm8.view\('i8'\)` to cast represent Timedelta in its "
|
| 770 |
+
r"own unit \(here, s\).$"
|
| 771 |
+
)
|
| 772 |
+
td = Timedelta(1_000, "D").as_unit("s") * 1_000
|
| 773 |
+
with pytest.raises(OverflowError, match=msg):
|
| 774 |
+
td.value
|
| 775 |
+
# check that the suggested workaround actually works
|
| 776 |
+
result = td.asm8.view("i8")
|
| 777 |
+
assert result == 86400000000
|
| 778 |
+
|
| 779 |
+
|
| 780 |
+
def test_parsed_unit():
|
| 781 |
+
td = Timedelta("1 Day")
|
| 782 |
+
assert td.unit == "us"
|
| 783 |
+
|
| 784 |
+
td = Timedelta("1 Day 2 hours 3 minutes 4 ns")
|
| 785 |
+
assert td.unit == "ns"
|
| 786 |
+
|
| 787 |
+
td = Timedelta("1 Day 2:03:04.012345")
|
| 788 |
+
assert td.unit == "us"
|
| 789 |
+
|
| 790 |
+
td = Timedelta("1 Day 2:03:04.012345000")
|
| 791 |
+
assert td.unit == "ns"
|
| 792 |
+
|
| 793 |
+
# 7 digits after the decimal
|
| 794 |
+
td = Timedelta("1 Day 2:03:04.0123450")
|
| 795 |
+
assert td.unit == "ns"
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_formats.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas import Timedelta
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@pytest.mark.parametrize(
|
| 7 |
+
"td, expected_repr",
|
| 8 |
+
[
|
| 9 |
+
(Timedelta(10, unit="D"), "Timedelta('10 days 00:00:00')"),
|
| 10 |
+
(Timedelta(10, unit="s"), "Timedelta('0 days 00:00:10')"),
|
| 11 |
+
(Timedelta(10, unit="ms"), "Timedelta('0 days 00:00:00.010000')"),
|
| 12 |
+
(Timedelta(-10, unit="ms"), "Timedelta('-1 days +23:59:59.990000')"),
|
| 13 |
+
],
|
| 14 |
+
)
|
| 15 |
+
def test_repr(td, expected_repr):
|
| 16 |
+
assert repr(td) == expected_repr
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@pytest.mark.parametrize(
|
| 20 |
+
"td, expected_iso",
|
| 21 |
+
[
|
| 22 |
+
(
|
| 23 |
+
Timedelta(
|
| 24 |
+
days=6,
|
| 25 |
+
minutes=50,
|
| 26 |
+
seconds=3,
|
| 27 |
+
milliseconds=10,
|
| 28 |
+
microseconds=10,
|
| 29 |
+
nanoseconds=12,
|
| 30 |
+
),
|
| 31 |
+
"P6DT0H50M3.010010012S",
|
| 32 |
+
),
|
| 33 |
+
(Timedelta(days=4, hours=12, minutes=30, seconds=5), "P4DT12H30M5S"),
|
| 34 |
+
(Timedelta(nanoseconds=123), "P0DT0H0M0.000000123S"),
|
| 35 |
+
# trim nano
|
| 36 |
+
(Timedelta(microseconds=10), "P0DT0H0M0.00001S"),
|
| 37 |
+
# trim micro
|
| 38 |
+
(Timedelta(milliseconds=1), "P0DT0H0M0.001S"),
|
| 39 |
+
# don't strip every 0
|
| 40 |
+
(Timedelta(minutes=1), "P0DT0H1M0S"),
|
| 41 |
+
],
|
| 42 |
+
)
|
| 43 |
+
def test_isoformat(td, expected_iso):
|
| 44 |
+
assert td.isoformat() == expected_iso
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class TestReprBase:
|
| 48 |
+
def test_none(self):
|
| 49 |
+
delta_1d = Timedelta(1, unit="D")
|
| 50 |
+
delta_0d = Timedelta(0, unit="D")
|
| 51 |
+
delta_1s = Timedelta(1, unit="s")
|
| 52 |
+
delta_500ms = Timedelta(500, unit="ms")
|
| 53 |
+
|
| 54 |
+
drepr = lambda x: x._repr_base()
|
| 55 |
+
assert drepr(delta_1d) == "1 days"
|
| 56 |
+
assert drepr(-delta_1d) == "-1 days"
|
| 57 |
+
assert drepr(delta_0d) == "0 days"
|
| 58 |
+
assert drepr(delta_1s) == "0 days 00:00:01"
|
| 59 |
+
assert drepr(delta_500ms) == "0 days 00:00:00.500000"
|
| 60 |
+
assert drepr(delta_1d + delta_1s) == "1 days 00:00:01"
|
| 61 |
+
assert drepr(-delta_1d + delta_1s) == "-1 days +00:00:01"
|
| 62 |
+
assert drepr(delta_1d + delta_500ms) == "1 days 00:00:00.500000"
|
| 63 |
+
assert drepr(-delta_1d + delta_500ms) == "-1 days +00:00:00.500000"
|
| 64 |
+
|
| 65 |
+
def test_sub_day(self):
|
| 66 |
+
delta_1d = Timedelta(1, unit="D")
|
| 67 |
+
delta_0d = Timedelta(0, unit="D")
|
| 68 |
+
delta_1s = Timedelta(1, unit="s")
|
| 69 |
+
delta_500ms = Timedelta(500, unit="ms")
|
| 70 |
+
|
| 71 |
+
drepr = lambda x: x._repr_base(format="sub_day")
|
| 72 |
+
assert drepr(delta_1d) == "1 days"
|
| 73 |
+
assert drepr(-delta_1d) == "-1 days"
|
| 74 |
+
assert drepr(delta_0d) == "00:00:00"
|
| 75 |
+
assert drepr(delta_1s) == "00:00:01"
|
| 76 |
+
assert drepr(delta_500ms) == "00:00:00.500000"
|
| 77 |
+
assert drepr(delta_1d + delta_1s) == "1 days 00:00:01"
|
| 78 |
+
assert drepr(-delta_1d + delta_1s) == "-1 days +00:00:01"
|
| 79 |
+
assert drepr(delta_1d + delta_500ms) == "1 days 00:00:00.500000"
|
| 80 |
+
assert drepr(-delta_1d + delta_500ms) == "-1 days +00:00:00.500000"
|
| 81 |
+
|
| 82 |
+
def test_long(self):
|
| 83 |
+
delta_1d = Timedelta(1, unit="D")
|
| 84 |
+
delta_0d = Timedelta(0, unit="D")
|
| 85 |
+
delta_1s = Timedelta(1, unit="s")
|
| 86 |
+
delta_500ms = Timedelta(500, unit="ms")
|
| 87 |
+
|
| 88 |
+
drepr = lambda x: x._repr_base(format="long")
|
| 89 |
+
assert drepr(delta_1d) == "1 days 00:00:00"
|
| 90 |
+
assert drepr(-delta_1d) == "-1 days +00:00:00"
|
| 91 |
+
assert drepr(delta_0d) == "0 days 00:00:00"
|
| 92 |
+
assert drepr(delta_1s) == "0 days 00:00:01"
|
| 93 |
+
assert drepr(delta_500ms) == "0 days 00:00:00.500000"
|
| 94 |
+
assert drepr(delta_1d + delta_1s) == "1 days 00:00:01"
|
| 95 |
+
assert drepr(-delta_1d + delta_1s) == "-1 days +00:00:01"
|
| 96 |
+
assert drepr(delta_1d + delta_500ms) == "1 days 00:00:00.500000"
|
| 97 |
+
assert drepr(-delta_1d + delta_500ms) == "-1 days +00:00:00.500000"
|
| 98 |
+
|
| 99 |
+
def test_all(self):
|
| 100 |
+
delta_1d = Timedelta(1, unit="D")
|
| 101 |
+
delta_0d = Timedelta(0, unit="D")
|
| 102 |
+
delta_1ns = Timedelta(1, unit="ns")
|
| 103 |
+
|
| 104 |
+
drepr = lambda x: x._repr_base(format="all")
|
| 105 |
+
assert drepr(delta_1d) == "1 days 00:00:00.000000000"
|
| 106 |
+
assert drepr(-delta_1d) == "-1 days +00:00:00.000000000"
|
| 107 |
+
assert drepr(delta_0d) == "0 days 00:00:00.000000000"
|
| 108 |
+
assert drepr(delta_1ns) == "0 days 00:00:00.000000001"
|
| 109 |
+
assert drepr(-delta_1d + delta_1ns) == "-1 days +00:00:00.000000001"
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_timedelta.py
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""test the scalar Timedelta"""
|
| 2 |
+
|
| 3 |
+
from datetime import timedelta
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
from hypothesis import (
|
| 7 |
+
given,
|
| 8 |
+
strategies as st,
|
| 9 |
+
)
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pytest
|
| 12 |
+
|
| 13 |
+
from pandas._libs import lib
|
| 14 |
+
from pandas._libs.missing import NA
|
| 15 |
+
from pandas._libs.tslibs import (
|
| 16 |
+
NaT,
|
| 17 |
+
iNaT,
|
| 18 |
+
)
|
| 19 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 20 |
+
from pandas.errors import (
|
| 21 |
+
OutOfBoundsTimedelta,
|
| 22 |
+
Pandas4Warning,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
from pandas import (
|
| 26 |
+
Timedelta,
|
| 27 |
+
to_timedelta,
|
| 28 |
+
)
|
| 29 |
+
import pandas._testing as tm
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class TestNonNano:
|
| 33 |
+
@pytest.fixture(params=["s", "ms", "us"])
|
| 34 |
+
def unit_str(self, request):
|
| 35 |
+
return request.param
|
| 36 |
+
|
| 37 |
+
@pytest.fixture
|
| 38 |
+
def unit(self, unit_str):
|
| 39 |
+
# 7, 8, 9 correspond to second, millisecond, and microsecond, respectively
|
| 40 |
+
attr = f"NPY_FR_{unit_str}"
|
| 41 |
+
return getattr(NpyDatetimeUnit, attr).value
|
| 42 |
+
|
| 43 |
+
@pytest.fixture
|
| 44 |
+
def val(self, unit):
|
| 45 |
+
# microsecond that would be just out of bounds for nano
|
| 46 |
+
us = 9223372800000000
|
| 47 |
+
if unit == NpyDatetimeUnit.NPY_FR_us.value:
|
| 48 |
+
value = us
|
| 49 |
+
elif unit == NpyDatetimeUnit.NPY_FR_ms.value:
|
| 50 |
+
value = us // 1000
|
| 51 |
+
else:
|
| 52 |
+
value = us // 1_000_000
|
| 53 |
+
return value
|
| 54 |
+
|
| 55 |
+
@pytest.fixture
|
| 56 |
+
def td(self, unit, val):
|
| 57 |
+
return Timedelta._from_value_and_reso(val, unit)
|
| 58 |
+
|
| 59 |
+
def test_from_value_and_reso(self, unit, val):
|
| 60 |
+
# Just checking that the fixture is giving us what we asked for
|
| 61 |
+
td = Timedelta._from_value_and_reso(val, unit)
|
| 62 |
+
assert td._value == val
|
| 63 |
+
assert td._creso == unit
|
| 64 |
+
assert td.days == 106752
|
| 65 |
+
|
| 66 |
+
def test_unary_non_nano(self, td, unit):
|
| 67 |
+
assert abs(td)._creso == unit
|
| 68 |
+
assert (-td)._creso == unit
|
| 69 |
+
assert (+td)._creso == unit
|
| 70 |
+
|
| 71 |
+
def test_sub_preserves_reso(self, td, unit):
|
| 72 |
+
res = td - td
|
| 73 |
+
expected = Timedelta._from_value_and_reso(0, unit)
|
| 74 |
+
assert res == expected
|
| 75 |
+
assert res._creso == unit
|
| 76 |
+
|
| 77 |
+
def test_mul_preserves_reso(self, td, unit):
|
| 78 |
+
# The td fixture should always be far from the implementation
|
| 79 |
+
# bound, so doubling does not risk overflow.
|
| 80 |
+
res = td * 2
|
| 81 |
+
assert res._value == td._value * 2
|
| 82 |
+
assert res._creso == unit
|
| 83 |
+
|
| 84 |
+
def test_cmp_cross_reso(self, td):
|
| 85 |
+
# numpy gets this wrong because of silent overflow
|
| 86 |
+
other = Timedelta(days=106751)
|
| 87 |
+
assert other < td
|
| 88 |
+
assert td > other
|
| 89 |
+
assert not other == td
|
| 90 |
+
assert td != other
|
| 91 |
+
|
| 92 |
+
def test_to_pytimedelta(self, td):
|
| 93 |
+
res = td.to_pytimedelta()
|
| 94 |
+
expected = timedelta(days=106752)
|
| 95 |
+
assert type(res) is timedelta
|
| 96 |
+
assert res == expected
|
| 97 |
+
|
| 98 |
+
def test_to_timedelta64(self, td, unit):
|
| 99 |
+
for res in [td.to_timedelta64(), td.to_numpy(), td.asm8]:
|
| 100 |
+
assert isinstance(res, np.timedelta64)
|
| 101 |
+
assert res.view("i8") == td._value
|
| 102 |
+
if unit == NpyDatetimeUnit.NPY_FR_s.value:
|
| 103 |
+
assert res.dtype == "m8[s]"
|
| 104 |
+
elif unit == NpyDatetimeUnit.NPY_FR_ms.value:
|
| 105 |
+
assert res.dtype == "m8[ms]"
|
| 106 |
+
elif unit == NpyDatetimeUnit.NPY_FR_us.value:
|
| 107 |
+
assert res.dtype == "m8[us]"
|
| 108 |
+
|
| 109 |
+
def test_truediv_timedeltalike(self, td):
|
| 110 |
+
assert td / td == 1
|
| 111 |
+
assert (2.5 * td) / td == 2.5
|
| 112 |
+
|
| 113 |
+
other = Timedelta(td._value)
|
| 114 |
+
msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow."
|
| 115 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 116 |
+
td / other
|
| 117 |
+
|
| 118 |
+
# Timedelta(other.to_pytimedelta()) has microsecond resolution,
|
| 119 |
+
# so the division doesn't require casting all the way to nanos,
|
| 120 |
+
# so succeeds
|
| 121 |
+
res = other.to_pytimedelta() / td
|
| 122 |
+
expected = other.to_pytimedelta() / td.to_pytimedelta()
|
| 123 |
+
assert res == expected
|
| 124 |
+
|
| 125 |
+
# if there's no overflow, we cast to the higher reso
|
| 126 |
+
left = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_us.value)
|
| 127 |
+
right = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_ms.value)
|
| 128 |
+
result = left / right
|
| 129 |
+
assert result == 0.001
|
| 130 |
+
|
| 131 |
+
result = right / left
|
| 132 |
+
assert result == 1000
|
| 133 |
+
|
| 134 |
+
def test_truediv_numeric(self, td):
|
| 135 |
+
assert td / np.nan is NaT
|
| 136 |
+
|
| 137 |
+
res = td / 2
|
| 138 |
+
assert res._value == td._value / 2
|
| 139 |
+
assert res._creso == td._creso
|
| 140 |
+
|
| 141 |
+
res = td / 2.0
|
| 142 |
+
assert res._value == td._value / 2
|
| 143 |
+
assert res._creso == td._creso
|
| 144 |
+
|
| 145 |
+
def test_truediv_na_type_not_supported(self, td):
|
| 146 |
+
msg_td_floordiv_na = (
|
| 147 |
+
r"unsupported operand type\(s\) for /: 'Timedelta' and 'NAType'"
|
| 148 |
+
)
|
| 149 |
+
with pytest.raises(TypeError, match=msg_td_floordiv_na):
|
| 150 |
+
td / NA
|
| 151 |
+
|
| 152 |
+
msg_na_floordiv_td = (
|
| 153 |
+
r"unsupported operand type\(s\) for /: 'NAType' and 'Timedelta'"
|
| 154 |
+
)
|
| 155 |
+
with pytest.raises(TypeError, match=msg_na_floordiv_td):
|
| 156 |
+
NA / td
|
| 157 |
+
|
| 158 |
+
def test_floordiv_timedeltalike(self, td):
|
| 159 |
+
assert td // td == 1
|
| 160 |
+
assert (2.5 * td) // td == 2
|
| 161 |
+
|
| 162 |
+
other = Timedelta(td._value)
|
| 163 |
+
msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow"
|
| 164 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 165 |
+
td // other
|
| 166 |
+
|
| 167 |
+
# Timedelta(other.to_pytimedelta()) has microsecond resolution,
|
| 168 |
+
# so the floordiv doesn't require casting all the way to nanos,
|
| 169 |
+
# so succeeds
|
| 170 |
+
res = other.to_pytimedelta() // td
|
| 171 |
+
assert res == 0
|
| 172 |
+
|
| 173 |
+
# if there's no overflow, we cast to the higher reso
|
| 174 |
+
left = Timedelta._from_value_and_reso(50050, NpyDatetimeUnit.NPY_FR_us.value)
|
| 175 |
+
right = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_ms.value)
|
| 176 |
+
result = left // right
|
| 177 |
+
assert result == 1
|
| 178 |
+
result = right // left
|
| 179 |
+
assert result == 0
|
| 180 |
+
|
| 181 |
+
def test_floordiv_numeric(self, td):
|
| 182 |
+
assert td // np.nan is NaT
|
| 183 |
+
|
| 184 |
+
res = td // 2
|
| 185 |
+
assert res._value == td._value // 2
|
| 186 |
+
assert res._creso == td._creso
|
| 187 |
+
|
| 188 |
+
res = td // 2.0
|
| 189 |
+
assert res._value == td._value // 2
|
| 190 |
+
assert res._creso == td._creso
|
| 191 |
+
|
| 192 |
+
assert td // np.array(np.nan) is NaT
|
| 193 |
+
|
| 194 |
+
res = td // np.array(2)
|
| 195 |
+
assert res._value == td._value // 2
|
| 196 |
+
assert res._creso == td._creso
|
| 197 |
+
|
| 198 |
+
res = td // np.array(2.0)
|
| 199 |
+
assert res._value == td._value // 2
|
| 200 |
+
assert res._creso == td._creso
|
| 201 |
+
|
| 202 |
+
def test_floordiv_na_type_not_supported(self, td):
|
| 203 |
+
msg_td_floordiv_na = (
|
| 204 |
+
r"unsupported operand type\(s\) for //: 'Timedelta' and 'NAType'"
|
| 205 |
+
)
|
| 206 |
+
with pytest.raises(TypeError, match=msg_td_floordiv_na):
|
| 207 |
+
td // NA
|
| 208 |
+
|
| 209 |
+
msg_na_floordiv_td = (
|
| 210 |
+
r"unsupported operand type\(s\) for //: 'NAType' and 'Timedelta'"
|
| 211 |
+
)
|
| 212 |
+
with pytest.raises(TypeError, match=msg_na_floordiv_td):
|
| 213 |
+
NA // td
|
| 214 |
+
|
| 215 |
+
def test_addsub_mismatched_reso(self, td):
|
| 216 |
+
# need to cast to since td is out of bounds for ns, so
|
| 217 |
+
# so we would raise OverflowError without casting
|
| 218 |
+
other = Timedelta(days=1).as_unit("us")
|
| 219 |
+
|
| 220 |
+
# td is out of bounds for ns
|
| 221 |
+
result = td + other
|
| 222 |
+
assert result._creso == other._creso
|
| 223 |
+
assert result.days == td.days + 1
|
| 224 |
+
|
| 225 |
+
result = other + td
|
| 226 |
+
assert result._creso == other._creso
|
| 227 |
+
assert result.days == td.days + 1
|
| 228 |
+
|
| 229 |
+
result = td - other
|
| 230 |
+
assert result._creso == other._creso
|
| 231 |
+
assert result.days == td.days - 1
|
| 232 |
+
|
| 233 |
+
result = other - td
|
| 234 |
+
assert result._creso == other._creso
|
| 235 |
+
assert result.days == 1 - td.days
|
| 236 |
+
|
| 237 |
+
other2 = Timedelta(500)
|
| 238 |
+
msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow"
|
| 239 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 240 |
+
td + other2
|
| 241 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 242 |
+
other2 + td
|
| 243 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 244 |
+
td - other2
|
| 245 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 246 |
+
other2 - td
|
| 247 |
+
|
| 248 |
+
def test_min(self, td):
|
| 249 |
+
assert td.min <= td
|
| 250 |
+
assert td.min._creso == td._creso
|
| 251 |
+
assert td.min._value == NaT._value + 1
|
| 252 |
+
|
| 253 |
+
def test_max(self, td):
|
| 254 |
+
assert td.max >= td
|
| 255 |
+
assert td.max._creso == td._creso
|
| 256 |
+
assert td.max._value == np.iinfo(np.int64).max
|
| 257 |
+
|
| 258 |
+
def test_resolution(self, td):
|
| 259 |
+
expected = Timedelta._from_value_and_reso(1, td._creso)
|
| 260 |
+
result = td.resolution
|
| 261 |
+
assert result == expected
|
| 262 |
+
assert result._creso == expected._creso
|
| 263 |
+
|
| 264 |
+
def test_hash(self) -> None:
|
| 265 |
+
# GH#54037
|
| 266 |
+
second_resolution_max = Timedelta(0).as_unit("s").max
|
| 267 |
+
|
| 268 |
+
assert hash(second_resolution_max)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def test_timedelta_class_min_max_resolution():
|
| 272 |
+
# when accessed on the class (as opposed to an instance), we default
|
| 273 |
+
# to nanoseconds
|
| 274 |
+
assert Timedelta.min == Timedelta(NaT._value + 1)
|
| 275 |
+
assert Timedelta.min._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 276 |
+
|
| 277 |
+
assert Timedelta.max == Timedelta(np.iinfo(np.int64).max)
|
| 278 |
+
assert Timedelta.max._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 279 |
+
|
| 280 |
+
assert Timedelta.resolution == Timedelta(1)
|
| 281 |
+
assert Timedelta.resolution._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
class TestTimedeltaUnaryOps:
|
| 285 |
+
def test_invert(self):
|
| 286 |
+
td = Timedelta(10, unit="D")
|
| 287 |
+
|
| 288 |
+
msg = "bad operand type for unary ~"
|
| 289 |
+
with pytest.raises(TypeError, match=msg):
|
| 290 |
+
~td
|
| 291 |
+
|
| 292 |
+
# check this matches pytimedelta and timedelta64
|
| 293 |
+
with pytest.raises(TypeError, match=msg):
|
| 294 |
+
~(td.to_pytimedelta())
|
| 295 |
+
|
| 296 |
+
umsg = "ufunc 'invert' not supported for the input types"
|
| 297 |
+
with pytest.raises(TypeError, match=umsg):
|
| 298 |
+
~(td.to_timedelta64())
|
| 299 |
+
|
| 300 |
+
def test_unary_ops(self):
|
| 301 |
+
td = Timedelta(10, unit="D")
|
| 302 |
+
|
| 303 |
+
# __neg__, __pos__
|
| 304 |
+
assert -td == Timedelta(-10, unit="D")
|
| 305 |
+
assert -td == Timedelta("-10D")
|
| 306 |
+
assert +td == Timedelta(10, unit="D")
|
| 307 |
+
|
| 308 |
+
# __abs__, __abs__(__neg__)
|
| 309 |
+
assert abs(td) == td
|
| 310 |
+
assert abs(-td) == td
|
| 311 |
+
assert abs(-td) == Timedelta("10D")
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
class TestTimedeltas:
|
| 315 |
+
@pytest.mark.parametrize(
|
| 316 |
+
"unit, value, expected",
|
| 317 |
+
[
|
| 318 |
+
("us", 9.999, 9999),
|
| 319 |
+
("ms", 9.999999, 9999999),
|
| 320 |
+
("s", 9.999999999, 9999999999),
|
| 321 |
+
],
|
| 322 |
+
)
|
| 323 |
+
def test_rounding_on_int_unit_construction(self, unit, value, expected):
|
| 324 |
+
# GH 12690
|
| 325 |
+
result = Timedelta(value, unit=unit)
|
| 326 |
+
assert result._value == expected
|
| 327 |
+
result = Timedelta(str(value) + unit)
|
| 328 |
+
assert result._value == expected
|
| 329 |
+
|
| 330 |
+
def test_total_seconds_scalar(self):
|
| 331 |
+
# see gh-10939
|
| 332 |
+
rng = Timedelta("1 days, 10:11:12.100123456")
|
| 333 |
+
expt = 1 * 86400 + 10 * 3600 + 11 * 60 + 12 + 100123456.0 / 1e9
|
| 334 |
+
tm.assert_almost_equal(rng.total_seconds(), expt)
|
| 335 |
+
|
| 336 |
+
rng = Timedelta(np.nan)
|
| 337 |
+
assert np.isnan(rng.total_seconds())
|
| 338 |
+
|
| 339 |
+
def test_conversion(self):
|
| 340 |
+
for td in [Timedelta(10, unit="D"), Timedelta("1 days, 10:11:12.012345")]:
|
| 341 |
+
td = td.as_unit("ns")
|
| 342 |
+
pydt = td.to_pytimedelta()
|
| 343 |
+
assert td == Timedelta(pydt)
|
| 344 |
+
assert td == pydt
|
| 345 |
+
assert isinstance(pydt, timedelta) and not isinstance(pydt, Timedelta)
|
| 346 |
+
|
| 347 |
+
assert td == np.timedelta64(td._value, "ns")
|
| 348 |
+
td64 = td.to_timedelta64()
|
| 349 |
+
|
| 350 |
+
assert td64 == np.timedelta64(td._value, "ns")
|
| 351 |
+
assert td == td64
|
| 352 |
+
|
| 353 |
+
assert isinstance(td64, np.timedelta64)
|
| 354 |
+
|
| 355 |
+
# this is NOT equal and cannot be roundtripped (because of the nanos)
|
| 356 |
+
td = Timedelta("1 days, 10:11:12.012345678")
|
| 357 |
+
assert td != td.to_pytimedelta()
|
| 358 |
+
|
| 359 |
+
def test_fields(self):
|
| 360 |
+
def check(value):
|
| 361 |
+
# that we are int
|
| 362 |
+
assert isinstance(value, int)
|
| 363 |
+
|
| 364 |
+
# compat to datetime.timedelta
|
| 365 |
+
rng = to_timedelta("1 days, 10:11:12")
|
| 366 |
+
assert rng.days == 1
|
| 367 |
+
assert rng.seconds == 10 * 3600 + 11 * 60 + 12
|
| 368 |
+
assert rng.microseconds == 0
|
| 369 |
+
assert rng.nanoseconds == 0
|
| 370 |
+
|
| 371 |
+
msg = "'Timedelta' object has no attribute '{}'"
|
| 372 |
+
with pytest.raises(AttributeError, match=msg.format("hours")):
|
| 373 |
+
rng.hours
|
| 374 |
+
with pytest.raises(AttributeError, match=msg.format("minutes")):
|
| 375 |
+
rng.minutes
|
| 376 |
+
with pytest.raises(AttributeError, match=msg.format("milliseconds")):
|
| 377 |
+
rng.milliseconds
|
| 378 |
+
|
| 379 |
+
# GH 10050
|
| 380 |
+
check(rng.days)
|
| 381 |
+
check(rng.seconds)
|
| 382 |
+
check(rng.microseconds)
|
| 383 |
+
check(rng.nanoseconds)
|
| 384 |
+
|
| 385 |
+
td = Timedelta("-1 days, 10:11:12")
|
| 386 |
+
assert abs(td) == Timedelta("13:48:48")
|
| 387 |
+
assert str(td) == "-1 days +10:11:12"
|
| 388 |
+
assert -td == Timedelta("0 days 13:48:48")
|
| 389 |
+
assert -Timedelta("-1 days, 10:11:12")._value == 49728000000
|
| 390 |
+
assert Timedelta("-1 days, 10:11:12")._value == -49728000000
|
| 391 |
+
|
| 392 |
+
rng = to_timedelta("-1 days, 10:11:12.100123456")
|
| 393 |
+
assert rng.days == -1
|
| 394 |
+
assert rng.seconds == 10 * 3600 + 11 * 60 + 12
|
| 395 |
+
assert rng.microseconds == 100 * 1000 + 123
|
| 396 |
+
assert rng.nanoseconds == 456
|
| 397 |
+
msg = "'Timedelta' object has no attribute '{}'"
|
| 398 |
+
with pytest.raises(AttributeError, match=msg.format("hours")):
|
| 399 |
+
rng.hours
|
| 400 |
+
with pytest.raises(AttributeError, match=msg.format("minutes")):
|
| 401 |
+
rng.minutes
|
| 402 |
+
with pytest.raises(AttributeError, match=msg.format("milliseconds")):
|
| 403 |
+
rng.milliseconds
|
| 404 |
+
|
| 405 |
+
# components
|
| 406 |
+
tup = to_timedelta(-1, "us").components
|
| 407 |
+
assert tup.days == -1
|
| 408 |
+
assert tup.hours == 23
|
| 409 |
+
assert tup.minutes == 59
|
| 410 |
+
assert tup.seconds == 59
|
| 411 |
+
assert tup.milliseconds == 999
|
| 412 |
+
assert tup.microseconds == 999
|
| 413 |
+
assert tup.nanoseconds == 0
|
| 414 |
+
|
| 415 |
+
# GH 10050
|
| 416 |
+
check(tup.days)
|
| 417 |
+
check(tup.hours)
|
| 418 |
+
check(tup.minutes)
|
| 419 |
+
check(tup.seconds)
|
| 420 |
+
check(tup.milliseconds)
|
| 421 |
+
check(tup.microseconds)
|
| 422 |
+
check(tup.nanoseconds)
|
| 423 |
+
|
| 424 |
+
tup = Timedelta("-1 days 1 us").components
|
| 425 |
+
assert tup.days == -2
|
| 426 |
+
assert tup.hours == 23
|
| 427 |
+
assert tup.minutes == 59
|
| 428 |
+
assert tup.seconds == 59
|
| 429 |
+
assert tup.milliseconds == 999
|
| 430 |
+
assert tup.microseconds == 999
|
| 431 |
+
assert tup.nanoseconds == 0
|
| 432 |
+
|
| 433 |
+
# TODO: this is a test of to_timedelta string parsing
|
| 434 |
+
def test_iso_conversion(self):
|
| 435 |
+
# GH #21877
|
| 436 |
+
expected = Timedelta(1, unit="s")
|
| 437 |
+
assert to_timedelta("P0DT0H0M1S") == expected
|
| 438 |
+
|
| 439 |
+
# TODO: this is a test of to_timedelta returning NaT
|
| 440 |
+
def test_nat_converters(self):
|
| 441 |
+
result = to_timedelta("nat").to_numpy()
|
| 442 |
+
assert result.dtype.kind == "M"
|
| 443 |
+
assert result.astype("int64") == iNaT
|
| 444 |
+
|
| 445 |
+
result = to_timedelta("nan").to_numpy()
|
| 446 |
+
assert result.dtype.kind == "M"
|
| 447 |
+
assert result.astype("int64") == iNaT
|
| 448 |
+
|
| 449 |
+
def test_numeric_conversions(self):
|
| 450 |
+
assert Timedelta(0) == np.timedelta64(0, "ns")
|
| 451 |
+
assert Timedelta(10) == np.timedelta64(10, "ns")
|
| 452 |
+
assert Timedelta(10, unit="ns") == np.timedelta64(10, "ns")
|
| 453 |
+
|
| 454 |
+
assert Timedelta(10, unit="us") == np.timedelta64(10, "us")
|
| 455 |
+
assert Timedelta(10, unit="ms") == np.timedelta64(10, "ms")
|
| 456 |
+
assert Timedelta(10, unit="s") == np.timedelta64(10, "s")
|
| 457 |
+
assert Timedelta(10, unit="D") == np.timedelta64(10, "D")
|
| 458 |
+
|
| 459 |
+
def test_timedelta_conversions(self):
|
| 460 |
+
assert Timedelta(timedelta(seconds=1)) == np.timedelta64(1, "s").astype(
|
| 461 |
+
"m8[ns]"
|
| 462 |
+
)
|
| 463 |
+
assert Timedelta(timedelta(microseconds=1)) == np.timedelta64(1, "us").astype(
|
| 464 |
+
"m8[ns]"
|
| 465 |
+
)
|
| 466 |
+
assert Timedelta(timedelta(days=1)) == np.timedelta64(1, "D").astype("m8[ns]")
|
| 467 |
+
|
| 468 |
+
def test_to_numpy_alias(self):
|
| 469 |
+
# GH 24653: alias .to_numpy() for scalars
|
| 470 |
+
td = Timedelta("10m7s")
|
| 471 |
+
assert td.to_timedelta64() == td.to_numpy()
|
| 472 |
+
|
| 473 |
+
# GH#44460
|
| 474 |
+
msg = "dtype and copy arguments are ignored"
|
| 475 |
+
with pytest.raises(ValueError, match=msg):
|
| 476 |
+
td.to_numpy("m8[s]")
|
| 477 |
+
with pytest.raises(ValueError, match=msg):
|
| 478 |
+
td.to_numpy(copy=True)
|
| 479 |
+
|
| 480 |
+
def test_identity(self):
|
| 481 |
+
td = Timedelta(10, unit="D")
|
| 482 |
+
assert isinstance(td, Timedelta)
|
| 483 |
+
assert isinstance(td, timedelta)
|
| 484 |
+
|
| 485 |
+
def test_short_format_converters(self):
|
| 486 |
+
def conv(v):
|
| 487 |
+
return v.astype("m8[ns]")
|
| 488 |
+
|
| 489 |
+
assert Timedelta("10") == np.timedelta64(10, "ns")
|
| 490 |
+
assert Timedelta("10ns") == np.timedelta64(10, "ns")
|
| 491 |
+
assert Timedelta("100") == np.timedelta64(100, "ns")
|
| 492 |
+
assert Timedelta("100ns") == np.timedelta64(100, "ns")
|
| 493 |
+
|
| 494 |
+
assert Timedelta("1000") == np.timedelta64(1000, "ns")
|
| 495 |
+
assert Timedelta("1000ns") == np.timedelta64(1000, "ns")
|
| 496 |
+
|
| 497 |
+
msg = "'NS' is deprecated and will be removed in a future version."
|
| 498 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 499 |
+
assert Timedelta("1000NS") == np.timedelta64(1000, "ns")
|
| 500 |
+
|
| 501 |
+
assert Timedelta("10us") == np.timedelta64(10000, "ns")
|
| 502 |
+
assert Timedelta("100us") == np.timedelta64(100000, "ns")
|
| 503 |
+
assert Timedelta("1000us") == np.timedelta64(1000000, "ns")
|
| 504 |
+
assert Timedelta("1000Us") == np.timedelta64(1000000, "ns")
|
| 505 |
+
assert Timedelta("1000uS") == np.timedelta64(1000000, "ns")
|
| 506 |
+
|
| 507 |
+
assert Timedelta("1ms") == np.timedelta64(1000000, "ns")
|
| 508 |
+
assert Timedelta("10ms") == np.timedelta64(10000000, "ns")
|
| 509 |
+
assert Timedelta("100ms") == np.timedelta64(100000000, "ns")
|
| 510 |
+
assert Timedelta("1000ms") == np.timedelta64(1000000000, "ns")
|
| 511 |
+
|
| 512 |
+
assert Timedelta("-1s") == -np.timedelta64(1000000000, "ns")
|
| 513 |
+
assert Timedelta("1s") == np.timedelta64(1000000000, "ns")
|
| 514 |
+
assert Timedelta("10s") == np.timedelta64(10000000000, "ns")
|
| 515 |
+
assert Timedelta("100s") == np.timedelta64(100000000000, "ns")
|
| 516 |
+
assert Timedelta("1000s") == np.timedelta64(1000000000000, "ns")
|
| 517 |
+
|
| 518 |
+
msg = "'d' is deprecated and will be removed in a future version."
|
| 519 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 520 |
+
assert Timedelta("1d") == conv(np.timedelta64(1, "D"))
|
| 521 |
+
assert Timedelta("-1D") == -conv(np.timedelta64(1, "D"))
|
| 522 |
+
assert Timedelta("1D") == conv(np.timedelta64(1, "D"))
|
| 523 |
+
assert Timedelta("10D") == conv(np.timedelta64(10, "D"))
|
| 524 |
+
assert Timedelta("100D") == conv(np.timedelta64(100, "D"))
|
| 525 |
+
assert Timedelta("1000D") == conv(np.timedelta64(1000, "D"))
|
| 526 |
+
assert Timedelta("10000D") == conv(np.timedelta64(10000, "D"))
|
| 527 |
+
|
| 528 |
+
# space
|
| 529 |
+
assert Timedelta(" 10000D ") == conv(np.timedelta64(10000, "D"))
|
| 530 |
+
assert Timedelta(" - 10000D ") == -conv(np.timedelta64(10000, "D"))
|
| 531 |
+
|
| 532 |
+
# invalid
|
| 533 |
+
msg = "invalid unit abbreviation"
|
| 534 |
+
with pytest.raises(ValueError, match=msg):
|
| 535 |
+
Timedelta("1foo")
|
| 536 |
+
msg = "unit abbreviation w/o a number"
|
| 537 |
+
with pytest.raises(ValueError, match=msg):
|
| 538 |
+
Timedelta("foo")
|
| 539 |
+
|
| 540 |
+
def test_full_format_converters(self):
|
| 541 |
+
def conv(v):
|
| 542 |
+
return v.astype("m8[ns]")
|
| 543 |
+
|
| 544 |
+
d1 = np.timedelta64(1, "D")
|
| 545 |
+
|
| 546 |
+
assert Timedelta("1days") == conv(d1)
|
| 547 |
+
assert Timedelta("1days,") == conv(d1)
|
| 548 |
+
assert Timedelta("- 1days,") == -conv(d1)
|
| 549 |
+
|
| 550 |
+
assert Timedelta("00:00:01") == conv(np.timedelta64(1, "s"))
|
| 551 |
+
assert Timedelta("06:00:01") == conv(np.timedelta64(6 * 3600 + 1, "s"))
|
| 552 |
+
assert Timedelta("06:00:01.0") == conv(np.timedelta64(6 * 3600 + 1, "s"))
|
| 553 |
+
assert Timedelta("06:00:01.01") == conv(
|
| 554 |
+
np.timedelta64(1000 * (6 * 3600 + 1) + 10, "ms")
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
assert Timedelta("- 1days, 00:00:01") == conv(-d1 + np.timedelta64(1, "s"))
|
| 558 |
+
assert Timedelta("1days, 06:00:01") == conv(
|
| 559 |
+
d1 + np.timedelta64(6 * 3600 + 1, "s")
|
| 560 |
+
)
|
| 561 |
+
assert Timedelta("1days, 06:00:01.01") == conv(
|
| 562 |
+
d1 + np.timedelta64(1000 * (6 * 3600 + 1) + 10, "ms")
|
| 563 |
+
)
|
| 564 |
+
|
| 565 |
+
# invalid
|
| 566 |
+
msg = "have leftover units"
|
| 567 |
+
with pytest.raises(ValueError, match=msg):
|
| 568 |
+
Timedelta("- 1days, 00")
|
| 569 |
+
|
| 570 |
+
def test_pickle(self, temp_file):
|
| 571 |
+
v = Timedelta("1 days 10:11:12.0123456")
|
| 572 |
+
v_p = tm.round_trip_pickle(v, temp_file)
|
| 573 |
+
assert v == v_p
|
| 574 |
+
|
| 575 |
+
def test_timedelta_hash_equality(self):
|
| 576 |
+
# GH 11129
|
| 577 |
+
v = Timedelta(1, "D")
|
| 578 |
+
td = timedelta(days=1)
|
| 579 |
+
assert hash(v) == hash(td)
|
| 580 |
+
|
| 581 |
+
d = {td: 2}
|
| 582 |
+
assert d[v] == 2
|
| 583 |
+
|
| 584 |
+
tds = [Timedelta(seconds=1) + Timedelta(days=n) for n in range(20)]
|
| 585 |
+
assert all(hash(td) == hash(td.to_pytimedelta()) for td in tds)
|
| 586 |
+
|
| 587 |
+
# python timedeltas drop ns resolution
|
| 588 |
+
ns_td = Timedelta(1, "ns")
|
| 589 |
+
assert hash(ns_td) != hash(ns_td.to_pytimedelta())
|
| 590 |
+
|
| 591 |
+
@pytest.mark.slow
|
| 592 |
+
@pytest.mark.xfail(
|
| 593 |
+
reason="pd.Timedelta violates the Python hash invariant (GH#44504).",
|
| 594 |
+
)
|
| 595 |
+
@given(
|
| 596 |
+
st.integers(
|
| 597 |
+
min_value=(-sys.maxsize - 1) // 500,
|
| 598 |
+
max_value=sys.maxsize // 500,
|
| 599 |
+
)
|
| 600 |
+
)
|
| 601 |
+
def test_hash_equality_invariance(self, half_microseconds: int) -> None:
|
| 602 |
+
# GH#44504
|
| 603 |
+
|
| 604 |
+
nanoseconds = half_microseconds * 500
|
| 605 |
+
|
| 606 |
+
pandas_timedelta = Timedelta(nanoseconds)
|
| 607 |
+
numpy_timedelta = np.timedelta64(nanoseconds)
|
| 608 |
+
|
| 609 |
+
# See: https://docs.python.org/3/glossary.html#term-hashable
|
| 610 |
+
# Hashable objects which compare equal must have the same hash value.
|
| 611 |
+
assert pandas_timedelta != numpy_timedelta or hash(pandas_timedelta) == hash(
|
| 612 |
+
numpy_timedelta
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
def test_implementation_limits(self):
|
| 616 |
+
min_td = Timedelta(Timedelta.min)
|
| 617 |
+
max_td = Timedelta(Timedelta.max)
|
| 618 |
+
|
| 619 |
+
# GH 12727
|
| 620 |
+
# timedelta limits correspond to int64 boundaries
|
| 621 |
+
assert min_td._value == iNaT + 1
|
| 622 |
+
assert max_td._value == lib.i8max
|
| 623 |
+
|
| 624 |
+
# Beyond lower limit, a NAT before the Overflow
|
| 625 |
+
assert (min_td - Timedelta(1, "ns")) is NaT
|
| 626 |
+
|
| 627 |
+
msg = "int too (large|big) to convert"
|
| 628 |
+
with pytest.raises(OverflowError, match=msg):
|
| 629 |
+
min_td - Timedelta(2, "ns")
|
| 630 |
+
|
| 631 |
+
with pytest.raises(OverflowError, match=msg):
|
| 632 |
+
max_td + Timedelta(1, "ns")
|
| 633 |
+
|
| 634 |
+
# Same tests using the internal nanosecond values
|
| 635 |
+
td = Timedelta(min_td._value - 1, "ns")
|
| 636 |
+
assert td is NaT
|
| 637 |
+
|
| 638 |
+
msg = "Cannot cast -9223372036854775809 from ns to 'ns' without overflow"
|
| 639 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 640 |
+
Timedelta(min_td._value - 2, "ns")
|
| 641 |
+
|
| 642 |
+
msg = "Cannot cast 9223372036854775808 from ns to 'ns' without overflow"
|
| 643 |
+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
|
| 644 |
+
Timedelta(max_td._value + 1, "ns")
|
| 645 |
+
|
| 646 |
+
def test_total_seconds_precision(self):
|
| 647 |
+
# GH 19458
|
| 648 |
+
assert Timedelta("30s").total_seconds() == 30.0
|
| 649 |
+
assert Timedelta("0").total_seconds() == 0.0
|
| 650 |
+
assert Timedelta("-2s").total_seconds() == -2.0
|
| 651 |
+
assert Timedelta("5.324s").total_seconds() == 5.324
|
| 652 |
+
assert (Timedelta("30s").total_seconds() - 30.0) < 1e-20
|
| 653 |
+
assert (30.0 - Timedelta("30s").total_seconds()) < 1e-20
|
| 654 |
+
|
| 655 |
+
def test_resolution_string(self):
|
| 656 |
+
assert Timedelta(days=1).resolution_string == "D"
|
| 657 |
+
assert Timedelta(days=1, hours=6).resolution_string == "h"
|
| 658 |
+
assert Timedelta(days=1, minutes=6).resolution_string == "min"
|
| 659 |
+
assert Timedelta(days=1, seconds=6).resolution_string == "s"
|
| 660 |
+
assert Timedelta(days=1, milliseconds=6).resolution_string == "ms"
|
| 661 |
+
assert Timedelta(days=1, microseconds=6).resolution_string == "us"
|
| 662 |
+
assert Timedelta(days=1, nanoseconds=6).resolution_string == "ns"
|
| 663 |
+
|
| 664 |
+
def test_resolution_deprecated(self):
|
| 665 |
+
# GH#21344
|
| 666 |
+
td = Timedelta(days=4, hours=3)
|
| 667 |
+
result = td.resolution
|
| 668 |
+
assert result == Timedelta(microseconds=1)
|
| 669 |
+
|
| 670 |
+
# Check that the attribute is available on the class, mirroring
|
| 671 |
+
# the stdlib timedelta behavior
|
| 672 |
+
result = Timedelta.resolution
|
| 673 |
+
assert result == Timedelta(nanoseconds=1)
|
| 674 |
+
|
| 675 |
+
@pytest.mark.parametrize(
|
| 676 |
+
"unit,unit_depr",
|
| 677 |
+
[
|
| 678 |
+
("W", "w"),
|
| 679 |
+
("D", "d"),
|
| 680 |
+
("min", "MIN"),
|
| 681 |
+
("s", "S"),
|
| 682 |
+
("h", "H"),
|
| 683 |
+
("ms", "MS"),
|
| 684 |
+
("us", "US"),
|
| 685 |
+
],
|
| 686 |
+
)
|
| 687 |
+
def test_unit_deprecated(self, unit, unit_depr):
|
| 688 |
+
# GH#59051
|
| 689 |
+
msg = f"'{unit_depr}' is deprecated and will be removed in a future version."
|
| 690 |
+
|
| 691 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 692 |
+
result = Timedelta(1, unit_depr)
|
| 693 |
+
assert result == Timedelta(1, unit)
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
@pytest.mark.parametrize(
|
| 697 |
+
"value, expected",
|
| 698 |
+
[
|
| 699 |
+
(Timedelta("10s"), True),
|
| 700 |
+
(Timedelta("-10s"), True),
|
| 701 |
+
(Timedelta(10, unit="ns"), True),
|
| 702 |
+
(Timedelta(0, unit="ns"), False),
|
| 703 |
+
(Timedelta(-10, unit="ns"), True),
|
| 704 |
+
(Timedelta(None), True),
|
| 705 |
+
(NaT, True),
|
| 706 |
+
],
|
| 707 |
+
)
|
| 708 |
+
def test_truthiness(value, expected):
|
| 709 |
+
# https://github.com/pandas-dev/pandas/issues/21484
|
| 710 |
+
assert bool(value) is expected
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
def test_timedelta_attribute_precision():
|
| 714 |
+
# GH 31354
|
| 715 |
+
td = Timedelta(1552211999999999872, unit="ns")
|
| 716 |
+
result = td.days * 86400
|
| 717 |
+
result += td.seconds
|
| 718 |
+
result *= 1000000
|
| 719 |
+
result += td.microseconds
|
| 720 |
+
result *= 1000
|
| 721 |
+
result += td.nanoseconds
|
| 722 |
+
expected = td._value
|
| 723 |
+
assert result == expected
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
def test_to_pytimedelta_large_values():
|
| 727 |
+
td = Timedelta(1152921504609987375, unit="ns")
|
| 728 |
+
result = td.to_pytimedelta()
|
| 729 |
+
expected = timedelta(days=13343, seconds=86304, microseconds=609987)
|
| 730 |
+
assert result == expected
|
| 731 |
+
|
| 732 |
+
|
| 733 |
+
def test_timedelta_week_suffix():
|
| 734 |
+
# GH#12691 ensure 'W' suffix works as a string passed to Timedelta
|
| 735 |
+
expected = Timedelta("7 days")
|
| 736 |
+
result = Timedelta(1, unit="W")
|
| 737 |
+
assert result == expected
|
| 738 |
+
|
| 739 |
+
result = Timedelta("1W")
|
| 740 |
+
assert result == expected
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_as_unit.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 4 |
+
from pandas.errors import OutOfBoundsDatetime
|
| 5 |
+
|
| 6 |
+
from pandas import Timestamp
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestTimestampAsUnit:
|
| 10 |
+
def test_as_unit(self):
|
| 11 |
+
ts = Timestamp("1970-01-01").as_unit("ns")
|
| 12 |
+
assert ts.unit == "ns"
|
| 13 |
+
|
| 14 |
+
assert ts.as_unit("ns") is ts
|
| 15 |
+
|
| 16 |
+
res = ts.as_unit("us")
|
| 17 |
+
assert res._value == ts._value // 1000
|
| 18 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_us.value
|
| 19 |
+
|
| 20 |
+
rt = res.as_unit("ns")
|
| 21 |
+
assert rt._value == ts._value
|
| 22 |
+
assert rt._creso == ts._creso
|
| 23 |
+
|
| 24 |
+
res = ts.as_unit("ms")
|
| 25 |
+
assert res._value == ts._value // 1_000_000
|
| 26 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 27 |
+
|
| 28 |
+
rt = res.as_unit("ns")
|
| 29 |
+
assert rt._value == ts._value
|
| 30 |
+
assert rt._creso == ts._creso
|
| 31 |
+
|
| 32 |
+
res = ts.as_unit("s")
|
| 33 |
+
assert res._value == ts._value // 1_000_000_000
|
| 34 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 35 |
+
|
| 36 |
+
rt = res.as_unit("ns")
|
| 37 |
+
assert rt._value == ts._value
|
| 38 |
+
assert rt._creso == ts._creso
|
| 39 |
+
|
| 40 |
+
def test_as_unit_overflows(self):
|
| 41 |
+
# microsecond that would be just out of bounds for nano
|
| 42 |
+
us = 9223372800000000
|
| 43 |
+
ts = Timestamp._from_value_and_reso(us, NpyDatetimeUnit.NPY_FR_us.value, None)
|
| 44 |
+
|
| 45 |
+
msg = "Cannot cast 2262-04-12 00:00:00 to unit='ns' without overflow"
|
| 46 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 47 |
+
ts.as_unit("ns")
|
| 48 |
+
|
| 49 |
+
res = ts.as_unit("ms")
|
| 50 |
+
assert res._value == us // 1000
|
| 51 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 52 |
+
|
| 53 |
+
def test_as_unit_rounding(self):
|
| 54 |
+
ts = Timestamp(1_500_000) # i.e. 1500 microseconds
|
| 55 |
+
res = ts.as_unit("ms")
|
| 56 |
+
|
| 57 |
+
expected = Timestamp(1_000_000) # i.e. 1 millisecond
|
| 58 |
+
assert res == expected
|
| 59 |
+
|
| 60 |
+
assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 61 |
+
assert res._value == 1
|
| 62 |
+
|
| 63 |
+
with pytest.raises(ValueError, match="Cannot losslessly convert units"):
|
| 64 |
+
ts.as_unit("ms", round_ok=False)
|
| 65 |
+
|
| 66 |
+
def test_as_unit_non_nano(self):
|
| 67 |
+
# case where we are going neither to nor from nano
|
| 68 |
+
ts = Timestamp("1970-01-02").as_unit("ms")
|
| 69 |
+
assert ts.year == 1970
|
| 70 |
+
assert ts.month == 1
|
| 71 |
+
assert ts.day == 2
|
| 72 |
+
assert ts.hour == ts.minute == ts.second == ts.microsecond == ts.nanosecond == 0
|
| 73 |
+
|
| 74 |
+
res = ts.as_unit("s")
|
| 75 |
+
assert res._value == 24 * 3600
|
| 76 |
+
assert res.year == 1970
|
| 77 |
+
assert res.month == 1
|
| 78 |
+
assert res.day == 2
|
| 79 |
+
assert (
|
| 80 |
+
res.hour
|
| 81 |
+
== res.minute
|
| 82 |
+
== res.second
|
| 83 |
+
== res.microsecond
|
| 84 |
+
== res.nanosecond
|
| 85 |
+
== 0
|
| 86 |
+
)
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_normalize.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas._libs.tslibs import Timestamp
|
| 4 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TestTimestampNormalize:
|
| 8 |
+
@pytest.mark.parametrize("arg", ["2013-11-30", "2013-11-30 12:00:00"])
|
| 9 |
+
def test_normalize(self, tz_naive_fixture, arg, unit):
|
| 10 |
+
tz = tz_naive_fixture
|
| 11 |
+
ts = Timestamp(arg, tz=tz).as_unit(unit)
|
| 12 |
+
result = ts.normalize()
|
| 13 |
+
expected = Timestamp("2013-11-30", tz=tz)
|
| 14 |
+
assert result == expected
|
| 15 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 16 |
+
|
| 17 |
+
def test_normalize_pre_epoch_dates(self):
|
| 18 |
+
# GH: 36294
|
| 19 |
+
result = Timestamp("1969-01-01 09:00:00").normalize()
|
| 20 |
+
expected = Timestamp("1969-01-01 00:00:00")
|
| 21 |
+
assert result == expected
|
| 22 |
+
|
| 23 |
+
def test_normalize_overflow_raises(self):
|
| 24 |
+
# GH#60583
|
| 25 |
+
ts = Timestamp.min
|
| 26 |
+
|
| 27 |
+
msg = "Cannot normalize Timestamp without integer overflow"
|
| 28 |
+
with pytest.raises(ValueError, match=msg):
|
| 29 |
+
ts.normalize()
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_replace.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime
|
| 2 |
+
import zoneinfo
|
| 3 |
+
|
| 4 |
+
from dateutil.tz import gettz
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
from pandas._libs.tslibs import (
|
| 9 |
+
OutOfBoundsDatetime,
|
| 10 |
+
Timestamp,
|
| 11 |
+
conversion,
|
| 12 |
+
)
|
| 13 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 14 |
+
from pandas.compat import WASM
|
| 15 |
+
import pandas.util._test_decorators as td
|
| 16 |
+
|
| 17 |
+
import pandas._testing as tm
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TestTimestampReplace:
|
| 21 |
+
def test_replace_out_of_pydatetime_bounds(self):
|
| 22 |
+
# GH#50348
|
| 23 |
+
ts = Timestamp("2016-01-01").as_unit("ns")
|
| 24 |
+
|
| 25 |
+
msg = "Out of bounds timestamp: 99999-01-01 00:00:00 with frequency 'ns'"
|
| 26 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 27 |
+
ts.replace(year=99_999)
|
| 28 |
+
|
| 29 |
+
ts = ts.as_unit("ms")
|
| 30 |
+
result = ts.replace(year=99_999)
|
| 31 |
+
assert result.year == 99_999
|
| 32 |
+
assert result._value == Timestamp(np.datetime64("99999-01-01", "ms"))._value
|
| 33 |
+
|
| 34 |
+
def test_replace_non_nano(self):
|
| 35 |
+
ts = Timestamp._from_value_and_reso(
|
| 36 |
+
91514880000000000, NpyDatetimeUnit.NPY_FR_us.value, None
|
| 37 |
+
)
|
| 38 |
+
assert ts.to_pydatetime() == datetime(4869, 12, 28)
|
| 39 |
+
|
| 40 |
+
result = ts.replace(year=4900)
|
| 41 |
+
assert result._creso == ts._creso
|
| 42 |
+
assert result.to_pydatetime() == datetime(4900, 12, 28)
|
| 43 |
+
|
| 44 |
+
def test_replace_naive(self):
|
| 45 |
+
# GH#14621, GH#7825
|
| 46 |
+
ts = Timestamp("2016-01-01 09:00:00")
|
| 47 |
+
result = ts.replace(hour=0)
|
| 48 |
+
expected = Timestamp("2016-01-01 00:00:00")
|
| 49 |
+
assert result == expected
|
| 50 |
+
|
| 51 |
+
def test_replace_aware(self, tz_aware_fixture):
|
| 52 |
+
tz = tz_aware_fixture
|
| 53 |
+
# GH#14621, GH#7825
|
| 54 |
+
# replacing datetime components with and w/o presence of a timezone
|
| 55 |
+
ts = Timestamp("2016-01-01 09:00:00", tz=tz)
|
| 56 |
+
result = ts.replace(hour=0)
|
| 57 |
+
expected = Timestamp("2016-01-01 00:00:00", tz=tz)
|
| 58 |
+
assert result == expected
|
| 59 |
+
|
| 60 |
+
def test_replace_preserves_nanos(self, tz_aware_fixture):
|
| 61 |
+
tz = tz_aware_fixture
|
| 62 |
+
# GH#14621, GH#7825
|
| 63 |
+
ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz)
|
| 64 |
+
result = ts.replace(hour=0)
|
| 65 |
+
expected = Timestamp("2016-01-01 00:00:00.000000123", tz=tz)
|
| 66 |
+
assert result == expected
|
| 67 |
+
|
| 68 |
+
def test_replace_multiple(self, tz_aware_fixture):
|
| 69 |
+
tz = tz_aware_fixture
|
| 70 |
+
# GH#14621, GH#7825
|
| 71 |
+
# replacing datetime components with and w/o presence of a timezone
|
| 72 |
+
# test all
|
| 73 |
+
ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz)
|
| 74 |
+
result = ts.replace(
|
| 75 |
+
year=2015,
|
| 76 |
+
month=2,
|
| 77 |
+
day=2,
|
| 78 |
+
hour=0,
|
| 79 |
+
minute=5,
|
| 80 |
+
second=5,
|
| 81 |
+
microsecond=5,
|
| 82 |
+
nanosecond=5,
|
| 83 |
+
)
|
| 84 |
+
expected = Timestamp("2015-02-02 00:05:05.000005005", tz=tz)
|
| 85 |
+
assert result == expected
|
| 86 |
+
|
| 87 |
+
def test_replace_invalid_kwarg(self, tz_aware_fixture):
|
| 88 |
+
tz = tz_aware_fixture
|
| 89 |
+
# GH#14621, GH#7825
|
| 90 |
+
ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz)
|
| 91 |
+
msg = r"replace\(\) got an unexpected keyword argument"
|
| 92 |
+
with pytest.raises(TypeError, match=msg):
|
| 93 |
+
ts.replace(foo=5)
|
| 94 |
+
|
| 95 |
+
def test_replace_integer_args(self, tz_aware_fixture):
|
| 96 |
+
tz = tz_aware_fixture
|
| 97 |
+
# GH#14621, GH#7825
|
| 98 |
+
ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz)
|
| 99 |
+
msg = "value must be an integer, received <class 'float'> for hour"
|
| 100 |
+
with pytest.raises(ValueError, match=msg):
|
| 101 |
+
ts.replace(hour=0.1)
|
| 102 |
+
|
| 103 |
+
@pytest.mark.skipif(WASM, reason="tzset is not available on WASM")
|
| 104 |
+
def test_replace_tzinfo_equiv_tz_localize_none(self):
|
| 105 |
+
# GH#14621, GH#7825
|
| 106 |
+
# assert conversion to naive is the same as replacing tzinfo with None
|
| 107 |
+
ts = Timestamp("2013-11-03 01:59:59.999999-0400", tz="US/Eastern")
|
| 108 |
+
assert ts.tz_localize(None) == ts.replace(tzinfo=None)
|
| 109 |
+
|
| 110 |
+
@td.skip_if_windows
|
| 111 |
+
@pytest.mark.skipif(WASM, reason="tzset is not available on WASM")
|
| 112 |
+
def test_replace_tzinfo(self):
|
| 113 |
+
# GH#15683
|
| 114 |
+
dt = datetime(2016, 3, 27, 1, fold=1)
|
| 115 |
+
tzinfo = dt.astimezone(zoneinfo.ZoneInfo("Europe/Berlin")).tzinfo
|
| 116 |
+
|
| 117 |
+
result_dt = dt.replace(tzinfo=tzinfo)
|
| 118 |
+
result_pd = Timestamp(dt).replace(tzinfo=tzinfo)
|
| 119 |
+
|
| 120 |
+
# datetime.timestamp() converts in the local timezone
|
| 121 |
+
with tm.set_timezone("UTC"):
|
| 122 |
+
assert result_dt.timestamp() == result_pd.timestamp()
|
| 123 |
+
|
| 124 |
+
assert result_dt == result_pd
|
| 125 |
+
assert result_dt == result_pd.to_pydatetime()
|
| 126 |
+
|
| 127 |
+
result_dt = dt.replace(tzinfo=tzinfo).replace(tzinfo=None)
|
| 128 |
+
result_pd = Timestamp(dt).replace(tzinfo=tzinfo).replace(tzinfo=None)
|
| 129 |
+
|
| 130 |
+
# datetime.timestamp() converts in the local timezone
|
| 131 |
+
with tm.set_timezone("UTC"):
|
| 132 |
+
assert result_dt.timestamp() == result_pd.timestamp()
|
| 133 |
+
|
| 134 |
+
assert result_dt == result_pd
|
| 135 |
+
assert result_dt == result_pd.to_pydatetime()
|
| 136 |
+
|
| 137 |
+
@pytest.mark.parametrize(
|
| 138 |
+
"tz, normalize",
|
| 139 |
+
[
|
| 140 |
+
("pytz/US/Eastern", lambda x: x.tzinfo.normalize(x)),
|
| 141 |
+
(gettz("US/Eastern"), lambda x: x),
|
| 142 |
+
],
|
| 143 |
+
)
|
| 144 |
+
def test_replace_across_dst(self, tz, normalize):
|
| 145 |
+
# GH#18319 check that 1) timezone is correctly normalized and
|
| 146 |
+
# 2) that hour is not incorrectly changed by this normalization
|
| 147 |
+
if isinstance(tz, str) and tz.startswith("pytz/"):
|
| 148 |
+
pytz = pytest.importorskip("pytz")
|
| 149 |
+
tz = pytz.timezone(tz.removeprefix("pytz/"))
|
| 150 |
+
ts_naive = Timestamp("2017-12-03 16:03:30")
|
| 151 |
+
ts_aware = conversion.localize_pydatetime(ts_naive, tz)
|
| 152 |
+
|
| 153 |
+
# Preliminary sanity-check
|
| 154 |
+
assert ts_aware == normalize(ts_aware)
|
| 155 |
+
|
| 156 |
+
# Replace across DST boundary
|
| 157 |
+
ts2 = ts_aware.replace(month=6)
|
| 158 |
+
|
| 159 |
+
# Check that `replace` preserves hour literal
|
| 160 |
+
assert (ts2.hour, ts2.minute) == (ts_aware.hour, ts_aware.minute)
|
| 161 |
+
|
| 162 |
+
# Check that post-replace object is appropriately normalized
|
| 163 |
+
ts2b = normalize(ts2)
|
| 164 |
+
assert ts2 == ts2b
|
| 165 |
+
|
| 166 |
+
def test_replace_dst_border(self, unit):
|
| 167 |
+
# Gh 7825
|
| 168 |
+
t = Timestamp("2013-11-3", tz="America/Chicago").as_unit(unit)
|
| 169 |
+
result = t.replace(hour=3)
|
| 170 |
+
expected = Timestamp("2013-11-3 03:00:00", tz="America/Chicago")
|
| 171 |
+
assert result == expected
|
| 172 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 173 |
+
|
| 174 |
+
@pytest.mark.parametrize("fold", [0, 1])
|
| 175 |
+
@pytest.mark.parametrize("tz", ["dateutil/Europe/London", "Europe/London"])
|
| 176 |
+
def test_replace_dst_fold(self, fold, tz, unit):
|
| 177 |
+
# GH 25017
|
| 178 |
+
d = datetime(2019, 10, 27, 2, 30)
|
| 179 |
+
ts = Timestamp(d, tz=tz).as_unit(unit)
|
| 180 |
+
result = ts.replace(hour=1, fold=fold)
|
| 181 |
+
expected = Timestamp(datetime(2019, 10, 27, 1, 30)).tz_localize(
|
| 182 |
+
tz, ambiguous=not fold
|
| 183 |
+
)
|
| 184 |
+
assert result == expected
|
| 185 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 186 |
+
|
| 187 |
+
@pytest.mark.parametrize("fold", [0, 1])
|
| 188 |
+
def test_replace_preserves_fold(self, fold):
|
| 189 |
+
# GH#37610. Check that replace preserves Timestamp fold property
|
| 190 |
+
tz = gettz("Europe/Moscow")
|
| 191 |
+
|
| 192 |
+
ts = Timestamp(
|
| 193 |
+
year=2009, month=10, day=25, hour=2, minute=30, fold=fold, tzinfo=tz
|
| 194 |
+
)
|
| 195 |
+
ts_replaced = ts.replace(second=1)
|
| 196 |
+
|
| 197 |
+
assert ts_replaced.fold == fold
|
| 198 |
+
|
| 199 |
+
def test_replace_updates_unit(self):
|
| 200 |
+
# GH#57749
|
| 201 |
+
ts = Timestamp("2023-07-15 23:08:12.134567123")
|
| 202 |
+
ts2 = Timestamp("2023-07-15 23:08:12.000000")
|
| 203 |
+
assert ts2.unit == "us"
|
| 204 |
+
result = ts2.replace(microsecond=ts.microsecond, nanosecond=ts.nanosecond)
|
| 205 |
+
assert result.unit == "ns"
|
| 206 |
+
assert result == ts
|
| 207 |
+
|
| 208 |
+
ts3 = Timestamp("2023-07-15 23:08:12").as_unit("s")
|
| 209 |
+
result = ts3.replace(microsecond=ts2.microsecond)
|
| 210 |
+
assert result.unit == "us"
|
| 211 |
+
assert result == ts2
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_round.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from hypothesis import (
|
| 2 |
+
given,
|
| 3 |
+
strategies as st,
|
| 4 |
+
)
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
from pandas._libs import lib
|
| 9 |
+
from pandas._libs.tslibs import (
|
| 10 |
+
NaT,
|
| 11 |
+
OutOfBoundsDatetime,
|
| 12 |
+
Timedelta,
|
| 13 |
+
Timestamp,
|
| 14 |
+
iNaT,
|
| 15 |
+
to_offset,
|
| 16 |
+
)
|
| 17 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 18 |
+
from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
|
| 19 |
+
|
| 20 |
+
import pandas._testing as tm
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TestTimestampRound:
|
| 24 |
+
def test_round_division_by_zero_raises(self):
|
| 25 |
+
ts = Timestamp("2016-01-01")
|
| 26 |
+
|
| 27 |
+
msg = "Division by zero in rounding"
|
| 28 |
+
with pytest.raises(ValueError, match=msg):
|
| 29 |
+
ts.round("0ns")
|
| 30 |
+
|
| 31 |
+
@pytest.mark.parametrize(
|
| 32 |
+
"timestamp, freq, expected",
|
| 33 |
+
[
|
| 34 |
+
("20130101 09:10:11", "D", "20130101"),
|
| 35 |
+
("20130101 19:10:11", "D", "20130102"),
|
| 36 |
+
("20130201 12:00:00", "D", "20130202"),
|
| 37 |
+
("20130104 12:00:00", "D", "20130105"),
|
| 38 |
+
("2000-01-05 05:09:15.13", "D", "2000-01-05 00:00:00"),
|
| 39 |
+
("2000-01-05 05:09:15.13", "h", "2000-01-05 05:00:00"),
|
| 40 |
+
("2000-01-05 05:09:15.13", "s", "2000-01-05 05:09:15"),
|
| 41 |
+
],
|
| 42 |
+
)
|
| 43 |
+
def test_round_frequencies(self, timestamp, freq, expected):
|
| 44 |
+
dt = Timestamp(timestamp)
|
| 45 |
+
result = dt.round(freq)
|
| 46 |
+
expected = Timestamp(expected)
|
| 47 |
+
assert result == expected
|
| 48 |
+
|
| 49 |
+
def test_round_tzaware(self):
|
| 50 |
+
dt = Timestamp("20130101 09:10:11", tz="US/Eastern")
|
| 51 |
+
result = dt.round("D")
|
| 52 |
+
expected = Timestamp("20130101", tz="US/Eastern")
|
| 53 |
+
assert result == expected
|
| 54 |
+
|
| 55 |
+
dt = Timestamp("20130101 09:10:11", tz="US/Eastern")
|
| 56 |
+
result = dt.round("s")
|
| 57 |
+
assert result == dt
|
| 58 |
+
|
| 59 |
+
def test_round_30min(self):
|
| 60 |
+
# round
|
| 61 |
+
dt = Timestamp("20130104 12:32:00")
|
| 62 |
+
result = dt.round("30Min")
|
| 63 |
+
expected = Timestamp("20130104 12:30:00")
|
| 64 |
+
assert result == expected
|
| 65 |
+
|
| 66 |
+
def test_round_subsecond(self):
|
| 67 |
+
# GH#14440 & GH#15578
|
| 68 |
+
result = Timestamp("2016-10-17 12:00:00.0015").round("ms")
|
| 69 |
+
expected = Timestamp("2016-10-17 12:00:00.002000")
|
| 70 |
+
assert result == expected
|
| 71 |
+
|
| 72 |
+
result = Timestamp("2016-10-17 12:00:00.00149").round("ms")
|
| 73 |
+
expected = Timestamp("2016-10-17 12:00:00.001000")
|
| 74 |
+
assert result == expected
|
| 75 |
+
|
| 76 |
+
ts = Timestamp("2016-10-17 12:00:00.0015")
|
| 77 |
+
for freq in ["us", "ns"]:
|
| 78 |
+
assert ts == ts.round(freq)
|
| 79 |
+
|
| 80 |
+
result = Timestamp("2016-10-17 12:00:00.001501031").round("10ns")
|
| 81 |
+
expected = Timestamp("2016-10-17 12:00:00.001501030")
|
| 82 |
+
assert result == expected
|
| 83 |
+
|
| 84 |
+
def test_round_nonstandard_freq(self):
|
| 85 |
+
with tm.assert_produces_warning(False):
|
| 86 |
+
Timestamp("2016-10-17 12:00:00.001501031").round("1010ns")
|
| 87 |
+
|
| 88 |
+
def test_round_invalid_arg(self):
|
| 89 |
+
stamp = Timestamp("2000-01-05 05:09:15.13")
|
| 90 |
+
with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
|
| 91 |
+
stamp.round("foo")
|
| 92 |
+
|
| 93 |
+
@pytest.mark.parametrize(
|
| 94 |
+
"test_input, rounder, freq, expected",
|
| 95 |
+
[
|
| 96 |
+
("2117-01-01 00:00:45", "floor", "15s", "2117-01-01 00:00:45"),
|
| 97 |
+
("2117-01-01 00:00:45", "ceil", "15s", "2117-01-01 00:00:45"),
|
| 98 |
+
(
|
| 99 |
+
"2117-01-01 00:00:45.000000012",
|
| 100 |
+
"floor",
|
| 101 |
+
"10ns",
|
| 102 |
+
"2117-01-01 00:00:45.000000010",
|
| 103 |
+
),
|
| 104 |
+
(
|
| 105 |
+
"1823-01-01 00:00:01.000000012",
|
| 106 |
+
"ceil",
|
| 107 |
+
"10ns",
|
| 108 |
+
"1823-01-01 00:00:01.000000020",
|
| 109 |
+
),
|
| 110 |
+
("1823-01-01 00:00:01", "floor", "1s", "1823-01-01 00:00:01"),
|
| 111 |
+
("1823-01-01 00:00:01", "ceil", "1s", "1823-01-01 00:00:01"),
|
| 112 |
+
("NaT", "floor", "1s", "NaT"),
|
| 113 |
+
("NaT", "ceil", "1s", "NaT"),
|
| 114 |
+
],
|
| 115 |
+
)
|
| 116 |
+
def test_ceil_floor_edge(self, test_input, rounder, freq, expected):
|
| 117 |
+
dt = Timestamp(test_input)
|
| 118 |
+
func = getattr(dt, rounder)
|
| 119 |
+
result = func(freq)
|
| 120 |
+
|
| 121 |
+
if dt is NaT:
|
| 122 |
+
assert result is NaT
|
| 123 |
+
else:
|
| 124 |
+
expected = Timestamp(expected)
|
| 125 |
+
assert result == expected
|
| 126 |
+
|
| 127 |
+
@pytest.mark.parametrize(
|
| 128 |
+
"test_input, freq, expected",
|
| 129 |
+
[
|
| 130 |
+
("2018-01-01 00:02:06", "2s", "2018-01-01 00:02:06"),
|
| 131 |
+
("2018-01-01 00:02:00", "2min", "2018-01-01 00:02:00"),
|
| 132 |
+
("2018-01-01 00:04:00", "4min", "2018-01-01 00:04:00"),
|
| 133 |
+
("2018-01-01 00:15:00", "15min", "2018-01-01 00:15:00"),
|
| 134 |
+
("2018-01-01 00:20:00", "20min", "2018-01-01 00:20:00"),
|
| 135 |
+
("2018-01-01 03:00:00", "3h", "2018-01-01 03:00:00"),
|
| 136 |
+
],
|
| 137 |
+
)
|
| 138 |
+
@pytest.mark.parametrize("rounder", ["ceil", "floor", "round"])
|
| 139 |
+
def test_round_minute_freq(self, test_input, freq, expected, rounder):
|
| 140 |
+
# Ensure timestamps that shouldn't round dont!
|
| 141 |
+
# GH#21262
|
| 142 |
+
|
| 143 |
+
dt = Timestamp(test_input)
|
| 144 |
+
expected = Timestamp(expected)
|
| 145 |
+
func = getattr(dt, rounder)
|
| 146 |
+
result = func(freq)
|
| 147 |
+
assert result == expected
|
| 148 |
+
|
| 149 |
+
def test_ceil(self, unit):
|
| 150 |
+
dt = Timestamp("20130101 09:10:11").as_unit(unit)
|
| 151 |
+
result = dt.ceil("D")
|
| 152 |
+
expected = Timestamp("20130102")
|
| 153 |
+
assert result == expected
|
| 154 |
+
assert result._creso == dt._creso
|
| 155 |
+
|
| 156 |
+
def test_floor(self, unit):
|
| 157 |
+
dt = Timestamp("20130101 09:10:11").as_unit(unit)
|
| 158 |
+
result = dt.floor("D")
|
| 159 |
+
expected = Timestamp("20130101")
|
| 160 |
+
assert result == expected
|
| 161 |
+
assert result._creso == dt._creso
|
| 162 |
+
|
| 163 |
+
@pytest.mark.parametrize("method", ["ceil", "round", "floor"])
|
| 164 |
+
def test_round_dst_border_ambiguous(self, method, unit):
|
| 165 |
+
# GH 18946 round near "fall back" DST
|
| 166 |
+
ts = Timestamp("2017-10-29 00:00:00", tz="UTC").tz_convert("Europe/Madrid")
|
| 167 |
+
ts = ts.as_unit(unit)
|
| 168 |
+
result = getattr(ts, method)("h", ambiguous=True)
|
| 169 |
+
assert result == ts
|
| 170 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 171 |
+
|
| 172 |
+
result = getattr(ts, method)("h", ambiguous=False)
|
| 173 |
+
expected = Timestamp("2017-10-29 01:00:00", tz="UTC").tz_convert(
|
| 174 |
+
"Europe/Madrid"
|
| 175 |
+
)
|
| 176 |
+
assert result == expected
|
| 177 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 178 |
+
|
| 179 |
+
result = getattr(ts, method)("h", ambiguous="NaT")
|
| 180 |
+
assert result is NaT
|
| 181 |
+
|
| 182 |
+
msg = "Cannot infer dst time"
|
| 183 |
+
with pytest.raises(ValueError, match=msg):
|
| 184 |
+
getattr(ts, method)("h", ambiguous="raise")
|
| 185 |
+
|
| 186 |
+
@pytest.mark.parametrize(
|
| 187 |
+
"method, ts_str, freq",
|
| 188 |
+
[
|
| 189 |
+
["ceil", "2018-03-11 01:59:00-0600", "5min"],
|
| 190 |
+
["round", "2018-03-11 01:59:00-0600", "5min"],
|
| 191 |
+
["floor", "2018-03-11 03:01:00-0500", "2h"],
|
| 192 |
+
],
|
| 193 |
+
)
|
| 194 |
+
def test_round_dst_border_nonexistent(self, method, ts_str, freq, unit):
|
| 195 |
+
# GH 23324 round near "spring forward" DST
|
| 196 |
+
ts = Timestamp(ts_str, tz="America/Chicago").as_unit(unit)
|
| 197 |
+
result = getattr(ts, method)(freq, nonexistent="shift_forward")
|
| 198 |
+
expected = Timestamp("2018-03-11 03:00:00", tz="America/Chicago")
|
| 199 |
+
assert result == expected
|
| 200 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 201 |
+
|
| 202 |
+
result = getattr(ts, method)(freq, nonexistent="NaT")
|
| 203 |
+
assert result is NaT
|
| 204 |
+
|
| 205 |
+
msg = "2018-03-11 02:00:00"
|
| 206 |
+
with pytest.raises(ValueError, match=msg):
|
| 207 |
+
getattr(ts, method)(freq, nonexistent="raise")
|
| 208 |
+
|
| 209 |
+
@pytest.mark.parametrize(
|
| 210 |
+
"timestamp",
|
| 211 |
+
[
|
| 212 |
+
"2018-01-01 0:0:0.124999360",
|
| 213 |
+
"2018-01-01 0:0:0.125000367",
|
| 214 |
+
"2018-01-01 0:0:0.125500",
|
| 215 |
+
"2018-01-01 0:0:0.126500",
|
| 216 |
+
"2018-01-01 12:00:00",
|
| 217 |
+
"2019-01-01 12:00:00",
|
| 218 |
+
],
|
| 219 |
+
)
|
| 220 |
+
@pytest.mark.parametrize(
|
| 221 |
+
"freq",
|
| 222 |
+
[
|
| 223 |
+
"2ns",
|
| 224 |
+
"3ns",
|
| 225 |
+
"4ns",
|
| 226 |
+
"5ns",
|
| 227 |
+
"6ns",
|
| 228 |
+
"7ns",
|
| 229 |
+
"250ns",
|
| 230 |
+
"500ns",
|
| 231 |
+
"750ns",
|
| 232 |
+
"1us",
|
| 233 |
+
"19us",
|
| 234 |
+
"250us",
|
| 235 |
+
"500us",
|
| 236 |
+
"750us",
|
| 237 |
+
"1s",
|
| 238 |
+
"2s",
|
| 239 |
+
"3s",
|
| 240 |
+
"1D",
|
| 241 |
+
],
|
| 242 |
+
)
|
| 243 |
+
def test_round_int64(self, timestamp, freq):
|
| 244 |
+
# check that all rounding modes are accurate to int64 precision
|
| 245 |
+
# see GH#22591
|
| 246 |
+
dt = Timestamp(timestamp).as_unit("ns")
|
| 247 |
+
unit = to_offset(freq).nanos
|
| 248 |
+
|
| 249 |
+
# test floor
|
| 250 |
+
result = dt.floor(freq)
|
| 251 |
+
assert result._value % unit == 0, f"floor not a {freq} multiple"
|
| 252 |
+
assert 0 <= dt._value - result._value < unit, "floor error"
|
| 253 |
+
|
| 254 |
+
# test ceil
|
| 255 |
+
result = dt.ceil(freq)
|
| 256 |
+
assert result._value % unit == 0, f"ceil not a {freq} multiple"
|
| 257 |
+
assert 0 <= result._value - dt._value < unit, "ceil error"
|
| 258 |
+
|
| 259 |
+
# test round
|
| 260 |
+
result = dt.round(freq)
|
| 261 |
+
assert result._value % unit == 0, f"round not a {freq} multiple"
|
| 262 |
+
assert abs(result._value - dt._value) <= unit // 2, "round error"
|
| 263 |
+
if unit % 2 == 0 and abs(result._value - dt._value) == unit // 2:
|
| 264 |
+
# round half to even
|
| 265 |
+
assert result._value // unit % 2 == 0, "round half to even error"
|
| 266 |
+
|
| 267 |
+
def test_round_implementation_bounds(self):
|
| 268 |
+
# See also: analogous test for Timedelta
|
| 269 |
+
result = Timestamp.min.ceil("s")
|
| 270 |
+
expected = Timestamp(1677, 9, 21, 0, 12, 44)
|
| 271 |
+
assert result == expected
|
| 272 |
+
|
| 273 |
+
result = Timestamp.max.floor("s")
|
| 274 |
+
expected = Timestamp.max - Timedelta(854775807)
|
| 275 |
+
assert result == expected
|
| 276 |
+
|
| 277 |
+
msg = "Cannot round 1677-09-21 00:12:43.145224193 to freq=<Second>"
|
| 278 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 279 |
+
Timestamp.min.floor("s")
|
| 280 |
+
|
| 281 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 282 |
+
Timestamp.min.round("s")
|
| 283 |
+
|
| 284 |
+
msg = "Cannot round 2262-04-11 23:47:16.854775807 to freq=<Second>"
|
| 285 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 286 |
+
Timestamp.max.ceil("s")
|
| 287 |
+
|
| 288 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 289 |
+
Timestamp.max.round("s")
|
| 290 |
+
|
| 291 |
+
@pytest.mark.slow
|
| 292 |
+
@given(val=st.integers(iNaT + 1, lib.i8max))
|
| 293 |
+
@pytest.mark.parametrize(
|
| 294 |
+
"method", [Timestamp.round, Timestamp.floor, Timestamp.ceil]
|
| 295 |
+
)
|
| 296 |
+
def test_round_sanity(self, val, method):
|
| 297 |
+
cls = Timestamp
|
| 298 |
+
err_cls = OutOfBoundsDatetime
|
| 299 |
+
|
| 300 |
+
val = np.int64(val)
|
| 301 |
+
ts = cls(val)
|
| 302 |
+
|
| 303 |
+
def checker(ts, nanos, unit):
|
| 304 |
+
# First check that we do raise in cases where we should
|
| 305 |
+
if nanos == 1:
|
| 306 |
+
pass
|
| 307 |
+
else:
|
| 308 |
+
div, mod = divmod(ts._value, nanos)
|
| 309 |
+
diff = int(nanos - mod)
|
| 310 |
+
lb = ts._value - mod
|
| 311 |
+
assert lb <= ts._value # i.e. no overflows with python ints
|
| 312 |
+
ub = ts._value + diff
|
| 313 |
+
assert ub > ts._value # i.e. no overflows with python ints
|
| 314 |
+
|
| 315 |
+
msg = "without overflow"
|
| 316 |
+
if mod == 0:
|
| 317 |
+
# We should never be raising in this
|
| 318 |
+
pass
|
| 319 |
+
elif method is cls.ceil:
|
| 320 |
+
if ub > cls.max._value:
|
| 321 |
+
with pytest.raises(err_cls, match=msg):
|
| 322 |
+
method(ts, unit)
|
| 323 |
+
return
|
| 324 |
+
elif method is cls.floor:
|
| 325 |
+
if lb < cls.min._value:
|
| 326 |
+
with pytest.raises(err_cls, match=msg):
|
| 327 |
+
method(ts, unit)
|
| 328 |
+
return
|
| 329 |
+
elif mod >= diff:
|
| 330 |
+
if ub > cls.max._value:
|
| 331 |
+
with pytest.raises(err_cls, match=msg):
|
| 332 |
+
method(ts, unit)
|
| 333 |
+
return
|
| 334 |
+
elif lb < cls.min._value:
|
| 335 |
+
with pytest.raises(err_cls, match=msg):
|
| 336 |
+
method(ts, unit)
|
| 337 |
+
return
|
| 338 |
+
|
| 339 |
+
res = method(ts, unit)
|
| 340 |
+
|
| 341 |
+
td = res - ts
|
| 342 |
+
diff = abs(td._value)
|
| 343 |
+
assert diff < nanos
|
| 344 |
+
assert res._value % nanos == 0
|
| 345 |
+
|
| 346 |
+
if method is cls.round:
|
| 347 |
+
assert diff <= nanos / 2
|
| 348 |
+
elif method is cls.floor:
|
| 349 |
+
assert res <= ts
|
| 350 |
+
elif method is cls.ceil:
|
| 351 |
+
assert res >= ts
|
| 352 |
+
|
| 353 |
+
nanos = 1
|
| 354 |
+
checker(ts, nanos, "ns")
|
| 355 |
+
|
| 356 |
+
nanos = 1000
|
| 357 |
+
checker(ts, nanos, "us")
|
| 358 |
+
|
| 359 |
+
nanos = 1_000_000
|
| 360 |
+
checker(ts, nanos, "ms")
|
| 361 |
+
|
| 362 |
+
nanos = 1_000_000_000
|
| 363 |
+
checker(ts, nanos, "s")
|
| 364 |
+
|
| 365 |
+
nanos = 60 * 1_000_000_000
|
| 366 |
+
checker(ts, nanos, "min")
|
| 367 |
+
|
| 368 |
+
nanos = 60 * 60 * 1_000_000_000
|
| 369 |
+
checker(ts, nanos, "h")
|
| 370 |
+
|
| 371 |
+
nanos = 24 * 60 * 60 * 1_000_000_000
|
| 372 |
+
checker(ts, nanos, "D")
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_timestamp_method.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# NB: This is for the Timestamp.timestamp *method* specifically, not
|
| 2 |
+
# the Timestamp class in general.
|
| 3 |
+
from datetime import timezone
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
|
| 7 |
+
from pandas._libs.tslibs import Timestamp
|
| 8 |
+
from pandas.compat import WASM
|
| 9 |
+
import pandas.util._test_decorators as td
|
| 10 |
+
|
| 11 |
+
import pandas._testing as tm
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TestTimestampMethod:
|
| 15 |
+
@td.skip_if_windows
|
| 16 |
+
@pytest.mark.skipif(WASM, reason="tzset is not available on WASM")
|
| 17 |
+
def test_timestamp(self, fixed_now_ts):
|
| 18 |
+
# GH#17329
|
| 19 |
+
# tz-naive --> treat it as if it were UTC for purposes of timestamp()
|
| 20 |
+
ts = fixed_now_ts
|
| 21 |
+
uts = ts.replace(tzinfo=timezone.utc)
|
| 22 |
+
assert ts.timestamp() == uts.timestamp()
|
| 23 |
+
|
| 24 |
+
tsc = Timestamp("2014-10-11 11:00:01.12345678", tz="US/Central")
|
| 25 |
+
utsc = tsc.tz_convert("UTC")
|
| 26 |
+
|
| 27 |
+
# utsc is a different representation of the same time
|
| 28 |
+
assert tsc.timestamp() == utsc.timestamp()
|
| 29 |
+
|
| 30 |
+
# datetime.timestamp() converts in the local timezone
|
| 31 |
+
with tm.set_timezone("UTC"):
|
| 32 |
+
# should agree with datetime.timestamp method
|
| 33 |
+
dt = ts.to_pydatetime()
|
| 34 |
+
assert dt.timestamp() == ts.timestamp()
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_julian_date.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pandas import Timestamp
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class TestTimestampToJulianDate:
|
| 5 |
+
def test_compare_1700(self):
|
| 6 |
+
ts = Timestamp("1700-06-23")
|
| 7 |
+
res = ts.to_julian_date()
|
| 8 |
+
assert res == 2_342_145.5
|
| 9 |
+
|
| 10 |
+
def test_compare_2000(self):
|
| 11 |
+
ts = Timestamp("2000-04-12")
|
| 12 |
+
res = ts.to_julian_date()
|
| 13 |
+
assert res == 2_451_646.5
|
| 14 |
+
|
| 15 |
+
def test_compare_2100(self):
|
| 16 |
+
ts = Timestamp("2100-08-12")
|
| 17 |
+
res = ts.to_julian_date()
|
| 18 |
+
assert res == 2_488_292.5
|
| 19 |
+
|
| 20 |
+
def test_compare_hour01(self):
|
| 21 |
+
ts = Timestamp("2000-08-12T01:00:00")
|
| 22 |
+
res = ts.to_julian_date()
|
| 23 |
+
assert res == 2_451_768.5416666666666666
|
| 24 |
+
|
| 25 |
+
def test_compare_hour13(self):
|
| 26 |
+
ts = Timestamp("2000-08-12T13:00:00")
|
| 27 |
+
res = ts.to_julian_date()
|
| 28 |
+
assert res == 2_451_769.0416666666666666
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_pydatetime.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import (
|
| 2 |
+
datetime,
|
| 3 |
+
timedelta,
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
from pandas._libs.tslibs.timezones import dateutil_gettz as gettz
|
| 9 |
+
import pandas.util._test_decorators as td
|
| 10 |
+
|
| 11 |
+
from pandas import Timestamp
|
| 12 |
+
import pandas._testing as tm
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class TestTimestampToPyDatetime:
|
| 16 |
+
def test_to_pydatetime_fold(self):
|
| 17 |
+
# GH#45087
|
| 18 |
+
tzstr = "dateutil/usr/share/zoneinfo/America/Chicago"
|
| 19 |
+
ts = Timestamp(year=2013, month=11, day=3, hour=1, minute=0, fold=1, tz=tzstr)
|
| 20 |
+
dt = ts.to_pydatetime()
|
| 21 |
+
assert dt.fold == 1
|
| 22 |
+
|
| 23 |
+
def test_to_pydatetime_nonzero_nano(self):
|
| 24 |
+
ts = Timestamp("2011-01-01 9:00:00.123456789")
|
| 25 |
+
|
| 26 |
+
# Warn the user of data loss (nanoseconds).
|
| 27 |
+
msg = "Discarding nonzero nanoseconds in conversion"
|
| 28 |
+
with tm.assert_produces_warning(UserWarning, match=msg):
|
| 29 |
+
expected = datetime(2011, 1, 1, 9, 0, 0, 123456)
|
| 30 |
+
result = ts.to_pydatetime()
|
| 31 |
+
assert result == expected
|
| 32 |
+
|
| 33 |
+
def test_timestamp_to_datetime(self):
|
| 34 |
+
stamp = Timestamp("20090415", tz="US/Eastern")
|
| 35 |
+
dtval = stamp.to_pydatetime()
|
| 36 |
+
assert stamp == dtval
|
| 37 |
+
assert stamp.tzinfo == dtval.tzinfo
|
| 38 |
+
|
| 39 |
+
def test_timestamp_to_pydatetime_dateutil(self):
|
| 40 |
+
stamp = Timestamp("20090415", tz="dateutil/US/Eastern")
|
| 41 |
+
dtval = stamp.to_pydatetime()
|
| 42 |
+
assert stamp == dtval
|
| 43 |
+
assert stamp.tzinfo == dtval.tzinfo
|
| 44 |
+
|
| 45 |
+
def test_timestamp_to_pydatetime_explicit_pytz(self):
|
| 46 |
+
pytz = pytest.importorskip("pytz")
|
| 47 |
+
stamp = Timestamp("20090415", tz=pytz.timezone("US/Eastern"))
|
| 48 |
+
dtval = stamp.to_pydatetime()
|
| 49 |
+
assert stamp == dtval
|
| 50 |
+
assert stamp.tzinfo == dtval.tzinfo
|
| 51 |
+
|
| 52 |
+
@td.skip_if_windows
|
| 53 |
+
def test_timestamp_to_pydatetime_explicit_dateutil(self):
|
| 54 |
+
stamp = Timestamp("20090415", tz=gettz("US/Eastern"))
|
| 55 |
+
dtval = stamp.to_pydatetime()
|
| 56 |
+
assert stamp == dtval
|
| 57 |
+
assert stamp.tzinfo == dtval.tzinfo
|
| 58 |
+
|
| 59 |
+
def test_to_pydatetime_bijective(self):
|
| 60 |
+
# Ensure that converting to datetime and back only loses precision
|
| 61 |
+
# by going from nanoseconds to microseconds.
|
| 62 |
+
exp_warning = None if Timestamp.max.nanosecond == 0 else UserWarning
|
| 63 |
+
with tm.assert_produces_warning(exp_warning):
|
| 64 |
+
pydt_max = Timestamp.max.to_pydatetime()
|
| 65 |
+
|
| 66 |
+
assert (
|
| 67 |
+
Timestamp(pydt_max).as_unit("ns")._value / 1000
|
| 68 |
+
== Timestamp.max._value / 1000
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
exp_warning = None if Timestamp.min.nanosecond == 0 else UserWarning
|
| 72 |
+
with tm.assert_produces_warning(exp_warning):
|
| 73 |
+
pydt_min = Timestamp.min.to_pydatetime()
|
| 74 |
+
|
| 75 |
+
# The next assertion can be enabled once GH#39221 is merged
|
| 76 |
+
# assert pydt_min < Timestamp.min # this is bc nanos are dropped
|
| 77 |
+
tdus = timedelta(microseconds=1)
|
| 78 |
+
assert pydt_min + tdus > Timestamp.min
|
| 79 |
+
|
| 80 |
+
assert (
|
| 81 |
+
Timestamp(pydt_min + tdus).as_unit("ns")._value / 1000
|
| 82 |
+
== Timestamp.min._value / 1000
|
| 83 |
+
)
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_convert.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import dateutil
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas._libs.tslibs import timezones
|
| 5 |
+
import pandas.util._test_decorators as td
|
| 6 |
+
|
| 7 |
+
from pandas import Timestamp
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class TestTimestampTZConvert:
|
| 11 |
+
@pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
|
| 12 |
+
def test_astimezone(self, tzstr):
|
| 13 |
+
# astimezone is an alias for tz_convert, so keep it with
|
| 14 |
+
# the tz_convert tests
|
| 15 |
+
utcdate = Timestamp("3/11/2012 22:00", tz="UTC")
|
| 16 |
+
expected = utcdate.tz_convert(tzstr)
|
| 17 |
+
result = utcdate.astimezone(tzstr)
|
| 18 |
+
assert expected == result
|
| 19 |
+
assert isinstance(result, Timestamp)
|
| 20 |
+
|
| 21 |
+
@pytest.mark.parametrize(
|
| 22 |
+
"stamp",
|
| 23 |
+
[
|
| 24 |
+
"2014-02-01 09:00",
|
| 25 |
+
"2014-07-08 09:00",
|
| 26 |
+
"2014-11-01 17:00",
|
| 27 |
+
"2014-11-05 00:00",
|
| 28 |
+
],
|
| 29 |
+
)
|
| 30 |
+
def test_tz_convert_roundtrip(self, stamp, tz_aware_fixture):
|
| 31 |
+
tz = tz_aware_fixture
|
| 32 |
+
|
| 33 |
+
ts = Timestamp(stamp, tz="UTC")
|
| 34 |
+
converted = ts.tz_convert(tz)
|
| 35 |
+
|
| 36 |
+
reset = converted.tz_convert(None)
|
| 37 |
+
assert reset == Timestamp(stamp)
|
| 38 |
+
assert reset.tzinfo is None
|
| 39 |
+
assert reset == converted.tz_convert("UTC").tz_localize(None)
|
| 40 |
+
|
| 41 |
+
@td.skip_if_windows
|
| 42 |
+
def test_tz_convert_utc_with_system_utc(self):
|
| 43 |
+
# from system utc to real utc
|
| 44 |
+
ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC"))
|
| 45 |
+
# check that the time hasn't changed.
|
| 46 |
+
assert ts == ts.tz_convert(dateutil.tz.tzutc())
|
| 47 |
+
|
| 48 |
+
# from system utc to real utc
|
| 49 |
+
ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC"))
|
| 50 |
+
# check that the time hasn't changed.
|
| 51 |
+
assert ts == ts.tz_convert(dateutil.tz.tzutc())
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_localize.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import timedelta
|
| 2 |
+
import re
|
| 3 |
+
import zoneinfo
|
| 4 |
+
|
| 5 |
+
from dateutil.tz import gettz
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 9 |
+
from pandas.errors import OutOfBoundsDatetime
|
| 10 |
+
|
| 11 |
+
from pandas import (
|
| 12 |
+
NaT,
|
| 13 |
+
Timestamp,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _distant_date_only_for_zoneinfo(year, tz):
|
| 18 |
+
if int(year) >= 2200:
|
| 19 |
+
if isinstance(tz, zoneinfo.ZoneInfo):
|
| 20 |
+
return
|
| 21 |
+
elif isinstance(tz, str) and not tz.startswith(("dateutil/", "pytz/")):
|
| 22 |
+
# zoneinfo tz passed as string
|
| 23 |
+
return
|
| 24 |
+
else:
|
| 25 |
+
pytest.skip("ZoneInfo required for year >= 2200")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class TestTimestampTZLocalize:
|
| 29 |
+
def test_tz_localize_pushes_out_of_bounds(self):
|
| 30 |
+
# GH#12677
|
| 31 |
+
# tz_localize that pushes away from the boundary is OK
|
| 32 |
+
pytz = pytest.importorskip("pytz")
|
| 33 |
+
msg = (
|
| 34 |
+
f"Converting {Timestamp.min.strftime('%Y-%m-%d %H:%M:%S')} "
|
| 35 |
+
f"underflows past {Timestamp.min}"
|
| 36 |
+
)
|
| 37 |
+
pac = Timestamp.min.tz_localize(pytz.timezone("US/Pacific"))
|
| 38 |
+
assert pac._value > Timestamp.min._value
|
| 39 |
+
pac.tz_convert("Asia/Tokyo") # tz_convert doesn't change value
|
| 40 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 41 |
+
Timestamp.min.tz_localize(pytz.timezone("Asia/Tokyo"))
|
| 42 |
+
|
| 43 |
+
# tz_localize that pushes away from the boundary is OK
|
| 44 |
+
msg = (
|
| 45 |
+
f"Converting {Timestamp.max.strftime('%Y-%m-%d %H:%M:%S')} "
|
| 46 |
+
f"overflows past {Timestamp.max}"
|
| 47 |
+
)
|
| 48 |
+
tokyo = Timestamp.max.tz_localize(pytz.timezone("Asia/Tokyo"))
|
| 49 |
+
assert tokyo._value < Timestamp.max._value
|
| 50 |
+
tokyo.tz_convert("US/Pacific") # tz_convert doesn't change value
|
| 51 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 52 |
+
Timestamp.max.tz_localize(pytz.timezone("US/Pacific"))
|
| 53 |
+
|
| 54 |
+
@pytest.mark.parametrize(
|
| 55 |
+
"tz",
|
| 56 |
+
[zoneinfo.ZoneInfo("US/Central"), "dateutil/US/Central", "pytz/US/Central"],
|
| 57 |
+
)
|
| 58 |
+
def test_tz_localize_ambiguous_bool(self, unit, tz):
|
| 59 |
+
# make sure that we are correctly accepting bool values as ambiguous
|
| 60 |
+
# GH#14402
|
| 61 |
+
if isinstance(tz, str) and tz.startswith("pytz/"):
|
| 62 |
+
pytz = pytest.importorskip("pytz")
|
| 63 |
+
tz = pytz.timezone(tz.removeprefix("pytz/"))
|
| 64 |
+
ts = Timestamp("2015-11-01 01:00:03").as_unit(unit)
|
| 65 |
+
expected0 = Timestamp("2015-11-01 01:00:03-0500", tz=tz)
|
| 66 |
+
expected1 = Timestamp("2015-11-01 01:00:03-0600", tz=tz)
|
| 67 |
+
|
| 68 |
+
msg = "Cannot infer dst time from 2015-11-01 01:00:03"
|
| 69 |
+
with pytest.raises(ValueError, match=msg):
|
| 70 |
+
ts.tz_localize(tz)
|
| 71 |
+
|
| 72 |
+
result = ts.tz_localize(tz, ambiguous=True)
|
| 73 |
+
assert result == expected0
|
| 74 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 75 |
+
|
| 76 |
+
result = ts.tz_localize(tz, ambiguous=False)
|
| 77 |
+
assert result == expected1
|
| 78 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 79 |
+
|
| 80 |
+
@pytest.mark.parametrize("year", ["2014", "2200"])
|
| 81 |
+
def test_tz_localize_ambiguous(self, year):
|
| 82 |
+
ts = Timestamp(f"{year}-11-02 01:00").as_unit("s")
|
| 83 |
+
ts_dst = ts.tz_localize("US/Eastern", ambiguous=True)
|
| 84 |
+
ts_no_dst = ts.tz_localize("US/Eastern", ambiguous=False)
|
| 85 |
+
|
| 86 |
+
assert ts_no_dst._value - ts_dst._value == 3600
|
| 87 |
+
msg = re.escape(
|
| 88 |
+
"'ambiguous' parameter must be one of: "
|
| 89 |
+
"True, False, 'NaT', 'raise' (default)"
|
| 90 |
+
)
|
| 91 |
+
with pytest.raises(ValueError, match=msg):
|
| 92 |
+
ts.tz_localize("US/Eastern", ambiguous="infer")
|
| 93 |
+
|
| 94 |
+
def test_tz_localize_invalid(self):
|
| 95 |
+
# GH#8025
|
| 96 |
+
msg = "Cannot localize tz-aware Timestamp, use tz_convert for conversions"
|
| 97 |
+
with pytest.raises(TypeError, match=msg):
|
| 98 |
+
Timestamp("2011-01-01", tz="US/Eastern").tz_localize("Asia/Tokyo")
|
| 99 |
+
|
| 100 |
+
msg = "Cannot convert tz-naive Timestamp, use tz_localize to localize"
|
| 101 |
+
with pytest.raises(TypeError, match=msg):
|
| 102 |
+
Timestamp("2011-01-01").tz_convert("Asia/Tokyo")
|
| 103 |
+
|
| 104 |
+
@pytest.mark.parametrize("year", ["2015", "2201"])
|
| 105 |
+
@pytest.mark.parametrize(
|
| 106 |
+
"stamp, tz",
|
| 107 |
+
[
|
| 108 |
+
("2015-03-08 02:00", "US/Eastern"),
|
| 109 |
+
("2015-03-08 02:30", "US/Pacific"),
|
| 110 |
+
("2015-03-29 02:00", "Europe/Paris"),
|
| 111 |
+
("2015-03-29 02:30", "Europe/Belgrade"),
|
| 112 |
+
],
|
| 113 |
+
)
|
| 114 |
+
def test_tz_localize_nonexistent(self, stamp, tz, year):
|
| 115 |
+
# GH#13057
|
| 116 |
+
stamp = stamp.replace("2015", year)
|
| 117 |
+
ts = Timestamp(stamp)
|
| 118 |
+
with pytest.raises(ValueError, match=stamp):
|
| 119 |
+
ts.tz_localize(tz)
|
| 120 |
+
# GH 22644
|
| 121 |
+
with pytest.raises(ValueError, match=stamp):
|
| 122 |
+
ts.tz_localize(tz, nonexistent="raise")
|
| 123 |
+
assert ts.tz_localize(tz, nonexistent="NaT") is NaT
|
| 124 |
+
|
| 125 |
+
@pytest.mark.parametrize(
|
| 126 |
+
"stamp, tz, forward_expected, backward_expected",
|
| 127 |
+
[
|
| 128 |
+
(
|
| 129 |
+
"2015-03-29 02:00:00",
|
| 130 |
+
"Europe/Warsaw",
|
| 131 |
+
"2015-03-29 03:00:00",
|
| 132 |
+
"2015-03-29 01:59:59.999999",
|
| 133 |
+
), # utc+1 -> utc+2
|
| 134 |
+
(
|
| 135 |
+
"2023-03-12 02:00:00",
|
| 136 |
+
"America/Los_Angeles",
|
| 137 |
+
"2023-03-12 03:00:00",
|
| 138 |
+
"2023-03-12 01:59:59.999999",
|
| 139 |
+
), # utc-8 -> utc-7
|
| 140 |
+
(
|
| 141 |
+
"2023-03-26 01:00:00",
|
| 142 |
+
"Europe/London",
|
| 143 |
+
"2023-03-26 02:00:00",
|
| 144 |
+
"2023-03-26 00:59:59.999999",
|
| 145 |
+
), # utc+0 -> utc+1
|
| 146 |
+
(
|
| 147 |
+
"2023-03-26 00:00:00",
|
| 148 |
+
"Atlantic/Azores",
|
| 149 |
+
"2023-03-26 01:00:00",
|
| 150 |
+
"2023-03-25 23:59:59.999999",
|
| 151 |
+
), # utc-1 -> utc+0
|
| 152 |
+
],
|
| 153 |
+
)
|
| 154 |
+
def test_tz_localize_nonexistent_shift(
|
| 155 |
+
self, stamp, tz, forward_expected, backward_expected
|
| 156 |
+
):
|
| 157 |
+
ts = Timestamp(stamp)
|
| 158 |
+
forward_ts = ts.tz_localize(tz, nonexistent="shift_forward")
|
| 159 |
+
assert forward_ts == Timestamp(forward_expected, tz=tz)
|
| 160 |
+
backward_ts = ts.tz_localize(tz, nonexistent="shift_backward")
|
| 161 |
+
assert backward_ts == Timestamp(backward_expected, tz=tz)
|
| 162 |
+
|
| 163 |
+
def test_tz_localize_ambiguous_raise(self):
|
| 164 |
+
# GH#13057
|
| 165 |
+
ts = Timestamp("2015-11-1 01:00")
|
| 166 |
+
msg = "Cannot infer dst time from 2015-11-01 01:00:00,"
|
| 167 |
+
with pytest.raises(ValueError, match=msg):
|
| 168 |
+
ts.tz_localize("US/Pacific", ambiguous="raise")
|
| 169 |
+
|
| 170 |
+
def test_tz_localize_nonexistent_invalid_arg(self, warsaw):
|
| 171 |
+
# GH 22644
|
| 172 |
+
tz = warsaw
|
| 173 |
+
ts = Timestamp("2015-03-29 02:00:00")
|
| 174 |
+
msg = (
|
| 175 |
+
"The nonexistent argument must be one of 'raise', 'NaT', "
|
| 176 |
+
"'shift_forward', 'shift_backward' or a timedelta object"
|
| 177 |
+
)
|
| 178 |
+
with pytest.raises(ValueError, match=msg):
|
| 179 |
+
ts.tz_localize(tz, nonexistent="foo")
|
| 180 |
+
|
| 181 |
+
@pytest.mark.parametrize(
|
| 182 |
+
"stamp",
|
| 183 |
+
[
|
| 184 |
+
"2014-02-01 09:00",
|
| 185 |
+
"2014-07-08 09:00",
|
| 186 |
+
"2014-11-01 17:00",
|
| 187 |
+
"2014-11-05 00:00",
|
| 188 |
+
],
|
| 189 |
+
)
|
| 190 |
+
def test_tz_localize_roundtrip(self, stamp, tz_aware_fixture):
|
| 191 |
+
tz = tz_aware_fixture
|
| 192 |
+
ts = Timestamp(stamp)
|
| 193 |
+
localized = ts.tz_localize(tz)
|
| 194 |
+
assert localized == Timestamp(stamp, tz=tz)
|
| 195 |
+
|
| 196 |
+
msg = "Cannot localize tz-aware Timestamp"
|
| 197 |
+
with pytest.raises(TypeError, match=msg):
|
| 198 |
+
localized.tz_localize(tz)
|
| 199 |
+
|
| 200 |
+
reset = localized.tz_localize(None)
|
| 201 |
+
assert reset == ts
|
| 202 |
+
assert reset.tzinfo is None
|
| 203 |
+
|
| 204 |
+
def test_tz_localize_ambiguous_compat(self):
|
| 205 |
+
# validate that pytz and dateutil are compat for dst
|
| 206 |
+
# when the transition happens
|
| 207 |
+
pytz = pytest.importorskip("pytz")
|
| 208 |
+
naive = Timestamp("2013-10-27 01:00:00")
|
| 209 |
+
assert naive.unit == "us"
|
| 210 |
+
|
| 211 |
+
pytz_zone = pytz.timezone("Europe/London")
|
| 212 |
+
dateutil_zone = "dateutil/Europe/London"
|
| 213 |
+
result_pytz = naive.tz_localize(pytz_zone, ambiguous=False)
|
| 214 |
+
result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=False)
|
| 215 |
+
assert result_pytz._value == result_dateutil._value
|
| 216 |
+
assert result_pytz._value == 1_382_835_600 * 10**6
|
| 217 |
+
|
| 218 |
+
# fixed ambiguous behavior
|
| 219 |
+
# see gh-14621, GH#45087
|
| 220 |
+
assert result_pytz.to_pydatetime().tzname() == "GMT"
|
| 221 |
+
assert result_dateutil.to_pydatetime().tzname() == "GMT"
|
| 222 |
+
assert str(result_pytz) == str(result_dateutil)
|
| 223 |
+
|
| 224 |
+
# 1 hour difference
|
| 225 |
+
result_pytz = naive.tz_localize(pytz_zone, ambiguous=True)
|
| 226 |
+
result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=True)
|
| 227 |
+
assert result_pytz._value == result_dateutil._value
|
| 228 |
+
assert result_pytz._value == 1_382_832_000 * 10**6
|
| 229 |
+
|
| 230 |
+
# see gh-14621
|
| 231 |
+
assert str(result_pytz) == str(result_dateutil)
|
| 232 |
+
assert (
|
| 233 |
+
result_pytz.to_pydatetime().tzname()
|
| 234 |
+
== result_dateutil.to_pydatetime().tzname()
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
@pytest.mark.parametrize(
|
| 238 |
+
"tz",
|
| 239 |
+
[
|
| 240 |
+
"pytz/US/Eastern",
|
| 241 |
+
gettz("US/Eastern"),
|
| 242 |
+
zoneinfo.ZoneInfo("US/Eastern"),
|
| 243 |
+
"dateutil/US/Eastern",
|
| 244 |
+
],
|
| 245 |
+
)
|
| 246 |
+
def test_timestamp_tz_localize(self, tz):
|
| 247 |
+
if isinstance(tz, str) and tz.startswith("pytz/"):
|
| 248 |
+
pytz = pytest.importorskip("pytz")
|
| 249 |
+
tz = pytz.timezone(tz.removeprefix("pytz/"))
|
| 250 |
+
stamp = Timestamp("3/11/2012 04:00")
|
| 251 |
+
|
| 252 |
+
result = stamp.tz_localize(tz)
|
| 253 |
+
expected = Timestamp("3/11/2012 04:00", tz=tz)
|
| 254 |
+
assert result.hour == expected.hour
|
| 255 |
+
assert result == expected
|
| 256 |
+
|
| 257 |
+
@pytest.mark.parametrize("year", ["2018", "2204"])
|
| 258 |
+
@pytest.mark.parametrize(
|
| 259 |
+
"start_ts, tz, end_ts, shift",
|
| 260 |
+
[
|
| 261 |
+
["2018-03-25 02:20:00", "Europe/Warsaw", "2018-03-25 03:00:00", "forward"],
|
| 262 |
+
[
|
| 263 |
+
"2018-03-25 02:20:00",
|
| 264 |
+
"Europe/Warsaw",
|
| 265 |
+
"2018-03-25 01:59:59.999999999",
|
| 266 |
+
"backward",
|
| 267 |
+
],
|
| 268 |
+
[
|
| 269 |
+
"2018-03-25 02:20:00",
|
| 270 |
+
"Europe/Warsaw",
|
| 271 |
+
"2018-03-25 03:20:00",
|
| 272 |
+
timedelta(hours=1),
|
| 273 |
+
],
|
| 274 |
+
[
|
| 275 |
+
"2018-03-25 02:20:00",
|
| 276 |
+
"Europe/Warsaw",
|
| 277 |
+
"2018-03-25 01:20:00",
|
| 278 |
+
timedelta(hours=-1),
|
| 279 |
+
],
|
| 280 |
+
["2018-03-11 02:33:00", "US/Pacific", "2018-03-11 03:00:00", "forward"],
|
| 281 |
+
[
|
| 282 |
+
"2018-03-11 02:33:00",
|
| 283 |
+
"US/Pacific",
|
| 284 |
+
"2018-03-11 01:59:59.999999999",
|
| 285 |
+
"backward",
|
| 286 |
+
],
|
| 287 |
+
[
|
| 288 |
+
"2018-03-11 02:33:00",
|
| 289 |
+
"US/Pacific",
|
| 290 |
+
"2018-03-11 03:33:00",
|
| 291 |
+
timedelta(hours=1),
|
| 292 |
+
],
|
| 293 |
+
[
|
| 294 |
+
"2018-03-11 02:33:00",
|
| 295 |
+
"US/Pacific",
|
| 296 |
+
"2018-03-11 01:33:00",
|
| 297 |
+
timedelta(hours=-1),
|
| 298 |
+
],
|
| 299 |
+
],
|
| 300 |
+
)
|
| 301 |
+
@pytest.mark.parametrize("tz_type", ["", "dateutil/"])
|
| 302 |
+
def test_timestamp_tz_localize_nonexistent_shift(
|
| 303 |
+
self, start_ts, tz, end_ts, shift, year, tz_type, unit
|
| 304 |
+
):
|
| 305 |
+
# GH 8917, 24466
|
| 306 |
+
if year == "2204" and tz_type == "dateutil/":
|
| 307 |
+
pytest.skip("ZoneInfo required for year >= 2200")
|
| 308 |
+
start_ts = start_ts.replace("2018", year)
|
| 309 |
+
end_ts = end_ts.replace("2018", year)
|
| 310 |
+
tz = tz_type + tz
|
| 311 |
+
if isinstance(shift, str):
|
| 312 |
+
shift = "shift_" + shift
|
| 313 |
+
ts = Timestamp(start_ts).as_unit(unit)
|
| 314 |
+
result = ts.tz_localize(tz, nonexistent=shift)
|
| 315 |
+
expected = Timestamp(end_ts).tz_localize(tz)
|
| 316 |
+
|
| 317 |
+
if unit == "us":
|
| 318 |
+
assert result == expected.replace(nanosecond=0)
|
| 319 |
+
elif unit == "ms":
|
| 320 |
+
micros = expected.microsecond - expected.microsecond % 1000
|
| 321 |
+
assert result == expected.replace(microsecond=micros, nanosecond=0)
|
| 322 |
+
elif unit == "s":
|
| 323 |
+
assert result == expected.replace(microsecond=0, nanosecond=0)
|
| 324 |
+
else:
|
| 325 |
+
assert result == expected
|
| 326 |
+
assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
|
| 327 |
+
|
| 328 |
+
@pytest.mark.parametrize("year", ["2015", "2201"])
|
| 329 |
+
@pytest.mark.parametrize("offset", [-1, 1])
|
| 330 |
+
def test_timestamp_tz_localize_nonexistent_shift_invalid(
|
| 331 |
+
self, offset, year, warsaw
|
| 332 |
+
):
|
| 333 |
+
# GH 8917, 24466
|
| 334 |
+
tz = warsaw
|
| 335 |
+
_distant_date_only_for_zoneinfo(year, tz)
|
| 336 |
+
ts = Timestamp(f"{year}-03-29 02:20:00")
|
| 337 |
+
msg = "The provided timedelta will relocalize on a nonexistent time"
|
| 338 |
+
with pytest.raises(ValueError, match=msg):
|
| 339 |
+
ts.tz_localize(tz, nonexistent=timedelta(seconds=offset))
|
| 340 |
+
|
| 341 |
+
@pytest.mark.parametrize("year", ["2015", "2201"])
|
| 342 |
+
def test_timestamp_tz_localize_nonexistent_NaT(self, year, warsaw, unit):
|
| 343 |
+
# GH 8917
|
| 344 |
+
tz = warsaw
|
| 345 |
+
_distant_date_only_for_zoneinfo(year, tz)
|
| 346 |
+
ts = Timestamp(f"{year}-03-29 02:20:00").as_unit(unit)
|
| 347 |
+
result = ts.tz_localize(tz, nonexistent="NaT")
|
| 348 |
+
assert result is NaT
|
| 349 |
+
|
| 350 |
+
@pytest.mark.parametrize("year", ["2015", "2201"])
|
| 351 |
+
def test_timestamp_tz_localize_nonexistent_raise(self, year, warsaw, unit):
|
| 352 |
+
# GH 8917
|
| 353 |
+
tz = warsaw
|
| 354 |
+
_distant_date_only_for_zoneinfo(year, tz)
|
| 355 |
+
ts = Timestamp(f"{year}-03-29 02:20:00").as_unit(unit)
|
| 356 |
+
msg = f"{year}-03-29 02:20:00"
|
| 357 |
+
with pytest.raises(ValueError, match=msg):
|
| 358 |
+
ts.tz_localize(tz, nonexistent="raise")
|
| 359 |
+
msg = (
|
| 360 |
+
"The nonexistent argument must be one of 'raise', 'NaT', "
|
| 361 |
+
"'shift_forward', 'shift_backward' or a timedelta object"
|
| 362 |
+
)
|
| 363 |
+
with pytest.raises(ValueError, match=msg):
|
| 364 |
+
ts.tz_localize(tz, nonexistent="foo")
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_arithmetic.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import (
|
| 2 |
+
datetime,
|
| 3 |
+
timedelta,
|
| 4 |
+
timezone,
|
| 5 |
+
)
|
| 6 |
+
|
| 7 |
+
from dateutil.tz import gettz
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
from pandas._libs.tslibs import (
|
| 12 |
+
OutOfBoundsDatetime,
|
| 13 |
+
OutOfBoundsTimedelta,
|
| 14 |
+
Timedelta,
|
| 15 |
+
Timestamp,
|
| 16 |
+
offsets,
|
| 17 |
+
to_offset,
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
import pandas._testing as tm
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TestTimestampArithmetic:
|
| 24 |
+
def test_overflow_offset(self):
|
| 25 |
+
# no overflow expected
|
| 26 |
+
|
| 27 |
+
stamp = Timestamp("2000/1/1")
|
| 28 |
+
offset_no_overflow = to_offset("D") * 100
|
| 29 |
+
|
| 30 |
+
expected = Timestamp("2000/04/10")
|
| 31 |
+
assert stamp + offset_no_overflow == expected
|
| 32 |
+
|
| 33 |
+
assert offset_no_overflow + stamp == expected
|
| 34 |
+
|
| 35 |
+
expected = Timestamp("1999/09/23")
|
| 36 |
+
assert stamp - offset_no_overflow == expected
|
| 37 |
+
|
| 38 |
+
def test_overflow_offset_raises(self):
|
| 39 |
+
# xref https://github.com/statsmodels/statsmodels/issues/3374
|
| 40 |
+
# ends up multiplying really large numbers which overflow
|
| 41 |
+
|
| 42 |
+
stamp = Timestamp("2017-01-13 00:00:00").as_unit("ns")
|
| 43 |
+
offset_overflow = 20169940 * offsets.Day(1)
|
| 44 |
+
lmsg2 = r"Cannot cast -?20169940 days \+?00:00:00 to unit='ns' without overflow"
|
| 45 |
+
|
| 46 |
+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
|
| 47 |
+
stamp + offset_overflow
|
| 48 |
+
|
| 49 |
+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
|
| 50 |
+
offset_overflow + stamp
|
| 51 |
+
|
| 52 |
+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
|
| 53 |
+
stamp - offset_overflow
|
| 54 |
+
|
| 55 |
+
# xref https://github.com/pandas-dev/pandas/issues/14080
|
| 56 |
+
# used to crash, so check for proper overflow exception
|
| 57 |
+
|
| 58 |
+
stamp = Timestamp("2000/1/1").as_unit("ns")
|
| 59 |
+
offset_overflow = to_offset("D") * 100**5
|
| 60 |
+
|
| 61 |
+
lmsg3 = (
|
| 62 |
+
r"Cannot cast -?10000000000 days \+?00:00:00 to unit='ns' without overflow"
|
| 63 |
+
)
|
| 64 |
+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):
|
| 65 |
+
stamp + offset_overflow
|
| 66 |
+
|
| 67 |
+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):
|
| 68 |
+
offset_overflow + stamp
|
| 69 |
+
|
| 70 |
+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):
|
| 71 |
+
stamp - offset_overflow
|
| 72 |
+
|
| 73 |
+
def test_overflow_timestamp_raises(self):
|
| 74 |
+
# https://github.com/pandas-dev/pandas/issues/31774
|
| 75 |
+
msg = "Result is too large"
|
| 76 |
+
a = Timestamp("2101-01-01 00:00:00").as_unit("ns")
|
| 77 |
+
b = Timestamp("1688-01-01 00:00:00").as_unit("ns")
|
| 78 |
+
|
| 79 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 80 |
+
a - b
|
| 81 |
+
|
| 82 |
+
# but we're OK for timestamp and datetime.datetime
|
| 83 |
+
assert (a - b.to_pydatetime()) == (a.to_pydatetime() - b)
|
| 84 |
+
|
| 85 |
+
def test_delta_preserve_nanos(self):
|
| 86 |
+
val = Timestamp(1337299200000000123)
|
| 87 |
+
result = val + timedelta(1)
|
| 88 |
+
assert result.nanosecond == val.nanosecond
|
| 89 |
+
|
| 90 |
+
def test_rsub_dtscalars(self, tz_naive_fixture):
|
| 91 |
+
# In particular, check that datetime64 - Timestamp works GH#28286
|
| 92 |
+
td = Timedelta(1235345642000)
|
| 93 |
+
ts = Timestamp("2021-01-01", tz=tz_naive_fixture)
|
| 94 |
+
other = ts + td
|
| 95 |
+
|
| 96 |
+
assert other - ts == td
|
| 97 |
+
assert other.to_pydatetime() - ts == td
|
| 98 |
+
if tz_naive_fixture is None:
|
| 99 |
+
assert other.to_datetime64() - ts == td
|
| 100 |
+
else:
|
| 101 |
+
msg = "Cannot subtract tz-naive and tz-aware datetime-like objects"
|
| 102 |
+
with pytest.raises(TypeError, match=msg):
|
| 103 |
+
other.to_datetime64() - ts
|
| 104 |
+
|
| 105 |
+
def test_timestamp_sub_datetime(self):
|
| 106 |
+
dt = datetime(2013, 10, 12)
|
| 107 |
+
ts = Timestamp(datetime(2013, 10, 13))
|
| 108 |
+
assert (ts - dt).days == 1
|
| 109 |
+
assert (dt - ts).days == -1
|
| 110 |
+
|
| 111 |
+
def test_subtract_tzaware_datetime(self):
|
| 112 |
+
t1 = Timestamp("2020-10-22T22:00:00+00:00")
|
| 113 |
+
t2 = datetime(2020, 10, 22, 22, tzinfo=timezone.utc)
|
| 114 |
+
|
| 115 |
+
result = t1 - t2
|
| 116 |
+
|
| 117 |
+
assert isinstance(result, Timedelta)
|
| 118 |
+
assert result == Timedelta("0 days")
|
| 119 |
+
|
| 120 |
+
def test_subtract_timestamp_from_different_timezone(self):
|
| 121 |
+
t1 = Timestamp("20130101").tz_localize("US/Eastern")
|
| 122 |
+
t2 = Timestamp("20130101").tz_localize("CET")
|
| 123 |
+
|
| 124 |
+
result = t1 - t2
|
| 125 |
+
|
| 126 |
+
assert isinstance(result, Timedelta)
|
| 127 |
+
assert result == Timedelta("0 days 06:00:00")
|
| 128 |
+
|
| 129 |
+
def test_subtracting_involving_datetime_with_different_tz(self):
|
| 130 |
+
t1 = datetime(2013, 1, 1, tzinfo=timezone(timedelta(hours=-5)))
|
| 131 |
+
t2 = Timestamp("20130101").tz_localize("CET")
|
| 132 |
+
|
| 133 |
+
result = t1 - t2
|
| 134 |
+
|
| 135 |
+
assert isinstance(result, Timedelta)
|
| 136 |
+
assert result == Timedelta("0 days 06:00:00")
|
| 137 |
+
|
| 138 |
+
result = t2 - t1
|
| 139 |
+
assert isinstance(result, Timedelta)
|
| 140 |
+
assert result == Timedelta("-1 days +18:00:00")
|
| 141 |
+
|
| 142 |
+
def test_subtracting_different_timezones(self, tz_aware_fixture):
|
| 143 |
+
t_raw = Timestamp("20130101")
|
| 144 |
+
t_UTC = t_raw.tz_localize("UTC")
|
| 145 |
+
t_diff = t_UTC.tz_convert(tz_aware_fixture) + Timedelta("0 days 05:00:00")
|
| 146 |
+
|
| 147 |
+
result = t_diff - t_UTC
|
| 148 |
+
|
| 149 |
+
assert isinstance(result, Timedelta)
|
| 150 |
+
assert result == Timedelta("0 days 05:00:00")
|
| 151 |
+
|
| 152 |
+
def test_addition_subtraction_types(self):
|
| 153 |
+
# Assert on the types resulting from Timestamp +/- various date/time
|
| 154 |
+
# objects
|
| 155 |
+
dt = datetime(2014, 3, 4)
|
| 156 |
+
td = timedelta(seconds=1)
|
| 157 |
+
ts = Timestamp(dt)
|
| 158 |
+
|
| 159 |
+
msg = "Addition/subtraction of integers"
|
| 160 |
+
with pytest.raises(TypeError, match=msg):
|
| 161 |
+
# GH#22535 add/sub with integers is deprecated
|
| 162 |
+
ts + 1
|
| 163 |
+
with pytest.raises(TypeError, match=msg):
|
| 164 |
+
ts - 1
|
| 165 |
+
|
| 166 |
+
# Timestamp + datetime not supported, though subtraction is supported
|
| 167 |
+
# and yields timedelta more tests in tseries/base/tests/test_base.py
|
| 168 |
+
assert type(ts - dt) == Timedelta
|
| 169 |
+
assert type(ts + td) == Timestamp
|
| 170 |
+
assert type(ts - td) == Timestamp
|
| 171 |
+
|
| 172 |
+
# Timestamp +/- datetime64 not supported, so not tested (could possibly
|
| 173 |
+
# assert error raised?)
|
| 174 |
+
td64 = np.timedelta64(1, "D")
|
| 175 |
+
assert type(ts + td64) == Timestamp
|
| 176 |
+
assert type(ts - td64) == Timestamp
|
| 177 |
+
|
| 178 |
+
@pytest.mark.parametrize(
|
| 179 |
+
"td", [Timedelta(hours=3), np.timedelta64(3, "h"), timedelta(hours=3)]
|
| 180 |
+
)
|
| 181 |
+
def test_radd_tdscalar(self, td, fixed_now_ts):
|
| 182 |
+
# GH#24775 timedelta64+Timestamp should not raise
|
| 183 |
+
ts = fixed_now_ts
|
| 184 |
+
assert td + ts == ts + td
|
| 185 |
+
|
| 186 |
+
@pytest.mark.parametrize(
|
| 187 |
+
"other,expected_difference",
|
| 188 |
+
[
|
| 189 |
+
(np.timedelta64(-123, "ns"), -123),
|
| 190 |
+
(np.timedelta64(1234567898, "ns"), 1234567898),
|
| 191 |
+
(np.timedelta64(-123, "us"), -123000),
|
| 192 |
+
(np.timedelta64(-123, "ms"), -123000000),
|
| 193 |
+
],
|
| 194 |
+
)
|
| 195 |
+
def test_timestamp_add_timedelta64_unit(self, other, expected_difference):
|
| 196 |
+
now = datetime.now(timezone.utc)
|
| 197 |
+
ts = Timestamp(now).as_unit("ns")
|
| 198 |
+
result = ts + other
|
| 199 |
+
valdiff = result._value - ts._value
|
| 200 |
+
assert valdiff == expected_difference
|
| 201 |
+
|
| 202 |
+
ts2 = Timestamp(now)
|
| 203 |
+
assert ts2 + other == result
|
| 204 |
+
|
| 205 |
+
@pytest.mark.parametrize(
|
| 206 |
+
"ts",
|
| 207 |
+
[
|
| 208 |
+
Timestamp("1776-07-04"),
|
| 209 |
+
Timestamp("1776-07-04", tz="UTC"),
|
| 210 |
+
],
|
| 211 |
+
)
|
| 212 |
+
@pytest.mark.parametrize(
|
| 213 |
+
"other",
|
| 214 |
+
[
|
| 215 |
+
1,
|
| 216 |
+
np.int64(1),
|
| 217 |
+
np.array([1, 2], dtype=np.int32),
|
| 218 |
+
np.array([3, 4], dtype=np.uint64),
|
| 219 |
+
],
|
| 220 |
+
)
|
| 221 |
+
def test_add_int_with_freq(self, ts, other):
|
| 222 |
+
msg = "Addition/subtraction of integers and integer-arrays"
|
| 223 |
+
with pytest.raises(TypeError, match=msg):
|
| 224 |
+
ts + other
|
| 225 |
+
with pytest.raises(TypeError, match=msg):
|
| 226 |
+
other + ts
|
| 227 |
+
|
| 228 |
+
with pytest.raises(TypeError, match=msg):
|
| 229 |
+
ts - other
|
| 230 |
+
|
| 231 |
+
msg = "unsupported operand type"
|
| 232 |
+
with pytest.raises(TypeError, match=msg):
|
| 233 |
+
other - ts
|
| 234 |
+
|
| 235 |
+
@pytest.mark.parametrize("shape", [(6,), (2, 3)])
|
| 236 |
+
def test_addsub_m8ndarray(self, shape):
|
| 237 |
+
# GH#33296
|
| 238 |
+
ts = Timestamp("2020-04-04 15:45").as_unit("ns")
|
| 239 |
+
other = np.arange(6).astype("m8[h]").reshape(shape)
|
| 240 |
+
|
| 241 |
+
result = ts + other
|
| 242 |
+
|
| 243 |
+
ex_stamps = [ts + Timedelta(hours=n) for n in range(6)]
|
| 244 |
+
expected = np.array([x.asm8 for x in ex_stamps], dtype="M8[ns]").reshape(shape)
|
| 245 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 246 |
+
|
| 247 |
+
result = other + ts
|
| 248 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 249 |
+
|
| 250 |
+
result = ts - other
|
| 251 |
+
ex_stamps = [ts - Timedelta(hours=n) for n in range(6)]
|
| 252 |
+
expected = np.array([x.asm8 for x in ex_stamps], dtype="M8[ns]").reshape(shape)
|
| 253 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 254 |
+
|
| 255 |
+
msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timestamp'"
|
| 256 |
+
with pytest.raises(TypeError, match=msg):
|
| 257 |
+
other - ts
|
| 258 |
+
|
| 259 |
+
@pytest.mark.parametrize("shape", [(6,), (2, 3)])
|
| 260 |
+
def test_addsub_m8ndarray_tzaware(self, shape):
|
| 261 |
+
# GH#33296
|
| 262 |
+
ts = Timestamp("2020-04-04 15:45", tz="US/Pacific")
|
| 263 |
+
|
| 264 |
+
other = np.arange(6).astype("m8[h]").reshape(shape)
|
| 265 |
+
|
| 266 |
+
result = ts + other
|
| 267 |
+
|
| 268 |
+
ex_stamps = [ts + Timedelta(hours=n) for n in range(6)]
|
| 269 |
+
expected = np.array(ex_stamps).reshape(shape)
|
| 270 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 271 |
+
|
| 272 |
+
result = other + ts
|
| 273 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 274 |
+
|
| 275 |
+
result = ts - other
|
| 276 |
+
ex_stamps = [ts - Timedelta(hours=n) for n in range(6)]
|
| 277 |
+
expected = np.array(ex_stamps).reshape(shape)
|
| 278 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 279 |
+
|
| 280 |
+
msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timestamp'"
|
| 281 |
+
with pytest.raises(TypeError, match=msg):
|
| 282 |
+
other - ts
|
| 283 |
+
|
| 284 |
+
def test_subtract_different_utc_objects(self, utc_fixture, utc_fixture2):
|
| 285 |
+
# GH 32619
|
| 286 |
+
dt = datetime(2021, 1, 1)
|
| 287 |
+
ts1 = Timestamp(dt, tz=utc_fixture)
|
| 288 |
+
ts2 = Timestamp(dt, tz=utc_fixture2)
|
| 289 |
+
result = ts1 - ts2
|
| 290 |
+
expected = Timedelta(0)
|
| 291 |
+
assert result == expected
|
| 292 |
+
|
| 293 |
+
@pytest.mark.parametrize(
|
| 294 |
+
"tz",
|
| 295 |
+
[
|
| 296 |
+
"pytz/US/Eastern",
|
| 297 |
+
gettz("US/Eastern"),
|
| 298 |
+
"US/Eastern",
|
| 299 |
+
"dateutil/US/Eastern",
|
| 300 |
+
],
|
| 301 |
+
)
|
| 302 |
+
def test_timestamp_add_timedelta_push_over_dst_boundary(self, tz):
|
| 303 |
+
# GH#1389
|
| 304 |
+
if isinstance(tz, str) and tz.startswith("pytz/"):
|
| 305 |
+
pytz = pytest.importorskip("pytz")
|
| 306 |
+
tz = pytz.timezone(tz.removeprefix("pytz/"))
|
| 307 |
+
# 4 hours before DST transition
|
| 308 |
+
stamp = Timestamp("3/10/2012 22:00", tz=tz)
|
| 309 |
+
|
| 310 |
+
result = stamp + timedelta(hours=6)
|
| 311 |
+
|
| 312 |
+
# spring forward, + "7" hours
|
| 313 |
+
expected = Timestamp("3/11/2012 05:00", tz=tz)
|
| 314 |
+
|
| 315 |
+
assert result == expected
|
| 316 |
+
|
| 317 |
+
def test_timestamp_dst_transition(self):
|
| 318 |
+
# GH 60084
|
| 319 |
+
dt_str = "2023-11-05 01:00-08:00"
|
| 320 |
+
tz_str = "America/Los_Angeles"
|
| 321 |
+
|
| 322 |
+
ts1 = Timestamp(dt_str, tz=tz_str)
|
| 323 |
+
ts2 = ts1 + Timedelta(hours=0)
|
| 324 |
+
|
| 325 |
+
assert ts1 == ts2
|
| 326 |
+
assert hash(ts1) == hash(ts2)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
class SubDatetime(datetime):
|
| 330 |
+
pass
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
@pytest.mark.parametrize(
|
| 334 |
+
"lh,rh",
|
| 335 |
+
[
|
| 336 |
+
(SubDatetime(2000, 1, 1), Timedelta(hours=1)),
|
| 337 |
+
(Timedelta(hours=1), SubDatetime(2000, 1, 1)),
|
| 338 |
+
],
|
| 339 |
+
)
|
| 340 |
+
def test_dt_subclass_add_timedelta(lh, rh):
|
| 341 |
+
# GH#25851
|
| 342 |
+
# ensure that subclassed datetime works for
|
| 343 |
+
# Timedelta operations
|
| 344 |
+
result = lh + rh
|
| 345 |
+
expected = SubDatetime(2000, 1, 1, 1)
|
| 346 |
+
assert result == expected
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_comparisons.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import (
|
| 2 |
+
datetime,
|
| 3 |
+
timedelta,
|
| 4 |
+
)
|
| 5 |
+
import operator
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import pytest
|
| 9 |
+
|
| 10 |
+
from pandas import Timestamp
|
| 11 |
+
import pandas._testing as tm
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TestTimestampComparison:
|
| 15 |
+
def test_compare_non_nano_dt64(self):
|
| 16 |
+
# don't raise when converting dt64 to Timestamp in __richcmp__
|
| 17 |
+
dt = np.datetime64("1066-10-14")
|
| 18 |
+
ts = Timestamp(dt)
|
| 19 |
+
|
| 20 |
+
assert dt == ts
|
| 21 |
+
|
| 22 |
+
def test_comparison_dt64_ndarray(self):
|
| 23 |
+
ts = Timestamp("2021-01-01")
|
| 24 |
+
ts2 = Timestamp("2019-04-05")
|
| 25 |
+
arr = np.array([[ts.asm8, ts2.asm8]], dtype="M8[ns]")
|
| 26 |
+
|
| 27 |
+
result = ts == arr
|
| 28 |
+
expected = np.array([[True, False]], dtype=bool)
|
| 29 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 30 |
+
|
| 31 |
+
result = arr == ts
|
| 32 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 33 |
+
|
| 34 |
+
result = ts != arr
|
| 35 |
+
tm.assert_numpy_array_equal(result, ~expected)
|
| 36 |
+
|
| 37 |
+
result = arr != ts
|
| 38 |
+
tm.assert_numpy_array_equal(result, ~expected)
|
| 39 |
+
|
| 40 |
+
result = ts2 < arr
|
| 41 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 42 |
+
|
| 43 |
+
result = arr < ts2
|
| 44 |
+
tm.assert_numpy_array_equal(result, np.array([[False, False]], dtype=bool))
|
| 45 |
+
|
| 46 |
+
result = ts2 <= arr
|
| 47 |
+
tm.assert_numpy_array_equal(result, np.array([[True, True]], dtype=bool))
|
| 48 |
+
|
| 49 |
+
result = arr <= ts2
|
| 50 |
+
tm.assert_numpy_array_equal(result, ~expected)
|
| 51 |
+
|
| 52 |
+
result = ts >= arr
|
| 53 |
+
tm.assert_numpy_array_equal(result, np.array([[True, True]], dtype=bool))
|
| 54 |
+
|
| 55 |
+
result = arr >= ts
|
| 56 |
+
tm.assert_numpy_array_equal(result, np.array([[True, False]], dtype=bool))
|
| 57 |
+
|
| 58 |
+
@pytest.mark.parametrize("reverse", [True, False])
|
| 59 |
+
def test_comparison_dt64_ndarray_tzaware(self, reverse, comparison_op):
|
| 60 |
+
ts = Timestamp("2021-01-01 00:00:00.00000", tz="UTC")
|
| 61 |
+
arr = np.array([ts.asm8, ts.asm8], dtype="M8[ns]")
|
| 62 |
+
|
| 63 |
+
left, right = ts, arr
|
| 64 |
+
if reverse:
|
| 65 |
+
left, right = arr, ts
|
| 66 |
+
|
| 67 |
+
if comparison_op is operator.eq:
|
| 68 |
+
expected = np.array([False, False], dtype=bool)
|
| 69 |
+
result = comparison_op(left, right)
|
| 70 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 71 |
+
elif comparison_op is operator.ne:
|
| 72 |
+
expected = np.array([True, True], dtype=bool)
|
| 73 |
+
result = comparison_op(left, right)
|
| 74 |
+
tm.assert_numpy_array_equal(result, expected)
|
| 75 |
+
else:
|
| 76 |
+
msg = "Cannot compare tz-naive and tz-aware timestamps"
|
| 77 |
+
with pytest.raises(TypeError, match=msg):
|
| 78 |
+
comparison_op(left, right)
|
| 79 |
+
|
| 80 |
+
def test_comparison_object_array(self):
|
| 81 |
+
# GH#15183
|
| 82 |
+
ts = Timestamp("2011-01-03 00:00:00-0500", tz="US/Eastern")
|
| 83 |
+
other = Timestamp("2011-01-01 00:00:00-0500", tz="US/Eastern")
|
| 84 |
+
naive = Timestamp("2011-01-01 00:00:00")
|
| 85 |
+
|
| 86 |
+
arr = np.array([other, ts], dtype=object)
|
| 87 |
+
res = arr == ts
|
| 88 |
+
expected = np.array([False, True], dtype=bool)
|
| 89 |
+
assert (res == expected).all()
|
| 90 |
+
|
| 91 |
+
# 2D case
|
| 92 |
+
arr = np.array([[other, ts], [ts, other]], dtype=object)
|
| 93 |
+
res = arr != ts
|
| 94 |
+
expected = np.array([[True, False], [False, True]], dtype=bool)
|
| 95 |
+
assert res.shape == expected.shape
|
| 96 |
+
assert (res == expected).all()
|
| 97 |
+
|
| 98 |
+
# tzaware mismatch
|
| 99 |
+
arr = np.array([naive], dtype=object)
|
| 100 |
+
msg = "Cannot compare tz-naive and tz-aware timestamps"
|
| 101 |
+
with pytest.raises(TypeError, match=msg):
|
| 102 |
+
arr < ts
|
| 103 |
+
|
| 104 |
+
def test_comparison(self):
|
| 105 |
+
# 5-18-2012 00:00:00.000
|
| 106 |
+
stamp = 1337299200000000000
|
| 107 |
+
|
| 108 |
+
val = Timestamp(stamp)
|
| 109 |
+
|
| 110 |
+
assert val == val
|
| 111 |
+
assert not val != val
|
| 112 |
+
assert not val < val
|
| 113 |
+
assert val <= val
|
| 114 |
+
assert not val > val
|
| 115 |
+
assert val >= val
|
| 116 |
+
|
| 117 |
+
other = datetime(2012, 5, 18)
|
| 118 |
+
assert val == other
|
| 119 |
+
assert not val != other
|
| 120 |
+
assert not val < other
|
| 121 |
+
assert val <= other
|
| 122 |
+
assert not val > other
|
| 123 |
+
assert val >= other
|
| 124 |
+
|
| 125 |
+
other = Timestamp(stamp + 100)
|
| 126 |
+
|
| 127 |
+
assert val != other
|
| 128 |
+
assert val != other
|
| 129 |
+
assert val < other
|
| 130 |
+
assert val <= other
|
| 131 |
+
assert other > val
|
| 132 |
+
assert other >= val
|
| 133 |
+
|
| 134 |
+
def test_compare_invalid(self):
|
| 135 |
+
# GH#8058
|
| 136 |
+
val = Timestamp("20130101 12:01:02")
|
| 137 |
+
assert not val == "foo"
|
| 138 |
+
assert not val == 10.0
|
| 139 |
+
assert not val == 1
|
| 140 |
+
assert not val == []
|
| 141 |
+
assert not val == {"foo": 1}
|
| 142 |
+
assert not val == np.float64(1)
|
| 143 |
+
assert not val == np.int64(1)
|
| 144 |
+
|
| 145 |
+
assert val != "foo"
|
| 146 |
+
assert val != 10.0
|
| 147 |
+
assert val != 1
|
| 148 |
+
assert val != []
|
| 149 |
+
assert val != {"foo": 1}
|
| 150 |
+
assert val != np.float64(1)
|
| 151 |
+
assert val != np.int64(1)
|
| 152 |
+
|
| 153 |
+
@pytest.mark.parametrize("tz", [None, "US/Pacific"])
|
| 154 |
+
def test_compare_date(self, tz):
|
| 155 |
+
# GH#36131 comparing Timestamp with date object is deprecated
|
| 156 |
+
ts = Timestamp("2021-01-01 00:00:00.00000", tz=tz)
|
| 157 |
+
dt = ts.to_pydatetime().date()
|
| 158 |
+
# in 2.0 we disallow comparing pydate objects with Timestamps,
|
| 159 |
+
# following the stdlib datetime behavior.
|
| 160 |
+
|
| 161 |
+
msg = "Cannot compare Timestamp with datetime.date"
|
| 162 |
+
for left, right in [(ts, dt), (dt, ts)]:
|
| 163 |
+
assert not left == right
|
| 164 |
+
assert left != right
|
| 165 |
+
|
| 166 |
+
with pytest.raises(TypeError, match=msg):
|
| 167 |
+
left < right
|
| 168 |
+
with pytest.raises(TypeError, match=msg):
|
| 169 |
+
left <= right
|
| 170 |
+
with pytest.raises(TypeError, match=msg):
|
| 171 |
+
left > right
|
| 172 |
+
with pytest.raises(TypeError, match=msg):
|
| 173 |
+
left >= right
|
| 174 |
+
|
| 175 |
+
def test_cant_compare_tz_naive_w_aware(self, utc_fixture):
|
| 176 |
+
# see GH#1404
|
| 177 |
+
a = Timestamp("3/12/2012")
|
| 178 |
+
b = Timestamp("3/12/2012", tz=utc_fixture)
|
| 179 |
+
|
| 180 |
+
msg = "Cannot compare tz-naive and tz-aware timestamps"
|
| 181 |
+
assert not a == b
|
| 182 |
+
assert a != b
|
| 183 |
+
with pytest.raises(TypeError, match=msg):
|
| 184 |
+
a < b
|
| 185 |
+
with pytest.raises(TypeError, match=msg):
|
| 186 |
+
a <= b
|
| 187 |
+
with pytest.raises(TypeError, match=msg):
|
| 188 |
+
a > b
|
| 189 |
+
with pytest.raises(TypeError, match=msg):
|
| 190 |
+
a >= b
|
| 191 |
+
|
| 192 |
+
assert not b == a
|
| 193 |
+
assert b != a
|
| 194 |
+
with pytest.raises(TypeError, match=msg):
|
| 195 |
+
b < a
|
| 196 |
+
with pytest.raises(TypeError, match=msg):
|
| 197 |
+
b <= a
|
| 198 |
+
with pytest.raises(TypeError, match=msg):
|
| 199 |
+
b > a
|
| 200 |
+
with pytest.raises(TypeError, match=msg):
|
| 201 |
+
b >= a
|
| 202 |
+
|
| 203 |
+
assert not a == b.to_pydatetime()
|
| 204 |
+
assert not a.to_pydatetime() == b
|
| 205 |
+
|
| 206 |
+
def test_timestamp_compare_scalars(self):
|
| 207 |
+
# case where ndim == 0
|
| 208 |
+
lhs = np.datetime64(datetime(2013, 12, 6))
|
| 209 |
+
rhs = Timestamp("now")
|
| 210 |
+
nat = Timestamp("nat")
|
| 211 |
+
|
| 212 |
+
ops = {"gt": "lt", "lt": "gt", "ge": "le", "le": "ge", "eq": "eq", "ne": "ne"}
|
| 213 |
+
|
| 214 |
+
for left, right in ops.items():
|
| 215 |
+
left_f = getattr(operator, left)
|
| 216 |
+
right_f = getattr(operator, right)
|
| 217 |
+
expected = left_f(lhs, rhs)
|
| 218 |
+
|
| 219 |
+
result = right_f(rhs, lhs)
|
| 220 |
+
assert result == expected
|
| 221 |
+
|
| 222 |
+
expected = left_f(rhs, nat)
|
| 223 |
+
result = right_f(nat, rhs)
|
| 224 |
+
assert result == expected
|
| 225 |
+
|
| 226 |
+
def test_timestamp_compare_with_early_datetime(self):
|
| 227 |
+
# e.g. datetime.min
|
| 228 |
+
stamp = Timestamp("2012-01-01")
|
| 229 |
+
|
| 230 |
+
assert not stamp == datetime.min
|
| 231 |
+
assert not stamp == datetime(1600, 1, 1)
|
| 232 |
+
assert not stamp == datetime(2700, 1, 1)
|
| 233 |
+
assert stamp != datetime.min
|
| 234 |
+
assert stamp != datetime(1600, 1, 1)
|
| 235 |
+
assert stamp != datetime(2700, 1, 1)
|
| 236 |
+
assert stamp > datetime(1600, 1, 1)
|
| 237 |
+
assert stamp >= datetime(1600, 1, 1)
|
| 238 |
+
assert stamp < datetime(2700, 1, 1)
|
| 239 |
+
assert stamp <= datetime(2700, 1, 1)
|
| 240 |
+
|
| 241 |
+
other = Timestamp.min.to_pydatetime(warn=False)
|
| 242 |
+
assert other - timedelta(microseconds=1) < Timestamp.min
|
| 243 |
+
|
| 244 |
+
def test_timestamp_compare_oob_dt64(self):
|
| 245 |
+
us = np.timedelta64(1, "us")
|
| 246 |
+
other = np.datetime64(Timestamp.min).astype("M8[us]")
|
| 247 |
+
|
| 248 |
+
# This may change if the implementation bound is dropped to match
|
| 249 |
+
# DatetimeArray/DatetimeIndex GH#24124
|
| 250 |
+
assert Timestamp.min > other
|
| 251 |
+
# Note: numpy gets the reversed comparison wrong
|
| 252 |
+
|
| 253 |
+
other = np.datetime64(Timestamp.max).astype("M8[us]")
|
| 254 |
+
assert Timestamp.max > other # not actually OOB
|
| 255 |
+
assert other < Timestamp.max
|
| 256 |
+
|
| 257 |
+
assert Timestamp.max < other + us
|
| 258 |
+
# Note: numpy gets the reversed comparison wrong
|
| 259 |
+
|
| 260 |
+
# GH-42794
|
| 261 |
+
other = datetime(9999, 9, 9)
|
| 262 |
+
assert Timestamp.min < other
|
| 263 |
+
assert other > Timestamp.min
|
| 264 |
+
assert Timestamp.max < other
|
| 265 |
+
assert other > Timestamp.max
|
| 266 |
+
|
| 267 |
+
other = datetime(1, 1, 1)
|
| 268 |
+
assert Timestamp.max > other
|
| 269 |
+
assert other < Timestamp.max
|
| 270 |
+
assert Timestamp.min > other
|
| 271 |
+
assert other < Timestamp.min
|
| 272 |
+
|
| 273 |
+
def test_compare_zerodim_array(self, fixed_now_ts):
|
| 274 |
+
# GH#26916
|
| 275 |
+
ts = fixed_now_ts
|
| 276 |
+
dt64 = np.datetime64("2016-01-01", "ns")
|
| 277 |
+
arr = np.array(dt64)
|
| 278 |
+
assert arr.ndim == 0
|
| 279 |
+
|
| 280 |
+
result = arr < ts
|
| 281 |
+
assert result is np.bool_(True)
|
| 282 |
+
result = arr > ts
|
| 283 |
+
assert result is np.bool_(False)
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def test_rich_comparison_with_unsupported_type():
|
| 287 |
+
# Comparisons with unsupported objects should return NotImplemented
|
| 288 |
+
# (it previously raised TypeError, see #24011)
|
| 289 |
+
|
| 290 |
+
class Inf:
|
| 291 |
+
def __lt__(self, o):
|
| 292 |
+
return False
|
| 293 |
+
|
| 294 |
+
def __le__(self, o):
|
| 295 |
+
return isinstance(o, Inf)
|
| 296 |
+
|
| 297 |
+
def __gt__(self, o):
|
| 298 |
+
return not isinstance(o, Inf)
|
| 299 |
+
|
| 300 |
+
def __ge__(self, o):
|
| 301 |
+
return True
|
| 302 |
+
|
| 303 |
+
def __eq__(self, other) -> bool:
|
| 304 |
+
return isinstance(other, Inf)
|
| 305 |
+
|
| 306 |
+
inf = Inf()
|
| 307 |
+
timestamp = Timestamp("2018-11-30")
|
| 308 |
+
|
| 309 |
+
for left, right in [(inf, timestamp), (timestamp, inf)]:
|
| 310 |
+
assert left > right or left < right
|
| 311 |
+
assert left >= right or left <= right
|
| 312 |
+
assert not left == right
|
| 313 |
+
assert left != right
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_constructors.py
ADDED
|
@@ -0,0 +1,1107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import calendar
|
| 2 |
+
from datetime import (
|
| 3 |
+
date,
|
| 4 |
+
datetime,
|
| 5 |
+
timedelta,
|
| 6 |
+
timezone,
|
| 7 |
+
)
|
| 8 |
+
import zoneinfo
|
| 9 |
+
|
| 10 |
+
import dateutil.tz
|
| 11 |
+
from dateutil.tz import (
|
| 12 |
+
gettz,
|
| 13 |
+
tzoffset,
|
| 14 |
+
tzutc,
|
| 15 |
+
)
|
| 16 |
+
import numpy as np
|
| 17 |
+
import pytest
|
| 18 |
+
|
| 19 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 20 |
+
from pandas.compat import PY314
|
| 21 |
+
from pandas.errors import (
|
| 22 |
+
OutOfBoundsDatetime,
|
| 23 |
+
Pandas4Warning,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
from pandas import (
|
| 27 |
+
NA,
|
| 28 |
+
NaT,
|
| 29 |
+
Period,
|
| 30 |
+
Timedelta,
|
| 31 |
+
Timestamp,
|
| 32 |
+
)
|
| 33 |
+
import pandas._testing as tm
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class TestTimestampConstructorUnitKeyword:
|
| 37 |
+
@pytest.mark.parametrize("typ", [int, float])
|
| 38 |
+
def test_constructor_int_float_with_YM_unit(self, typ):
|
| 39 |
+
# GH#47266 avoid the conversions in cast_from_unit
|
| 40 |
+
val = typ(150)
|
| 41 |
+
|
| 42 |
+
ts = Timestamp(val, unit="Y")
|
| 43 |
+
expected = Timestamp("2120-01-01")
|
| 44 |
+
assert ts == expected
|
| 45 |
+
|
| 46 |
+
ts = Timestamp(val, unit="M")
|
| 47 |
+
expected = Timestamp("1982-07-01")
|
| 48 |
+
assert ts == expected
|
| 49 |
+
|
| 50 |
+
@pytest.mark.parametrize("typ", [int, float])
|
| 51 |
+
def test_construct_from_int_float_with_unit_out_of_bound_raises(self, typ):
|
| 52 |
+
# GH#50870 make sure we get an OutOfBoundsDatetime instead of OverflowError
|
| 53 |
+
val = typ(150000000000000)
|
| 54 |
+
|
| 55 |
+
msg = f"cannot convert input {int(val)} with the unit 'D'"
|
| 56 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 57 |
+
Timestamp(val, unit="D")
|
| 58 |
+
|
| 59 |
+
def test_constructor_float_not_round_with_YM_unit_raises(self):
|
| 60 |
+
# GH#47267 avoid the conversions in cast_from-unit
|
| 61 |
+
|
| 62 |
+
msg = "Conversion of non-round float with unit=[MY] is ambiguous"
|
| 63 |
+
with pytest.raises(ValueError, match=msg):
|
| 64 |
+
Timestamp(150.5, unit="Y")
|
| 65 |
+
|
| 66 |
+
with pytest.raises(ValueError, match=msg):
|
| 67 |
+
Timestamp(150.5, unit="M")
|
| 68 |
+
|
| 69 |
+
@pytest.mark.parametrize(
|
| 70 |
+
"value, check_kwargs",
|
| 71 |
+
[
|
| 72 |
+
[946688461000000000, {}],
|
| 73 |
+
[946688461000000000 / 1000, {"unit": "us"}],
|
| 74 |
+
[946688461000000000 / 1_000_000, {"unit": "ms"}],
|
| 75 |
+
[946688461000000000 / 1_000_000_000, {"unit": "s"}],
|
| 76 |
+
[10957, {"unit": "D", "h": 0}],
|
| 77 |
+
[
|
| 78 |
+
(946688461000000000 + 500000) / 1000000000,
|
| 79 |
+
{"unit": "s", "us": 499, "ns": 964},
|
| 80 |
+
],
|
| 81 |
+
[
|
| 82 |
+
(946688461000000000 + 500000000) / 1000000000,
|
| 83 |
+
{"unit": "s", "us": 500000},
|
| 84 |
+
],
|
| 85 |
+
[(946688461000000000 + 500000) / 1000000, {"unit": "ms", "us": 500}],
|
| 86 |
+
[(946688461000000000 + 500000) / 1000, {"unit": "us", "us": 500}],
|
| 87 |
+
[(946688461000000000 + 500000000) / 1000000, {"unit": "ms", "us": 500000}],
|
| 88 |
+
[946688461000000000 / 1000.0 + 5, {"unit": "us", "us": 5}],
|
| 89 |
+
[946688461000000000 / 1000.0 + 5000, {"unit": "us", "us": 5000}],
|
| 90 |
+
[946688461000000000 / 1000000.0 + 0.5, {"unit": "ms", "us": 500}],
|
| 91 |
+
[946688461000000000 / 1000000.0 + 0.005, {"unit": "ms", "us": 5, "ns": 5}],
|
| 92 |
+
[946688461000000000 / 1000000000.0 + 0.5, {"unit": "s", "us": 500000}],
|
| 93 |
+
[10957 + 0.5, {"unit": "D", "h": 12}],
|
| 94 |
+
],
|
| 95 |
+
)
|
| 96 |
+
def test_construct_with_unit(self, value, check_kwargs):
|
| 97 |
+
def check(value, unit=None, h=1, s=1, us=0, ns=0):
|
| 98 |
+
stamp = Timestamp(value, unit=unit)
|
| 99 |
+
assert stamp.year == 2000
|
| 100 |
+
assert stamp.month == 1
|
| 101 |
+
assert stamp.day == 1
|
| 102 |
+
assert stamp.hour == h
|
| 103 |
+
if unit != "D":
|
| 104 |
+
assert stamp.minute == 1
|
| 105 |
+
assert stamp.second == s
|
| 106 |
+
assert stamp.microsecond == us
|
| 107 |
+
else:
|
| 108 |
+
assert stamp.minute == 0
|
| 109 |
+
assert stamp.second == 0
|
| 110 |
+
assert stamp.microsecond == 0
|
| 111 |
+
assert stamp.nanosecond == ns
|
| 112 |
+
|
| 113 |
+
check(value, **check_kwargs)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class TestTimestampConstructorFoldKeyword:
|
| 117 |
+
def test_timestamp_constructor_invalid_fold_raise(self):
|
| 118 |
+
# Test for GH#25057
|
| 119 |
+
# Valid fold values are only [None, 0, 1]
|
| 120 |
+
msg = "Valid values for the fold argument are None, 0, or 1."
|
| 121 |
+
with pytest.raises(ValueError, match=msg):
|
| 122 |
+
Timestamp(123, fold=2)
|
| 123 |
+
|
| 124 |
+
def test_timestamp_constructor_pytz_fold_raise(self):
|
| 125 |
+
# Test for GH#25057
|
| 126 |
+
# pytz doesn't support fold. Check that we raise
|
| 127 |
+
# if fold is passed with pytz
|
| 128 |
+
pytz = pytest.importorskip("pytz")
|
| 129 |
+
msg = "pytz timezones do not support fold. Please use dateutil timezones."
|
| 130 |
+
tz = pytz.timezone("Europe/London")
|
| 131 |
+
with pytest.raises(ValueError, match=msg):
|
| 132 |
+
Timestamp(datetime(2019, 10, 27, 0, 30, 0, 0), tz=tz, fold=0)
|
| 133 |
+
|
| 134 |
+
@pytest.mark.parametrize("fold", [0, 1])
|
| 135 |
+
@pytest.mark.parametrize(
|
| 136 |
+
"ts_input",
|
| 137 |
+
[
|
| 138 |
+
1572136200000000000,
|
| 139 |
+
1572136200000000000.0,
|
| 140 |
+
np.datetime64(1572136200000000000, "ns"),
|
| 141 |
+
"2019-10-27 01:30:00+01:00",
|
| 142 |
+
datetime(2019, 10, 27, 0, 30, 0, 0, tzinfo=timezone.utc),
|
| 143 |
+
],
|
| 144 |
+
)
|
| 145 |
+
def test_timestamp_constructor_fold_conflict(self, ts_input, fold):
|
| 146 |
+
# Test for GH#25057
|
| 147 |
+
# Check that we raise on fold conflict
|
| 148 |
+
msg = (
|
| 149 |
+
"Cannot pass fold with possibly unambiguous input: int, float, "
|
| 150 |
+
"numpy.datetime64, str, or timezone-aware datetime-like. "
|
| 151 |
+
"Pass naive datetime-like or build Timestamp from components."
|
| 152 |
+
)
|
| 153 |
+
with pytest.raises(ValueError, match=msg):
|
| 154 |
+
Timestamp(ts_input=ts_input, fold=fold)
|
| 155 |
+
|
| 156 |
+
@pytest.mark.parametrize("tz", ["dateutil/Europe/London", None])
|
| 157 |
+
@pytest.mark.parametrize("fold", [0, 1])
|
| 158 |
+
def test_timestamp_constructor_retain_fold(self, tz, fold):
|
| 159 |
+
# Test for GH#25057
|
| 160 |
+
# Check that we retain fold
|
| 161 |
+
ts = Timestamp(year=2019, month=10, day=27, hour=1, minute=30, tz=tz, fold=fold)
|
| 162 |
+
result = ts.fold
|
| 163 |
+
expected = fold
|
| 164 |
+
assert result == expected
|
| 165 |
+
|
| 166 |
+
@pytest.mark.parametrize(
|
| 167 |
+
"tz",
|
| 168 |
+
[
|
| 169 |
+
"dateutil/Europe/London",
|
| 170 |
+
zoneinfo.ZoneInfo("Europe/London"),
|
| 171 |
+
],
|
| 172 |
+
)
|
| 173 |
+
@pytest.mark.parametrize(
|
| 174 |
+
"ts_input,fold_out",
|
| 175 |
+
[
|
| 176 |
+
(1572136200000000000, 0),
|
| 177 |
+
(1572139800000000000, 1),
|
| 178 |
+
("2019-10-27 01:30:00+01:00", 0),
|
| 179 |
+
("2019-10-27 01:30:00+00:00", 1),
|
| 180 |
+
(datetime(2019, 10, 27, 1, 30, 0, 0, fold=0), 0),
|
| 181 |
+
(datetime(2019, 10, 27, 1, 30, 0, 0, fold=1), 1),
|
| 182 |
+
],
|
| 183 |
+
)
|
| 184 |
+
def test_timestamp_constructor_infer_fold_from_value(self, tz, ts_input, fold_out):
|
| 185 |
+
# Test for GH#25057
|
| 186 |
+
# Check that we infer fold correctly based on timestamps since utc
|
| 187 |
+
# or strings
|
| 188 |
+
ts = Timestamp(ts_input, tz=tz)
|
| 189 |
+
result = ts.fold
|
| 190 |
+
expected = fold_out
|
| 191 |
+
assert result == expected
|
| 192 |
+
|
| 193 |
+
@pytest.mark.parametrize("tz", ["dateutil/Europe/London"])
|
| 194 |
+
@pytest.mark.parametrize(
|
| 195 |
+
"fold,value_out",
|
| 196 |
+
[
|
| 197 |
+
(0, 1572136200000000),
|
| 198 |
+
(1, 1572139800000000),
|
| 199 |
+
],
|
| 200 |
+
)
|
| 201 |
+
def test_timestamp_constructor_adjust_value_for_fold(self, tz, fold, value_out):
|
| 202 |
+
# Test for GH#25057
|
| 203 |
+
# Check that we adjust value for fold correctly
|
| 204 |
+
# based on timestamps since utc
|
| 205 |
+
ts_input = datetime(2019, 10, 27, 1, 30)
|
| 206 |
+
ts = Timestamp(ts_input, tz=tz, fold=fold)
|
| 207 |
+
result = ts._value
|
| 208 |
+
expected = value_out
|
| 209 |
+
assert result == expected
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class TestTimestampConstructorPositionalAndKeywordSupport:
|
| 213 |
+
def test_constructor_positional(self):
|
| 214 |
+
# see GH#10758
|
| 215 |
+
msg = "'NoneType' object cannot be interpreted as an integer"
|
| 216 |
+
with pytest.raises(TypeError, match=msg):
|
| 217 |
+
Timestamp(2000, 1)
|
| 218 |
+
|
| 219 |
+
msg = "month must be in 1..12"
|
| 220 |
+
with pytest.raises(ValueError, match=msg):
|
| 221 |
+
Timestamp(2000, 0, 1)
|
| 222 |
+
with pytest.raises(ValueError, match=msg):
|
| 223 |
+
Timestamp(2000, 13, 1)
|
| 224 |
+
|
| 225 |
+
if PY314:
|
| 226 |
+
msg = "must be in range 1..31 for month 1 in year 2000"
|
| 227 |
+
else:
|
| 228 |
+
msg = "day is out of range for month"
|
| 229 |
+
with pytest.raises(ValueError, match=msg):
|
| 230 |
+
Timestamp(2000, 1, 0)
|
| 231 |
+
with pytest.raises(ValueError, match=msg):
|
| 232 |
+
Timestamp(2000, 1, 32)
|
| 233 |
+
|
| 234 |
+
# see gh-11630
|
| 235 |
+
assert repr(Timestamp(2015, 11, 12)) == repr(Timestamp("20151112"))
|
| 236 |
+
assert repr(Timestamp(2015, 11, 12, 1, 2, 3, 999999)) == repr(
|
| 237 |
+
Timestamp("2015-11-12 01:02:03.999999")
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
def test_constructor_keyword(self):
|
| 241 |
+
# GH#10758
|
| 242 |
+
msg = "function missing required argument 'day'|Required argument 'day'"
|
| 243 |
+
with pytest.raises(TypeError, match=msg):
|
| 244 |
+
Timestamp(year=2000, month=1)
|
| 245 |
+
|
| 246 |
+
msg = "month must be in 1..12"
|
| 247 |
+
with pytest.raises(ValueError, match=msg):
|
| 248 |
+
Timestamp(year=2000, month=0, day=1)
|
| 249 |
+
with pytest.raises(ValueError, match=msg):
|
| 250 |
+
Timestamp(year=2000, month=13, day=1)
|
| 251 |
+
|
| 252 |
+
if PY314:
|
| 253 |
+
msg = "must be in range 1..31 for month 1 in year 2000"
|
| 254 |
+
else:
|
| 255 |
+
msg = "day is out of range for month"
|
| 256 |
+
with pytest.raises(ValueError, match=msg):
|
| 257 |
+
Timestamp(year=2000, month=1, day=0)
|
| 258 |
+
with pytest.raises(ValueError, match=msg):
|
| 259 |
+
Timestamp(year=2000, month=1, day=32)
|
| 260 |
+
|
| 261 |
+
assert repr(Timestamp(year=2015, month=11, day=12)) == repr(
|
| 262 |
+
Timestamp("20151112")
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
assert repr(
|
| 266 |
+
Timestamp(
|
| 267 |
+
year=2015,
|
| 268 |
+
month=11,
|
| 269 |
+
day=12,
|
| 270 |
+
hour=1,
|
| 271 |
+
minute=2,
|
| 272 |
+
second=3,
|
| 273 |
+
microsecond=999999,
|
| 274 |
+
)
|
| 275 |
+
) == repr(Timestamp("2015-11-12 01:02:03.999999"))
|
| 276 |
+
|
| 277 |
+
@pytest.mark.parametrize(
|
| 278 |
+
"arg",
|
| 279 |
+
[
|
| 280 |
+
"year",
|
| 281 |
+
"month",
|
| 282 |
+
"day",
|
| 283 |
+
"hour",
|
| 284 |
+
"minute",
|
| 285 |
+
"second",
|
| 286 |
+
"microsecond",
|
| 287 |
+
"nanosecond",
|
| 288 |
+
],
|
| 289 |
+
)
|
| 290 |
+
def test_invalid_date_kwarg_with_string_input(self, arg):
|
| 291 |
+
kwarg = {arg: 1}
|
| 292 |
+
msg = "Cannot pass a date attribute keyword argument"
|
| 293 |
+
with pytest.raises(ValueError, match=msg):
|
| 294 |
+
Timestamp("2010-10-10 12:59:59.999999999", **kwarg)
|
| 295 |
+
|
| 296 |
+
@pytest.mark.parametrize("kwargs", [{}, {"year": 2020}, {"year": 2020, "month": 1}])
|
| 297 |
+
def test_constructor_missing_keyword(self, kwargs):
|
| 298 |
+
# GH#31200
|
| 299 |
+
|
| 300 |
+
# The exact error message of datetime() depends on its version
|
| 301 |
+
msg1 = r"function missing required argument '(year|month|day)' \(pos [123]\)"
|
| 302 |
+
msg2 = r"Required argument '(year|month|day)' \(pos [123]\) not found"
|
| 303 |
+
msg = "|".join([msg1, msg2])
|
| 304 |
+
|
| 305 |
+
with pytest.raises(TypeError, match=msg):
|
| 306 |
+
Timestamp(**kwargs)
|
| 307 |
+
|
| 308 |
+
def test_constructor_positional_with_tzinfo(self):
|
| 309 |
+
# GH#31929
|
| 310 |
+
ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc)
|
| 311 |
+
expected = Timestamp("2020-12-31", tzinfo=timezone.utc)
|
| 312 |
+
assert ts == expected
|
| 313 |
+
|
| 314 |
+
@pytest.mark.parametrize("kwd", ["nanosecond", "microsecond", "second", "minute"])
|
| 315 |
+
def test_constructor_positional_keyword_mixed_with_tzinfo(self, kwd, request):
|
| 316 |
+
# TODO: if we passed microsecond with a keyword we would mess up
|
| 317 |
+
# xref GH#45307
|
| 318 |
+
if kwd != "nanosecond":
|
| 319 |
+
# nanosecond is keyword-only as of 2.0, others are not
|
| 320 |
+
mark = pytest.mark.xfail(reason="GH#45307")
|
| 321 |
+
request.applymarker(mark)
|
| 322 |
+
|
| 323 |
+
kwargs = {kwd: 4}
|
| 324 |
+
ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc, **kwargs)
|
| 325 |
+
|
| 326 |
+
td_kwargs = {kwd + "s": 4}
|
| 327 |
+
td = Timedelta(**td_kwargs)
|
| 328 |
+
expected = Timestamp("2020-12-31", tz=timezone.utc) + td
|
| 329 |
+
assert ts == expected
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
class TestTimestampClassMethodConstructors:
|
| 333 |
+
# Timestamp constructors other than __new__
|
| 334 |
+
|
| 335 |
+
def test_utcnow_deprecated(self):
|
| 336 |
+
# GH#56680
|
| 337 |
+
msg = "Timestamp.utcnow is deprecated"
|
| 338 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 339 |
+
Timestamp.utcnow()
|
| 340 |
+
|
| 341 |
+
def test_utcfromtimestamp_deprecated(self):
|
| 342 |
+
# GH#56680
|
| 343 |
+
msg = "Timestamp.utcfromtimestamp is deprecated"
|
| 344 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 345 |
+
Timestamp.utcfromtimestamp(43)
|
| 346 |
+
|
| 347 |
+
def test_constructor_strptime(self):
|
| 348 |
+
# GH#25016
|
| 349 |
+
# Test support for Timestamp.strptime
|
| 350 |
+
fmt = "%Y%m%d-%H%M%S-%f%z"
|
| 351 |
+
ts = "20190129-235348-000001+0000"
|
| 352 |
+
msg = r"Timestamp.strptime\(\) is not implemented"
|
| 353 |
+
with pytest.raises(NotImplementedError, match=msg):
|
| 354 |
+
Timestamp.strptime(ts, fmt)
|
| 355 |
+
|
| 356 |
+
def test_constructor_fromisocalendar(self):
|
| 357 |
+
# GH#30395
|
| 358 |
+
expected_timestamp = Timestamp("2000-01-03 00:00:00")
|
| 359 |
+
expected_stdlib = datetime.fromisocalendar(2000, 1, 1)
|
| 360 |
+
result = Timestamp.fromisocalendar(2000, 1, 1)
|
| 361 |
+
assert result == expected_timestamp
|
| 362 |
+
assert result == expected_stdlib
|
| 363 |
+
assert isinstance(result, Timestamp)
|
| 364 |
+
|
| 365 |
+
def test_constructor_fromordinal(self):
|
| 366 |
+
base = datetime(2000, 1, 1)
|
| 367 |
+
|
| 368 |
+
ts = Timestamp.fromordinal(base.toordinal())
|
| 369 |
+
assert base == ts
|
| 370 |
+
assert base.toordinal() == ts.toordinal()
|
| 371 |
+
|
| 372 |
+
ts = Timestamp.fromordinal(base.toordinal(), tz="US/Eastern")
|
| 373 |
+
assert Timestamp("2000-01-01", tz="US/Eastern") == ts
|
| 374 |
+
assert base.toordinal() == ts.toordinal()
|
| 375 |
+
|
| 376 |
+
# GH#3042
|
| 377 |
+
dt = datetime(2011, 4, 16, 0, 0)
|
| 378 |
+
ts = Timestamp.fromordinal(dt.toordinal())
|
| 379 |
+
assert ts.to_pydatetime() == dt
|
| 380 |
+
|
| 381 |
+
# with a tzinfo
|
| 382 |
+
stamp = Timestamp("2011-4-16", tz="US/Eastern")
|
| 383 |
+
dt_tz = stamp.to_pydatetime()
|
| 384 |
+
ts = Timestamp.fromordinal(dt_tz.toordinal(), tz="US/Eastern")
|
| 385 |
+
assert ts.to_pydatetime() == dt_tz
|
| 386 |
+
|
| 387 |
+
def test_now(self):
|
| 388 |
+
# GH#9000
|
| 389 |
+
ts_from_string = Timestamp("now")
|
| 390 |
+
ts_from_method = Timestamp.now()
|
| 391 |
+
ts_datetime = datetime.now()
|
| 392 |
+
|
| 393 |
+
ts_from_string_tz = Timestamp("now", tz="US/Eastern")
|
| 394 |
+
ts_from_method_tz = Timestamp.now(tz="US/Eastern")
|
| 395 |
+
|
| 396 |
+
# Check that the delta between the times is less than 1s (arbitrarily
|
| 397 |
+
# small)
|
| 398 |
+
delta = Timedelta(seconds=1)
|
| 399 |
+
assert abs(ts_from_method - ts_from_string) < delta
|
| 400 |
+
assert abs(ts_datetime - ts_from_method) < delta
|
| 401 |
+
assert abs(ts_from_method_tz - ts_from_string_tz) < delta
|
| 402 |
+
assert (
|
| 403 |
+
abs(
|
| 404 |
+
ts_from_string_tz.tz_localize(None)
|
| 405 |
+
- ts_from_method_tz.tz_localize(None)
|
| 406 |
+
)
|
| 407 |
+
< delta
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
def test_today(self):
|
| 411 |
+
ts_from_string = Timestamp("today")
|
| 412 |
+
ts_from_method = Timestamp.today()
|
| 413 |
+
ts_datetime = datetime.today()
|
| 414 |
+
|
| 415 |
+
ts_from_string_tz = Timestamp("today", tz="US/Eastern")
|
| 416 |
+
ts_from_method_tz = Timestamp.today(tz="US/Eastern")
|
| 417 |
+
|
| 418 |
+
# Check that the delta between the times is less than 1s (arbitrarily
|
| 419 |
+
# small)
|
| 420 |
+
delta = Timedelta(seconds=1)
|
| 421 |
+
assert abs(ts_from_method - ts_from_string) < delta
|
| 422 |
+
assert abs(ts_datetime - ts_from_method) < delta
|
| 423 |
+
assert abs(ts_from_method_tz - ts_from_string_tz) < delta
|
| 424 |
+
assert (
|
| 425 |
+
abs(
|
| 426 |
+
ts_from_string_tz.tz_localize(None)
|
| 427 |
+
- ts_from_method_tz.tz_localize(None)
|
| 428 |
+
)
|
| 429 |
+
< delta
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
class TestTimestampResolutionInference:
|
| 434 |
+
def test_construct_from_time_unit(self):
|
| 435 |
+
# GH#54097 only passing a time component, no date
|
| 436 |
+
ts = Timestamp("01:01:01.111")
|
| 437 |
+
assert ts.unit == "us"
|
| 438 |
+
|
| 439 |
+
def test_constructor_str_infer_reso(self):
|
| 440 |
+
# non-iso8601 path
|
| 441 |
+
|
| 442 |
+
# _parse_delimited_date path
|
| 443 |
+
ts = Timestamp("01/30/2023")
|
| 444 |
+
assert ts.unit == "us"
|
| 445 |
+
|
| 446 |
+
# _parse_dateabbr_string path
|
| 447 |
+
ts = Timestamp("2015Q1")
|
| 448 |
+
assert ts.unit == "us"
|
| 449 |
+
|
| 450 |
+
# dateutil_parse path
|
| 451 |
+
ts = Timestamp("2016-01-01 1:30:01 PM")
|
| 452 |
+
assert ts.unit == "us"
|
| 453 |
+
|
| 454 |
+
ts = Timestamp("2016 June 3 15:25:01.345")
|
| 455 |
+
assert ts.unit == "us"
|
| 456 |
+
|
| 457 |
+
ts = Timestamp("300-01-01")
|
| 458 |
+
assert ts.unit == "us"
|
| 459 |
+
|
| 460 |
+
ts = Timestamp("300 June 1:30:01.300")
|
| 461 |
+
assert ts.unit == "us"
|
| 462 |
+
|
| 463 |
+
# dateutil path -> don't drop trailing zeros
|
| 464 |
+
ts = Timestamp("01-01-2013T00:00:00.000000000+0000")
|
| 465 |
+
assert ts.unit == "ns"
|
| 466 |
+
|
| 467 |
+
ts = Timestamp("2016/01/02 03:04:05.001000 UTC")
|
| 468 |
+
assert ts.unit == "us"
|
| 469 |
+
|
| 470 |
+
# higher-than-nanosecond -> we drop the trailing bits
|
| 471 |
+
ts = Timestamp("01-01-2013T00:00:00.000000002100+0000")
|
| 472 |
+
assert ts == Timestamp("01-01-2013T00:00:00.000000002+0000")
|
| 473 |
+
assert ts.unit == "ns"
|
| 474 |
+
|
| 475 |
+
# GH#56208 minute reso through the ISO8601 path with tz offset
|
| 476 |
+
ts = Timestamp("2020-01-01 00:00+00:00")
|
| 477 |
+
assert ts.unit == "us"
|
| 478 |
+
|
| 479 |
+
ts = Timestamp("2020-01-01 00+00:00")
|
| 480 |
+
assert ts.unit == "us"
|
| 481 |
+
|
| 482 |
+
@pytest.mark.parametrize("method", ["now", "today"])
|
| 483 |
+
def test_now_today_unit(self, method):
|
| 484 |
+
# GH#55879
|
| 485 |
+
ts_from_method = getattr(Timestamp, method)()
|
| 486 |
+
ts_from_string = Timestamp(method)
|
| 487 |
+
assert ts_from_method.unit == ts_from_string.unit == "us"
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
class TestTimestampConstructors:
|
| 491 |
+
def test_disallow_dt64_with_weird_unit(self):
|
| 492 |
+
# GH#25611
|
| 493 |
+
dt64 = np.datetime64(1, "500m")
|
| 494 |
+
msg = "np.datetime64 objects with units containing a multiplier"
|
| 495 |
+
with pytest.raises(ValueError, match=msg):
|
| 496 |
+
Timestamp(dt64)
|
| 497 |
+
|
| 498 |
+
def test_weekday_but_no_day_raises(self):
|
| 499 |
+
# GH#52659
|
| 500 |
+
msg = "Parsing datetimes with weekday but no day information is not supported"
|
| 501 |
+
with pytest.raises(ValueError, match=msg):
|
| 502 |
+
Timestamp("2023 Sept Thu")
|
| 503 |
+
|
| 504 |
+
def test_construct_from_string_invalid_raises(self):
|
| 505 |
+
# dateutil (weirdly) parses "200622-12-31" as
|
| 506 |
+
# datetime(2022, 6, 20, 12, 0, tzinfo=tzoffset(None, -111600)
|
| 507 |
+
# which besides being mis-parsed, is a tzoffset that will cause
|
| 508 |
+
# str(ts) to raise ValueError. Ensure we raise in the constructor
|
| 509 |
+
# instead.
|
| 510 |
+
# see test_to_datetime_malformed_raise for analogous to_datetime test
|
| 511 |
+
with pytest.raises(ValueError, match="gives an invalid tzoffset"):
|
| 512 |
+
Timestamp("200622-12-31")
|
| 513 |
+
|
| 514 |
+
def test_constructor_from_iso8601_str_with_offset_reso(self):
|
| 515 |
+
# GH#49737
|
| 516 |
+
ts = Timestamp("2016-01-01 04:05:06-01:00")
|
| 517 |
+
assert ts.unit == "us"
|
| 518 |
+
|
| 519 |
+
ts = Timestamp("2016-01-01 04:05:06.000-01:00")
|
| 520 |
+
assert ts.unit == "us"
|
| 521 |
+
|
| 522 |
+
ts = Timestamp("2016-01-01 04:05:06.000000-01:00")
|
| 523 |
+
assert ts.unit == "us"
|
| 524 |
+
|
| 525 |
+
ts = Timestamp("2016-01-01 04:05:06.000000001-01:00")
|
| 526 |
+
assert ts.unit == "ns"
|
| 527 |
+
|
| 528 |
+
def test_constructor_from_date_second_reso(self):
|
| 529 |
+
# GH#49034 constructing from a pydate object gets lowest supported
|
| 530 |
+
# reso, i.e. seconds
|
| 531 |
+
obj = date(2012, 9, 1)
|
| 532 |
+
ts = Timestamp(obj)
|
| 533 |
+
assert ts.unit == "s"
|
| 534 |
+
|
| 535 |
+
def test_constructor_datetime64_with_tz(self):
|
| 536 |
+
# GH#42288, GH#24559
|
| 537 |
+
dt = np.datetime64("1970-01-01 05:00:00")
|
| 538 |
+
tzstr = "UTC+05:00"
|
| 539 |
+
|
| 540 |
+
# pre-2.0 this interpreted dt as a UTC time. in 2.0 this is treated
|
| 541 |
+
# as a wall-time, consistent with DatetimeIndex behavior
|
| 542 |
+
ts = Timestamp(dt, tz=tzstr)
|
| 543 |
+
|
| 544 |
+
alt = Timestamp(dt).tz_localize(tzstr)
|
| 545 |
+
assert ts == alt
|
| 546 |
+
assert ts.hour == 5
|
| 547 |
+
|
| 548 |
+
def test_constructor(self):
|
| 549 |
+
base_str = "2014-07-01 09:00"
|
| 550 |
+
base_dt = datetime(2014, 7, 1, 9)
|
| 551 |
+
base_expected = 1_404_205_200_000_000_000
|
| 552 |
+
|
| 553 |
+
# confirm base representation is correct
|
| 554 |
+
assert calendar.timegm(base_dt.timetuple()) * 1_000_000_000 == base_expected
|
| 555 |
+
|
| 556 |
+
tests = [
|
| 557 |
+
(base_str, base_dt, base_expected),
|
| 558 |
+
(
|
| 559 |
+
"2014-07-01 10:00",
|
| 560 |
+
datetime(2014, 7, 1, 10),
|
| 561 |
+
base_expected + 3600 * 1_000_000_000,
|
| 562 |
+
),
|
| 563 |
+
(
|
| 564 |
+
"2014-07-01 09:00:00.000008000",
|
| 565 |
+
datetime(2014, 7, 1, 9, 0, 0, 8),
|
| 566 |
+
base_expected + 8000,
|
| 567 |
+
),
|
| 568 |
+
(
|
| 569 |
+
"2014-07-01 09:00:00.000000005",
|
| 570 |
+
Timestamp("2014-07-01 09:00:00.000000005"),
|
| 571 |
+
base_expected + 5,
|
| 572 |
+
),
|
| 573 |
+
]
|
| 574 |
+
|
| 575 |
+
timezones = [
|
| 576 |
+
(None, 0),
|
| 577 |
+
("UTC", 0),
|
| 578 |
+
(timezone.utc, 0),
|
| 579 |
+
("Asia/Tokyo", 9),
|
| 580 |
+
("US/Eastern", -4),
|
| 581 |
+
("dateutil/US/Pacific", -7),
|
| 582 |
+
(timezone(timedelta(hours=-3)), -3),
|
| 583 |
+
(dateutil.tz.tzoffset(None, 18000), 5),
|
| 584 |
+
]
|
| 585 |
+
|
| 586 |
+
for date_str, date_obj, expected in tests:
|
| 587 |
+
for result in [Timestamp(date_str), Timestamp(date_obj)]:
|
| 588 |
+
result = result.as_unit("ns") # test originally written before non-nano
|
| 589 |
+
# only with timestring
|
| 590 |
+
assert result.as_unit("ns")._value == expected
|
| 591 |
+
|
| 592 |
+
# re-creation shouldn't affect to internal value
|
| 593 |
+
result = Timestamp(result)
|
| 594 |
+
assert result.as_unit("ns")._value == expected
|
| 595 |
+
|
| 596 |
+
# with timezone
|
| 597 |
+
for tz, offset in timezones:
|
| 598 |
+
for result in [Timestamp(date_str, tz=tz), Timestamp(date_obj, tz=tz)]:
|
| 599 |
+
result = result.as_unit(
|
| 600 |
+
"ns"
|
| 601 |
+
) # test originally written before non-nano
|
| 602 |
+
expected_tz = expected - offset * 3600 * 1_000_000_000
|
| 603 |
+
assert result.as_unit("ns")._value == expected_tz
|
| 604 |
+
|
| 605 |
+
# should preserve tz
|
| 606 |
+
result = Timestamp(result)
|
| 607 |
+
assert result.as_unit("ns")._value == expected_tz
|
| 608 |
+
|
| 609 |
+
# should convert to UTC
|
| 610 |
+
if tz is not None:
|
| 611 |
+
result = Timestamp(result).tz_convert("UTC")
|
| 612 |
+
else:
|
| 613 |
+
result = Timestamp(result, tz="UTC")
|
| 614 |
+
expected_utc = expected - offset * 3600 * 1_000_000_000
|
| 615 |
+
assert result.as_unit("ns")._value == expected_utc
|
| 616 |
+
|
| 617 |
+
def test_constructor_with_stringoffset(self):
|
| 618 |
+
# GH 7833
|
| 619 |
+
base_str = "2014-07-01 11:00:00+02:00"
|
| 620 |
+
base_dt = datetime(2014, 7, 1, 9)
|
| 621 |
+
base_expected = 1_404_205_200_000_000_000
|
| 622 |
+
|
| 623 |
+
# confirm base representation is correct
|
| 624 |
+
assert calendar.timegm(base_dt.timetuple()) * 1_000_000_000 == base_expected
|
| 625 |
+
|
| 626 |
+
tests = [
|
| 627 |
+
(base_str, base_expected),
|
| 628 |
+
("2014-07-01 12:00:00+02:00", base_expected + 3600 * 1_000_000_000),
|
| 629 |
+
("2014-07-01 11:00:00.000008000+02:00", base_expected + 8000),
|
| 630 |
+
("2014-07-01 11:00:00.000000005+02:00", base_expected + 5),
|
| 631 |
+
]
|
| 632 |
+
|
| 633 |
+
timezones = [
|
| 634 |
+
("UTC", 0),
|
| 635 |
+
(timezone.utc, 0),
|
| 636 |
+
("Asia/Tokyo", 9),
|
| 637 |
+
("US/Eastern", -4),
|
| 638 |
+
("dateutil/US/Pacific", -7),
|
| 639 |
+
(timezone(timedelta(hours=-3)), -3),
|
| 640 |
+
(dateutil.tz.tzoffset(None, 18000), 5),
|
| 641 |
+
]
|
| 642 |
+
|
| 643 |
+
for date_str, expected in tests:
|
| 644 |
+
for result in [Timestamp(date_str)]:
|
| 645 |
+
# only with timestring
|
| 646 |
+
assert result.as_unit("ns")._value == expected
|
| 647 |
+
|
| 648 |
+
# re-creation shouldn't affect to internal value
|
| 649 |
+
result = Timestamp(result)
|
| 650 |
+
assert result.as_unit("ns")._value == expected
|
| 651 |
+
|
| 652 |
+
# with timezone
|
| 653 |
+
for tz, offset in timezones:
|
| 654 |
+
result = Timestamp(date_str, tz=tz)
|
| 655 |
+
expected_tz = expected
|
| 656 |
+
assert result.as_unit("ns")._value == expected_tz
|
| 657 |
+
|
| 658 |
+
# should preserve tz
|
| 659 |
+
result = Timestamp(result)
|
| 660 |
+
assert result.as_unit("ns")._value == expected_tz
|
| 661 |
+
|
| 662 |
+
# should convert to UTC
|
| 663 |
+
result = Timestamp(result).tz_convert("UTC")
|
| 664 |
+
expected_utc = expected
|
| 665 |
+
assert result.as_unit("ns")._value == expected_utc
|
| 666 |
+
|
| 667 |
+
# This should be 2013-11-01 05:00 in UTC
|
| 668 |
+
# converted to Chicago tz
|
| 669 |
+
result = Timestamp("2013-11-01 00:00:00-0500", tz="America/Chicago")
|
| 670 |
+
assert result._value == Timestamp("2013-11-01 05:00")._value
|
| 671 |
+
expected = "Timestamp('2013-11-01 00:00:00-0500', tz='America/Chicago')"
|
| 672 |
+
assert repr(result) == expected
|
| 673 |
+
assert result == eval(repr(result))
|
| 674 |
+
|
| 675 |
+
# This should be 2013-11-01 05:00 in UTC
|
| 676 |
+
# converted to Tokyo tz (+09:00)
|
| 677 |
+
result = Timestamp("2013-11-01 00:00:00-0500", tz="Asia/Tokyo")
|
| 678 |
+
assert result._value == Timestamp("2013-11-01 05:00")._value
|
| 679 |
+
expected = "Timestamp('2013-11-01 14:00:00+0900', tz='Asia/Tokyo')"
|
| 680 |
+
assert repr(result) == expected
|
| 681 |
+
assert result == eval(repr(result))
|
| 682 |
+
|
| 683 |
+
# GH11708
|
| 684 |
+
# This should be 2015-11-18 10:00 in UTC
|
| 685 |
+
# converted to Asia/Katmandu
|
| 686 |
+
result = Timestamp("2015-11-18 15:45:00+05:45", tz="Asia/Katmandu")
|
| 687 |
+
assert result._value == Timestamp("2015-11-18 10:00")._value
|
| 688 |
+
expected = "Timestamp('2015-11-18 15:45:00+0545', tz='Asia/Katmandu')"
|
| 689 |
+
assert repr(result) == expected
|
| 690 |
+
assert result == eval(repr(result))
|
| 691 |
+
|
| 692 |
+
# This should be 2015-11-18 10:00 in UTC
|
| 693 |
+
# converted to Asia/Kolkata
|
| 694 |
+
result = Timestamp("2015-11-18 15:30:00+05:30", tz="Asia/Kolkata")
|
| 695 |
+
assert result._value == Timestamp("2015-11-18 10:00")._value
|
| 696 |
+
expected = "Timestamp('2015-11-18 15:30:00+0530', tz='Asia/Kolkata')"
|
| 697 |
+
assert repr(result) == expected
|
| 698 |
+
assert result == eval(repr(result))
|
| 699 |
+
|
| 700 |
+
def test_constructor_invalid(self):
|
| 701 |
+
msg = "Cannot convert input"
|
| 702 |
+
with pytest.raises(TypeError, match=msg):
|
| 703 |
+
Timestamp(slice(2))
|
| 704 |
+
msg = "Cannot convert Period"
|
| 705 |
+
with pytest.raises(ValueError, match=msg):
|
| 706 |
+
Timestamp(Period("1000-01-01"))
|
| 707 |
+
|
| 708 |
+
def test_constructor_invalid_tz(self):
|
| 709 |
+
# GH#17690
|
| 710 |
+
msg = (
|
| 711 |
+
"Argument 'tzinfo' has incorrect type "
|
| 712 |
+
r"\(expected datetime.tzinfo, got str\)"
|
| 713 |
+
)
|
| 714 |
+
with pytest.raises(TypeError, match=msg):
|
| 715 |
+
Timestamp("2017-10-22", tzinfo="US/Eastern")
|
| 716 |
+
|
| 717 |
+
msg = "at most one of"
|
| 718 |
+
with pytest.raises(ValueError, match=msg):
|
| 719 |
+
Timestamp("2017-10-22", tzinfo=timezone.utc, tz="UTC")
|
| 720 |
+
|
| 721 |
+
msg = "Cannot pass a date attribute keyword argument when passing a date string"
|
| 722 |
+
with pytest.raises(ValueError, match=msg):
|
| 723 |
+
# GH#5168
|
| 724 |
+
# case where user tries to pass tz as an arg, not kwarg, gets
|
| 725 |
+
# interpreted as `year`
|
| 726 |
+
Timestamp("2012-01-01", "US/Pacific")
|
| 727 |
+
|
| 728 |
+
def test_constructor_tz_or_tzinfo(self):
|
| 729 |
+
# GH#17943, GH#17690, GH#5168
|
| 730 |
+
stamps = [
|
| 731 |
+
Timestamp(year=2017, month=10, day=22, tz="UTC"),
|
| 732 |
+
Timestamp(year=2017, month=10, day=22, tzinfo=timezone.utc),
|
| 733 |
+
Timestamp(year=2017, month=10, day=22, tz=timezone.utc),
|
| 734 |
+
Timestamp(datetime(2017, 10, 22), tzinfo=timezone.utc),
|
| 735 |
+
Timestamp(datetime(2017, 10, 22), tz="UTC"),
|
| 736 |
+
Timestamp(datetime(2017, 10, 22), tz=timezone.utc),
|
| 737 |
+
]
|
| 738 |
+
assert all(ts == stamps[0] for ts in stamps)
|
| 739 |
+
|
| 740 |
+
@pytest.mark.parametrize(
|
| 741 |
+
"result",
|
| 742 |
+
[
|
| 743 |
+
Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), nanosecond=1),
|
| 744 |
+
Timestamp(
|
| 745 |
+
year=2000,
|
| 746 |
+
month=1,
|
| 747 |
+
day=2,
|
| 748 |
+
hour=3,
|
| 749 |
+
minute=4,
|
| 750 |
+
second=5,
|
| 751 |
+
microsecond=6,
|
| 752 |
+
nanosecond=1,
|
| 753 |
+
),
|
| 754 |
+
Timestamp(
|
| 755 |
+
year=2000,
|
| 756 |
+
month=1,
|
| 757 |
+
day=2,
|
| 758 |
+
hour=3,
|
| 759 |
+
minute=4,
|
| 760 |
+
second=5,
|
| 761 |
+
microsecond=6,
|
| 762 |
+
nanosecond=1,
|
| 763 |
+
tz="UTC",
|
| 764 |
+
),
|
| 765 |
+
Timestamp(2000, 1, 2, 3, 4, 5, 6, None, nanosecond=1),
|
| 766 |
+
Timestamp(2000, 1, 2, 3, 4, 5, 6, tz=timezone.utc, nanosecond=1),
|
| 767 |
+
],
|
| 768 |
+
)
|
| 769 |
+
def test_constructor_nanosecond(self, result):
|
| 770 |
+
# GH 18898
|
| 771 |
+
# As of 2.0 (GH 49416), nanosecond should not be accepted positionally
|
| 772 |
+
expected = Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), tz=result.tz)
|
| 773 |
+
expected = expected + Timedelta(nanoseconds=1)
|
| 774 |
+
assert result == expected
|
| 775 |
+
|
| 776 |
+
@pytest.mark.parametrize("z", ["Z0", "Z00"])
|
| 777 |
+
def test_constructor_invalid_Z0_isostring(self, z):
|
| 778 |
+
# GH 8910
|
| 779 |
+
msg = f"Unknown datetime string format, unable to parse: 2014-11-02 01:00{z}"
|
| 780 |
+
with pytest.raises(ValueError, match=msg):
|
| 781 |
+
Timestamp(f"2014-11-02 01:00{z}")
|
| 782 |
+
|
| 783 |
+
def test_out_of_bounds_integer_value(self):
|
| 784 |
+
# GH#26651 check that we raise OutOfBoundsDatetime, not OverflowError
|
| 785 |
+
msg = str(Timestamp.max._value * 2)
|
| 786 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 787 |
+
Timestamp(Timestamp.max._value * 2)
|
| 788 |
+
msg = str(Timestamp.min._value * 2)
|
| 789 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 790 |
+
Timestamp(Timestamp.min._value * 2)
|
| 791 |
+
|
| 792 |
+
def test_out_of_bounds_value(self):
|
| 793 |
+
one_us = np.timedelta64(1, "ns").astype("timedelta64[us]")
|
| 794 |
+
|
| 795 |
+
# By definition we can't go out of bounds in [ns], so we
|
| 796 |
+
# convert the datetime64s to [us] so we can go out of bounds
|
| 797 |
+
min_ts_us = np.datetime64(Timestamp.min).astype("M8[us]") + one_us
|
| 798 |
+
max_ts_us = np.datetime64(Timestamp.max).astype("M8[us]")
|
| 799 |
+
|
| 800 |
+
# No error for the min/max datetimes
|
| 801 |
+
Timestamp(min_ts_us)
|
| 802 |
+
Timestamp(max_ts_us)
|
| 803 |
+
|
| 804 |
+
# We used to raise on these before supporting non-nano
|
| 805 |
+
us_val = NpyDatetimeUnit.NPY_FR_us.value
|
| 806 |
+
assert Timestamp(min_ts_us - one_us)._creso == us_val
|
| 807 |
+
assert Timestamp(max_ts_us + one_us)._creso == us_val
|
| 808 |
+
|
| 809 |
+
# https://github.com/numpy/numpy/issues/22346 for why
|
| 810 |
+
# we can't use the same construction as above with minute resolution
|
| 811 |
+
|
| 812 |
+
# too_low, too_high are the _just_ outside the range of M8[s]
|
| 813 |
+
too_low = np.datetime64("-292277022657-01-27T08:29", "m")
|
| 814 |
+
too_high = np.datetime64("292277026596-12-04T15:31", "m")
|
| 815 |
+
|
| 816 |
+
msg = "Out of bounds"
|
| 817 |
+
# One us less than the minimum is an error
|
| 818 |
+
with pytest.raises(ValueError, match=msg):
|
| 819 |
+
Timestamp(too_low)
|
| 820 |
+
|
| 821 |
+
# One us more than the maximum is an error
|
| 822 |
+
with pytest.raises(ValueError, match=msg):
|
| 823 |
+
Timestamp(too_high)
|
| 824 |
+
|
| 825 |
+
def test_out_of_bounds_string(self):
|
| 826 |
+
msg = "Cannot cast .* to unit='ns' without overflow"
|
| 827 |
+
with pytest.raises(ValueError, match=msg):
|
| 828 |
+
Timestamp("1676-01-01").as_unit("ns")
|
| 829 |
+
with pytest.raises(ValueError, match=msg):
|
| 830 |
+
Timestamp("2263-01-01").as_unit("ns")
|
| 831 |
+
|
| 832 |
+
ts = Timestamp("2263-01-01")
|
| 833 |
+
assert ts.unit == "us"
|
| 834 |
+
|
| 835 |
+
ts = Timestamp("1676-01-01")
|
| 836 |
+
assert ts.unit == "us"
|
| 837 |
+
|
| 838 |
+
def test_barely_out_of_bounds(self):
|
| 839 |
+
# GH#19529
|
| 840 |
+
# GH#19382 close enough to bounds that dropping nanos would result
|
| 841 |
+
# in an in-bounds datetime
|
| 842 |
+
msg = "Out of bounds nanosecond timestamp: 2262-04-11 23:47:16"
|
| 843 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 844 |
+
Timestamp("2262-04-11 23:47:16.854775808")
|
| 845 |
+
|
| 846 |
+
def test_bounds_with_different_units(self):
|
| 847 |
+
out_of_bounds_dates = ("1677-09-21", "2262-04-12")
|
| 848 |
+
|
| 849 |
+
time_units = ("D", "h", "m", "s", "ms", "us")
|
| 850 |
+
|
| 851 |
+
for date_string in out_of_bounds_dates:
|
| 852 |
+
for unit in time_units:
|
| 853 |
+
dt64 = np.datetime64(date_string, unit)
|
| 854 |
+
ts = Timestamp(dt64)
|
| 855 |
+
if unit in ["s", "ms", "us"]:
|
| 856 |
+
# We can preserve the input unit
|
| 857 |
+
assert ts._value == dt64.view("i8")
|
| 858 |
+
else:
|
| 859 |
+
# we chose the closest unit that we _do_ support
|
| 860 |
+
assert ts._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 861 |
+
|
| 862 |
+
# With more extreme cases, we can't even fit inside second resolution
|
| 863 |
+
info = np.iinfo(np.int64)
|
| 864 |
+
msg = "Out of bounds second timestamp:"
|
| 865 |
+
for value in [info.min + 1, info.max]:
|
| 866 |
+
for unit in ["D", "h", "m"]:
|
| 867 |
+
dt64 = np.datetime64(value, unit)
|
| 868 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 869 |
+
Timestamp(dt64)
|
| 870 |
+
|
| 871 |
+
in_bounds_dates = ("1677-09-23", "2262-04-11")
|
| 872 |
+
|
| 873 |
+
for date_string in in_bounds_dates:
|
| 874 |
+
for unit in time_units:
|
| 875 |
+
dt64 = np.datetime64(date_string, unit)
|
| 876 |
+
Timestamp(dt64)
|
| 877 |
+
|
| 878 |
+
@pytest.mark.parametrize("arg", ["001-01-01", "0001-01-01"])
|
| 879 |
+
def test_out_of_bounds_string_consistency(self, arg):
|
| 880 |
+
# GH 15829
|
| 881 |
+
msg = "Cannot cast 0001-01-01 00:00:00 to unit='ns' without overflow"
|
| 882 |
+
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
| 883 |
+
Timestamp(arg).as_unit("ns")
|
| 884 |
+
|
| 885 |
+
ts = Timestamp(arg)
|
| 886 |
+
assert ts.unit == "us"
|
| 887 |
+
assert ts.year == ts.month == ts.day == 1
|
| 888 |
+
|
| 889 |
+
def test_min_valid(self):
|
| 890 |
+
# Ensure that Timestamp.min is a valid Timestamp
|
| 891 |
+
Timestamp(Timestamp.min)
|
| 892 |
+
|
| 893 |
+
def test_max_valid(self):
|
| 894 |
+
# Ensure that Timestamp.max is a valid Timestamp
|
| 895 |
+
Timestamp(Timestamp.max)
|
| 896 |
+
|
| 897 |
+
@pytest.mark.parametrize("offset", ["+0300", "+0200"])
|
| 898 |
+
def test_construct_timestamp_near_dst(self, offset):
|
| 899 |
+
# GH 20854
|
| 900 |
+
expected = Timestamp(f"2016-10-30 03:00:00{offset}", tz="Europe/Helsinki")
|
| 901 |
+
result = Timestamp(expected).tz_convert("Europe/Helsinki")
|
| 902 |
+
assert result == expected
|
| 903 |
+
|
| 904 |
+
@pytest.mark.parametrize(
|
| 905 |
+
"arg", ["2013/01/01 00:00:00+09:00", "2013-01-01 00:00:00+09:00"]
|
| 906 |
+
)
|
| 907 |
+
def test_construct_with_different_string_format(self, arg):
|
| 908 |
+
# GH 12064
|
| 909 |
+
result = Timestamp(arg)
|
| 910 |
+
expected = Timestamp(datetime(2013, 1, 1), tz=timezone(timedelta(hours=9)))
|
| 911 |
+
assert result == expected
|
| 912 |
+
|
| 913 |
+
@pytest.mark.parametrize("box", [datetime, Timestamp])
|
| 914 |
+
def test_raise_tz_and_tzinfo_in_datetime_input(self, box):
|
| 915 |
+
# GH 23579
|
| 916 |
+
kwargs = {"year": 2018, "month": 1, "day": 1, "tzinfo": timezone.utc}
|
| 917 |
+
msg = "Cannot pass a datetime or Timestamp"
|
| 918 |
+
with pytest.raises(ValueError, match=msg):
|
| 919 |
+
Timestamp(box(**kwargs), tz="US/Pacific")
|
| 920 |
+
msg = "Cannot pass a datetime or Timestamp"
|
| 921 |
+
with pytest.raises(ValueError, match=msg):
|
| 922 |
+
Timestamp(box(**kwargs), tzinfo=zoneinfo.ZoneInfo("US/Pacific"))
|
| 923 |
+
|
| 924 |
+
def test_dont_convert_dateutil_utc_to_default_utc(self):
|
| 925 |
+
result = Timestamp(datetime(2018, 1, 1), tz=tzutc())
|
| 926 |
+
expected = Timestamp(datetime(2018, 1, 1)).tz_localize(tzutc())
|
| 927 |
+
assert result == expected
|
| 928 |
+
|
| 929 |
+
def test_constructor_subclassed_datetime(self):
|
| 930 |
+
# GH 25851
|
| 931 |
+
# ensure that subclassed datetime works for
|
| 932 |
+
# Timestamp creation
|
| 933 |
+
class SubDatetime(datetime):
|
| 934 |
+
pass
|
| 935 |
+
|
| 936 |
+
data = SubDatetime(2000, 1, 1)
|
| 937 |
+
result = Timestamp(data)
|
| 938 |
+
expected = Timestamp(2000, 1, 1)
|
| 939 |
+
assert result == expected
|
| 940 |
+
|
| 941 |
+
def test_timestamp_constructor_tz_utc(self):
|
| 942 |
+
utc_stamp = Timestamp("3/11/2012 05:00", tz="utc")
|
| 943 |
+
assert utc_stamp.tzinfo is timezone.utc
|
| 944 |
+
assert utc_stamp.hour == 5
|
| 945 |
+
|
| 946 |
+
utc_stamp = Timestamp("3/11/2012 05:00").tz_localize("utc")
|
| 947 |
+
assert utc_stamp.hour == 5
|
| 948 |
+
|
| 949 |
+
def test_timestamp_to_datetime_tzoffset(self):
|
| 950 |
+
tzinfo = tzoffset(None, 7200)
|
| 951 |
+
expected = Timestamp("3/11/2012 04:00", tz=tzinfo)
|
| 952 |
+
result = Timestamp(expected.to_pydatetime())
|
| 953 |
+
assert expected == result
|
| 954 |
+
|
| 955 |
+
def test_timestamp_constructor_near_dst_boundary(self):
|
| 956 |
+
# GH#11481 & GH#15777
|
| 957 |
+
# Naive string timestamps were being localized incorrectly
|
| 958 |
+
# with tz_convert_from_utc_single instead of tz_localize_to_utc
|
| 959 |
+
|
| 960 |
+
for tz in ["Europe/Brussels", "Europe/Prague"]:
|
| 961 |
+
result = Timestamp("2015-10-25 01:00", tz=tz)
|
| 962 |
+
expected = Timestamp("2015-10-25 01:00").tz_localize(tz)
|
| 963 |
+
assert result == expected
|
| 964 |
+
|
| 965 |
+
msg = "Cannot infer dst time from 2015-10-25 02:00:00"
|
| 966 |
+
with pytest.raises(ValueError, match=msg):
|
| 967 |
+
Timestamp("2015-10-25 02:00", tz=tz)
|
| 968 |
+
|
| 969 |
+
result = Timestamp("2017-03-26 01:00", tz="Europe/Paris")
|
| 970 |
+
expected = Timestamp("2017-03-26 01:00").tz_localize("Europe/Paris")
|
| 971 |
+
assert result == expected
|
| 972 |
+
|
| 973 |
+
msg = "2017-03-26 02:00"
|
| 974 |
+
with pytest.raises(ValueError, match=msg):
|
| 975 |
+
Timestamp("2017-03-26 02:00", tz="Europe/Paris")
|
| 976 |
+
|
| 977 |
+
# GH#11708
|
| 978 |
+
naive = Timestamp("2015-11-18 10:00:00")
|
| 979 |
+
result = naive.tz_localize("UTC").tz_convert("Asia/Kolkata")
|
| 980 |
+
expected = Timestamp("2015-11-18 15:30:00+0530", tz="Asia/Kolkata")
|
| 981 |
+
assert result == expected
|
| 982 |
+
|
| 983 |
+
# GH#15823
|
| 984 |
+
result = Timestamp("2017-03-26 00:00", tz="Europe/Paris")
|
| 985 |
+
expected = Timestamp("2017-03-26 00:00:00+0100", tz="Europe/Paris")
|
| 986 |
+
assert result == expected
|
| 987 |
+
|
| 988 |
+
result = Timestamp("2017-03-26 01:00", tz="Europe/Paris")
|
| 989 |
+
expected = Timestamp("2017-03-26 01:00:00+0100", tz="Europe/Paris")
|
| 990 |
+
assert result == expected
|
| 991 |
+
|
| 992 |
+
msg = "2017-03-26 02:00"
|
| 993 |
+
with pytest.raises(ValueError, match=msg):
|
| 994 |
+
Timestamp("2017-03-26 02:00", tz="Europe/Paris")
|
| 995 |
+
|
| 996 |
+
result = Timestamp("2017-03-26 02:00:00+0100", tz="Europe/Paris")
|
| 997 |
+
naive = Timestamp(result.as_unit("ns")._value)
|
| 998 |
+
expected = naive.tz_localize("UTC").tz_convert("Europe/Paris")
|
| 999 |
+
assert result == expected
|
| 1000 |
+
|
| 1001 |
+
result = Timestamp("2017-03-26 03:00", tz="Europe/Paris")
|
| 1002 |
+
expected = Timestamp("2017-03-26 03:00:00+0200", tz="Europe/Paris")
|
| 1003 |
+
assert result == expected
|
| 1004 |
+
|
| 1005 |
+
@pytest.mark.parametrize(
|
| 1006 |
+
"tz",
|
| 1007 |
+
[
|
| 1008 |
+
"pytz/US/Eastern",
|
| 1009 |
+
gettz("US/Eastern"),
|
| 1010 |
+
"US/Eastern",
|
| 1011 |
+
"dateutil/US/Eastern",
|
| 1012 |
+
],
|
| 1013 |
+
)
|
| 1014 |
+
def test_timestamp_constructed_by_date_and_tz(self, tz):
|
| 1015 |
+
# GH#2993, Timestamp cannot be constructed by datetime.date
|
| 1016 |
+
# and tz correctly
|
| 1017 |
+
if isinstance(tz, str) and tz.startswith("pytz/"):
|
| 1018 |
+
pytz = pytest.importorskip("pytz")
|
| 1019 |
+
tz = pytz.timezone(tz.removeprefix("pytz/"))
|
| 1020 |
+
result = Timestamp(date(2012, 3, 11), tz=tz)
|
| 1021 |
+
|
| 1022 |
+
expected = Timestamp("3/11/2012", tz=tz)
|
| 1023 |
+
assert result.hour == expected.hour
|
| 1024 |
+
assert result == expected
|
| 1025 |
+
|
| 1026 |
+
def test_explicit_tz_none(self):
|
| 1027 |
+
# GH#48688
|
| 1028 |
+
msg = "Passed data is timezone-aware, incompatible with 'tz=None'"
|
| 1029 |
+
with pytest.raises(ValueError, match=msg):
|
| 1030 |
+
Timestamp(datetime(2022, 1, 1, tzinfo=timezone.utc), tz=None)
|
| 1031 |
+
|
| 1032 |
+
with pytest.raises(ValueError, match=msg):
|
| 1033 |
+
Timestamp("2022-01-01 00:00:00", tzinfo=timezone.utc, tz=None)
|
| 1034 |
+
|
| 1035 |
+
with pytest.raises(ValueError, match=msg):
|
| 1036 |
+
Timestamp("2022-01-01 00:00:00-0400", tz=None)
|
| 1037 |
+
|
| 1038 |
+
|
| 1039 |
+
def test_constructor_ambiguous_dst():
|
| 1040 |
+
# GH 24329
|
| 1041 |
+
# Make sure that calling Timestamp constructor
|
| 1042 |
+
# on Timestamp created from ambiguous time
|
| 1043 |
+
# doesn't change Timestamp.value
|
| 1044 |
+
ts = Timestamp(1382835600000000000, tz="dateutil/Europe/London")
|
| 1045 |
+
expected = ts._value
|
| 1046 |
+
result = Timestamp(ts)._value
|
| 1047 |
+
assert result == expected
|
| 1048 |
+
|
| 1049 |
+
|
| 1050 |
+
@pytest.mark.parametrize("epoch", [1552211999999999872, 1552211999999999999])
|
| 1051 |
+
def test_constructor_before_dst_switch(epoch):
|
| 1052 |
+
# GH 31043
|
| 1053 |
+
# Make sure that calling Timestamp constructor
|
| 1054 |
+
# on time just before DST switch doesn't lead to
|
| 1055 |
+
# nonexistent time or value change
|
| 1056 |
+
ts = Timestamp(epoch, tz="dateutil/America/Los_Angeles")
|
| 1057 |
+
result = ts.tz.dst(ts)
|
| 1058 |
+
expected = timedelta(seconds=0)
|
| 1059 |
+
assert Timestamp(ts)._value == epoch
|
| 1060 |
+
assert result == expected
|
| 1061 |
+
|
| 1062 |
+
|
| 1063 |
+
def test_timestamp_constructor_identity():
|
| 1064 |
+
# Test for #30543
|
| 1065 |
+
expected = Timestamp("2017-01-01T12")
|
| 1066 |
+
result = Timestamp(expected)
|
| 1067 |
+
assert result is expected
|
| 1068 |
+
|
| 1069 |
+
|
| 1070 |
+
@pytest.mark.parametrize("nano", [-1, 1000])
|
| 1071 |
+
def test_timestamp_nano_range(nano):
|
| 1072 |
+
# GH 48255
|
| 1073 |
+
with pytest.raises(ValueError, match="nanosecond must be in 0..999"):
|
| 1074 |
+
Timestamp(year=2022, month=1, day=1, nanosecond=nano)
|
| 1075 |
+
|
| 1076 |
+
|
| 1077 |
+
def test_non_nano_value():
|
| 1078 |
+
# https://github.com/pandas-dev/pandas/issues/49076
|
| 1079 |
+
msg = "The 'unit' keyword is only used when"
|
| 1080 |
+
with tm.assert_produces_warning(UserWarning, match=msg):
|
| 1081 |
+
result = Timestamp("1800-01-01", unit="s").value
|
| 1082 |
+
# `.value` shows nanoseconds, even though unit is 's'
|
| 1083 |
+
assert result == -5364662400000000000
|
| 1084 |
+
|
| 1085 |
+
# out-of-nanoseconds-bounds `.value` raises informative message
|
| 1086 |
+
msg = (
|
| 1087 |
+
r"Cannot convert Timestamp to nanoseconds without overflow. "
|
| 1088 |
+
r"Use `.asm8.view\('i8'\)` to cast represent Timestamp in its "
|
| 1089 |
+
r"own unit \(here, us\).$"
|
| 1090 |
+
)
|
| 1091 |
+
ts = Timestamp("0300-01-01")
|
| 1092 |
+
assert ts.unit == "us"
|
| 1093 |
+
with pytest.raises(OverflowError, match=msg):
|
| 1094 |
+
ts.value
|
| 1095 |
+
# check that the suggested workaround actually works
|
| 1096 |
+
result = ts.asm8.view("i8")
|
| 1097 |
+
assert result == -52_700_112_000 * 10**6
|
| 1098 |
+
|
| 1099 |
+
|
| 1100 |
+
@pytest.mark.parametrize(
|
| 1101 |
+
"na_value", [None, np.nan, np.datetime64("NaT", "ns"), NaT, NA]
|
| 1102 |
+
)
|
| 1103 |
+
def test_timestamp_constructor_na_value(na_value):
|
| 1104 |
+
# GH45481
|
| 1105 |
+
result = Timestamp(na_value)
|
| 1106 |
+
expected = NaT
|
| 1107 |
+
assert result is expected
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_formats.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import (
|
| 2 |
+
datetime,
|
| 3 |
+
timezone,
|
| 4 |
+
)
|
| 5 |
+
import pprint
|
| 6 |
+
|
| 7 |
+
import dateutil.tz
|
| 8 |
+
import pytest
|
| 9 |
+
|
| 10 |
+
from pandas.compat import WASM
|
| 11 |
+
|
| 12 |
+
from pandas import Timestamp
|
| 13 |
+
|
| 14 |
+
ts_no_ns = Timestamp(
|
| 15 |
+
year=2019,
|
| 16 |
+
month=5,
|
| 17 |
+
day=18,
|
| 18 |
+
hour=15,
|
| 19 |
+
minute=17,
|
| 20 |
+
second=8,
|
| 21 |
+
microsecond=132263,
|
| 22 |
+
)
|
| 23 |
+
ts_no_ns_year1 = Timestamp(
|
| 24 |
+
year=1,
|
| 25 |
+
month=5,
|
| 26 |
+
day=18,
|
| 27 |
+
hour=15,
|
| 28 |
+
minute=17,
|
| 29 |
+
second=8,
|
| 30 |
+
microsecond=132263,
|
| 31 |
+
)
|
| 32 |
+
ts_ns = Timestamp(
|
| 33 |
+
year=2019,
|
| 34 |
+
month=5,
|
| 35 |
+
day=18,
|
| 36 |
+
hour=15,
|
| 37 |
+
minute=17,
|
| 38 |
+
second=8,
|
| 39 |
+
microsecond=132263,
|
| 40 |
+
nanosecond=123,
|
| 41 |
+
)
|
| 42 |
+
ts_ns_tz = Timestamp(
|
| 43 |
+
year=2019,
|
| 44 |
+
month=5,
|
| 45 |
+
day=18,
|
| 46 |
+
hour=15,
|
| 47 |
+
minute=17,
|
| 48 |
+
second=8,
|
| 49 |
+
microsecond=132263,
|
| 50 |
+
nanosecond=123,
|
| 51 |
+
tz="UTC",
|
| 52 |
+
)
|
| 53 |
+
ts_no_us = Timestamp(
|
| 54 |
+
year=2019,
|
| 55 |
+
month=5,
|
| 56 |
+
day=18,
|
| 57 |
+
hour=15,
|
| 58 |
+
minute=17,
|
| 59 |
+
second=8,
|
| 60 |
+
microsecond=0,
|
| 61 |
+
nanosecond=123,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@pytest.mark.parametrize(
|
| 66 |
+
"ts, timespec, expected_iso",
|
| 67 |
+
[
|
| 68 |
+
(ts_no_ns, "auto", "2019-05-18T15:17:08.132263"),
|
| 69 |
+
(ts_no_ns, "seconds", "2019-05-18T15:17:08"),
|
| 70 |
+
(ts_no_ns, "nanoseconds", "2019-05-18T15:17:08.132263000"),
|
| 71 |
+
(ts_no_ns_year1, "seconds", "0001-05-18T15:17:08"),
|
| 72 |
+
(ts_no_ns_year1, "nanoseconds", "0001-05-18T15:17:08.132263000"),
|
| 73 |
+
(ts_ns, "auto", "2019-05-18T15:17:08.132263123"),
|
| 74 |
+
(ts_ns, "hours", "2019-05-18T15"),
|
| 75 |
+
(ts_ns, "minutes", "2019-05-18T15:17"),
|
| 76 |
+
(ts_ns, "seconds", "2019-05-18T15:17:08"),
|
| 77 |
+
(ts_ns, "milliseconds", "2019-05-18T15:17:08.132"),
|
| 78 |
+
(ts_ns, "microseconds", "2019-05-18T15:17:08.132263"),
|
| 79 |
+
(ts_ns, "nanoseconds", "2019-05-18T15:17:08.132263123"),
|
| 80 |
+
(ts_ns_tz, "auto", "2019-05-18T15:17:08.132263123+00:00"),
|
| 81 |
+
(ts_ns_tz, "hours", "2019-05-18T15+00:00"),
|
| 82 |
+
(ts_ns_tz, "minutes", "2019-05-18T15:17+00:00"),
|
| 83 |
+
(ts_ns_tz, "seconds", "2019-05-18T15:17:08+00:00"),
|
| 84 |
+
(ts_ns_tz, "milliseconds", "2019-05-18T15:17:08.132+00:00"),
|
| 85 |
+
(ts_ns_tz, "microseconds", "2019-05-18T15:17:08.132263+00:00"),
|
| 86 |
+
(ts_ns_tz, "nanoseconds", "2019-05-18T15:17:08.132263123+00:00"),
|
| 87 |
+
(ts_no_us, "auto", "2019-05-18T15:17:08.000000123"),
|
| 88 |
+
],
|
| 89 |
+
)
|
| 90 |
+
def test_isoformat(ts, timespec, expected_iso):
|
| 91 |
+
assert ts.isoformat(timespec=timespec) == expected_iso
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class TestTimestampRendering:
|
| 95 |
+
@pytest.mark.parametrize(
|
| 96 |
+
"tz", ["UTC", "Asia/Tokyo", "US/Eastern", "dateutil/America/Los_Angeles"]
|
| 97 |
+
)
|
| 98 |
+
@pytest.mark.parametrize("freq", ["D", "M", "S", "N"])
|
| 99 |
+
@pytest.mark.parametrize(
|
| 100 |
+
"date", ["2014-03-07", "2014-01-01 09:00", "2014-01-01 00:00:00.000000001"]
|
| 101 |
+
)
|
| 102 |
+
@pytest.mark.skipif(WASM, reason="tzset is not available on WASM")
|
| 103 |
+
def test_repr(self, date, freq, tz):
|
| 104 |
+
# avoid to match with timezone name
|
| 105 |
+
freq_repr = f"'{freq}'"
|
| 106 |
+
if tz.startswith("dateutil"):
|
| 107 |
+
tz_repr = tz.replace("dateutil", "")
|
| 108 |
+
else:
|
| 109 |
+
tz_repr = tz
|
| 110 |
+
|
| 111 |
+
date_only = Timestamp(date)
|
| 112 |
+
assert date in repr(date_only)
|
| 113 |
+
assert tz_repr not in repr(date_only)
|
| 114 |
+
assert freq_repr not in repr(date_only)
|
| 115 |
+
assert date_only == eval(repr(date_only))
|
| 116 |
+
|
| 117 |
+
date_tz = Timestamp(date, tz=tz)
|
| 118 |
+
assert date in repr(date_tz)
|
| 119 |
+
assert tz_repr in repr(date_tz)
|
| 120 |
+
assert freq_repr not in repr(date_tz)
|
| 121 |
+
assert date_tz == eval(repr(date_tz))
|
| 122 |
+
|
| 123 |
+
def test_repr_utcoffset(self):
|
| 124 |
+
# This can cause the tz field to be populated, but it's redundant to
|
| 125 |
+
# include this information in the date-string.
|
| 126 |
+
date_with_utc_offset = Timestamp("2014-03-13 00:00:00-0400")
|
| 127 |
+
assert "2014-03-13 00:00:00-0400" in repr(date_with_utc_offset)
|
| 128 |
+
assert "tzoffset" not in repr(date_with_utc_offset)
|
| 129 |
+
assert "UTC-04:00" in repr(date_with_utc_offset)
|
| 130 |
+
expr = repr(date_with_utc_offset)
|
| 131 |
+
assert date_with_utc_offset == eval(expr)
|
| 132 |
+
|
| 133 |
+
def test_timestamp_repr_pre1900(self):
|
| 134 |
+
# pre-1900
|
| 135 |
+
stamp = Timestamp("1850-01-01", tz="US/Eastern")
|
| 136 |
+
repr(stamp)
|
| 137 |
+
|
| 138 |
+
iso8601 = "1850-01-01 01:23:45.012345"
|
| 139 |
+
stamp = Timestamp(iso8601, tz="US/Eastern")
|
| 140 |
+
result = repr(stamp)
|
| 141 |
+
assert iso8601 in result
|
| 142 |
+
|
| 143 |
+
def test_pprint(self):
|
| 144 |
+
# GH#12622
|
| 145 |
+
nested_obj = {"foo": 1, "bar": [{"w": {"a": Timestamp("2011-01-01")}}] * 10}
|
| 146 |
+
result = pprint.pformat(nested_obj, width=50)
|
| 147 |
+
expected = r"""{'bar': [{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 148 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 149 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 150 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 151 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 152 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 153 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 154 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 155 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}},
|
| 156 |
+
{'w': {'a': Timestamp('2011-01-01 00:00:00')}}],
|
| 157 |
+
'foo': 1}"""
|
| 158 |
+
assert result == expected
|
| 159 |
+
|
| 160 |
+
def test_to_timestamp_repr_is_code(self):
|
| 161 |
+
zs = [
|
| 162 |
+
Timestamp("99-04-17 00:00:00", tz="UTC"),
|
| 163 |
+
Timestamp("2001-04-17 00:00:00", tz="UTC"),
|
| 164 |
+
Timestamp("2001-04-17 00:00:00", tz="America/Los_Angeles"),
|
| 165 |
+
Timestamp("2001-04-17 00:00:00", tz=None),
|
| 166 |
+
]
|
| 167 |
+
for z in zs:
|
| 168 |
+
assert eval(repr(z)) == z
|
| 169 |
+
|
| 170 |
+
def test_repr_matches_pydatetime_no_tz(self):
|
| 171 |
+
dt_date = datetime(2013, 1, 2)
|
| 172 |
+
assert str(dt_date) == str(Timestamp(dt_date))
|
| 173 |
+
|
| 174 |
+
dt_datetime = datetime(2013, 1, 2, 12, 1, 3)
|
| 175 |
+
assert str(dt_datetime) == str(Timestamp(dt_datetime))
|
| 176 |
+
|
| 177 |
+
dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45)
|
| 178 |
+
assert str(dt_datetime_us) == str(Timestamp(dt_datetime_us))
|
| 179 |
+
|
| 180 |
+
ts_nanos_only = Timestamp(200)
|
| 181 |
+
assert str(ts_nanos_only) == "1970-01-01 00:00:00.000000200"
|
| 182 |
+
|
| 183 |
+
ts_nanos_micros = Timestamp(1200)
|
| 184 |
+
assert str(ts_nanos_micros) == "1970-01-01 00:00:00.000001200"
|
| 185 |
+
|
| 186 |
+
def test_repr_matches_pydatetime_tz_stdlib(self):
|
| 187 |
+
dt_date = datetime(2013, 1, 2, tzinfo=timezone.utc)
|
| 188 |
+
assert str(dt_date) == str(Timestamp(dt_date))
|
| 189 |
+
|
| 190 |
+
dt_datetime = datetime(2013, 1, 2, 12, 1, 3, tzinfo=timezone.utc)
|
| 191 |
+
assert str(dt_datetime) == str(Timestamp(dt_datetime))
|
| 192 |
+
|
| 193 |
+
dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45, tzinfo=timezone.utc)
|
| 194 |
+
assert str(dt_datetime_us) == str(Timestamp(dt_datetime_us))
|
| 195 |
+
|
| 196 |
+
def test_repr_matches_pydatetime_tz_dateutil(self):
|
| 197 |
+
utc = dateutil.tz.tzutc()
|
| 198 |
+
|
| 199 |
+
dt_date = datetime(2013, 1, 2, tzinfo=utc)
|
| 200 |
+
assert str(dt_date) == str(Timestamp(dt_date))
|
| 201 |
+
|
| 202 |
+
dt_datetime = datetime(2013, 1, 2, 12, 1, 3, tzinfo=utc)
|
| 203 |
+
assert str(dt_datetime) == str(Timestamp(dt_datetime))
|
| 204 |
+
|
| 205 |
+
dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45, tzinfo=utc)
|
| 206 |
+
assert str(dt_datetime_us) == str(Timestamp(dt_datetime_us))
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timestamp.py
ADDED
|
@@ -0,0 +1,938 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""test the scalar Timestamp"""
|
| 2 |
+
|
| 3 |
+
import calendar
|
| 4 |
+
from datetime import (
|
| 5 |
+
datetime,
|
| 6 |
+
timedelta,
|
| 7 |
+
timezone,
|
| 8 |
+
)
|
| 9 |
+
import locale
|
| 10 |
+
import time
|
| 11 |
+
import unicodedata
|
| 12 |
+
import zoneinfo
|
| 13 |
+
|
| 14 |
+
from dateutil.tz import (
|
| 15 |
+
tzlocal,
|
| 16 |
+
tzutc,
|
| 17 |
+
)
|
| 18 |
+
from hypothesis import (
|
| 19 |
+
given,
|
| 20 |
+
strategies as st,
|
| 21 |
+
)
|
| 22 |
+
import numpy as np
|
| 23 |
+
import pytest
|
| 24 |
+
|
| 25 |
+
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
| 26 |
+
from pandas._libs.tslibs.timezones import (
|
| 27 |
+
dateutil_gettz as gettz,
|
| 28 |
+
get_timezone,
|
| 29 |
+
maybe_get_tz,
|
| 30 |
+
tz_compare,
|
| 31 |
+
)
|
| 32 |
+
from pandas.compat import IS64
|
| 33 |
+
from pandas.compat.numpy import (
|
| 34 |
+
is_numpy_dev,
|
| 35 |
+
np_version_gt2_5,
|
| 36 |
+
)
|
| 37 |
+
from pandas.errors import Pandas4Warning
|
| 38 |
+
|
| 39 |
+
from pandas import (
|
| 40 |
+
NaT,
|
| 41 |
+
Timedelta,
|
| 42 |
+
Timestamp,
|
| 43 |
+
)
|
| 44 |
+
import pandas._testing as tm
|
| 45 |
+
|
| 46 |
+
from pandas.tseries import offsets
|
| 47 |
+
from pandas.tseries.frequencies import to_offset
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class TestTimestampProperties:
|
| 51 |
+
def test_properties_business(self):
|
| 52 |
+
freq = to_offset("B")
|
| 53 |
+
|
| 54 |
+
ts = Timestamp("2017-10-01")
|
| 55 |
+
assert ts.dayofweek == 6
|
| 56 |
+
assert ts.day_of_week == 6
|
| 57 |
+
assert ts.is_month_start # not a weekday
|
| 58 |
+
assert not freq.is_month_start(ts)
|
| 59 |
+
assert freq.is_month_start(ts + Timedelta(days=1))
|
| 60 |
+
assert not freq.is_quarter_start(ts)
|
| 61 |
+
assert freq.is_quarter_start(ts + Timedelta(days=1))
|
| 62 |
+
|
| 63 |
+
ts = Timestamp("2017-09-30")
|
| 64 |
+
assert ts.dayofweek == 5
|
| 65 |
+
assert ts.day_of_week == 5
|
| 66 |
+
assert ts.is_month_end
|
| 67 |
+
assert not freq.is_month_end(ts)
|
| 68 |
+
assert freq.is_month_end(ts - Timedelta(days=1))
|
| 69 |
+
assert ts.is_quarter_end
|
| 70 |
+
assert not freq.is_quarter_end(ts)
|
| 71 |
+
assert freq.is_quarter_end(ts - Timedelta(days=1))
|
| 72 |
+
|
| 73 |
+
@pytest.mark.parametrize(
|
| 74 |
+
"attr, expected",
|
| 75 |
+
[
|
| 76 |
+
["year", 2014],
|
| 77 |
+
["month", 12],
|
| 78 |
+
["day", 31],
|
| 79 |
+
["hour", 23],
|
| 80 |
+
["minute", 59],
|
| 81 |
+
["second", 0],
|
| 82 |
+
["microsecond", 0],
|
| 83 |
+
["nanosecond", 0],
|
| 84 |
+
["dayofweek", 2],
|
| 85 |
+
["day_of_week", 2],
|
| 86 |
+
["quarter", 4],
|
| 87 |
+
["dayofyear", 365],
|
| 88 |
+
["day_of_year", 365],
|
| 89 |
+
["week", 1],
|
| 90 |
+
["daysinmonth", 31],
|
| 91 |
+
],
|
| 92 |
+
)
|
| 93 |
+
@pytest.mark.parametrize("tz", [None, "US/Eastern"])
|
| 94 |
+
def test_fields(self, attr, expected, tz):
|
| 95 |
+
# GH 10050
|
| 96 |
+
# GH 13303
|
| 97 |
+
ts = Timestamp("2014-12-31 23:59:00", tz=tz)
|
| 98 |
+
result = getattr(ts, attr)
|
| 99 |
+
# that we are int like
|
| 100 |
+
assert isinstance(result, int)
|
| 101 |
+
assert result == expected
|
| 102 |
+
|
| 103 |
+
@pytest.mark.parametrize("tz", [None, "US/Eastern"])
|
| 104 |
+
def test_millisecond_raises(self, tz):
|
| 105 |
+
ts = Timestamp("2014-12-31 23:59:00", tz=tz)
|
| 106 |
+
msg = "'Timestamp' object has no attribute 'millisecond'"
|
| 107 |
+
with pytest.raises(AttributeError, match=msg):
|
| 108 |
+
ts.millisecond
|
| 109 |
+
|
| 110 |
+
@pytest.mark.parametrize(
|
| 111 |
+
"start", ["is_month_start", "is_quarter_start", "is_year_start"]
|
| 112 |
+
)
|
| 113 |
+
@pytest.mark.parametrize("tz", [None, "US/Eastern"])
|
| 114 |
+
def test_is_start(self, start, tz):
|
| 115 |
+
ts = Timestamp("2014-01-01 00:00:00", tz=tz)
|
| 116 |
+
assert getattr(ts, start)
|
| 117 |
+
|
| 118 |
+
@pytest.mark.parametrize("end", ["is_month_end", "is_year_end", "is_quarter_end"])
|
| 119 |
+
@pytest.mark.parametrize("tz", [None, "US/Eastern"])
|
| 120 |
+
def test_is_end(self, end, tz):
|
| 121 |
+
ts = Timestamp("2014-12-31 23:59:59", tz=tz)
|
| 122 |
+
assert getattr(ts, end)
|
| 123 |
+
|
| 124 |
+
# GH 12806
|
| 125 |
+
@pytest.mark.parametrize("tz", [None, "EST"])
|
| 126 |
+
@pytest.mark.parametrize(
|
| 127 |
+
"time_locale",
|
| 128 |
+
[None, *tm.get_locales()],
|
| 129 |
+
)
|
| 130 |
+
def test_names(self, tz, time_locale):
|
| 131 |
+
# GH 17354
|
| 132 |
+
# Test .day_name(), .month_name
|
| 133 |
+
data = Timestamp("2017-08-28 23:00:00", tz=tz)
|
| 134 |
+
if time_locale is None:
|
| 135 |
+
expected_day = "Monday"
|
| 136 |
+
expected_month = "August"
|
| 137 |
+
else:
|
| 138 |
+
with tm.set_locale(time_locale, locale.LC_TIME):
|
| 139 |
+
expected_day = calendar.day_name[0].capitalize()
|
| 140 |
+
expected_month = calendar.month_name[8].capitalize()
|
| 141 |
+
|
| 142 |
+
result_day = data.day_name(time_locale)
|
| 143 |
+
result_month = data.month_name(time_locale)
|
| 144 |
+
|
| 145 |
+
# Work around https://github.com/pandas-dev/pandas/issues/22342
|
| 146 |
+
# different normalizations
|
| 147 |
+
expected_day = unicodedata.normalize("NFD", expected_day)
|
| 148 |
+
expected_month = unicodedata.normalize("NFD", expected_month)
|
| 149 |
+
|
| 150 |
+
result_day = unicodedata.normalize("NFD", result_day)
|
| 151 |
+
result_month = unicodedata.normalize("NFD", result_month)
|
| 152 |
+
|
| 153 |
+
assert result_day == expected_day
|
| 154 |
+
assert result_month == expected_month
|
| 155 |
+
|
| 156 |
+
# Test NaT
|
| 157 |
+
nan_ts = Timestamp(NaT)
|
| 158 |
+
assert np.isnan(nan_ts.day_name(time_locale))
|
| 159 |
+
assert np.isnan(nan_ts.month_name(time_locale))
|
| 160 |
+
|
| 161 |
+
def test_is_leap_year(self, tz_naive_fixture):
|
| 162 |
+
tz = tz_naive_fixture
|
| 163 |
+
if not IS64 and tz == tzlocal():
|
| 164 |
+
# https://github.com/dateutil/dateutil/issues/197
|
| 165 |
+
pytest.skip(
|
| 166 |
+
"tzlocal() on a 32 bit platform causes internal overflow errors"
|
| 167 |
+
)
|
| 168 |
+
# GH 13727
|
| 169 |
+
dt = Timestamp("2000-01-01 00:00:00", tz=tz)
|
| 170 |
+
assert dt.is_leap_year
|
| 171 |
+
assert isinstance(dt.is_leap_year, bool)
|
| 172 |
+
|
| 173 |
+
dt = Timestamp("1999-01-01 00:00:00", tz=tz)
|
| 174 |
+
assert not dt.is_leap_year
|
| 175 |
+
|
| 176 |
+
dt = Timestamp("2004-01-01 00:00:00", tz=tz)
|
| 177 |
+
assert dt.is_leap_year
|
| 178 |
+
|
| 179 |
+
dt = Timestamp("2100-01-01 00:00:00", tz=tz)
|
| 180 |
+
assert not dt.is_leap_year
|
| 181 |
+
|
| 182 |
+
def test_woy_boundary(self):
|
| 183 |
+
# make sure weeks at year boundaries are correct
|
| 184 |
+
d = datetime(2013, 12, 31)
|
| 185 |
+
result = Timestamp(d).week
|
| 186 |
+
expected = 1 # ISO standard
|
| 187 |
+
assert result == expected
|
| 188 |
+
|
| 189 |
+
d = datetime(2008, 12, 28)
|
| 190 |
+
result = Timestamp(d).week
|
| 191 |
+
expected = 52 # ISO standard
|
| 192 |
+
assert result == expected
|
| 193 |
+
|
| 194 |
+
d = datetime(2009, 12, 31)
|
| 195 |
+
result = Timestamp(d).week
|
| 196 |
+
expected = 53 # ISO standard
|
| 197 |
+
assert result == expected
|
| 198 |
+
|
| 199 |
+
d = datetime(2010, 1, 1)
|
| 200 |
+
result = Timestamp(d).week
|
| 201 |
+
expected = 53 # ISO standard
|
| 202 |
+
assert result == expected
|
| 203 |
+
|
| 204 |
+
d = datetime(2010, 1, 3)
|
| 205 |
+
result = Timestamp(d).week
|
| 206 |
+
expected = 53 # ISO standard
|
| 207 |
+
assert result == expected
|
| 208 |
+
|
| 209 |
+
result = np.array(
|
| 210 |
+
[
|
| 211 |
+
Timestamp(datetime(*args)).week
|
| 212 |
+
for args in [(2000, 1, 1), (2000, 1, 2), (2005, 1, 1), (2005, 1, 2)]
|
| 213 |
+
]
|
| 214 |
+
)
|
| 215 |
+
assert (result == [52, 52, 53, 53]).all()
|
| 216 |
+
|
| 217 |
+
def test_resolution(self):
|
| 218 |
+
# GH#21336, GH#21365
|
| 219 |
+
dt = Timestamp("2100-01-01 00:00:00.000000000")
|
| 220 |
+
assert dt.resolution == Timedelta(nanoseconds=1)
|
| 221 |
+
|
| 222 |
+
# Check that the attribute is available on the class, mirroring
|
| 223 |
+
# the stdlib datetime behavior
|
| 224 |
+
assert Timestamp.resolution == Timedelta(nanoseconds=1)
|
| 225 |
+
|
| 226 |
+
assert dt.as_unit("us").resolution == Timedelta(microseconds=1)
|
| 227 |
+
assert dt.as_unit("ms").resolution == Timedelta(milliseconds=1)
|
| 228 |
+
assert dt.as_unit("s").resolution == Timedelta(seconds=1)
|
| 229 |
+
|
| 230 |
+
@pytest.mark.parametrize(
|
| 231 |
+
"date_string, expected",
|
| 232 |
+
[
|
| 233 |
+
("0000-2-29", 1),
|
| 234 |
+
("0000-3-1", 2),
|
| 235 |
+
("1582-10-14", 3),
|
| 236 |
+
("-0040-1-1", 4),
|
| 237 |
+
("2023-06-18", 6),
|
| 238 |
+
],
|
| 239 |
+
)
|
| 240 |
+
def test_dow_historic(self, date_string, expected):
|
| 241 |
+
# GH 53738
|
| 242 |
+
ts = Timestamp(date_string)
|
| 243 |
+
dow = ts.weekday()
|
| 244 |
+
assert dow == expected
|
| 245 |
+
|
| 246 |
+
@pytest.mark.slow
|
| 247 |
+
@given(
|
| 248 |
+
ts=st.datetimes(),
|
| 249 |
+
sign=st.sampled_from(["-", ""]),
|
| 250 |
+
)
|
| 251 |
+
def test_dow_parametric(self, ts, sign):
|
| 252 |
+
# GH 53738
|
| 253 |
+
ts = (
|
| 254 |
+
f"{sign}{str(ts.year).zfill(4)}"
|
| 255 |
+
f"-{str(ts.month).zfill(2)}"
|
| 256 |
+
f"-{str(ts.day).zfill(2)}"
|
| 257 |
+
)
|
| 258 |
+
result = Timestamp(ts).weekday()
|
| 259 |
+
expected = (
|
| 260 |
+
(np.datetime64(ts) - np.datetime64("1970-01-01")).astype("int64") - 4
|
| 261 |
+
) % 7
|
| 262 |
+
assert result == expected
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
class TestTimestamp:
|
| 266 |
+
@pytest.mark.parametrize("tz", [None, zoneinfo.ZoneInfo("US/Pacific")])
|
| 267 |
+
def test_disallow_setting_tz(self, tz):
|
| 268 |
+
# GH#3746
|
| 269 |
+
ts = Timestamp("2010")
|
| 270 |
+
msg = "Cannot directly set timezone"
|
| 271 |
+
with pytest.raises(AttributeError, match=msg):
|
| 272 |
+
ts.tz = tz
|
| 273 |
+
|
| 274 |
+
def test_default_to_stdlib_utc(self):
|
| 275 |
+
msg = "Timestamp.utcnow is deprecated"
|
| 276 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 277 |
+
assert Timestamp.utcnow().tz is timezone.utc
|
| 278 |
+
assert Timestamp.now("UTC").tz is timezone.utc
|
| 279 |
+
assert Timestamp("2016-01-01", tz="UTC").tz is timezone.utc
|
| 280 |
+
|
| 281 |
+
def test_tz(self):
|
| 282 |
+
tstr = "2014-02-01 09:00"
|
| 283 |
+
ts = Timestamp(tstr)
|
| 284 |
+
local = ts.tz_localize("Asia/Tokyo")
|
| 285 |
+
assert local.hour == 9
|
| 286 |
+
assert local == Timestamp(tstr, tz="Asia/Tokyo")
|
| 287 |
+
conv = local.tz_convert("US/Eastern")
|
| 288 |
+
assert conv == Timestamp("2014-01-31 19:00", tz="US/Eastern")
|
| 289 |
+
assert conv.hour == 19
|
| 290 |
+
|
| 291 |
+
# preserves nanosecond
|
| 292 |
+
ts = Timestamp(tstr) + offsets.Nano(5)
|
| 293 |
+
local = ts.tz_localize("Asia/Tokyo")
|
| 294 |
+
assert local.hour == 9
|
| 295 |
+
assert local.nanosecond == 5
|
| 296 |
+
conv = local.tz_convert("US/Eastern")
|
| 297 |
+
assert conv.nanosecond == 5
|
| 298 |
+
assert conv.hour == 19
|
| 299 |
+
|
| 300 |
+
def test_utc_z_designator(self):
|
| 301 |
+
assert get_timezone(Timestamp("2014-11-02 01:00Z").tzinfo) is timezone.utc
|
| 302 |
+
|
| 303 |
+
def test_asm8(self):
|
| 304 |
+
ns = [Timestamp.min._value, Timestamp.max._value, 1000]
|
| 305 |
+
|
| 306 |
+
for n in ns:
|
| 307 |
+
assert (
|
| 308 |
+
Timestamp(n).asm8.view("i8") == np.datetime64(n, "ns").view("i8") == n
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
assert Timestamp("nat").asm8.view("i8") == np.datetime64("nat", "ns").view("i8")
|
| 312 |
+
|
| 313 |
+
def test_class_ops(self):
|
| 314 |
+
def compare(x, y):
|
| 315 |
+
assert int((Timestamp(x)._value - Timestamp(y)._value) / 1e9) == 0
|
| 316 |
+
|
| 317 |
+
compare(Timestamp.now(), datetime.now())
|
| 318 |
+
compare(Timestamp.now("UTC"), datetime.now(timezone.utc))
|
| 319 |
+
compare(Timestamp.now("UTC"), datetime.now(tzutc()))
|
| 320 |
+
msg = "Timestamp.utcnow is deprecated"
|
| 321 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 322 |
+
compare(Timestamp.utcnow(), datetime.now(timezone.utc))
|
| 323 |
+
compare(Timestamp.today(), datetime.today())
|
| 324 |
+
current_time = calendar.timegm(datetime.now().utctimetuple())
|
| 325 |
+
|
| 326 |
+
msg = "Timestamp.utcfromtimestamp is deprecated"
|
| 327 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 328 |
+
ts_utc = Timestamp.utcfromtimestamp(current_time)
|
| 329 |
+
assert ts_utc.timestamp() == current_time
|
| 330 |
+
compare(
|
| 331 |
+
Timestamp.fromtimestamp(current_time), datetime.fromtimestamp(current_time)
|
| 332 |
+
)
|
| 333 |
+
compare(
|
| 334 |
+
# Support tz kwarg in Timestamp.fromtimestamp
|
| 335 |
+
Timestamp.fromtimestamp(current_time, "UTC"),
|
| 336 |
+
datetime.fromtimestamp(current_time, timezone.utc),
|
| 337 |
+
)
|
| 338 |
+
compare(
|
| 339 |
+
# Support tz kwarg in Timestamp.fromtimestamp
|
| 340 |
+
Timestamp.fromtimestamp(current_time, tz="UTC"),
|
| 341 |
+
datetime.fromtimestamp(current_time, timezone.utc),
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
date_component = datetime.now(timezone.utc)
|
| 345 |
+
time_component = (date_component + timedelta(minutes=10)).time()
|
| 346 |
+
compare(
|
| 347 |
+
Timestamp.combine(date_component, time_component),
|
| 348 |
+
datetime.combine(date_component, time_component),
|
| 349 |
+
)
|
| 350 |
+
|
| 351 |
+
def test_basics_nanos(self):
|
| 352 |
+
val = np.int64(946_684_800_000_000_000).view("M8[ns]")
|
| 353 |
+
stamp = Timestamp(val.view("i8") + 500)
|
| 354 |
+
assert stamp.year == 2000
|
| 355 |
+
assert stamp.month == 1
|
| 356 |
+
assert stamp.microsecond == 0
|
| 357 |
+
assert stamp.nanosecond == 500
|
| 358 |
+
|
| 359 |
+
# GH 14415
|
| 360 |
+
val = np.iinfo(np.int64).min + 80_000_000_000_000
|
| 361 |
+
stamp = Timestamp(val)
|
| 362 |
+
assert stamp.year == 1677
|
| 363 |
+
assert stamp.month == 9
|
| 364 |
+
assert stamp.day == 21
|
| 365 |
+
assert stamp.microsecond == 145224
|
| 366 |
+
assert stamp.nanosecond == 192
|
| 367 |
+
|
| 368 |
+
def test_roundtrip(self):
|
| 369 |
+
# test value to string and back conversions
|
| 370 |
+
# further test accessors
|
| 371 |
+
base = Timestamp("20140101 00:00:00").as_unit("ns")
|
| 372 |
+
|
| 373 |
+
result = Timestamp(base._value + Timedelta("5ms").value)
|
| 374 |
+
assert result == Timestamp(f"{base}.005000")
|
| 375 |
+
assert result.microsecond == 5000
|
| 376 |
+
|
| 377 |
+
result = Timestamp(base._value + Timedelta("5us").value)
|
| 378 |
+
assert result == Timestamp(f"{base}.000005")
|
| 379 |
+
assert result.microsecond == 5
|
| 380 |
+
|
| 381 |
+
result = Timestamp(base._value + Timedelta("5ns")._value)
|
| 382 |
+
assert result == Timestamp(f"{base}.000000005")
|
| 383 |
+
assert result.nanosecond == 5
|
| 384 |
+
assert result.microsecond == 0
|
| 385 |
+
|
| 386 |
+
result = Timestamp(base._value + Timedelta("6ms 5us").value)
|
| 387 |
+
assert result == Timestamp(f"{base}.006005")
|
| 388 |
+
assert result.microsecond == 5 + 6 * 1000
|
| 389 |
+
|
| 390 |
+
result = Timestamp(base._value + Timedelta("200ms 5us").value)
|
| 391 |
+
assert result == Timestamp(f"{base}.200005")
|
| 392 |
+
assert result.microsecond == 5 + 200 * 1000
|
| 393 |
+
|
| 394 |
+
def test_hash_equivalent(self):
|
| 395 |
+
d = {datetime(2011, 1, 1): 5}
|
| 396 |
+
stamp = Timestamp(datetime(2011, 1, 1))
|
| 397 |
+
assert d[stamp] == 5
|
| 398 |
+
|
| 399 |
+
@pytest.mark.parametrize(
|
| 400 |
+
"timezone, year, month, day, hour",
|
| 401 |
+
[["America/Chicago", 2013, 11, 3, 1], ["America/Santiago", 2021, 4, 3, 23]],
|
| 402 |
+
)
|
| 403 |
+
def test_hash_timestamp_with_fold(self, timezone, year, month, day, hour):
|
| 404 |
+
# see gh-33931
|
| 405 |
+
test_timezone = gettz(timezone)
|
| 406 |
+
transition_1 = Timestamp(
|
| 407 |
+
year=year,
|
| 408 |
+
month=month,
|
| 409 |
+
day=day,
|
| 410 |
+
hour=hour,
|
| 411 |
+
minute=0,
|
| 412 |
+
fold=0,
|
| 413 |
+
tzinfo=test_timezone,
|
| 414 |
+
)
|
| 415 |
+
transition_2 = Timestamp(
|
| 416 |
+
year=year,
|
| 417 |
+
month=month,
|
| 418 |
+
day=day,
|
| 419 |
+
hour=hour,
|
| 420 |
+
minute=0,
|
| 421 |
+
fold=1,
|
| 422 |
+
tzinfo=test_timezone,
|
| 423 |
+
)
|
| 424 |
+
assert hash(transition_1) == hash(transition_2)
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
class TestTimestampNsOperations:
|
| 428 |
+
def test_nanosecond_string_parsing(self):
|
| 429 |
+
ts = Timestamp("2013-05-01 07:15:45.123456789")
|
| 430 |
+
# GH 7878
|
| 431 |
+
expected_repr = "2013-05-01 07:15:45.123456789"
|
| 432 |
+
expected_value = 1_367_392_545_123_456_789
|
| 433 |
+
assert ts._value == expected_value
|
| 434 |
+
assert expected_repr in repr(ts)
|
| 435 |
+
|
| 436 |
+
ts = Timestamp("2013-05-01 07:15:45.123456789+09:00", tz="Asia/Tokyo")
|
| 437 |
+
assert ts._value == expected_value - 9 * 3600 * 1_000_000_000
|
| 438 |
+
assert expected_repr in repr(ts)
|
| 439 |
+
|
| 440 |
+
ts = Timestamp("2013-05-01 07:15:45.123456789", tz="UTC")
|
| 441 |
+
assert ts._value == expected_value
|
| 442 |
+
assert expected_repr in repr(ts)
|
| 443 |
+
|
| 444 |
+
ts = Timestamp("2013-05-01 07:15:45.123456789", tz="US/Eastern")
|
| 445 |
+
assert ts._value == expected_value + 4 * 3600 * 1_000_000_000
|
| 446 |
+
assert expected_repr in repr(ts)
|
| 447 |
+
|
| 448 |
+
# GH 10041
|
| 449 |
+
ts = Timestamp("20130501T071545.123456789")
|
| 450 |
+
assert ts._value == expected_value
|
| 451 |
+
assert expected_repr in repr(ts)
|
| 452 |
+
|
| 453 |
+
def test_nanosecond_timestamp(self):
|
| 454 |
+
# GH 7610
|
| 455 |
+
expected = 1_293_840_000_000_000_005
|
| 456 |
+
t = Timestamp("2011-01-01") + offsets.Nano(5)
|
| 457 |
+
assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000005')"
|
| 458 |
+
assert t._value == expected
|
| 459 |
+
assert t.nanosecond == 5
|
| 460 |
+
|
| 461 |
+
t = Timestamp(t)
|
| 462 |
+
assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000005')"
|
| 463 |
+
assert t._value == expected
|
| 464 |
+
assert t.nanosecond == 5
|
| 465 |
+
|
| 466 |
+
t = Timestamp("2011-01-01 00:00:00.000000005")
|
| 467 |
+
assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000005')"
|
| 468 |
+
assert t._value == expected
|
| 469 |
+
assert t.nanosecond == 5
|
| 470 |
+
|
| 471 |
+
expected = 1_293_840_000_000_000_010
|
| 472 |
+
t = t + offsets.Nano(5)
|
| 473 |
+
assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000010')"
|
| 474 |
+
assert t._value == expected
|
| 475 |
+
assert t.nanosecond == 10
|
| 476 |
+
|
| 477 |
+
t = Timestamp(t)
|
| 478 |
+
assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000010')"
|
| 479 |
+
assert t._value == expected
|
| 480 |
+
assert t.nanosecond == 10
|
| 481 |
+
|
| 482 |
+
t = Timestamp("2011-01-01 00:00:00.000000010")
|
| 483 |
+
assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000010')"
|
| 484 |
+
assert t._value == expected
|
| 485 |
+
assert t.nanosecond == 10
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
class TestTimestampConversion:
|
| 489 |
+
def test_conversion(self):
|
| 490 |
+
# GH#9255
|
| 491 |
+
ts = Timestamp("2000-01-01").as_unit("ns")
|
| 492 |
+
|
| 493 |
+
result = ts.to_pydatetime()
|
| 494 |
+
expected = datetime(2000, 1, 1)
|
| 495 |
+
assert result == expected
|
| 496 |
+
assert type(result) == type(expected)
|
| 497 |
+
|
| 498 |
+
result = ts.to_datetime64()
|
| 499 |
+
expected = np.datetime64(ts._value, "ns")
|
| 500 |
+
assert result == expected
|
| 501 |
+
assert type(result) == type(expected)
|
| 502 |
+
assert result.dtype == expected.dtype
|
| 503 |
+
|
| 504 |
+
def test_to_period_tz_warning(self):
|
| 505 |
+
# GH#21333 make sure a warning is issued when timezone
|
| 506 |
+
# info is lost
|
| 507 |
+
ts = Timestamp("2009-04-15 16:17:18", tz="US/Eastern")
|
| 508 |
+
with tm.assert_produces_warning(UserWarning, match="drop timezone information"):
|
| 509 |
+
ts.to_period("D")
|
| 510 |
+
|
| 511 |
+
def test_to_numpy_alias(self):
|
| 512 |
+
# GH 24653: alias .to_numpy() for scalars
|
| 513 |
+
ts = Timestamp(datetime.now())
|
| 514 |
+
assert ts.to_datetime64() == ts.to_numpy()
|
| 515 |
+
|
| 516 |
+
# GH#44460
|
| 517 |
+
msg = "dtype and copy arguments are ignored"
|
| 518 |
+
with pytest.raises(ValueError, match=msg):
|
| 519 |
+
ts.to_numpy("M8[s]")
|
| 520 |
+
with pytest.raises(ValueError, match=msg):
|
| 521 |
+
ts.to_numpy(copy=True)
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
class TestNonNano:
|
| 525 |
+
@pytest.fixture(params=["s", "ms", "us"])
|
| 526 |
+
def reso(self, request):
|
| 527 |
+
return request.param
|
| 528 |
+
|
| 529 |
+
@pytest.fixture
|
| 530 |
+
def dt64(self, reso):
|
| 531 |
+
# cases that are in-bounds for nanosecond, so we can compare against
|
| 532 |
+
# the existing implementation.
|
| 533 |
+
return np.datetime64("2016-01-01", reso)
|
| 534 |
+
|
| 535 |
+
@pytest.fixture
|
| 536 |
+
def ts(self, dt64):
|
| 537 |
+
return Timestamp._from_dt64(dt64)
|
| 538 |
+
|
| 539 |
+
@pytest.fixture
|
| 540 |
+
def ts_tz(self, ts, tz_aware_fixture):
|
| 541 |
+
tz = maybe_get_tz(tz_aware_fixture)
|
| 542 |
+
return Timestamp._from_value_and_reso(ts._value, ts._creso, tz)
|
| 543 |
+
|
| 544 |
+
def test_non_nano_construction(self, dt64, ts, reso):
|
| 545 |
+
assert ts._value == dt64.view("i8")
|
| 546 |
+
|
| 547 |
+
if reso == "s":
|
| 548 |
+
assert ts._creso == NpyDatetimeUnit.NPY_FR_s.value
|
| 549 |
+
elif reso == "ms":
|
| 550 |
+
assert ts._creso == NpyDatetimeUnit.NPY_FR_ms.value
|
| 551 |
+
elif reso == "us":
|
| 552 |
+
assert ts._creso == NpyDatetimeUnit.NPY_FR_us.value
|
| 553 |
+
|
| 554 |
+
def test_non_nano_fields(self, dt64, ts):
|
| 555 |
+
alt = Timestamp(dt64)
|
| 556 |
+
|
| 557 |
+
assert ts.year == alt.year
|
| 558 |
+
assert ts.month == alt.month
|
| 559 |
+
assert ts.day == alt.day
|
| 560 |
+
assert ts.hour == ts.minute == ts.second == ts.microsecond == 0
|
| 561 |
+
assert ts.nanosecond == 0
|
| 562 |
+
|
| 563 |
+
assert ts.to_julian_date() == alt.to_julian_date()
|
| 564 |
+
assert ts.weekday() == alt.weekday()
|
| 565 |
+
assert ts.isoweekday() == alt.isoweekday()
|
| 566 |
+
|
| 567 |
+
def test_start_end_fields(self, ts):
|
| 568 |
+
assert ts.is_year_start
|
| 569 |
+
assert ts.is_quarter_start
|
| 570 |
+
assert ts.is_month_start
|
| 571 |
+
assert not ts.is_year_end
|
| 572 |
+
assert not ts.is_month_end
|
| 573 |
+
assert not ts.is_month_end
|
| 574 |
+
|
| 575 |
+
# 2016-01-01 is a Friday, so is year/quarter/month start with this freq
|
| 576 |
+
assert ts.is_year_start
|
| 577 |
+
assert ts.is_quarter_start
|
| 578 |
+
assert ts.is_month_start
|
| 579 |
+
assert not ts.is_year_end
|
| 580 |
+
assert not ts.is_month_end
|
| 581 |
+
assert not ts.is_month_end
|
| 582 |
+
|
| 583 |
+
def test_day_name(self, dt64, ts):
|
| 584 |
+
alt = Timestamp(dt64)
|
| 585 |
+
assert ts.day_name() == alt.day_name()
|
| 586 |
+
|
| 587 |
+
def test_month_name(self, dt64, ts):
|
| 588 |
+
alt = Timestamp(dt64)
|
| 589 |
+
assert ts.month_name() == alt.month_name()
|
| 590 |
+
|
| 591 |
+
def test_tz_convert(self, ts):
|
| 592 |
+
ts = Timestamp._from_value_and_reso(ts._value, ts._creso, timezone.utc)
|
| 593 |
+
|
| 594 |
+
tz = zoneinfo.ZoneInfo("US/Pacific")
|
| 595 |
+
result = ts.tz_convert(tz)
|
| 596 |
+
|
| 597 |
+
assert isinstance(result, Timestamp)
|
| 598 |
+
assert result._creso == ts._creso
|
| 599 |
+
assert tz_compare(result.tz, tz)
|
| 600 |
+
|
| 601 |
+
def test_repr(self, dt64, ts):
|
| 602 |
+
alt = Timestamp(dt64)
|
| 603 |
+
|
| 604 |
+
assert str(ts) == str(alt)
|
| 605 |
+
assert repr(ts) == repr(alt)
|
| 606 |
+
|
| 607 |
+
def test_comparison(self, dt64, ts):
|
| 608 |
+
alt = Timestamp(dt64)
|
| 609 |
+
|
| 610 |
+
assert ts == dt64
|
| 611 |
+
assert dt64 == ts
|
| 612 |
+
assert ts == alt
|
| 613 |
+
assert alt == ts
|
| 614 |
+
|
| 615 |
+
assert not ts != dt64
|
| 616 |
+
assert not dt64 != ts
|
| 617 |
+
assert not ts != alt
|
| 618 |
+
assert not alt != ts
|
| 619 |
+
|
| 620 |
+
assert not ts < dt64
|
| 621 |
+
assert not dt64 < ts
|
| 622 |
+
assert not ts < alt
|
| 623 |
+
assert not alt < ts
|
| 624 |
+
|
| 625 |
+
assert not ts > dt64
|
| 626 |
+
assert not dt64 > ts
|
| 627 |
+
assert not ts > alt
|
| 628 |
+
assert not alt > ts
|
| 629 |
+
|
| 630 |
+
assert ts >= dt64
|
| 631 |
+
assert dt64 >= ts
|
| 632 |
+
assert ts >= alt
|
| 633 |
+
assert alt >= ts
|
| 634 |
+
|
| 635 |
+
assert ts <= dt64
|
| 636 |
+
assert dt64 <= ts
|
| 637 |
+
assert ts <= alt
|
| 638 |
+
assert alt <= ts
|
| 639 |
+
|
| 640 |
+
def test_cmp_cross_reso(self):
|
| 641 |
+
# numpy gets this wrong because of silent overflow
|
| 642 |
+
dt64 = np.datetime64(9223372800, "s") # won't fit in M8[ns]
|
| 643 |
+
ts = Timestamp._from_dt64(dt64)
|
| 644 |
+
|
| 645 |
+
# subtracting 3600*24 gives a datetime64 that _can_ fit inside the
|
| 646 |
+
# nanosecond implementation bounds.
|
| 647 |
+
other = Timestamp(dt64 - np.timedelta64(3600 * 24, "s")).as_unit("ns")
|
| 648 |
+
assert other < ts
|
| 649 |
+
if not is_numpy_dev and not np_version_gt2_5:
|
| 650 |
+
assert other.asm8 > ts.asm8 # <- numpy gets this wrong
|
| 651 |
+
assert ts > other
|
| 652 |
+
if not is_numpy_dev and not np_version_gt2_5:
|
| 653 |
+
assert ts.asm8 < other.asm8 # <- numpy gets this wrong
|
| 654 |
+
assert not other == ts
|
| 655 |
+
assert ts != other
|
| 656 |
+
|
| 657 |
+
@pytest.mark.xfail(reason="Dispatches to np.datetime64 which is wrong")
|
| 658 |
+
def test_cmp_cross_reso_reversed_dt64(self):
|
| 659 |
+
dt64 = np.datetime64(106752, "D") # won't fit in M8[ns]
|
| 660 |
+
ts = Timestamp._from_dt64(dt64)
|
| 661 |
+
other = Timestamp(dt64 - 1)
|
| 662 |
+
|
| 663 |
+
assert other.asm8 < ts
|
| 664 |
+
|
| 665 |
+
def test_pickle(self, ts, tz_aware_fixture, temp_file):
|
| 666 |
+
tz = tz_aware_fixture
|
| 667 |
+
tz = maybe_get_tz(tz)
|
| 668 |
+
ts = Timestamp._from_value_and_reso(ts._value, ts._creso, tz)
|
| 669 |
+
rt = tm.round_trip_pickle(ts, temp_file)
|
| 670 |
+
assert rt._creso == ts._creso
|
| 671 |
+
assert rt == ts
|
| 672 |
+
|
| 673 |
+
def test_normalize(self, dt64, ts):
|
| 674 |
+
alt = Timestamp(dt64)
|
| 675 |
+
result = ts.normalize()
|
| 676 |
+
assert result._creso == ts._creso
|
| 677 |
+
assert result == alt.normalize()
|
| 678 |
+
|
| 679 |
+
def test_asm8(self, dt64, ts):
|
| 680 |
+
rt = ts.asm8
|
| 681 |
+
assert rt == dt64
|
| 682 |
+
assert rt.dtype == dt64.dtype
|
| 683 |
+
|
| 684 |
+
def test_to_numpy(self, dt64, ts):
|
| 685 |
+
res = ts.to_numpy()
|
| 686 |
+
assert res == dt64
|
| 687 |
+
assert res.dtype == dt64.dtype
|
| 688 |
+
|
| 689 |
+
def test_to_datetime64(self, dt64, ts):
|
| 690 |
+
res = ts.to_datetime64()
|
| 691 |
+
assert res == dt64
|
| 692 |
+
assert res.dtype == dt64.dtype
|
| 693 |
+
|
| 694 |
+
def test_timestamp(self, dt64, ts):
|
| 695 |
+
alt = Timestamp(dt64)
|
| 696 |
+
assert ts.timestamp() == alt.timestamp()
|
| 697 |
+
|
| 698 |
+
def test_to_period(self, dt64, ts):
|
| 699 |
+
alt = Timestamp(dt64)
|
| 700 |
+
assert ts.to_period("D") == alt.to_period("D")
|
| 701 |
+
|
| 702 |
+
@pytest.mark.parametrize(
|
| 703 |
+
"td", [timedelta(days=4), Timedelta(days=4), np.timedelta64(4, "D")]
|
| 704 |
+
)
|
| 705 |
+
def test_addsub_timedeltalike_non_nano(self, dt64, ts, td):
|
| 706 |
+
exp_reso = max(ts._creso, Timedelta(td)._creso)
|
| 707 |
+
|
| 708 |
+
result = ts - td
|
| 709 |
+
expected = Timestamp(dt64) - td
|
| 710 |
+
assert isinstance(result, Timestamp)
|
| 711 |
+
assert result._creso == exp_reso
|
| 712 |
+
assert result == expected
|
| 713 |
+
|
| 714 |
+
result = ts + td
|
| 715 |
+
expected = Timestamp(dt64) + td
|
| 716 |
+
assert isinstance(result, Timestamp)
|
| 717 |
+
assert result._creso == exp_reso
|
| 718 |
+
assert result == expected
|
| 719 |
+
|
| 720 |
+
result = td + ts
|
| 721 |
+
expected = td + Timestamp(dt64)
|
| 722 |
+
assert isinstance(result, Timestamp)
|
| 723 |
+
assert result._creso == exp_reso
|
| 724 |
+
assert result == expected
|
| 725 |
+
|
| 726 |
+
def test_addsub_offset(self, ts_tz):
|
| 727 |
+
# specifically non-Tick offset
|
| 728 |
+
off = offsets.YearEnd(1)
|
| 729 |
+
result = ts_tz + off
|
| 730 |
+
|
| 731 |
+
assert isinstance(result, Timestamp)
|
| 732 |
+
assert result._creso == ts_tz._creso
|
| 733 |
+
if ts_tz.month == 12 and ts_tz.day == 31:
|
| 734 |
+
assert result.year == ts_tz.year + 1
|
| 735 |
+
else:
|
| 736 |
+
assert result.year == ts_tz.year
|
| 737 |
+
assert result.day == 31
|
| 738 |
+
assert result.month == 12
|
| 739 |
+
assert tz_compare(result.tz, ts_tz.tz)
|
| 740 |
+
|
| 741 |
+
result = ts_tz - off
|
| 742 |
+
|
| 743 |
+
assert isinstance(result, Timestamp)
|
| 744 |
+
assert result._creso == ts_tz._creso
|
| 745 |
+
assert result.year == ts_tz.year - 1
|
| 746 |
+
assert result.day == 31
|
| 747 |
+
assert result.month == 12
|
| 748 |
+
assert tz_compare(result.tz, ts_tz.tz)
|
| 749 |
+
|
| 750 |
+
def test_sub_datetimelike_mismatched_reso(self, ts_tz):
|
| 751 |
+
# case with non-lossy rounding
|
| 752 |
+
ts = ts_tz
|
| 753 |
+
|
| 754 |
+
# choose a unit for `other` that doesn't match ts_tz's;
|
| 755 |
+
# this construction ensures we get cases with other._creso < ts._creso
|
| 756 |
+
# and cases with other._creso > ts._creso
|
| 757 |
+
unit = {
|
| 758 |
+
NpyDatetimeUnit.NPY_FR_us.value: "ms",
|
| 759 |
+
NpyDatetimeUnit.NPY_FR_ms.value: "s",
|
| 760 |
+
NpyDatetimeUnit.NPY_FR_s.value: "us",
|
| 761 |
+
}[ts._creso]
|
| 762 |
+
other = ts.as_unit(unit)
|
| 763 |
+
assert other._creso != ts._creso
|
| 764 |
+
|
| 765 |
+
result = ts - other
|
| 766 |
+
assert isinstance(result, Timedelta)
|
| 767 |
+
assert result._value == 0
|
| 768 |
+
assert result._creso == max(ts._creso, other._creso)
|
| 769 |
+
|
| 770 |
+
result = other - ts
|
| 771 |
+
assert isinstance(result, Timedelta)
|
| 772 |
+
assert result._value == 0
|
| 773 |
+
assert result._creso == max(ts._creso, other._creso)
|
| 774 |
+
|
| 775 |
+
if ts._creso < other._creso:
|
| 776 |
+
# Case where rounding is lossy
|
| 777 |
+
other2 = other + Timedelta._from_value_and_reso(1, other._creso)
|
| 778 |
+
exp = ts.as_unit(other.unit) - other2
|
| 779 |
+
|
| 780 |
+
res = ts - other2
|
| 781 |
+
assert res == exp
|
| 782 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 783 |
+
|
| 784 |
+
res = other2 - ts
|
| 785 |
+
assert res == -exp
|
| 786 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 787 |
+
else:
|
| 788 |
+
ts2 = ts + Timedelta._from_value_and_reso(1, ts._creso)
|
| 789 |
+
exp = ts2 - other.as_unit(ts2.unit)
|
| 790 |
+
|
| 791 |
+
res = ts2 - other
|
| 792 |
+
assert res == exp
|
| 793 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 794 |
+
res = other - ts2
|
| 795 |
+
assert res == -exp
|
| 796 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 797 |
+
|
| 798 |
+
def test_sub_timedeltalike_mismatched_reso(self, ts_tz):
|
| 799 |
+
# case with non-lossy rounding
|
| 800 |
+
ts = ts_tz
|
| 801 |
+
|
| 802 |
+
# choose a unit for `other` that doesn't match ts_tz's;
|
| 803 |
+
# this construction ensures we get cases with other._creso < ts._creso
|
| 804 |
+
# and cases with other._creso > ts._creso
|
| 805 |
+
unit = {
|
| 806 |
+
NpyDatetimeUnit.NPY_FR_us.value: "ms",
|
| 807 |
+
NpyDatetimeUnit.NPY_FR_ms.value: "s",
|
| 808 |
+
NpyDatetimeUnit.NPY_FR_s.value: "us",
|
| 809 |
+
}[ts._creso]
|
| 810 |
+
other = Timedelta(0).as_unit(unit)
|
| 811 |
+
assert other._creso != ts._creso
|
| 812 |
+
|
| 813 |
+
result = ts + other
|
| 814 |
+
assert isinstance(result, Timestamp)
|
| 815 |
+
assert result == ts
|
| 816 |
+
assert result._creso == max(ts._creso, other._creso)
|
| 817 |
+
|
| 818 |
+
result = other + ts
|
| 819 |
+
assert isinstance(result, Timestamp)
|
| 820 |
+
assert result == ts
|
| 821 |
+
assert result._creso == max(ts._creso, other._creso)
|
| 822 |
+
|
| 823 |
+
if ts._creso < other._creso:
|
| 824 |
+
# Case where rounding is lossy
|
| 825 |
+
other2 = other + Timedelta._from_value_and_reso(1, other._creso)
|
| 826 |
+
exp = ts.as_unit(other.unit) + other2
|
| 827 |
+
res = ts + other2
|
| 828 |
+
assert res == exp
|
| 829 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 830 |
+
res = other2 + ts
|
| 831 |
+
assert res == exp
|
| 832 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 833 |
+
else:
|
| 834 |
+
ts2 = ts + Timedelta._from_value_and_reso(1, ts._creso)
|
| 835 |
+
exp = ts2 + other.as_unit(ts2.unit)
|
| 836 |
+
|
| 837 |
+
res = ts2 + other
|
| 838 |
+
assert res == exp
|
| 839 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 840 |
+
res = other + ts2
|
| 841 |
+
assert res == exp
|
| 842 |
+
assert res._creso == max(ts._creso, other._creso)
|
| 843 |
+
|
| 844 |
+
def test_addition_doesnt_downcast_reso(self):
|
| 845 |
+
# https://github.com/pandas-dev/pandas/pull/48748#pullrequestreview-1122635413
|
| 846 |
+
ts = Timestamp(year=2022, month=1, day=1, microsecond=999999).as_unit("us")
|
| 847 |
+
td = Timedelta(microseconds=1).as_unit("us")
|
| 848 |
+
res = ts + td
|
| 849 |
+
assert res._creso == ts._creso
|
| 850 |
+
|
| 851 |
+
def test_sub_timedelta64_mismatched_reso(self, ts_tz):
|
| 852 |
+
ts = ts_tz
|
| 853 |
+
|
| 854 |
+
res = ts + np.timedelta64(1, "ns")
|
| 855 |
+
exp = ts.as_unit("ns") + np.timedelta64(1, "ns")
|
| 856 |
+
assert exp == res
|
| 857 |
+
assert exp._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 858 |
+
|
| 859 |
+
def test_min(self, ts):
|
| 860 |
+
assert ts.min <= ts
|
| 861 |
+
assert ts.min._creso == ts._creso
|
| 862 |
+
assert ts.min._value == NaT._value + 1
|
| 863 |
+
|
| 864 |
+
def test_max(self, ts):
|
| 865 |
+
assert ts.max >= ts
|
| 866 |
+
assert ts.max._creso == ts._creso
|
| 867 |
+
assert ts.max._value == np.iinfo(np.int64).max
|
| 868 |
+
|
| 869 |
+
def test_resolution(self, ts):
|
| 870 |
+
expected = Timedelta._from_value_and_reso(1, ts._creso)
|
| 871 |
+
result = ts.resolution
|
| 872 |
+
assert result == expected
|
| 873 |
+
assert result._creso == expected._creso
|
| 874 |
+
|
| 875 |
+
def test_out_of_ns_bounds(self):
|
| 876 |
+
# https://github.com/pandas-dev/pandas/issues/51060
|
| 877 |
+
result = Timestamp(-52700112000, unit="s")
|
| 878 |
+
assert result == Timestamp("0300-01-01")
|
| 879 |
+
assert result.to_numpy() == np.datetime64("0300-01-01T00:00:00", "s")
|
| 880 |
+
|
| 881 |
+
|
| 882 |
+
def test_timestamp_class_min_max_resolution():
|
| 883 |
+
# when accessed on the class (as opposed to an instance), we default
|
| 884 |
+
# to nanoseconds
|
| 885 |
+
assert Timestamp.min == Timestamp(NaT._value + 1)
|
| 886 |
+
assert Timestamp.min._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 887 |
+
|
| 888 |
+
assert Timestamp.max == Timestamp(np.iinfo(np.int64).max)
|
| 889 |
+
assert Timestamp.max._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 890 |
+
|
| 891 |
+
assert Timestamp.resolution == Timedelta(1)
|
| 892 |
+
assert Timestamp.resolution._creso == NpyDatetimeUnit.NPY_FR_ns.value
|
| 893 |
+
|
| 894 |
+
|
| 895 |
+
def test_delimited_date():
|
| 896 |
+
# https://github.com/pandas-dev/pandas/issues/50231
|
| 897 |
+
with tm.assert_produces_warning(None):
|
| 898 |
+
result = Timestamp("13-01-2000")
|
| 899 |
+
expected = Timestamp(2000, 1, 13)
|
| 900 |
+
assert result == expected
|
| 901 |
+
|
| 902 |
+
|
| 903 |
+
def test_utctimetuple():
|
| 904 |
+
# GH 32174
|
| 905 |
+
ts = Timestamp("2000-01-01", tz="UTC")
|
| 906 |
+
result = ts.utctimetuple()
|
| 907 |
+
expected = time.struct_time((2000, 1, 1, 0, 0, 0, 5, 1, 0))
|
| 908 |
+
assert result == expected
|
| 909 |
+
|
| 910 |
+
|
| 911 |
+
def test_negative_dates():
|
| 912 |
+
# https://github.com/pandas-dev/pandas/issues/50787
|
| 913 |
+
ts = Timestamp("-2000-01-01")
|
| 914 |
+
msg = (
|
| 915 |
+
" not yet supported on Timestamps which are outside the range of "
|
| 916 |
+
"Python's standard library. For now, please call the components you need "
|
| 917 |
+
r"\(such as `.year` and `.month`\) and construct your string from there.$"
|
| 918 |
+
)
|
| 919 |
+
func = "^strftime"
|
| 920 |
+
with pytest.raises(NotImplementedError, match=func + msg):
|
| 921 |
+
ts.strftime("%Y")
|
| 922 |
+
|
| 923 |
+
msg = (
|
| 924 |
+
" not yet supported on Timestamps which "
|
| 925 |
+
"are outside the range of Python's standard library. "
|
| 926 |
+
)
|
| 927 |
+
func = "^date"
|
| 928 |
+
with pytest.raises(NotImplementedError, match=func + msg):
|
| 929 |
+
ts.date()
|
| 930 |
+
func = "^isocalendar"
|
| 931 |
+
with pytest.raises(NotImplementedError, match=func + msg):
|
| 932 |
+
ts.isocalendar()
|
| 933 |
+
func = "^timetuple"
|
| 934 |
+
with pytest.raises(NotImplementedError, match=func + msg):
|
| 935 |
+
ts.timetuple()
|
| 936 |
+
func = "^toordinal"
|
| 937 |
+
with pytest.raises(NotImplementedError, match=func + msg):
|
| 938 |
+
ts.toordinal()
|
.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timezones.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Tests for Timestamp timezone-related methods
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
from pandas._libs.tslibs import timezones
|
| 8 |
+
|
| 9 |
+
from pandas import Timestamp
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TestTimestampTZOperations:
|
| 13 |
+
# ------------------------------------------------------------------
|
| 14 |
+
|
| 15 |
+
def test_timestamp_timetz_equivalent_with_datetime_tz(self, tz_naive_fixture):
|
| 16 |
+
# GH21358
|
| 17 |
+
tz = timezones.maybe_get_tz(tz_naive_fixture)
|
| 18 |
+
|
| 19 |
+
stamp = Timestamp("2018-06-04 10:20:30", tz=tz)
|
| 20 |
+
_datetime = datetime(2018, 6, 4, hour=10, minute=20, second=30, tzinfo=tz)
|
| 21 |
+
|
| 22 |
+
result = stamp.timetz()
|
| 23 |
+
expected = _datetime.timetz()
|
| 24 |
+
|
| 25 |
+
assert result == expected
|
.venv/lib/python3.13/site-packages/pandas/tests/series/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_cat_accessor.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas.errors import Pandas4Warning
|
| 5 |
+
|
| 6 |
+
from pandas import (
|
| 7 |
+
Categorical,
|
| 8 |
+
DataFrame,
|
| 9 |
+
Index,
|
| 10 |
+
Series,
|
| 11 |
+
Timestamp,
|
| 12 |
+
date_range,
|
| 13 |
+
period_range,
|
| 14 |
+
timedelta_range,
|
| 15 |
+
)
|
| 16 |
+
import pandas._testing as tm
|
| 17 |
+
from pandas.core.arrays.categorical import CategoricalAccessor
|
| 18 |
+
from pandas.core.indexes.accessors import Properties
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class TestCatAccessor:
|
| 22 |
+
@pytest.mark.parametrize(
|
| 23 |
+
"method",
|
| 24 |
+
[
|
| 25 |
+
lambda x: x.cat.set_categories([1, 2, 3]),
|
| 26 |
+
lambda x: x.cat.reorder_categories([2, 3, 1], ordered=True),
|
| 27 |
+
lambda x: x.cat.rename_categories([1, 2, 3]),
|
| 28 |
+
lambda x: x.cat.remove_unused_categories(),
|
| 29 |
+
lambda x: x.cat.remove_categories([2]),
|
| 30 |
+
lambda x: x.cat.add_categories([4]),
|
| 31 |
+
lambda x: x.cat.as_ordered(),
|
| 32 |
+
lambda x: x.cat.as_unordered(),
|
| 33 |
+
],
|
| 34 |
+
)
|
| 35 |
+
def test_getname_categorical_accessor(self, method):
|
| 36 |
+
# GH#17509
|
| 37 |
+
ser = Series([1, 2, 3], name="A").astype("category")
|
| 38 |
+
expected = "A"
|
| 39 |
+
result = method(ser).name
|
| 40 |
+
assert result == expected
|
| 41 |
+
|
| 42 |
+
def test_cat_accessor(self):
|
| 43 |
+
ser = Series(Categorical(["a", "b", np.nan, "a"]))
|
| 44 |
+
tm.assert_index_equal(ser.cat.categories, Index(["a", "b"]))
|
| 45 |
+
assert not ser.cat.ordered
|
| 46 |
+
|
| 47 |
+
exp = Categorical(["a", "b", np.nan, "a"], categories=["b", "a"])
|
| 48 |
+
|
| 49 |
+
res = ser.cat.set_categories(["b", "a"])
|
| 50 |
+
tm.assert_categorical_equal(res.values, exp)
|
| 51 |
+
|
| 52 |
+
ser[:] = "a"
|
| 53 |
+
ser = ser.cat.remove_unused_categories()
|
| 54 |
+
tm.assert_index_equal(ser.cat.categories, Index(["a"]))
|
| 55 |
+
|
| 56 |
+
def test_cat_accessor_api(self):
|
| 57 |
+
# GH#9322
|
| 58 |
+
|
| 59 |
+
assert Series.cat is CategoricalAccessor
|
| 60 |
+
ser = Series(list("aabbcde")).astype("category")
|
| 61 |
+
assert isinstance(ser.cat, CategoricalAccessor)
|
| 62 |
+
|
| 63 |
+
invalid = Series([1])
|
| 64 |
+
with pytest.raises(AttributeError, match="only use .cat accessor"):
|
| 65 |
+
invalid.cat
|
| 66 |
+
assert not hasattr(invalid, "cat")
|
| 67 |
+
|
| 68 |
+
def test_cat_accessor_no_new_attributes(self):
|
| 69 |
+
# https://github.com/pandas-dev/pandas/issues/10673
|
| 70 |
+
cat = Series(list("aabbcde")).astype("category")
|
| 71 |
+
with pytest.raises(AttributeError, match="You cannot add any new attribute"):
|
| 72 |
+
cat.cat.xlabel = "a"
|
| 73 |
+
|
| 74 |
+
def test_categorical_delegations(self):
|
| 75 |
+
# invalid accessor
|
| 76 |
+
msg = r"Can only use \.cat accessor with a 'category' dtype"
|
| 77 |
+
with pytest.raises(AttributeError, match=msg):
|
| 78 |
+
Series([1, 2, 3]).cat
|
| 79 |
+
with pytest.raises(AttributeError, match=msg):
|
| 80 |
+
Series([1, 2, 3]).cat()
|
| 81 |
+
with pytest.raises(AttributeError, match=msg):
|
| 82 |
+
Series(["a", "b", "c"]).cat
|
| 83 |
+
with pytest.raises(AttributeError, match=msg):
|
| 84 |
+
Series(np.arange(5.0)).cat
|
| 85 |
+
with pytest.raises(AttributeError, match=msg):
|
| 86 |
+
Series([Timestamp("20130101")]).cat
|
| 87 |
+
|
| 88 |
+
# Series should delegate calls to '.categories', '.codes', '.ordered'
|
| 89 |
+
# and the methods '.set_categories()' 'drop_unused_categories()' to the
|
| 90 |
+
# categorical
|
| 91 |
+
ser = Series(Categorical(["a", "b", "c", "a"], ordered=True))
|
| 92 |
+
exp_categories = Index(["a", "b", "c"])
|
| 93 |
+
tm.assert_index_equal(ser.cat.categories, exp_categories)
|
| 94 |
+
ser = ser.cat.rename_categories([1, 2, 3])
|
| 95 |
+
exp_categories = Index([1, 2, 3])
|
| 96 |
+
tm.assert_index_equal(ser.cat.categories, exp_categories)
|
| 97 |
+
|
| 98 |
+
exp_codes = Series([0, 1, 2, 0], dtype="int8")
|
| 99 |
+
tm.assert_series_equal(ser.cat.codes, exp_codes)
|
| 100 |
+
|
| 101 |
+
assert ser.cat.ordered
|
| 102 |
+
ser = ser.cat.as_unordered()
|
| 103 |
+
assert not ser.cat.ordered
|
| 104 |
+
|
| 105 |
+
ser = ser.cat.as_ordered()
|
| 106 |
+
assert ser.cat.ordered
|
| 107 |
+
|
| 108 |
+
# reorder
|
| 109 |
+
ser = Series(Categorical(["a", "b", "c", "a"], ordered=True))
|
| 110 |
+
exp_categories = Index(["c", "b", "a"])
|
| 111 |
+
exp_values = np.array(["a", "b", "c", "a"], dtype=np.object_)
|
| 112 |
+
ser = ser.cat.set_categories(["c", "b", "a"])
|
| 113 |
+
tm.assert_index_equal(ser.cat.categories, exp_categories)
|
| 114 |
+
tm.assert_numpy_array_equal(ser.values.__array__(), exp_values)
|
| 115 |
+
tm.assert_numpy_array_equal(ser.__array__(), exp_values)
|
| 116 |
+
|
| 117 |
+
# remove unused categories
|
| 118 |
+
ser = Series(Categorical(["a", "b", "b", "a"], categories=["a", "b", "c"]))
|
| 119 |
+
exp_categories = Index(["a", "b"])
|
| 120 |
+
exp_values = np.array(["a", "b", "b", "a"], dtype=np.object_)
|
| 121 |
+
ser = ser.cat.remove_unused_categories()
|
| 122 |
+
tm.assert_index_equal(ser.cat.categories, exp_categories)
|
| 123 |
+
tm.assert_numpy_array_equal(ser.values.__array__(), exp_values)
|
| 124 |
+
tm.assert_numpy_array_equal(ser.__array__(), exp_values)
|
| 125 |
+
|
| 126 |
+
# This method is likely to be confused, so test that it raises an error
|
| 127 |
+
# on wrong inputs:
|
| 128 |
+
msg = "'Series' object has no attribute 'set_categories'"
|
| 129 |
+
with pytest.raises(AttributeError, match=msg):
|
| 130 |
+
ser.set_categories([4, 3, 2, 1])
|
| 131 |
+
|
| 132 |
+
# right: ser.cat.set_categories([4,3,2,1])
|
| 133 |
+
|
| 134 |
+
# GH#18862 (let Series.cat.rename_categories take callables)
|
| 135 |
+
ser = Series(Categorical(["a", "b", "c", "a"], ordered=True))
|
| 136 |
+
result = ser.cat.rename_categories(lambda x: x.upper())
|
| 137 |
+
expected = Series(
|
| 138 |
+
Categorical(["A", "B", "C", "A"], categories=["A", "B", "C"], ordered=True)
|
| 139 |
+
)
|
| 140 |
+
tm.assert_series_equal(result, expected)
|
| 141 |
+
|
| 142 |
+
@pytest.mark.parametrize(
|
| 143 |
+
"idx",
|
| 144 |
+
[
|
| 145 |
+
date_range("1/1/2015", periods=5, unit="ns"),
|
| 146 |
+
date_range("1/1/2015", periods=5, tz="MET", unit="ns"),
|
| 147 |
+
period_range("1/1/2015", freq="D", periods=5),
|
| 148 |
+
timedelta_range("1 days", "10 days"),
|
| 149 |
+
],
|
| 150 |
+
)
|
| 151 |
+
def test_dt_accessor_api_for_categorical(self, idx):
|
| 152 |
+
# https://github.com/pandas-dev/pandas/issues/10661
|
| 153 |
+
|
| 154 |
+
ser = Series(idx)
|
| 155 |
+
cat = ser.astype("category")
|
| 156 |
+
|
| 157 |
+
# only testing field (like .day)
|
| 158 |
+
# and bool (is_month_start)
|
| 159 |
+
attr_names = type(ser._values)._datetimelike_ops
|
| 160 |
+
|
| 161 |
+
assert isinstance(cat.dt, Properties)
|
| 162 |
+
|
| 163 |
+
special_func_defs = [
|
| 164 |
+
("strftime", ("%Y-%m-%d",), {}),
|
| 165 |
+
("round", ("D",), {}),
|
| 166 |
+
("floor", ("D",), {}),
|
| 167 |
+
("ceil", ("D",), {}),
|
| 168 |
+
("asfreq", ("D",), {}),
|
| 169 |
+
("as_unit", ("s"), {}),
|
| 170 |
+
]
|
| 171 |
+
if idx.dtype == "M8[ns]":
|
| 172 |
+
# exclude dt64tz since that is already localized and would raise
|
| 173 |
+
tup = ("tz_localize", ("UTC",), {})
|
| 174 |
+
special_func_defs.append(tup)
|
| 175 |
+
elif idx.dtype.kind == "M":
|
| 176 |
+
# exclude dt64 since that is not localized so would raise
|
| 177 |
+
tup = ("tz_convert", ("EST",), {})
|
| 178 |
+
special_func_defs.append(tup)
|
| 179 |
+
|
| 180 |
+
_special_func_names = [f[0] for f in special_func_defs]
|
| 181 |
+
|
| 182 |
+
_ignore_names = ["components", "tz_localize", "tz_convert"]
|
| 183 |
+
|
| 184 |
+
func_names = [
|
| 185 |
+
fname
|
| 186 |
+
for fname in dir(ser.dt)
|
| 187 |
+
if not (
|
| 188 |
+
fname.startswith("_")
|
| 189 |
+
or fname in attr_names
|
| 190 |
+
or fname in _special_func_names
|
| 191 |
+
or fname in _ignore_names
|
| 192 |
+
)
|
| 193 |
+
]
|
| 194 |
+
|
| 195 |
+
func_defs = [(fname, (), {}) for fname in func_names]
|
| 196 |
+
func_defs.extend(
|
| 197 |
+
f_def for f_def in special_func_defs if f_def[0] in dir(ser.dt)
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
for func, args, kwargs in func_defs:
|
| 201 |
+
warn_cls = []
|
| 202 |
+
if func == "to_period" and getattr(idx, "tz", None) is not None:
|
| 203 |
+
# dropping TZ
|
| 204 |
+
warn_cls.append(UserWarning)
|
| 205 |
+
elif func == "to_pytimedelta":
|
| 206 |
+
# GH 57463
|
| 207 |
+
warn_cls.append(Pandas4Warning)
|
| 208 |
+
if warn_cls:
|
| 209 |
+
warn_cls = tuple(warn_cls)
|
| 210 |
+
else:
|
| 211 |
+
warn_cls = None
|
| 212 |
+
with tm.assert_produces_warning(warn_cls):
|
| 213 |
+
res = getattr(cat.dt, func)(*args, **kwargs)
|
| 214 |
+
exp = getattr(ser.dt, func)(*args, **kwargs)
|
| 215 |
+
|
| 216 |
+
tm.assert_equal(res, exp)
|
| 217 |
+
|
| 218 |
+
for attr in attr_names:
|
| 219 |
+
res = getattr(cat.dt, attr)
|
| 220 |
+
exp = getattr(ser.dt, attr)
|
| 221 |
+
|
| 222 |
+
tm.assert_equal(res, exp)
|
| 223 |
+
|
| 224 |
+
def test_dt_accessor_api_for_categorical_invalid(self):
|
| 225 |
+
invalid = Series([1, 2, 3]).astype("category")
|
| 226 |
+
msg = "Can only use .dt accessor with datetimelike"
|
| 227 |
+
|
| 228 |
+
with pytest.raises(AttributeError, match=msg):
|
| 229 |
+
invalid.dt
|
| 230 |
+
assert not hasattr(invalid, "str")
|
| 231 |
+
|
| 232 |
+
def test_set_categories_setitem(self):
|
| 233 |
+
# GH#43334
|
| 234 |
+
|
| 235 |
+
df = DataFrame({"Survived": [1, 0, 1], "Sex": [0, 1, 1]}, dtype="category")
|
| 236 |
+
|
| 237 |
+
df["Survived"] = df["Survived"].cat.rename_categories(["No", "Yes"])
|
| 238 |
+
df["Sex"] = df["Sex"].cat.rename_categories(["female", "male"])
|
| 239 |
+
|
| 240 |
+
# values should not be coerced to NaN
|
| 241 |
+
assert list(df["Sex"]) == ["female", "male", "male"]
|
| 242 |
+
assert list(df["Survived"]) == ["Yes", "No", "Yes"]
|
| 243 |
+
|
| 244 |
+
df["Sex"] = Categorical(df["Sex"], categories=["female", "male"], ordered=False)
|
| 245 |
+
df["Survived"] = Categorical(
|
| 246 |
+
df["Survived"], categories=["No", "Yes"], ordered=False
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
# values should not be coerced to NaN
|
| 250 |
+
assert list(df["Sex"]) == ["female", "male", "male"]
|
| 251 |
+
assert list(df["Survived"]) == ["Yes", "No", "Yes"]
|
| 252 |
+
|
| 253 |
+
def test_categorical_of_booleans_is_boolean(self):
|
| 254 |
+
# https://github.com/pandas-dev/pandas/issues/46313
|
| 255 |
+
df = DataFrame(
|
| 256 |
+
{"int_cat": [1, 2, 3], "bool_cat": [True, False, False]}, dtype="category"
|
| 257 |
+
)
|
| 258 |
+
value = df["bool_cat"].cat.categories.dtype
|
| 259 |
+
expected = np.dtype(np.bool_)
|
| 260 |
+
assert value is expected
|
.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_dt_accessor.py
ADDED
|
@@ -0,0 +1,832 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import calendar
|
| 2 |
+
from datetime import (
|
| 3 |
+
date,
|
| 4 |
+
datetime,
|
| 5 |
+
time,
|
| 6 |
+
)
|
| 7 |
+
import locale
|
| 8 |
+
import unicodedata
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pytest
|
| 12 |
+
|
| 13 |
+
from pandas._libs.tslibs.timezones import maybe_get_tz
|
| 14 |
+
from pandas.errors import Pandas4Warning
|
| 15 |
+
|
| 16 |
+
from pandas.core.dtypes.common import (
|
| 17 |
+
is_integer_dtype,
|
| 18 |
+
is_list_like,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
import pandas as pd
|
| 22 |
+
from pandas import (
|
| 23 |
+
DataFrame,
|
| 24 |
+
DatetimeIndex,
|
| 25 |
+
Index,
|
| 26 |
+
Period,
|
| 27 |
+
PeriodIndex,
|
| 28 |
+
Series,
|
| 29 |
+
StringDtype,
|
| 30 |
+
TimedeltaIndex,
|
| 31 |
+
date_range,
|
| 32 |
+
period_range,
|
| 33 |
+
timedelta_range,
|
| 34 |
+
)
|
| 35 |
+
import pandas._testing as tm
|
| 36 |
+
from pandas.core.arrays import (
|
| 37 |
+
DatetimeArray,
|
| 38 |
+
PeriodArray,
|
| 39 |
+
TimedeltaArray,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
ok_for_period = PeriodArray._datetimelike_ops
|
| 43 |
+
ok_for_period_methods = ["strftime", "to_timestamp", "asfreq"]
|
| 44 |
+
ok_for_dt = DatetimeArray._datetimelike_ops
|
| 45 |
+
ok_for_dt_methods = [
|
| 46 |
+
"to_period",
|
| 47 |
+
"to_pydatetime",
|
| 48 |
+
"tz_localize",
|
| 49 |
+
"tz_convert",
|
| 50 |
+
"normalize",
|
| 51 |
+
"strftime",
|
| 52 |
+
"round",
|
| 53 |
+
"floor",
|
| 54 |
+
"ceil",
|
| 55 |
+
"day_name",
|
| 56 |
+
"month_name",
|
| 57 |
+
"isocalendar",
|
| 58 |
+
"as_unit",
|
| 59 |
+
]
|
| 60 |
+
ok_for_td = TimedeltaArray._datetimelike_ops
|
| 61 |
+
ok_for_td_methods = [
|
| 62 |
+
"components",
|
| 63 |
+
"to_pytimedelta",
|
| 64 |
+
"total_seconds",
|
| 65 |
+
"round",
|
| 66 |
+
"floor",
|
| 67 |
+
"ceil",
|
| 68 |
+
"as_unit",
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def get_dir(ser):
|
| 73 |
+
# check limited display api
|
| 74 |
+
results = [r for r in ser.dt.__dir__() if not r.startswith("_")]
|
| 75 |
+
return sorted(set(results))
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class TestSeriesDatetimeValues:
|
| 79 |
+
def _compare(self, ser, name):
|
| 80 |
+
# GH 7207, 11128
|
| 81 |
+
# test .dt namespace accessor
|
| 82 |
+
|
| 83 |
+
def get_expected(ser, prop):
|
| 84 |
+
result = getattr(Index(ser._values), prop)
|
| 85 |
+
if isinstance(result, np.ndarray):
|
| 86 |
+
if is_integer_dtype(result):
|
| 87 |
+
result = result.astype("int64")
|
| 88 |
+
elif not is_list_like(result) or isinstance(result, DataFrame):
|
| 89 |
+
return result
|
| 90 |
+
return Series(result, index=ser.index, name=ser.name)
|
| 91 |
+
|
| 92 |
+
left = getattr(ser.dt, name)
|
| 93 |
+
right = get_expected(ser, name)
|
| 94 |
+
if not (is_list_like(left) and is_list_like(right)):
|
| 95 |
+
assert left == right
|
| 96 |
+
elif isinstance(left, DataFrame):
|
| 97 |
+
tm.assert_frame_equal(left, right)
|
| 98 |
+
else:
|
| 99 |
+
tm.assert_series_equal(left, right)
|
| 100 |
+
|
| 101 |
+
@pytest.mark.parametrize("freq", ["D", "s", "ms"])
|
| 102 |
+
def test_dt_namespace_accessor_datetime64(self, freq):
|
| 103 |
+
# GH#7207, GH#11128
|
| 104 |
+
# test .dt namespace accessor
|
| 105 |
+
|
| 106 |
+
# datetimeindex
|
| 107 |
+
dti = date_range("20130101", periods=5, freq=freq)
|
| 108 |
+
ser = Series(dti, name="xxx")
|
| 109 |
+
|
| 110 |
+
for prop in ok_for_dt:
|
| 111 |
+
# we test freq below
|
| 112 |
+
if prop != "freq":
|
| 113 |
+
self._compare(ser, prop)
|
| 114 |
+
|
| 115 |
+
for prop in ok_for_dt_methods:
|
| 116 |
+
getattr(ser.dt, prop)
|
| 117 |
+
|
| 118 |
+
result = ser.dt.to_pydatetime()
|
| 119 |
+
assert isinstance(result, Series)
|
| 120 |
+
assert result.dtype == object
|
| 121 |
+
|
| 122 |
+
result = ser.dt.tz_localize("US/Eastern")
|
| 123 |
+
exp_values = DatetimeIndex(ser.values).tz_localize("US/Eastern")
|
| 124 |
+
expected = Series(exp_values, index=ser.index, name="xxx")
|
| 125 |
+
tm.assert_series_equal(result, expected)
|
| 126 |
+
|
| 127 |
+
tz_result = result.dt.tz
|
| 128 |
+
assert str(tz_result) == "US/Eastern"
|
| 129 |
+
freq_result = ser.dt.freq
|
| 130 |
+
assert freq_result == DatetimeIndex(ser.values, freq="infer").freq
|
| 131 |
+
|
| 132 |
+
# let's localize, then convert
|
| 133 |
+
result = ser.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
|
| 134 |
+
exp_values = (
|
| 135 |
+
DatetimeIndex(ser.values).tz_localize("UTC").tz_convert("US/Eastern")
|
| 136 |
+
)
|
| 137 |
+
expected = Series(exp_values, index=ser.index, name="xxx")
|
| 138 |
+
tm.assert_series_equal(result, expected)
|
| 139 |
+
|
| 140 |
+
def test_dt_namespace_accessor_datetime64tz(self):
|
| 141 |
+
# GH#7207, GH#11128
|
| 142 |
+
# test .dt namespace accessor
|
| 143 |
+
|
| 144 |
+
# datetimeindex with tz
|
| 145 |
+
dti = date_range("20130101", periods=5, tz="US/Eastern")
|
| 146 |
+
ser = Series(dti, name="xxx")
|
| 147 |
+
for prop in ok_for_dt:
|
| 148 |
+
# we test freq below
|
| 149 |
+
if prop != "freq":
|
| 150 |
+
self._compare(ser, prop)
|
| 151 |
+
|
| 152 |
+
for prop in ok_for_dt_methods:
|
| 153 |
+
getattr(ser.dt, prop)
|
| 154 |
+
|
| 155 |
+
result = ser.dt.to_pydatetime()
|
| 156 |
+
assert isinstance(result, Series)
|
| 157 |
+
assert result.dtype == object
|
| 158 |
+
|
| 159 |
+
result = ser.dt.tz_convert("CET")
|
| 160 |
+
expected = Series(ser._values.tz_convert("CET"), index=ser.index, name="xxx")
|
| 161 |
+
tm.assert_series_equal(result, expected)
|
| 162 |
+
|
| 163 |
+
tz_result = result.dt.tz
|
| 164 |
+
assert str(tz_result) == "CET"
|
| 165 |
+
freq_result = ser.dt.freq
|
| 166 |
+
assert freq_result == DatetimeIndex(ser.values, freq="infer").freq
|
| 167 |
+
|
| 168 |
+
def test_dt_namespace_accessor_timedelta(self):
|
| 169 |
+
# GH#7207, GH#11128
|
| 170 |
+
# test .dt namespace accessor
|
| 171 |
+
|
| 172 |
+
# timedelta index
|
| 173 |
+
cases = [
|
| 174 |
+
Series(
|
| 175 |
+
timedelta_range("1 day", periods=5), index=list("abcde"), name="xxx"
|
| 176 |
+
),
|
| 177 |
+
Series(timedelta_range("1 day 01:23:45", periods=5, freq="s"), name="xxx"),
|
| 178 |
+
Series(
|
| 179 |
+
timedelta_range("2 days 01:23:45.012345", periods=5, freq="ms"),
|
| 180 |
+
name="xxx",
|
| 181 |
+
),
|
| 182 |
+
]
|
| 183 |
+
for ser in cases:
|
| 184 |
+
for prop in ok_for_td:
|
| 185 |
+
# we test freq below
|
| 186 |
+
if prop != "freq":
|
| 187 |
+
self._compare(ser, prop)
|
| 188 |
+
|
| 189 |
+
for prop in ok_for_td_methods:
|
| 190 |
+
getattr(ser.dt, prop)
|
| 191 |
+
|
| 192 |
+
result = ser.dt.components
|
| 193 |
+
assert isinstance(result, DataFrame)
|
| 194 |
+
tm.assert_index_equal(result.index, ser.index)
|
| 195 |
+
|
| 196 |
+
msg = "The behavior of TimedeltaProperties.to_pytimedelta is deprecated"
|
| 197 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 198 |
+
result = ser.dt.to_pytimedelta()
|
| 199 |
+
assert isinstance(result, np.ndarray)
|
| 200 |
+
assert result.dtype == object
|
| 201 |
+
|
| 202 |
+
result = ser.dt.total_seconds()
|
| 203 |
+
assert isinstance(result, Series)
|
| 204 |
+
assert result.dtype == "float64"
|
| 205 |
+
|
| 206 |
+
freq_result = ser.dt.freq
|
| 207 |
+
assert freq_result == TimedeltaIndex(ser.values, freq="infer").freq
|
| 208 |
+
|
| 209 |
+
def test_dt_namespace_accessor_period(self):
|
| 210 |
+
# GH#7207, GH#11128
|
| 211 |
+
# test .dt namespace accessor
|
| 212 |
+
|
| 213 |
+
# periodindex
|
| 214 |
+
pi = period_range("20130101", periods=5, freq="D")
|
| 215 |
+
ser = Series(pi, name="xxx")
|
| 216 |
+
|
| 217 |
+
for prop in ok_for_period:
|
| 218 |
+
# we test freq below
|
| 219 |
+
if prop != "freq":
|
| 220 |
+
self._compare(ser, prop)
|
| 221 |
+
|
| 222 |
+
for prop in ok_for_period_methods:
|
| 223 |
+
getattr(ser.dt, prop)
|
| 224 |
+
|
| 225 |
+
freq_result = ser.dt.freq
|
| 226 |
+
assert freq_result == PeriodIndex(ser.values).freq
|
| 227 |
+
|
| 228 |
+
def test_dt_namespace_accessor_index_and_values(self):
|
| 229 |
+
# both
|
| 230 |
+
index = date_range("20130101", periods=3, freq="D")
|
| 231 |
+
dti = date_range("20140204", periods=3, freq="s")
|
| 232 |
+
ser = Series(dti, index=index, name="xxx")
|
| 233 |
+
exp = Series(
|
| 234 |
+
np.array([2014, 2014, 2014], dtype="int32"), index=index, name="xxx"
|
| 235 |
+
)
|
| 236 |
+
tm.assert_series_equal(ser.dt.year, exp)
|
| 237 |
+
|
| 238 |
+
exp = Series(np.array([2, 2, 2], dtype="int32"), index=index, name="xxx")
|
| 239 |
+
tm.assert_series_equal(ser.dt.month, exp)
|
| 240 |
+
|
| 241 |
+
exp = Series(np.array([0, 1, 2], dtype="int32"), index=index, name="xxx")
|
| 242 |
+
tm.assert_series_equal(ser.dt.second, exp)
|
| 243 |
+
|
| 244 |
+
exp = Series([ser.iloc[0]] * 3, index=index, name="xxx")
|
| 245 |
+
tm.assert_series_equal(ser.dt.normalize(), exp)
|
| 246 |
+
|
| 247 |
+
def test_dt_accessor_limited_display_api(self):
|
| 248 |
+
# tznaive
|
| 249 |
+
ser = Series(date_range("20130101", periods=5, freq="D"), name="xxx")
|
| 250 |
+
results = get_dir(ser)
|
| 251 |
+
tm.assert_almost_equal(results, sorted(set(ok_for_dt + ok_for_dt_methods)))
|
| 252 |
+
|
| 253 |
+
# tzaware
|
| 254 |
+
ser = Series(date_range("2015-01-01", "2016-01-01", freq="min"), name="xxx")
|
| 255 |
+
ser = ser.dt.tz_localize("UTC").dt.tz_convert("America/Chicago")
|
| 256 |
+
results = get_dir(ser)
|
| 257 |
+
tm.assert_almost_equal(results, sorted(set(ok_for_dt + ok_for_dt_methods)))
|
| 258 |
+
|
| 259 |
+
# Period
|
| 260 |
+
idx = period_range("20130101", periods=5, freq="D", name="xxx")
|
| 261 |
+
ser = Series(idx)
|
| 262 |
+
results = get_dir(ser)
|
| 263 |
+
tm.assert_almost_equal(
|
| 264 |
+
results, sorted(set(ok_for_period + ok_for_period_methods))
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
def test_dt_accessor_ambiguous_freq_conversions(self):
|
| 268 |
+
# GH#11295
|
| 269 |
+
# ambiguous time error on the conversions
|
| 270 |
+
ser = Series(date_range("2015-01-01", "2016-01-01", freq="min"), name="xxx")
|
| 271 |
+
ser = ser.dt.tz_localize("UTC").dt.tz_convert("America/Chicago")
|
| 272 |
+
|
| 273 |
+
exp_values = date_range(
|
| 274 |
+
"2015-01-01", "2016-01-01", freq="min", tz="UTC"
|
| 275 |
+
).tz_convert("America/Chicago")
|
| 276 |
+
# freq not preserved by tz_localize above
|
| 277 |
+
exp_values = exp_values._with_freq(None)
|
| 278 |
+
expected = Series(exp_values, name="xxx")
|
| 279 |
+
tm.assert_series_equal(ser, expected)
|
| 280 |
+
|
| 281 |
+
def test_dt_accessor_not_writeable(self):
|
| 282 |
+
# no setting allowed
|
| 283 |
+
ser = Series(date_range("20130101", periods=5, freq="D"), name="xxx")
|
| 284 |
+
with pytest.raises(ValueError, match="modifications"):
|
| 285 |
+
ser.dt.hour = 5
|
| 286 |
+
|
| 287 |
+
# trying to set a copy
|
| 288 |
+
with tm.raises_chained_assignment_error():
|
| 289 |
+
ser.dt.hour[0] = 5
|
| 290 |
+
|
| 291 |
+
@pytest.mark.parametrize(
|
| 292 |
+
"method, dates",
|
| 293 |
+
[
|
| 294 |
+
["round", ["2012-01-02", "2012-01-02", "2012-01-01"]],
|
| 295 |
+
["floor", ["2012-01-01", "2012-01-01", "2012-01-01"]],
|
| 296 |
+
["ceil", ["2012-01-02", "2012-01-02", "2012-01-02"]],
|
| 297 |
+
],
|
| 298 |
+
)
|
| 299 |
+
def test_dt_round(self, method, dates):
|
| 300 |
+
# round
|
| 301 |
+
ser = Series(
|
| 302 |
+
pd.to_datetime(
|
| 303 |
+
["2012-01-01 13:00:00", "2012-01-01 12:01:00", "2012-01-01 08:00:00"]
|
| 304 |
+
),
|
| 305 |
+
name="xxx",
|
| 306 |
+
)
|
| 307 |
+
result = getattr(ser.dt, method)("D")
|
| 308 |
+
expected = Series(pd.to_datetime(dates), name="xxx")
|
| 309 |
+
tm.assert_series_equal(result, expected)
|
| 310 |
+
|
| 311 |
+
def test_dt_round_tz(self):
|
| 312 |
+
ser = Series(
|
| 313 |
+
pd.to_datetime(
|
| 314 |
+
["2012-01-01 13:00:00", "2012-01-01 12:01:00", "2012-01-01 08:00:00"]
|
| 315 |
+
),
|
| 316 |
+
name="xxx",
|
| 317 |
+
)
|
| 318 |
+
result = ser.dt.tz_localize("UTC").dt.tz_convert("US/Eastern").dt.round("D")
|
| 319 |
+
|
| 320 |
+
exp_values = pd.to_datetime(
|
| 321 |
+
["2012-01-01", "2012-01-01", "2012-01-01"]
|
| 322 |
+
).tz_localize("US/Eastern")
|
| 323 |
+
expected = Series(exp_values, name="xxx")
|
| 324 |
+
tm.assert_series_equal(result, expected)
|
| 325 |
+
|
| 326 |
+
@pytest.mark.parametrize("method", ["ceil", "round", "floor"])
|
| 327 |
+
def test_dt_round_tz_ambiguous(self, method):
|
| 328 |
+
# GH 18946 round near "fall back" DST
|
| 329 |
+
df1 = DataFrame(
|
| 330 |
+
[
|
| 331 |
+
pd.to_datetime("2017-10-29 02:00:00+02:00", utc=True),
|
| 332 |
+
pd.to_datetime("2017-10-29 02:00:00+01:00", utc=True),
|
| 333 |
+
pd.to_datetime("2017-10-29 03:00:00+01:00", utc=True),
|
| 334 |
+
],
|
| 335 |
+
columns=["date"],
|
| 336 |
+
)
|
| 337 |
+
df1["date"] = df1["date"].dt.tz_convert("Europe/Madrid")
|
| 338 |
+
# infer
|
| 339 |
+
result = getattr(df1.date.dt, method)("h", ambiguous="infer")
|
| 340 |
+
expected = df1["date"]
|
| 341 |
+
tm.assert_series_equal(result, expected)
|
| 342 |
+
|
| 343 |
+
# bool-array
|
| 344 |
+
result = getattr(df1.date.dt, method)("h", ambiguous=[True, False, False])
|
| 345 |
+
tm.assert_series_equal(result, expected)
|
| 346 |
+
|
| 347 |
+
# NaT
|
| 348 |
+
result = getattr(df1.date.dt, method)("h", ambiguous="NaT")
|
| 349 |
+
expected = df1["date"].copy()
|
| 350 |
+
expected.iloc[0:2] = pd.NaT
|
| 351 |
+
tm.assert_series_equal(result, expected)
|
| 352 |
+
|
| 353 |
+
# raise
|
| 354 |
+
with tm.external_error_raised(ValueError):
|
| 355 |
+
getattr(df1.date.dt, method)("h", ambiguous="raise")
|
| 356 |
+
|
| 357 |
+
@pytest.mark.parametrize(
|
| 358 |
+
"method, ts_str, freq",
|
| 359 |
+
[
|
| 360 |
+
["ceil", "2018-03-11 01:59:00-0600", "5min"],
|
| 361 |
+
["round", "2018-03-11 01:59:00-0600", "5min"],
|
| 362 |
+
["floor", "2018-03-11 03:01:00-0500", "2h"],
|
| 363 |
+
],
|
| 364 |
+
)
|
| 365 |
+
def test_dt_round_tz_nonexistent(self, method, ts_str, freq):
|
| 366 |
+
# GH 23324 round near "spring forward" DST
|
| 367 |
+
ser = Series([pd.Timestamp(ts_str, tz="America/Chicago").as_unit("s")])
|
| 368 |
+
result = getattr(ser.dt, method)(freq, nonexistent="shift_forward")
|
| 369 |
+
expected = Series(
|
| 370 |
+
[pd.Timestamp("2018-03-11 03:00:00", tz="America/Chicago").as_unit("s")]
|
| 371 |
+
)
|
| 372 |
+
tm.assert_series_equal(result, expected)
|
| 373 |
+
|
| 374 |
+
result = getattr(ser.dt, method)(freq, nonexistent="NaT")
|
| 375 |
+
expected = Series([pd.NaT]).dt.tz_localize(result.dt.tz)
|
| 376 |
+
tm.assert_series_equal(result, expected)
|
| 377 |
+
|
| 378 |
+
with pytest.raises(ValueError, match="2018-03-11 02:00:00"):
|
| 379 |
+
getattr(ser.dt, method)(freq, nonexistent="raise")
|
| 380 |
+
|
| 381 |
+
@pytest.mark.parametrize("freq", ["ns", "us", "1000us"])
|
| 382 |
+
def test_dt_round_nonnano_higher_resolution_no_op(self, freq):
|
| 383 |
+
# GH 52761
|
| 384 |
+
ser = Series(
|
| 385 |
+
["2020-05-31 08:00:00", "2000-12-31 04:00:05", "1800-03-14 07:30:20"],
|
| 386 |
+
dtype="datetime64[ms]",
|
| 387 |
+
)
|
| 388 |
+
expected = ser.copy()
|
| 389 |
+
result = ser.dt.round(freq)
|
| 390 |
+
tm.assert_series_equal(result, expected)
|
| 391 |
+
|
| 392 |
+
assert not np.shares_memory(ser.array._ndarray, result.array._ndarray)
|
| 393 |
+
|
| 394 |
+
def test_dt_namespace_accessor_categorical(self):
|
| 395 |
+
# GH 19468
|
| 396 |
+
dti = DatetimeIndex(["20171111", "20181212"]).repeat(2)
|
| 397 |
+
ser = Series(pd.Categorical(dti), name="foo")
|
| 398 |
+
result = ser.dt.year
|
| 399 |
+
expected = Series([2017, 2017, 2018, 2018], dtype="int32", name="foo")
|
| 400 |
+
tm.assert_series_equal(result, expected)
|
| 401 |
+
|
| 402 |
+
def test_dt_tz_localize_categorical(self, tz_aware_fixture):
|
| 403 |
+
# GH 27952
|
| 404 |
+
tz = tz_aware_fixture
|
| 405 |
+
datetimes = Series(
|
| 406 |
+
["2019-01-01", "2019-01-01", "2019-01-02"], dtype="datetime64[ns]"
|
| 407 |
+
)
|
| 408 |
+
categorical = datetimes.astype("category")
|
| 409 |
+
result = categorical.dt.tz_localize(tz)
|
| 410 |
+
expected = datetimes.dt.tz_localize(tz)
|
| 411 |
+
tm.assert_series_equal(result, expected)
|
| 412 |
+
|
| 413 |
+
def test_dt_tz_convert_categorical(self, tz_aware_fixture):
|
| 414 |
+
# GH 27952
|
| 415 |
+
tz = tz_aware_fixture
|
| 416 |
+
datetimes = Series(
|
| 417 |
+
["2019-01-01", "2019-01-01", "2019-01-02"], dtype="datetime64[ns, MET]"
|
| 418 |
+
)
|
| 419 |
+
categorical = datetimes.astype("category")
|
| 420 |
+
result = categorical.dt.tz_convert(tz)
|
| 421 |
+
expected = datetimes.dt.tz_convert(tz)
|
| 422 |
+
tm.assert_series_equal(result, expected)
|
| 423 |
+
|
| 424 |
+
@pytest.mark.parametrize("accessor", ["year", "month", "day"])
|
| 425 |
+
def test_dt_other_accessors_categorical(self, accessor):
|
| 426 |
+
# GH 27952
|
| 427 |
+
datetimes = Series(
|
| 428 |
+
["2018-01-01", "2018-01-01", "2019-01-02"], dtype="datetime64[ns]"
|
| 429 |
+
)
|
| 430 |
+
categorical = datetimes.astype("category")
|
| 431 |
+
result = getattr(categorical.dt, accessor)
|
| 432 |
+
expected = getattr(datetimes.dt, accessor)
|
| 433 |
+
tm.assert_series_equal(result, expected)
|
| 434 |
+
|
| 435 |
+
def test_dt_accessor_no_new_attributes(self):
|
| 436 |
+
# https://github.com/pandas-dev/pandas/issues/10673
|
| 437 |
+
ser = Series(date_range("20130101", periods=5, freq="D"))
|
| 438 |
+
with pytest.raises(AttributeError, match="You cannot add any new attribute"):
|
| 439 |
+
ser.dt.xlabel = "a"
|
| 440 |
+
|
| 441 |
+
@pytest.mark.parametrize(
|
| 442 |
+
"time_locale",
|
| 443 |
+
[None, *tm.get_locales()],
|
| 444 |
+
)
|
| 445 |
+
def test_dt_accessor_datetime_name_accessors(self, time_locale):
|
| 446 |
+
# Test Monday -> Sunday and January -> December, in that sequence
|
| 447 |
+
if time_locale is None:
|
| 448 |
+
# If the time_locale is None, day-name and month_name should
|
| 449 |
+
# return the english attributes
|
| 450 |
+
expected_days = [
|
| 451 |
+
"Monday",
|
| 452 |
+
"Tuesday",
|
| 453 |
+
"Wednesday",
|
| 454 |
+
"Thursday",
|
| 455 |
+
"Friday",
|
| 456 |
+
"Saturday",
|
| 457 |
+
"Sunday",
|
| 458 |
+
]
|
| 459 |
+
expected_months = [
|
| 460 |
+
"January",
|
| 461 |
+
"February",
|
| 462 |
+
"March",
|
| 463 |
+
"April",
|
| 464 |
+
"May",
|
| 465 |
+
"June",
|
| 466 |
+
"July",
|
| 467 |
+
"August",
|
| 468 |
+
"September",
|
| 469 |
+
"October",
|
| 470 |
+
"November",
|
| 471 |
+
"December",
|
| 472 |
+
]
|
| 473 |
+
else:
|
| 474 |
+
with tm.set_locale(time_locale, locale.LC_TIME):
|
| 475 |
+
expected_days = calendar.day_name[:]
|
| 476 |
+
expected_months = calendar.month_name[1:]
|
| 477 |
+
|
| 478 |
+
ser = Series(date_range(freq="D", start=datetime(1998, 1, 1), periods=365))
|
| 479 |
+
english_days = [
|
| 480 |
+
"Monday",
|
| 481 |
+
"Tuesday",
|
| 482 |
+
"Wednesday",
|
| 483 |
+
"Thursday",
|
| 484 |
+
"Friday",
|
| 485 |
+
"Saturday",
|
| 486 |
+
"Sunday",
|
| 487 |
+
]
|
| 488 |
+
for day, name, eng_name in zip(range(4, 11), expected_days, english_days):
|
| 489 |
+
name = name.capitalize()
|
| 490 |
+
assert ser.dt.day_name(locale=time_locale)[day] == name
|
| 491 |
+
assert ser.dt.day_name(locale=None)[day] == eng_name
|
| 492 |
+
ser = pd.concat([ser, Series([pd.NaT])])
|
| 493 |
+
assert np.isnan(ser.dt.day_name(locale=time_locale).iloc[-1])
|
| 494 |
+
|
| 495 |
+
ser = Series(date_range(freq="ME", start="2012", end="2013"))
|
| 496 |
+
result = ser.dt.month_name(locale=time_locale)
|
| 497 |
+
expected = Series([month.capitalize() for month in expected_months])
|
| 498 |
+
|
| 499 |
+
# work around https://github.com/pandas-dev/pandas/issues/22342
|
| 500 |
+
result = result.str.normalize("NFD")
|
| 501 |
+
expected = expected.str.normalize("NFD")
|
| 502 |
+
|
| 503 |
+
tm.assert_series_equal(result, expected)
|
| 504 |
+
|
| 505 |
+
for s_date, expected in zip(ser, expected_months):
|
| 506 |
+
result = s_date.month_name(locale=time_locale)
|
| 507 |
+
expected = expected.capitalize()
|
| 508 |
+
|
| 509 |
+
result = unicodedata.normalize("NFD", result)
|
| 510 |
+
expected = unicodedata.normalize("NFD", expected)
|
| 511 |
+
|
| 512 |
+
assert result == expected
|
| 513 |
+
|
| 514 |
+
ser = pd.concat([ser, Series([pd.NaT])])
|
| 515 |
+
assert np.isnan(ser.dt.month_name(locale=time_locale).iloc[-1])
|
| 516 |
+
|
| 517 |
+
def test_strftime(self):
|
| 518 |
+
# GH 10086
|
| 519 |
+
ser = Series(date_range("20130101", periods=5))
|
| 520 |
+
result = ser.dt.strftime("%Y/%m/%d")
|
| 521 |
+
expected = Series(
|
| 522 |
+
["2013/01/01", "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"]
|
| 523 |
+
)
|
| 524 |
+
tm.assert_series_equal(result, expected)
|
| 525 |
+
|
| 526 |
+
ser = Series(date_range("2015-02-03 11:22:33.4567", periods=5))
|
| 527 |
+
result = ser.dt.strftime("%Y/%m/%d %H-%M-%S")
|
| 528 |
+
expected = Series(
|
| 529 |
+
[
|
| 530 |
+
"2015/02/03 11-22-33",
|
| 531 |
+
"2015/02/04 11-22-33",
|
| 532 |
+
"2015/02/05 11-22-33",
|
| 533 |
+
"2015/02/06 11-22-33",
|
| 534 |
+
"2015/02/07 11-22-33",
|
| 535 |
+
]
|
| 536 |
+
)
|
| 537 |
+
tm.assert_series_equal(result, expected)
|
| 538 |
+
|
| 539 |
+
ser = Series(period_range("20130101", periods=5))
|
| 540 |
+
result = ser.dt.strftime("%Y/%m/%d")
|
| 541 |
+
expected = Series(
|
| 542 |
+
["2013/01/01", "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"]
|
| 543 |
+
)
|
| 544 |
+
tm.assert_series_equal(result, expected)
|
| 545 |
+
|
| 546 |
+
ser = Series(period_range("2015-02-03 11:22:33.4567", periods=5, freq="s"))
|
| 547 |
+
result = ser.dt.strftime("%Y/%m/%d %H-%M-%S")
|
| 548 |
+
expected = Series(
|
| 549 |
+
[
|
| 550 |
+
"2015/02/03 11-22-33",
|
| 551 |
+
"2015/02/03 11-22-34",
|
| 552 |
+
"2015/02/03 11-22-35",
|
| 553 |
+
"2015/02/03 11-22-36",
|
| 554 |
+
"2015/02/03 11-22-37",
|
| 555 |
+
]
|
| 556 |
+
)
|
| 557 |
+
tm.assert_series_equal(result, expected)
|
| 558 |
+
|
| 559 |
+
def test_strftime_dt64_days(self):
|
| 560 |
+
ser = Series(date_range("20130101", periods=5))
|
| 561 |
+
ser.iloc[0] = pd.NaT
|
| 562 |
+
result = ser.dt.strftime("%Y/%m/%d")
|
| 563 |
+
expected = Series(
|
| 564 |
+
[np.nan, "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"]
|
| 565 |
+
)
|
| 566 |
+
tm.assert_series_equal(result, expected)
|
| 567 |
+
|
| 568 |
+
datetime_index = date_range("20150301", periods=5)
|
| 569 |
+
result = datetime_index.strftime("%Y/%m/%d")
|
| 570 |
+
|
| 571 |
+
expected = Index(
|
| 572 |
+
["2015/03/01", "2015/03/02", "2015/03/03", "2015/03/04", "2015/03/05"],
|
| 573 |
+
)
|
| 574 |
+
# dtype may be S10 or U10 depending on python version
|
| 575 |
+
tm.assert_index_equal(result, expected)
|
| 576 |
+
|
| 577 |
+
def test_strftime_period_days(self, using_infer_string):
|
| 578 |
+
period_index = period_range("20150301", periods=5)
|
| 579 |
+
result = period_index.strftime("%Y/%m/%d")
|
| 580 |
+
expected = Index(
|
| 581 |
+
["2015/03/01", "2015/03/02", "2015/03/03", "2015/03/04", "2015/03/05"],
|
| 582 |
+
dtype="=U10",
|
| 583 |
+
)
|
| 584 |
+
if using_infer_string:
|
| 585 |
+
expected = expected.astype(StringDtype(na_value=np.nan))
|
| 586 |
+
tm.assert_index_equal(result, expected)
|
| 587 |
+
|
| 588 |
+
def test_strftime_dt64_microsecond_resolution(self):
|
| 589 |
+
ser = Series([datetime(2013, 1, 1, 2, 32, 59), datetime(2013, 1, 2, 14, 32, 1)])
|
| 590 |
+
result = ser.dt.strftime("%Y-%m-%d %H:%M:%S")
|
| 591 |
+
expected = Series(["2013-01-01 02:32:59", "2013-01-02 14:32:01"])
|
| 592 |
+
tm.assert_series_equal(result, expected)
|
| 593 |
+
|
| 594 |
+
def test_strftime_period_hours(self):
|
| 595 |
+
ser = Series(period_range("20130101", periods=4, freq="h"))
|
| 596 |
+
result = ser.dt.strftime("%Y/%m/%d %H:%M:%S")
|
| 597 |
+
expected = Series(
|
| 598 |
+
[
|
| 599 |
+
"2013/01/01 00:00:00",
|
| 600 |
+
"2013/01/01 01:00:00",
|
| 601 |
+
"2013/01/01 02:00:00",
|
| 602 |
+
"2013/01/01 03:00:00",
|
| 603 |
+
]
|
| 604 |
+
)
|
| 605 |
+
tm.assert_series_equal(result, expected)
|
| 606 |
+
|
| 607 |
+
def test_strftime_period_minutes(self):
|
| 608 |
+
ser = Series(period_range("20130101", periods=4, freq="ms"))
|
| 609 |
+
result = ser.dt.strftime("%Y/%m/%d %H:%M:%S.%l")
|
| 610 |
+
expected = Series(
|
| 611 |
+
[
|
| 612 |
+
"2013/01/01 00:00:00.000",
|
| 613 |
+
"2013/01/01 00:00:00.001",
|
| 614 |
+
"2013/01/01 00:00:00.002",
|
| 615 |
+
"2013/01/01 00:00:00.003",
|
| 616 |
+
]
|
| 617 |
+
)
|
| 618 |
+
tm.assert_series_equal(result, expected)
|
| 619 |
+
|
| 620 |
+
@pytest.mark.parametrize(
|
| 621 |
+
"data",
|
| 622 |
+
[
|
| 623 |
+
DatetimeIndex(["2019-01-01", pd.NaT]),
|
| 624 |
+
PeriodIndex(["2019-01-01", pd.NaT], dtype="period[D]"),
|
| 625 |
+
],
|
| 626 |
+
)
|
| 627 |
+
def test_strftime_nat(self, data):
|
| 628 |
+
# GH 29578
|
| 629 |
+
ser = Series(data)
|
| 630 |
+
result = ser.dt.strftime("%Y-%m-%d")
|
| 631 |
+
expected = Series(["2019-01-01", np.nan])
|
| 632 |
+
tm.assert_series_equal(result, expected)
|
| 633 |
+
|
| 634 |
+
@pytest.mark.parametrize(
|
| 635 |
+
"data", [DatetimeIndex([pd.NaT]), PeriodIndex([pd.NaT], dtype="period[D]")]
|
| 636 |
+
)
|
| 637 |
+
def test_strftime_all_nat(self, data):
|
| 638 |
+
# https://github.com/pandas-dev/pandas/issues/45858
|
| 639 |
+
ser = Series(data)
|
| 640 |
+
with tm.assert_produces_warning(None):
|
| 641 |
+
result = ser.dt.strftime("%Y-%m-%d")
|
| 642 |
+
expected = Series([np.nan], dtype="str")
|
| 643 |
+
tm.assert_series_equal(result, expected)
|
| 644 |
+
|
| 645 |
+
def test_valid_dt_with_missing_values(self):
|
| 646 |
+
# GH 8689
|
| 647 |
+
ser = Series(date_range("20130101", periods=5, freq="D"))
|
| 648 |
+
ser.iloc[2] = pd.NaT
|
| 649 |
+
|
| 650 |
+
for attr in ["microsecond", "nanosecond", "second", "minute", "hour", "day"]:
|
| 651 |
+
expected = getattr(ser.dt, attr).copy()
|
| 652 |
+
expected.iloc[2] = np.nan
|
| 653 |
+
result = getattr(ser.dt, attr)
|
| 654 |
+
tm.assert_series_equal(result, expected)
|
| 655 |
+
|
| 656 |
+
result = ser.dt.date
|
| 657 |
+
expected = Series(
|
| 658 |
+
[
|
| 659 |
+
date(2013, 1, 1),
|
| 660 |
+
date(2013, 1, 2),
|
| 661 |
+
pd.NaT,
|
| 662 |
+
date(2013, 1, 4),
|
| 663 |
+
date(2013, 1, 5),
|
| 664 |
+
],
|
| 665 |
+
dtype="object",
|
| 666 |
+
)
|
| 667 |
+
tm.assert_series_equal(result, expected)
|
| 668 |
+
|
| 669 |
+
result = ser.dt.time
|
| 670 |
+
expected = Series([time(0), time(0), pd.NaT, time(0), time(0)], dtype="object")
|
| 671 |
+
tm.assert_series_equal(result, expected)
|
| 672 |
+
|
| 673 |
+
def test_dt_accessor_api(self):
|
| 674 |
+
# GH 9322
|
| 675 |
+
from pandas.core.indexes.accessors import (
|
| 676 |
+
CombinedDatetimelikeProperties,
|
| 677 |
+
DatetimeProperties,
|
| 678 |
+
)
|
| 679 |
+
|
| 680 |
+
assert Series.dt is CombinedDatetimelikeProperties
|
| 681 |
+
|
| 682 |
+
ser = Series(date_range("2000-01-01", periods=3))
|
| 683 |
+
assert isinstance(ser.dt, DatetimeProperties)
|
| 684 |
+
|
| 685 |
+
@pytest.mark.parametrize(
|
| 686 |
+
"data",
|
| 687 |
+
[
|
| 688 |
+
np.arange(5),
|
| 689 |
+
list("abcde"),
|
| 690 |
+
np.random.default_rng(2).standard_normal(5),
|
| 691 |
+
],
|
| 692 |
+
)
|
| 693 |
+
def test_dt_accessor_invalid(self, data):
|
| 694 |
+
# GH#9322 check that series with incorrect dtypes don't have attr
|
| 695 |
+
ser = Series(data)
|
| 696 |
+
with pytest.raises(AttributeError, match="only use .dt accessor"):
|
| 697 |
+
ser.dt
|
| 698 |
+
assert not hasattr(ser, "dt")
|
| 699 |
+
|
| 700 |
+
def test_dt_accessor_updates_on_inplace(self):
|
| 701 |
+
ser = Series(date_range("2018-01-01", periods=10))
|
| 702 |
+
ser[2] = None
|
| 703 |
+
result = ser.fillna(pd.Timestamp("2018-01-01"), inplace=True)
|
| 704 |
+
assert result is ser
|
| 705 |
+
result = ser.dt.date
|
| 706 |
+
assert result[0] == result[2]
|
| 707 |
+
|
| 708 |
+
def test_date_tz(self):
|
| 709 |
+
# GH11757
|
| 710 |
+
rng = DatetimeIndex(
|
| 711 |
+
["2014-04-04 23:56", "2014-07-18 21:24", "2015-11-22 22:14"],
|
| 712 |
+
tz="US/Eastern",
|
| 713 |
+
)
|
| 714 |
+
ser = Series(rng)
|
| 715 |
+
expected = Series([date(2014, 4, 4), date(2014, 7, 18), date(2015, 11, 22)])
|
| 716 |
+
tm.assert_series_equal(ser.dt.date, expected)
|
| 717 |
+
tm.assert_series_equal(ser.apply(lambda x: x.date()), expected)
|
| 718 |
+
|
| 719 |
+
def test_dt_timetz_accessor(self, tz_naive_fixture):
|
| 720 |
+
# GH21358
|
| 721 |
+
tz = maybe_get_tz(tz_naive_fixture)
|
| 722 |
+
|
| 723 |
+
dtindex = DatetimeIndex(
|
| 724 |
+
["2014-04-04 23:56", "2014-07-18 21:24", "2015-11-22 22:14"], tz=tz
|
| 725 |
+
)
|
| 726 |
+
ser = Series(dtindex)
|
| 727 |
+
expected = Series(
|
| 728 |
+
[time(23, 56, tzinfo=tz), time(21, 24, tzinfo=tz), time(22, 14, tzinfo=tz)]
|
| 729 |
+
)
|
| 730 |
+
result = ser.dt.timetz
|
| 731 |
+
tm.assert_series_equal(result, expected)
|
| 732 |
+
|
| 733 |
+
@pytest.mark.parametrize(
|
| 734 |
+
"input_series, expected_output",
|
| 735 |
+
[
|
| 736 |
+
[["2020-01-01"], [[2020, 1, 3]]],
|
| 737 |
+
[[pd.NaT], [[None, None, None]]],
|
| 738 |
+
[["2019-12-31", "2019-12-29"], [[2020, 1, 2], [2019, 52, 7]]],
|
| 739 |
+
[["2010-01-01", pd.NaT], [[2009, 53, 5], [None, None, None]]],
|
| 740 |
+
# see GH#36032
|
| 741 |
+
[["2016-01-08", "2016-01-04"], [[2016, 1, 5], [2016, 1, 1]]],
|
| 742 |
+
[["2016-01-07", "2016-01-01"], [[2016, 1, 4], [2015, 53, 5]]],
|
| 743 |
+
],
|
| 744 |
+
)
|
| 745 |
+
def test_isocalendar(self, input_series, expected_output):
|
| 746 |
+
result = pd.to_datetime(Series(input_series)).dt.isocalendar()
|
| 747 |
+
expected_frame = DataFrame(
|
| 748 |
+
expected_output, columns=["year", "week", "day"], dtype="UInt32"
|
| 749 |
+
)
|
| 750 |
+
tm.assert_frame_equal(result, expected_frame)
|
| 751 |
+
|
| 752 |
+
def test_hour_index(self):
|
| 753 |
+
dt_series = Series(
|
| 754 |
+
date_range(start="2021-01-01", periods=5, freq="h"),
|
| 755 |
+
index=[2, 6, 7, 8, 11],
|
| 756 |
+
dtype="category",
|
| 757 |
+
)
|
| 758 |
+
result = dt_series.dt.hour
|
| 759 |
+
expected = Series(
|
| 760 |
+
[0, 1, 2, 3, 4],
|
| 761 |
+
dtype="int32",
|
| 762 |
+
index=[2, 6, 7, 8, 11],
|
| 763 |
+
)
|
| 764 |
+
tm.assert_series_equal(result, expected)
|
| 765 |
+
|
| 766 |
+
|
| 767 |
+
class TestSeriesPeriodValuesDtAccessor:
|
| 768 |
+
@pytest.mark.parametrize(
|
| 769 |
+
"input_vals",
|
| 770 |
+
[
|
| 771 |
+
[Period("2016-01", freq="M"), Period("2016-02", freq="M")],
|
| 772 |
+
[Period("2016-01-01", freq="D"), Period("2016-01-02", freq="D")],
|
| 773 |
+
[
|
| 774 |
+
Period("2016-01-01 00:00:00", freq="h"),
|
| 775 |
+
Period("2016-01-01 01:00:00", freq="h"),
|
| 776 |
+
],
|
| 777 |
+
[
|
| 778 |
+
Period("2016-01-01 00:00:00", freq="M"),
|
| 779 |
+
Period("2016-01-01 00:01:00", freq="M"),
|
| 780 |
+
],
|
| 781 |
+
[
|
| 782 |
+
Period("2016-01-01 00:00:00", freq="s"),
|
| 783 |
+
Period("2016-01-01 00:00:01", freq="s"),
|
| 784 |
+
],
|
| 785 |
+
],
|
| 786 |
+
)
|
| 787 |
+
def test_end_time_timevalues(self, input_vals):
|
| 788 |
+
# GH#17157
|
| 789 |
+
# Check that the time part of the Period is adjusted by end_time
|
| 790 |
+
# when using the dt accessor on a Series
|
| 791 |
+
dtype = pd.PeriodDtype(input_vals[0].freq)
|
| 792 |
+
input_vals = PeriodArray._from_sequence(np.asarray(input_vals), dtype=dtype)
|
| 793 |
+
|
| 794 |
+
ser = Series(input_vals)
|
| 795 |
+
result = ser.dt.end_time
|
| 796 |
+
expected = ser.apply(lambda x: x.end_time)
|
| 797 |
+
tm.assert_series_equal(result, expected)
|
| 798 |
+
|
| 799 |
+
@pytest.mark.parametrize("input_vals", [("2001"), ("NaT")])
|
| 800 |
+
def test_to_period(self, input_vals):
|
| 801 |
+
# GH#21205
|
| 802 |
+
expected = Series([input_vals], dtype="Period[D]")
|
| 803 |
+
result = Series([input_vals], dtype="datetime64[ns]").dt.to_period("D")
|
| 804 |
+
tm.assert_series_equal(result, expected)
|
| 805 |
+
|
| 806 |
+
|
| 807 |
+
def test_normalize_pre_epoch_dates():
|
| 808 |
+
# GH: 36294
|
| 809 |
+
ser = pd.to_datetime(Series(["1969-01-01 09:00:00", "2016-01-01 09:00:00"]))
|
| 810 |
+
result = ser.dt.normalize()
|
| 811 |
+
expected = pd.to_datetime(Series(["1969-01-01", "2016-01-01"]))
|
| 812 |
+
tm.assert_series_equal(result, expected)
|
| 813 |
+
|
| 814 |
+
|
| 815 |
+
def test_day_attribute_non_nano_beyond_int32():
|
| 816 |
+
# GH 52386
|
| 817 |
+
data = np.array(
|
| 818 |
+
[
|
| 819 |
+
136457654736252,
|
| 820 |
+
134736784364431,
|
| 821 |
+
245345345545332,
|
| 822 |
+
223432411,
|
| 823 |
+
2343241,
|
| 824 |
+
3634548734,
|
| 825 |
+
23234,
|
| 826 |
+
],
|
| 827 |
+
dtype="timedelta64[s]",
|
| 828 |
+
)
|
| 829 |
+
ser = Series(data)
|
| 830 |
+
result = ser.dt.days
|
| 831 |
+
expected = Series([1579371003, 1559453522, 2839645203, 2586, 27, 42066, 0])
|
| 832 |
+
tm.assert_series_equal(result, expected)
|
.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_list_accessor.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
from pandas import (
|
| 6 |
+
ArrowDtype,
|
| 7 |
+
Series,
|
| 8 |
+
)
|
| 9 |
+
import pandas._testing as tm
|
| 10 |
+
|
| 11 |
+
pa = pytest.importorskip("pyarrow")
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@pytest.mark.parametrize(
|
| 15 |
+
"list_dtype",
|
| 16 |
+
(
|
| 17 |
+
pa.list_(pa.int64()),
|
| 18 |
+
pa.list_(pa.int64(), list_size=3),
|
| 19 |
+
pa.large_list(pa.int64()),
|
| 20 |
+
),
|
| 21 |
+
)
|
| 22 |
+
def test_list_getitem(list_dtype):
|
| 23 |
+
ser = Series(
|
| 24 |
+
[[1, 2, 3], [4, None, 5], None],
|
| 25 |
+
dtype=ArrowDtype(list_dtype),
|
| 26 |
+
name="a",
|
| 27 |
+
)
|
| 28 |
+
actual = ser.list[1]
|
| 29 |
+
expected = Series([2, None, None], dtype="int64[pyarrow]", name="a")
|
| 30 |
+
tm.assert_series_equal(actual, expected)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def test_list_getitem_index():
|
| 34 |
+
# GH 58425
|
| 35 |
+
ser = Series(
|
| 36 |
+
[[1, 2, 3], [4, None, 5], None],
|
| 37 |
+
dtype=ArrowDtype(pa.list_(pa.int64())),
|
| 38 |
+
index=[1, 3, 7],
|
| 39 |
+
name="a",
|
| 40 |
+
)
|
| 41 |
+
actual = ser.list[1]
|
| 42 |
+
expected = Series(
|
| 43 |
+
[2, None, None],
|
| 44 |
+
dtype="int64[pyarrow]",
|
| 45 |
+
index=[1, 3, 7],
|
| 46 |
+
name="a",
|
| 47 |
+
)
|
| 48 |
+
tm.assert_series_equal(actual, expected)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_list_getitem_slice():
|
| 52 |
+
ser = Series(
|
| 53 |
+
[[1, 2, 3], [4, None, 5], None],
|
| 54 |
+
dtype=ArrowDtype(pa.list_(pa.int64())),
|
| 55 |
+
index=[1, 3, 7],
|
| 56 |
+
name="a",
|
| 57 |
+
)
|
| 58 |
+
actual = ser.list[1:None:None]
|
| 59 |
+
expected = Series(
|
| 60 |
+
[[2, 3], [None, 5], None],
|
| 61 |
+
dtype=ArrowDtype(pa.list_(pa.int64())),
|
| 62 |
+
index=[1, 3, 7],
|
| 63 |
+
name="a",
|
| 64 |
+
)
|
| 65 |
+
tm.assert_series_equal(actual, expected)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def test_list_len():
|
| 69 |
+
ser = Series(
|
| 70 |
+
[[1, 2, 3], [4, None], None],
|
| 71 |
+
dtype=ArrowDtype(pa.list_(pa.int64())),
|
| 72 |
+
name="a",
|
| 73 |
+
)
|
| 74 |
+
actual = ser.list.len()
|
| 75 |
+
expected = Series([3, 2, None], dtype=ArrowDtype(pa.int32()), name="a")
|
| 76 |
+
tm.assert_series_equal(actual, expected)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def test_list_flatten():
|
| 80 |
+
ser = Series(
|
| 81 |
+
[[1, 2, 3], None, [4, None], [], [7, 8]],
|
| 82 |
+
dtype=ArrowDtype(pa.list_(pa.int64())),
|
| 83 |
+
name="a",
|
| 84 |
+
)
|
| 85 |
+
actual = ser.list.flatten()
|
| 86 |
+
expected = Series(
|
| 87 |
+
[1, 2, 3, 4, None, 7, 8],
|
| 88 |
+
dtype=ArrowDtype(pa.int64()),
|
| 89 |
+
index=[0, 0, 0, 2, 2, 4, 4],
|
| 90 |
+
name="a",
|
| 91 |
+
)
|
| 92 |
+
tm.assert_series_equal(actual, expected)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def test_list_getitem_slice_invalid():
|
| 96 |
+
ser = Series(
|
| 97 |
+
[[1, 2, 3], [4, None, 5], None],
|
| 98 |
+
dtype=ArrowDtype(pa.list_(pa.int64())),
|
| 99 |
+
)
|
| 100 |
+
with tm.external_error_raised(pa.ArrowInvalid):
|
| 101 |
+
ser.list[1:None:0]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def test_list_accessor_non_list_dtype():
|
| 105 |
+
ser = Series(
|
| 106 |
+
[1, 2, 4],
|
| 107 |
+
dtype=ArrowDtype(pa.int64()),
|
| 108 |
+
)
|
| 109 |
+
with pytest.raises(
|
| 110 |
+
AttributeError,
|
| 111 |
+
match=re.escape(
|
| 112 |
+
"Can only use the '.list' accessor with 'list[pyarrow]' dtype, "
|
| 113 |
+
"not int64[pyarrow]."
|
| 114 |
+
),
|
| 115 |
+
):
|
| 116 |
+
ser.list[1:None:0]
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
@pytest.mark.parametrize(
|
| 120 |
+
"list_dtype",
|
| 121 |
+
(
|
| 122 |
+
pa.list_(pa.int64()),
|
| 123 |
+
pa.list_(pa.int64(), list_size=3),
|
| 124 |
+
pa.large_list(pa.int64()),
|
| 125 |
+
),
|
| 126 |
+
)
|
| 127 |
+
def test_list_getitem_invalid_index(list_dtype):
|
| 128 |
+
ser = Series(
|
| 129 |
+
[[1, 2, 3], [4, None, 5], None],
|
| 130 |
+
dtype=ArrowDtype(list_dtype),
|
| 131 |
+
)
|
| 132 |
+
with tm.external_error_raised(pa.ArrowInvalid):
|
| 133 |
+
ser.list[-1]
|
| 134 |
+
with tm.external_error_raised(pa.ArrowInvalid):
|
| 135 |
+
ser.list[5]
|
| 136 |
+
with pytest.raises(ValueError, match="key must be an int or slice, got str"):
|
| 137 |
+
ser.list["abc"]
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def test_list_accessor_not_iterable():
|
| 141 |
+
ser = Series(
|
| 142 |
+
[[1, 2, 3], [4, None], None],
|
| 143 |
+
dtype=ArrowDtype(pa.list_(pa.int64())),
|
| 144 |
+
)
|
| 145 |
+
with pytest.raises(TypeError, match="'ListAccessor' object is not iterable"):
|
| 146 |
+
iter(ser.list)
|
.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pandas import Series
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class TestSparseAccessor:
|
| 5 |
+
def test_sparse_accessor_updates_on_inplace(self):
|
| 6 |
+
ser = Series([1, 1, 2, 3], dtype="Sparse[int]")
|
| 7 |
+
return_value = ser.drop([0, 1], inplace=True)
|
| 8 |
+
assert return_value is None
|
| 9 |
+
assert ser.sparse.density == 1.0
|
.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_str_accessor.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas import Series
|
| 4 |
+
import pandas._testing as tm
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TestStrAccessor:
|
| 8 |
+
def test_str_attribute(self):
|
| 9 |
+
# GH#9068
|
| 10 |
+
methods = ["strip", "rstrip", "lstrip"]
|
| 11 |
+
ser = Series([" jack", "jill ", " jesse ", "frank"])
|
| 12 |
+
for method in methods:
|
| 13 |
+
expected = Series([getattr(str, method)(x) for x in ser.values])
|
| 14 |
+
tm.assert_series_equal(getattr(Series.str, method)(ser.str), expected)
|
| 15 |
+
|
| 16 |
+
# str accessor only valid with string values
|
| 17 |
+
ser = Series(range(5))
|
| 18 |
+
msg = "Can only use .str accessor with string values, not integer"
|
| 19 |
+
with pytest.raises(AttributeError, match=msg):
|
| 20 |
+
ser.str.repeat(2)
|
| 21 |
+
|
| 22 |
+
def test_str_accessor_updates_on_inplace(self):
|
| 23 |
+
ser = Series(list("abc"))
|
| 24 |
+
return_value = ser.drop([0], inplace=True)
|
| 25 |
+
assert return_value is None
|
| 26 |
+
assert len(ser.str.lower()) == 2
|
.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_struct_accessor.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
from pandas import (
|
| 6 |
+
ArrowDtype,
|
| 7 |
+
DataFrame,
|
| 8 |
+
Index,
|
| 9 |
+
Series,
|
| 10 |
+
)
|
| 11 |
+
import pandas._testing as tm
|
| 12 |
+
|
| 13 |
+
pa = pytest.importorskip("pyarrow")
|
| 14 |
+
pc = pytest.importorskip("pyarrow.compute")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_struct_accessor_dtypes():
|
| 18 |
+
ser = Series(
|
| 19 |
+
[],
|
| 20 |
+
dtype=ArrowDtype(
|
| 21 |
+
pa.struct(
|
| 22 |
+
[
|
| 23 |
+
("int_col", pa.int64()),
|
| 24 |
+
("string_col", pa.string()),
|
| 25 |
+
(
|
| 26 |
+
"struct_col",
|
| 27 |
+
pa.struct(
|
| 28 |
+
[
|
| 29 |
+
("int_col", pa.int64()),
|
| 30 |
+
("float_col", pa.float64()),
|
| 31 |
+
]
|
| 32 |
+
),
|
| 33 |
+
),
|
| 34 |
+
]
|
| 35 |
+
)
|
| 36 |
+
),
|
| 37 |
+
)
|
| 38 |
+
actual = ser.struct.dtypes
|
| 39 |
+
expected = Series(
|
| 40 |
+
[
|
| 41 |
+
ArrowDtype(pa.int64()),
|
| 42 |
+
ArrowDtype(pa.string()),
|
| 43 |
+
ArrowDtype(
|
| 44 |
+
pa.struct(
|
| 45 |
+
[
|
| 46 |
+
("int_col", pa.int64()),
|
| 47 |
+
("float_col", pa.float64()),
|
| 48 |
+
]
|
| 49 |
+
)
|
| 50 |
+
),
|
| 51 |
+
],
|
| 52 |
+
index=Index(["int_col", "string_col", "struct_col"]),
|
| 53 |
+
)
|
| 54 |
+
tm.assert_series_equal(actual, expected)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def test_struct_accessor_field():
|
| 58 |
+
index = Index([-100, 42, 123])
|
| 59 |
+
ser = Series(
|
| 60 |
+
[
|
| 61 |
+
{"rice": 1.0, "maize": -1, "wheat": "a"},
|
| 62 |
+
{"rice": 2.0, "maize": 0, "wheat": "b"},
|
| 63 |
+
{"rice": 3.0, "maize": 1, "wheat": "c"},
|
| 64 |
+
],
|
| 65 |
+
dtype=ArrowDtype(
|
| 66 |
+
pa.struct(
|
| 67 |
+
[
|
| 68 |
+
("rice", pa.float64()),
|
| 69 |
+
("maize", pa.int64()),
|
| 70 |
+
("wheat", pa.string()),
|
| 71 |
+
]
|
| 72 |
+
)
|
| 73 |
+
),
|
| 74 |
+
index=index,
|
| 75 |
+
)
|
| 76 |
+
by_name = ser.struct.field("maize")
|
| 77 |
+
by_name_expected = Series(
|
| 78 |
+
[-1, 0, 1],
|
| 79 |
+
dtype=ArrowDtype(pa.int64()),
|
| 80 |
+
index=index,
|
| 81 |
+
name="maize",
|
| 82 |
+
)
|
| 83 |
+
tm.assert_series_equal(by_name, by_name_expected)
|
| 84 |
+
|
| 85 |
+
by_index = ser.struct.field(2)
|
| 86 |
+
by_index_expected = Series(
|
| 87 |
+
["a", "b", "c"],
|
| 88 |
+
dtype=ArrowDtype(pa.string()),
|
| 89 |
+
index=index,
|
| 90 |
+
name="wheat",
|
| 91 |
+
)
|
| 92 |
+
tm.assert_series_equal(by_index, by_index_expected)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def test_struct_accessor_field_with_invalid_name_or_index():
|
| 96 |
+
ser = Series([], dtype=ArrowDtype(pa.struct([("field", pa.int64())])))
|
| 97 |
+
|
| 98 |
+
with pytest.raises(ValueError, match="name_or_index must be an int, str,"):
|
| 99 |
+
ser.struct.field(1.1)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def test_struct_accessor_explode():
|
| 103 |
+
index = Index([-100, 42, 123])
|
| 104 |
+
ser = Series(
|
| 105 |
+
[
|
| 106 |
+
{"painted": 1, "snapping": {"sea": "green"}},
|
| 107 |
+
{"painted": 2, "snapping": {"sea": "leatherback"}},
|
| 108 |
+
{"painted": 3, "snapping": {"sea": "hawksbill"}},
|
| 109 |
+
],
|
| 110 |
+
dtype=ArrowDtype(
|
| 111 |
+
pa.struct(
|
| 112 |
+
[
|
| 113 |
+
("painted", pa.int64()),
|
| 114 |
+
("snapping", pa.struct([("sea", pa.string())])),
|
| 115 |
+
]
|
| 116 |
+
)
|
| 117 |
+
),
|
| 118 |
+
index=index,
|
| 119 |
+
)
|
| 120 |
+
actual = ser.struct.explode()
|
| 121 |
+
expected = DataFrame(
|
| 122 |
+
{
|
| 123 |
+
"painted": Series([1, 2, 3], index=index, dtype=ArrowDtype(pa.int64())),
|
| 124 |
+
"snapping": Series(
|
| 125 |
+
[{"sea": "green"}, {"sea": "leatherback"}, {"sea": "hawksbill"}],
|
| 126 |
+
index=index,
|
| 127 |
+
dtype=ArrowDtype(pa.struct([("sea", pa.string())])),
|
| 128 |
+
),
|
| 129 |
+
},
|
| 130 |
+
)
|
| 131 |
+
tm.assert_frame_equal(actual, expected)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@pytest.mark.parametrize(
|
| 135 |
+
"invalid",
|
| 136 |
+
[
|
| 137 |
+
pytest.param(Series([1, 2, 3], dtype="int64"), id="int64"),
|
| 138 |
+
pytest.param(
|
| 139 |
+
Series(["a", "b", "c"], dtype="string[pyarrow]"), id="string-pyarrow"
|
| 140 |
+
),
|
| 141 |
+
],
|
| 142 |
+
)
|
| 143 |
+
def test_struct_accessor_api_for_invalid(invalid):
|
| 144 |
+
with pytest.raises(
|
| 145 |
+
AttributeError,
|
| 146 |
+
match=re.escape(
|
| 147 |
+
"Can only use the '.struct' accessor with 'struct[pyarrow]' dtype, "
|
| 148 |
+
f"not {invalid.dtype}."
|
| 149 |
+
),
|
| 150 |
+
):
|
| 151 |
+
invalid.struct
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@pytest.mark.parametrize(
|
| 155 |
+
["indices", "name"],
|
| 156 |
+
[
|
| 157 |
+
(0, "int_col"),
|
| 158 |
+
([1, 2], "str_col"),
|
| 159 |
+
(pc.field("int_col"), "int_col"),
|
| 160 |
+
("int_col", "int_col"),
|
| 161 |
+
(b"string_col", b"string_col"),
|
| 162 |
+
([b"string_col"], "string_col"),
|
| 163 |
+
],
|
| 164 |
+
)
|
| 165 |
+
def test_struct_accessor_field_expanded(indices, name):
|
| 166 |
+
arrow_type = pa.struct(
|
| 167 |
+
[
|
| 168 |
+
("int_col", pa.int64()),
|
| 169 |
+
(
|
| 170 |
+
"struct_col",
|
| 171 |
+
pa.struct(
|
| 172 |
+
[
|
| 173 |
+
("int_col", pa.int64()),
|
| 174 |
+
("float_col", pa.float64()),
|
| 175 |
+
("str_col", pa.string()),
|
| 176 |
+
]
|
| 177 |
+
),
|
| 178 |
+
),
|
| 179 |
+
(b"string_col", pa.string()),
|
| 180 |
+
]
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
data = pa.array([], type=arrow_type)
|
| 184 |
+
ser = Series(data, dtype=ArrowDtype(arrow_type))
|
| 185 |
+
expected = pc.struct_field(data, indices)
|
| 186 |
+
result = ser.struct.field(indices)
|
| 187 |
+
tm.assert_equal(result.array._pa_array.combine_chunks(), expected)
|
| 188 |
+
assert result.name == name
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_datetime.py
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Also test support for datetime64[ns] in Series / DataFrame
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from datetime import (
|
| 6 |
+
datetime,
|
| 7 |
+
timedelta,
|
| 8 |
+
)
|
| 9 |
+
import re
|
| 10 |
+
|
| 11 |
+
from dateutil.tz import (
|
| 12 |
+
gettz,
|
| 13 |
+
tzutc,
|
| 14 |
+
)
|
| 15 |
+
import numpy as np
|
| 16 |
+
import pytest
|
| 17 |
+
|
| 18 |
+
from pandas._libs import index as libindex
|
| 19 |
+
|
| 20 |
+
import pandas as pd
|
| 21 |
+
from pandas import (
|
| 22 |
+
DataFrame,
|
| 23 |
+
Series,
|
| 24 |
+
Timestamp,
|
| 25 |
+
date_range,
|
| 26 |
+
period_range,
|
| 27 |
+
)
|
| 28 |
+
import pandas._testing as tm
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_fancy_getitem():
|
| 32 |
+
dti = date_range(
|
| 33 |
+
freq="WOM-1FRI", start=datetime(2005, 1, 1), end=datetime(2010, 1, 1)
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
s = Series(np.arange(len(dti)), index=dti)
|
| 37 |
+
|
| 38 |
+
assert s["1/2/2009"] == 48
|
| 39 |
+
assert s["2009-1-2"] == 48
|
| 40 |
+
assert s[datetime(2009, 1, 2)] == 48
|
| 41 |
+
assert s[Timestamp(datetime(2009, 1, 2))] == 48
|
| 42 |
+
with pytest.raises(KeyError, match=r"^'2009-1-3'$"):
|
| 43 |
+
s["2009-1-3"]
|
| 44 |
+
tm.assert_series_equal(
|
| 45 |
+
s["3/6/2009":"2009-06-05"], s[datetime(2009, 3, 6) : datetime(2009, 6, 5)]
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def test_fancy_setitem():
|
| 50 |
+
dti = date_range(
|
| 51 |
+
freq="WOM-1FRI", start=datetime(2005, 1, 1), end=datetime(2010, 1, 1)
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
s = Series(np.arange(len(dti)), index=dti)
|
| 55 |
+
|
| 56 |
+
s["1/2/2009"] = -2
|
| 57 |
+
assert s.iloc[48] == -2
|
| 58 |
+
s["1/2/2009":"2009-06-05"] = -3
|
| 59 |
+
assert (s[48:54] == -3).all()
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@pytest.mark.parametrize("tz_source", ["pytz", "dateutil"])
|
| 63 |
+
def test_getitem_setitem_datetime_tz(tz_source):
|
| 64 |
+
if tz_source == "pytz":
|
| 65 |
+
pytz = pytest.importorskip(tz_source)
|
| 66 |
+
tzget = pytz.timezone
|
| 67 |
+
else:
|
| 68 |
+
# handle special case for utc in dateutil
|
| 69 |
+
tzget = lambda x: tzutc() if x == "UTC" else gettz(x)
|
| 70 |
+
|
| 71 |
+
N = 50
|
| 72 |
+
# testing with timezone, GH #2785
|
| 73 |
+
rng = date_range("1/1/1990", periods=N, freq="h", tz=tzget("US/Eastern"))
|
| 74 |
+
ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
|
| 75 |
+
|
| 76 |
+
# also test Timestamp tz handling, GH #2789
|
| 77 |
+
result = ts.copy()
|
| 78 |
+
result["1990-01-01 09:00:00+00:00"] = 0
|
| 79 |
+
result["1990-01-01 09:00:00+00:00"] = ts.iloc[4]
|
| 80 |
+
tm.assert_series_equal(result, ts)
|
| 81 |
+
|
| 82 |
+
result = ts.copy()
|
| 83 |
+
result["1990-01-01 03:00:00-06:00"] = 0
|
| 84 |
+
result["1990-01-01 03:00:00-06:00"] = ts.iloc[4]
|
| 85 |
+
tm.assert_series_equal(result, ts)
|
| 86 |
+
|
| 87 |
+
# repeat with datetimes
|
| 88 |
+
result = ts.copy()
|
| 89 |
+
result[datetime(1990, 1, 1, 9, tzinfo=tzget("UTC"))] = 0
|
| 90 |
+
result[datetime(1990, 1, 1, 9, tzinfo=tzget("UTC"))] = ts.iloc[4]
|
| 91 |
+
tm.assert_series_equal(result, ts)
|
| 92 |
+
|
| 93 |
+
result = ts.copy()
|
| 94 |
+
dt = Timestamp(1990, 1, 1, 3).tz_localize(tzget("US/Central"))
|
| 95 |
+
dt = dt.to_pydatetime()
|
| 96 |
+
result[dt] = 0
|
| 97 |
+
result[dt] = ts.iloc[4]
|
| 98 |
+
tm.assert_series_equal(result, ts)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def test_getitem_setitem_datetimeindex():
|
| 102 |
+
N = 50
|
| 103 |
+
# testing with timezone, GH #2785
|
| 104 |
+
rng = date_range("1/1/1990", periods=N, freq="h", tz="US/Eastern", unit="ns")
|
| 105 |
+
ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
|
| 106 |
+
|
| 107 |
+
result = ts["1990-01-01 04:00:00"]
|
| 108 |
+
expected = ts.iloc[4]
|
| 109 |
+
assert result == expected
|
| 110 |
+
|
| 111 |
+
result = ts.copy()
|
| 112 |
+
result["1990-01-01 04:00:00"] = 0
|
| 113 |
+
result["1990-01-01 04:00:00"] = ts.iloc[4]
|
| 114 |
+
tm.assert_series_equal(result, ts)
|
| 115 |
+
|
| 116 |
+
result = ts["1990-01-01 04:00:00":"1990-01-01 07:00:00"]
|
| 117 |
+
expected = ts[4:8]
|
| 118 |
+
tm.assert_series_equal(result, expected)
|
| 119 |
+
|
| 120 |
+
result = ts.copy()
|
| 121 |
+
result["1990-01-01 04:00:00":"1990-01-01 07:00:00"] = 0
|
| 122 |
+
result["1990-01-01 04:00:00":"1990-01-01 07:00:00"] = ts[4:8]
|
| 123 |
+
tm.assert_series_equal(result, ts)
|
| 124 |
+
|
| 125 |
+
lb = "1990-01-01 04:00:00"
|
| 126 |
+
rb = "1990-01-01 07:00:00"
|
| 127 |
+
# GH#18435 strings get a pass from tzawareness compat
|
| 128 |
+
result = ts[(ts.index >= lb) & (ts.index <= rb)]
|
| 129 |
+
expected = ts[4:8]
|
| 130 |
+
tm.assert_series_equal(result, expected)
|
| 131 |
+
|
| 132 |
+
lb = "1990-01-01 04:00:00-0500"
|
| 133 |
+
rb = "1990-01-01 07:00:00-0500"
|
| 134 |
+
result = ts[(ts.index >= lb) & (ts.index <= rb)]
|
| 135 |
+
expected = ts[4:8]
|
| 136 |
+
tm.assert_series_equal(result, expected)
|
| 137 |
+
|
| 138 |
+
# But we do not give datetimes a pass on tzawareness compat
|
| 139 |
+
msg = "Cannot compare tz-naive and tz-aware datetime-like objects"
|
| 140 |
+
naive = datetime(1990, 1, 1, 4)
|
| 141 |
+
for key in [naive, Timestamp(naive), np.datetime64(naive, "ns")]:
|
| 142 |
+
with pytest.raises(KeyError, match=re.escape(repr(key))):
|
| 143 |
+
# GH#36148 as of 2.0 we require tzawareness-compat
|
| 144 |
+
ts[key]
|
| 145 |
+
|
| 146 |
+
result = ts.copy()
|
| 147 |
+
# GH#36148 as of 2.0 we do not ignore tzawareness mismatch in indexing,
|
| 148 |
+
# so setting it as a new key casts to object rather than matching
|
| 149 |
+
# rng[4]
|
| 150 |
+
result[naive] = ts.iloc[4]
|
| 151 |
+
assert result.index.dtype == object
|
| 152 |
+
tm.assert_index_equal(result.index[:-1], rng.astype(object))
|
| 153 |
+
assert result.index[-1] == naive
|
| 154 |
+
|
| 155 |
+
msg = "Cannot compare tz-naive and tz-aware datetime-like objects"
|
| 156 |
+
with pytest.raises(TypeError, match=msg):
|
| 157 |
+
# GH#36148 require tzawareness compat as of 2.0
|
| 158 |
+
ts[naive : datetime(1990, 1, 1, 7)]
|
| 159 |
+
|
| 160 |
+
result = ts.copy()
|
| 161 |
+
with pytest.raises(TypeError, match=msg):
|
| 162 |
+
# GH#36148 require tzawareness compat as of 2.0
|
| 163 |
+
result[naive : datetime(1990, 1, 1, 7)] = 0
|
| 164 |
+
with pytest.raises(TypeError, match=msg):
|
| 165 |
+
# GH#36148 require tzawareness compat as of 2.0
|
| 166 |
+
result[naive : datetime(1990, 1, 1, 7)] = 99
|
| 167 |
+
# the __setitems__ here failed, so result should still match ts
|
| 168 |
+
tm.assert_series_equal(result, ts)
|
| 169 |
+
|
| 170 |
+
lb = naive
|
| 171 |
+
rb = datetime(1990, 1, 1, 7)
|
| 172 |
+
msg = r"Invalid comparison between dtype=datetime64\[ns, US/Eastern\] and datetime"
|
| 173 |
+
with pytest.raises(TypeError, match=msg):
|
| 174 |
+
# tznaive vs tzaware comparison is invalid
|
| 175 |
+
# see GH#18376, GH#18162
|
| 176 |
+
ts[(ts.index >= lb) & (ts.index <= rb)]
|
| 177 |
+
|
| 178 |
+
lb = Timestamp(naive).tz_localize(rng.tzinfo)
|
| 179 |
+
rb = Timestamp(datetime(1990, 1, 1, 7)).tz_localize(rng.tzinfo)
|
| 180 |
+
result = ts[(ts.index >= lb) & (ts.index <= rb)]
|
| 181 |
+
expected = ts[4:8]
|
| 182 |
+
tm.assert_series_equal(result, expected)
|
| 183 |
+
|
| 184 |
+
result = ts[ts.index[4]]
|
| 185 |
+
expected = ts.iloc[4]
|
| 186 |
+
assert result == expected
|
| 187 |
+
|
| 188 |
+
result = ts[ts.index[4:8]]
|
| 189 |
+
expected = ts[4:8]
|
| 190 |
+
tm.assert_series_equal(result, expected)
|
| 191 |
+
|
| 192 |
+
result = ts.copy()
|
| 193 |
+
result[ts.index[4:8]] = 0
|
| 194 |
+
result.iloc[4:8] = ts.iloc[4:8]
|
| 195 |
+
tm.assert_series_equal(result, ts)
|
| 196 |
+
|
| 197 |
+
# also test partial date slicing
|
| 198 |
+
result = ts["1990-01-02"]
|
| 199 |
+
expected = ts[24:48]
|
| 200 |
+
tm.assert_series_equal(result, expected)
|
| 201 |
+
|
| 202 |
+
result = ts.copy()
|
| 203 |
+
result["1990-01-02"] = 0
|
| 204 |
+
result["1990-01-02"] = ts[24:48]
|
| 205 |
+
tm.assert_series_equal(result, ts)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def test_getitem_setitem_periodindex():
|
| 209 |
+
N = 50
|
| 210 |
+
rng = period_range("1/1/1990", periods=N, freq="h")
|
| 211 |
+
ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
|
| 212 |
+
|
| 213 |
+
result = ts["1990-01-01 04"]
|
| 214 |
+
expected = ts.iloc[4]
|
| 215 |
+
assert result == expected
|
| 216 |
+
|
| 217 |
+
result = ts.copy()
|
| 218 |
+
result["1990-01-01 04"] = 0
|
| 219 |
+
result["1990-01-01 04"] = ts.iloc[4]
|
| 220 |
+
tm.assert_series_equal(result, ts)
|
| 221 |
+
|
| 222 |
+
result = ts["1990-01-01 04":"1990-01-01 07"]
|
| 223 |
+
expected = ts[4:8]
|
| 224 |
+
tm.assert_series_equal(result, expected)
|
| 225 |
+
|
| 226 |
+
result = ts.copy()
|
| 227 |
+
result["1990-01-01 04":"1990-01-01 07"] = 0
|
| 228 |
+
result["1990-01-01 04":"1990-01-01 07"] = ts[4:8]
|
| 229 |
+
tm.assert_series_equal(result, ts)
|
| 230 |
+
|
| 231 |
+
lb = "1990-01-01 04"
|
| 232 |
+
rb = "1990-01-01 07"
|
| 233 |
+
result = ts[(ts.index >= lb) & (ts.index <= rb)]
|
| 234 |
+
expected = ts[4:8]
|
| 235 |
+
tm.assert_series_equal(result, expected)
|
| 236 |
+
|
| 237 |
+
# GH 2782
|
| 238 |
+
result = ts[ts.index[4]]
|
| 239 |
+
expected = ts.iloc[4]
|
| 240 |
+
assert result == expected
|
| 241 |
+
|
| 242 |
+
result = ts[ts.index[4:8]]
|
| 243 |
+
expected = ts[4:8]
|
| 244 |
+
tm.assert_series_equal(result, expected)
|
| 245 |
+
|
| 246 |
+
result = ts.copy()
|
| 247 |
+
result[ts.index[4:8]] = 0
|
| 248 |
+
result.iloc[4:8] = ts.iloc[4:8]
|
| 249 |
+
tm.assert_series_equal(result, ts)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def test_datetime_indexing():
|
| 253 |
+
index = date_range("1/1/2000", "1/7/2000")
|
| 254 |
+
index = index.repeat(3)
|
| 255 |
+
|
| 256 |
+
s = Series(len(index), index=index)
|
| 257 |
+
stamp = Timestamp("1/8/2000")
|
| 258 |
+
|
| 259 |
+
with pytest.raises(KeyError, match=re.escape(repr(stamp))):
|
| 260 |
+
s[stamp]
|
| 261 |
+
s[stamp] = 0
|
| 262 |
+
assert s[stamp] == 0
|
| 263 |
+
|
| 264 |
+
# not monotonic
|
| 265 |
+
s = Series(len(index), index=index)
|
| 266 |
+
s = s[::-1]
|
| 267 |
+
|
| 268 |
+
with pytest.raises(KeyError, match=re.escape(repr(stamp))):
|
| 269 |
+
s[stamp]
|
| 270 |
+
s[stamp] = 0
|
| 271 |
+
assert s[stamp] == 0
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
# test duplicates in time series
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def test_indexing_with_duplicate_datetimeindex(
|
| 278 |
+
rand_series_with_duplicate_datetimeindex,
|
| 279 |
+
):
|
| 280 |
+
ts = rand_series_with_duplicate_datetimeindex
|
| 281 |
+
|
| 282 |
+
uniques = ts.index.unique()
|
| 283 |
+
for date in uniques:
|
| 284 |
+
result = ts[date]
|
| 285 |
+
|
| 286 |
+
mask = ts.index == date
|
| 287 |
+
total = (ts.index == date).sum()
|
| 288 |
+
expected = ts[mask]
|
| 289 |
+
if total > 1:
|
| 290 |
+
tm.assert_series_equal(result, expected)
|
| 291 |
+
else:
|
| 292 |
+
tm.assert_almost_equal(result, expected.iloc[0])
|
| 293 |
+
|
| 294 |
+
cp = ts.copy()
|
| 295 |
+
cp[date] = 0
|
| 296 |
+
expected = Series(np.where(mask, 0, ts), index=ts.index)
|
| 297 |
+
tm.assert_series_equal(cp, expected)
|
| 298 |
+
|
| 299 |
+
key = datetime(2000, 1, 6)
|
| 300 |
+
with pytest.raises(KeyError, match=re.escape(repr(key))):
|
| 301 |
+
ts[key]
|
| 302 |
+
|
| 303 |
+
# new index
|
| 304 |
+
ts[datetime(2000, 1, 6)] = 0
|
| 305 |
+
assert ts[datetime(2000, 1, 6)] == 0
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def test_loc_getitem_over_size_cutoff(monkeypatch):
|
| 309 |
+
# #1821
|
| 310 |
+
|
| 311 |
+
monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 1000)
|
| 312 |
+
|
| 313 |
+
# create large list of non periodic datetime
|
| 314 |
+
dates = []
|
| 315 |
+
sec = timedelta(seconds=1)
|
| 316 |
+
half_sec = timedelta(microseconds=500000)
|
| 317 |
+
d = datetime(2011, 12, 5, 20, 30)
|
| 318 |
+
n = 1100
|
| 319 |
+
for i in range(n):
|
| 320 |
+
dates.append(d)
|
| 321 |
+
dates.append(d + sec)
|
| 322 |
+
dates.append(d + sec + half_sec)
|
| 323 |
+
dates.append(d + sec + sec + half_sec)
|
| 324 |
+
d += 3 * sec
|
| 325 |
+
|
| 326 |
+
# duplicate some values in the list
|
| 327 |
+
duplicate_positions = np.random.default_rng(2).integers(0, len(dates) - 1, 20)
|
| 328 |
+
for p in duplicate_positions:
|
| 329 |
+
dates[p + 1] = dates[p]
|
| 330 |
+
|
| 331 |
+
df = DataFrame(
|
| 332 |
+
np.random.default_rng(2).standard_normal((len(dates), 4)),
|
| 333 |
+
index=dates,
|
| 334 |
+
columns=list("ABCD"),
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
pos = n * 3
|
| 338 |
+
timestamp = df.index[pos]
|
| 339 |
+
assert timestamp in df.index
|
| 340 |
+
|
| 341 |
+
# it works!
|
| 342 |
+
df.loc[timestamp]
|
| 343 |
+
assert len(df.loc[[timestamp]]) > 0
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def test_indexing_over_size_cutoff_period_index(monkeypatch):
|
| 347 |
+
# GH 27136
|
| 348 |
+
|
| 349 |
+
monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 1000)
|
| 350 |
+
|
| 351 |
+
n = 1100
|
| 352 |
+
idx = period_range("1/1/2000", freq="min", periods=n)
|
| 353 |
+
assert idx._engine.over_size_threshold
|
| 354 |
+
|
| 355 |
+
s = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx)
|
| 356 |
+
|
| 357 |
+
pos = n - 1
|
| 358 |
+
timestamp = idx[pos]
|
| 359 |
+
assert timestamp in s.index
|
| 360 |
+
|
| 361 |
+
# it works!
|
| 362 |
+
s[timestamp]
|
| 363 |
+
assert len(s.loc[[timestamp]]) > 0
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def test_indexing_unordered():
|
| 367 |
+
# GH 2437
|
| 368 |
+
rng = date_range(start="2011-01-01", end="2011-01-15")
|
| 369 |
+
ts = Series(np.random.default_rng(2).random(len(rng)), index=rng)
|
| 370 |
+
ts2 = pd.concat([ts[0:4], ts[-4:], ts[4:-4]])
|
| 371 |
+
|
| 372 |
+
for t in ts.index:
|
| 373 |
+
expected = ts[t]
|
| 374 |
+
result = ts2[t]
|
| 375 |
+
assert expected == result
|
| 376 |
+
|
| 377 |
+
# GH 3448 (ranges)
|
| 378 |
+
def compare(slobj):
|
| 379 |
+
result = ts2[slobj].copy()
|
| 380 |
+
result = result.sort_index()
|
| 381 |
+
expected = ts[slobj]
|
| 382 |
+
expected.index = expected.index._with_freq(None)
|
| 383 |
+
tm.assert_series_equal(result, expected)
|
| 384 |
+
|
| 385 |
+
for key in [
|
| 386 |
+
slice("2011-01-01", "2011-01-15"),
|
| 387 |
+
slice("2010-12-30", "2011-01-15"),
|
| 388 |
+
slice("2011-01-01", "2011-01-16"),
|
| 389 |
+
# partial ranges
|
| 390 |
+
slice("2011-01-01", "2011-01-6"),
|
| 391 |
+
slice("2011-01-06", "2011-01-8"),
|
| 392 |
+
slice("2011-01-06", "2011-01-12"),
|
| 393 |
+
]:
|
| 394 |
+
with pytest.raises(
|
| 395 |
+
KeyError, match="Value based partial slicing on non-monotonic"
|
| 396 |
+
):
|
| 397 |
+
compare(key)
|
| 398 |
+
|
| 399 |
+
# single values
|
| 400 |
+
result = ts2["2011"].sort_index()
|
| 401 |
+
expected = ts["2011"]
|
| 402 |
+
expected.index = expected.index._with_freq(None)
|
| 403 |
+
tm.assert_series_equal(result, expected)
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def test_indexing_unordered2():
|
| 407 |
+
# diff freq
|
| 408 |
+
rng = date_range(datetime(2005, 1, 1), periods=20, freq="ME")
|
| 409 |
+
ts = Series(np.arange(len(rng)), index=rng)
|
| 410 |
+
ts = ts.take(np.random.default_rng(2).permutation(20))
|
| 411 |
+
|
| 412 |
+
result = ts["2005"]
|
| 413 |
+
for t in result.index:
|
| 414 |
+
assert t.year == 2005
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def test_indexing():
|
| 418 |
+
idx = date_range("2001-1-1", periods=20, freq="ME")
|
| 419 |
+
ts = Series(np.random.default_rng(2).random(len(idx)), index=idx)
|
| 420 |
+
|
| 421 |
+
# getting
|
| 422 |
+
|
| 423 |
+
# GH 3070, make sure semantics work on Series/Frame
|
| 424 |
+
result = ts["2001"]
|
| 425 |
+
tm.assert_series_equal(result, ts.iloc[:12])
|
| 426 |
+
|
| 427 |
+
df = DataFrame({"A": ts})
|
| 428 |
+
|
| 429 |
+
# GH#36179 pre-2.0 df["2001"] operated as slicing on rows. in 2.0 it behaves
|
| 430 |
+
# like any other key, so raises
|
| 431 |
+
with pytest.raises(KeyError, match="2001"):
|
| 432 |
+
df["2001"]
|
| 433 |
+
|
| 434 |
+
# setting
|
| 435 |
+
ts = Series(np.random.default_rng(2).random(len(idx)), index=idx)
|
| 436 |
+
expected = ts.copy()
|
| 437 |
+
expected.iloc[:12] = 1
|
| 438 |
+
ts["2001"] = 1
|
| 439 |
+
tm.assert_series_equal(ts, expected)
|
| 440 |
+
|
| 441 |
+
expected = df.copy()
|
| 442 |
+
expected.iloc[:12, 0] = 1
|
| 443 |
+
df.loc["2001", "A"] = 1
|
| 444 |
+
tm.assert_frame_equal(df, expected)
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
def test_getitem_str_month_with_datetimeindex():
|
| 448 |
+
# GH3546 (not including times on the last day)
|
| 449 |
+
idx = date_range(start="2013-05-31 00:00", end="2013-05-31 23:00", freq="h")
|
| 450 |
+
ts = Series(range(len(idx)), index=idx)
|
| 451 |
+
expected = ts["2013-05"]
|
| 452 |
+
tm.assert_series_equal(expected, ts)
|
| 453 |
+
|
| 454 |
+
idx = date_range(start="2013-05-31 00:00", end="2013-05-31 23:59", freq="s")
|
| 455 |
+
ts = Series(range(len(idx)), index=idx)
|
| 456 |
+
expected = ts["2013-05"]
|
| 457 |
+
tm.assert_series_equal(expected, ts)
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
def test_getitem_str_year_with_datetimeindex():
|
| 461 |
+
idx = [
|
| 462 |
+
Timestamp("2013-05-31 00:00"),
|
| 463 |
+
Timestamp(datetime(2013, 5, 31, 23, 59, 59, 999999)),
|
| 464 |
+
]
|
| 465 |
+
ts = Series(range(len(idx)), index=idx)
|
| 466 |
+
expected = ts["2013"]
|
| 467 |
+
tm.assert_series_equal(expected, ts)
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def test_getitem_str_second_with_datetimeindex():
|
| 471 |
+
# GH14826, indexing with a seconds resolution string / datetime object
|
| 472 |
+
df = DataFrame(
|
| 473 |
+
np.random.default_rng(2).random((5, 5)),
|
| 474 |
+
columns=["open", "high", "low", "close", "volume"],
|
| 475 |
+
index=date_range("2012-01-02 18:01:00", periods=5, tz="US/Central", freq="s"),
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
# this is a single date, so will raise
|
| 479 |
+
with pytest.raises(KeyError, match=r"^'2012-01-02 18:01:02'$"):
|
| 480 |
+
df["2012-01-02 18:01:02"]
|
| 481 |
+
|
| 482 |
+
msg = r"Timestamp\('2012-01-02 18:01:02-0600', tz='US/Central'\)"
|
| 483 |
+
with pytest.raises(KeyError, match=msg):
|
| 484 |
+
df[df.index[2]]
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
def test_compare_datetime_with_all_none():
|
| 488 |
+
# GH#54870
|
| 489 |
+
ser = Series(["2020-01-01", "2020-01-02"], dtype="datetime64[ns]")
|
| 490 |
+
ser2 = Series([None, None])
|
| 491 |
+
result = ser > ser2
|
| 492 |
+
expected = Series([False, False])
|
| 493 |
+
tm.assert_series_equal(result, expected)
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_delitem.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
from pandas import (
|
| 4 |
+
Index,
|
| 5 |
+
Series,
|
| 6 |
+
date_range,
|
| 7 |
+
)
|
| 8 |
+
import pandas._testing as tm
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TestSeriesDelItem:
|
| 12 |
+
def test_delitem(self):
|
| 13 |
+
# GH#5542
|
| 14 |
+
# should delete the item inplace
|
| 15 |
+
s = Series(range(5))
|
| 16 |
+
del s[0]
|
| 17 |
+
|
| 18 |
+
expected = Series(range(1, 5), index=range(1, 5))
|
| 19 |
+
tm.assert_series_equal(s, expected)
|
| 20 |
+
|
| 21 |
+
del s[1]
|
| 22 |
+
expected = Series(range(2, 5), index=range(2, 5))
|
| 23 |
+
tm.assert_series_equal(s, expected)
|
| 24 |
+
|
| 25 |
+
# only 1 left, del, add, del
|
| 26 |
+
s = Series(1)
|
| 27 |
+
del s[0]
|
| 28 |
+
tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="int64")))
|
| 29 |
+
s[0] = 1
|
| 30 |
+
tm.assert_series_equal(s, Series(1))
|
| 31 |
+
del s[0]
|
| 32 |
+
tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="int64")))
|
| 33 |
+
|
| 34 |
+
def test_delitem_object_index(self):
|
| 35 |
+
# Index(dtype=object)
|
| 36 |
+
s = Series(1, index=Index(["a"], dtype="str"))
|
| 37 |
+
del s["a"]
|
| 38 |
+
tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="str")))
|
| 39 |
+
s["a"] = 1
|
| 40 |
+
tm.assert_series_equal(s, Series(1, index=Index(["a"], dtype="str")))
|
| 41 |
+
del s["a"]
|
| 42 |
+
tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="str")))
|
| 43 |
+
|
| 44 |
+
def test_delitem_missing_key(self):
|
| 45 |
+
# empty
|
| 46 |
+
s = Series(dtype=object)
|
| 47 |
+
|
| 48 |
+
with pytest.raises(KeyError, match=r"^0$"):
|
| 49 |
+
del s[0]
|
| 50 |
+
|
| 51 |
+
def test_delitem_extension_dtype(self):
|
| 52 |
+
# GH#40386
|
| 53 |
+
# DatetimeTZDtype
|
| 54 |
+
dti = date_range("2016-01-01", periods=3, tz="US/Pacific")
|
| 55 |
+
ser = Series(dti)
|
| 56 |
+
|
| 57 |
+
expected = ser[[0, 2]]
|
| 58 |
+
del ser[1]
|
| 59 |
+
assert ser.dtype == dti.dtype
|
| 60 |
+
tm.assert_series_equal(ser, expected)
|
| 61 |
+
|
| 62 |
+
# PeriodDtype
|
| 63 |
+
pi = dti.tz_localize(None).to_period("D")
|
| 64 |
+
ser = Series(pi)
|
| 65 |
+
|
| 66 |
+
expected = ser[:2]
|
| 67 |
+
del ser[2]
|
| 68 |
+
assert ser.dtype == pi.dtype
|
| 69 |
+
tm.assert_series_equal(ser, expected)
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_get.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from pandas import (
|
| 6 |
+
DatetimeIndex,
|
| 7 |
+
Index,
|
| 8 |
+
Series,
|
| 9 |
+
date_range,
|
| 10 |
+
)
|
| 11 |
+
import pandas._testing as tm
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_get():
|
| 15 |
+
# GH 6383
|
| 16 |
+
s = Series(
|
| 17 |
+
np.array(
|
| 18 |
+
[
|
| 19 |
+
43,
|
| 20 |
+
48,
|
| 21 |
+
60,
|
| 22 |
+
48,
|
| 23 |
+
50,
|
| 24 |
+
51,
|
| 25 |
+
50,
|
| 26 |
+
45,
|
| 27 |
+
57,
|
| 28 |
+
48,
|
| 29 |
+
56,
|
| 30 |
+
45,
|
| 31 |
+
51,
|
| 32 |
+
39,
|
| 33 |
+
55,
|
| 34 |
+
43,
|
| 35 |
+
54,
|
| 36 |
+
52,
|
| 37 |
+
51,
|
| 38 |
+
54,
|
| 39 |
+
]
|
| 40 |
+
)
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
result = s.get(25, 0)
|
| 44 |
+
expected = 0
|
| 45 |
+
assert result == expected
|
| 46 |
+
|
| 47 |
+
s = Series(
|
| 48 |
+
np.array(
|
| 49 |
+
[
|
| 50 |
+
43,
|
| 51 |
+
48,
|
| 52 |
+
60,
|
| 53 |
+
48,
|
| 54 |
+
50,
|
| 55 |
+
51,
|
| 56 |
+
50,
|
| 57 |
+
45,
|
| 58 |
+
57,
|
| 59 |
+
48,
|
| 60 |
+
56,
|
| 61 |
+
45,
|
| 62 |
+
51,
|
| 63 |
+
39,
|
| 64 |
+
55,
|
| 65 |
+
43,
|
| 66 |
+
54,
|
| 67 |
+
52,
|
| 68 |
+
51,
|
| 69 |
+
54,
|
| 70 |
+
]
|
| 71 |
+
),
|
| 72 |
+
index=Index(
|
| 73 |
+
[
|
| 74 |
+
25.0,
|
| 75 |
+
36.0,
|
| 76 |
+
49.0,
|
| 77 |
+
64.0,
|
| 78 |
+
81.0,
|
| 79 |
+
100.0,
|
| 80 |
+
121.0,
|
| 81 |
+
144.0,
|
| 82 |
+
169.0,
|
| 83 |
+
196.0,
|
| 84 |
+
1225.0,
|
| 85 |
+
1296.0,
|
| 86 |
+
1369.0,
|
| 87 |
+
1444.0,
|
| 88 |
+
1521.0,
|
| 89 |
+
1600.0,
|
| 90 |
+
1681.0,
|
| 91 |
+
1764.0,
|
| 92 |
+
1849.0,
|
| 93 |
+
1936.0,
|
| 94 |
+
],
|
| 95 |
+
dtype=np.float64,
|
| 96 |
+
),
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
result = s.get(25, 0)
|
| 100 |
+
expected = 43
|
| 101 |
+
assert result == expected
|
| 102 |
+
|
| 103 |
+
# GH 7407
|
| 104 |
+
# with a boolean accessor
|
| 105 |
+
df = pd.DataFrame({"i": [0] * 3, "b": [False] * 3})
|
| 106 |
+
vc = df.i.value_counts()
|
| 107 |
+
result = vc.get(99, default="Missing")
|
| 108 |
+
assert result == "Missing"
|
| 109 |
+
|
| 110 |
+
vc = df.b.value_counts()
|
| 111 |
+
result = vc.get(False, default="Missing")
|
| 112 |
+
assert result == 3
|
| 113 |
+
|
| 114 |
+
result = vc.get(True, default="Missing")
|
| 115 |
+
assert result == "Missing"
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def test_get_nan(float_numpy_dtype):
|
| 119 |
+
# GH 8569
|
| 120 |
+
s = Index(range(10), dtype=float_numpy_dtype).to_series()
|
| 121 |
+
assert s.get(np.nan) is None
|
| 122 |
+
assert s.get(np.nan, default="Missing") == "Missing"
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def test_get_nan_multiple(float_numpy_dtype):
|
| 126 |
+
# GH 8569
|
| 127 |
+
# ensure that fixing "test_get_nan" above hasn't broken get
|
| 128 |
+
# with multiple elements
|
| 129 |
+
s = Index(range(10), dtype=float_numpy_dtype).to_series()
|
| 130 |
+
|
| 131 |
+
idx = [2, 30]
|
| 132 |
+
assert s.get(idx) is None
|
| 133 |
+
|
| 134 |
+
idx = [2, np.nan]
|
| 135 |
+
assert s.get(idx) is None
|
| 136 |
+
|
| 137 |
+
# GH 17295 - all missing keys
|
| 138 |
+
idx = [20, 30]
|
| 139 |
+
assert s.get(idx) is None
|
| 140 |
+
|
| 141 |
+
idx = [np.nan, np.nan]
|
| 142 |
+
assert s.get(idx) is None
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def test_get_with_default():
|
| 146 |
+
# GH#7725
|
| 147 |
+
d0 = ["a", "b", "c", "d"]
|
| 148 |
+
d1 = np.arange(4, dtype="int64")
|
| 149 |
+
|
| 150 |
+
for data, index in ((d0, d1), (d1, d0)):
|
| 151 |
+
s = Series(data, index=index)
|
| 152 |
+
for i, d in zip(index, data):
|
| 153 |
+
assert s.get(i) == d
|
| 154 |
+
assert s.get(i, d) == d
|
| 155 |
+
assert s.get(i, "z") == d
|
| 156 |
+
|
| 157 |
+
assert s.get("e", "z") == "z"
|
| 158 |
+
assert s.get("e", "e") == "e"
|
| 159 |
+
|
| 160 |
+
assert s.get(10, "z") == "z"
|
| 161 |
+
assert s.get(10, 10) == 10
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@pytest.mark.parametrize(
|
| 165 |
+
"arr",
|
| 166 |
+
[
|
| 167 |
+
np.random.default_rng(2).standard_normal(10),
|
| 168 |
+
DatetimeIndex(date_range("2020-01-01", periods=10), name="a").tz_localize(
|
| 169 |
+
tz="US/Eastern"
|
| 170 |
+
),
|
| 171 |
+
],
|
| 172 |
+
)
|
| 173 |
+
def test_get_with_ea(arr):
|
| 174 |
+
# GH#21260
|
| 175 |
+
ser = Series(arr, index=[2 * i for i in range(len(arr))])
|
| 176 |
+
assert ser.get(4) == ser.iloc[2]
|
| 177 |
+
|
| 178 |
+
result = ser.get([4, 6])
|
| 179 |
+
expected = ser.iloc[[2, 3]]
|
| 180 |
+
tm.assert_series_equal(result, expected)
|
| 181 |
+
|
| 182 |
+
result = ser.get(slice(2))
|
| 183 |
+
expected = ser.iloc[[0, 1]]
|
| 184 |
+
tm.assert_series_equal(result, expected)
|
| 185 |
+
|
| 186 |
+
assert ser.get(-1) is None
|
| 187 |
+
assert ser.get(ser.index.max() + 1) is None
|
| 188 |
+
|
| 189 |
+
ser = Series(arr[:6], index=list("abcdef"))
|
| 190 |
+
assert ser.get("c") == ser.iloc[2]
|
| 191 |
+
|
| 192 |
+
result = ser.get(slice("b", "d"))
|
| 193 |
+
expected = ser.iloc[[1, 2, 3]]
|
| 194 |
+
tm.assert_series_equal(result, expected)
|
| 195 |
+
|
| 196 |
+
result = ser.get("Z")
|
| 197 |
+
assert result is None
|
| 198 |
+
|
| 199 |
+
# As of 3.0, ints are treated as labels
|
| 200 |
+
assert ser.get(4) is None
|
| 201 |
+
assert ser.get(-1) is None
|
| 202 |
+
assert ser.get(len(ser)) is None
|
| 203 |
+
|
| 204 |
+
# GH#21257
|
| 205 |
+
ser = Series(arr)
|
| 206 |
+
ser2 = ser[::2]
|
| 207 |
+
assert ser2.get(1) is None
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def test_getitem_get(string_series, object_series):
|
| 211 |
+
for obj in [string_series, object_series]:
|
| 212 |
+
idx = obj.index[5]
|
| 213 |
+
|
| 214 |
+
assert obj[idx] == obj.get(idx)
|
| 215 |
+
assert obj[idx] == obj.iloc[5]
|
| 216 |
+
|
| 217 |
+
# As of 3.0, ints are treated as labels
|
| 218 |
+
assert string_series.get(-1) is None
|
| 219 |
+
assert string_series.iloc[5] == string_series.get(string_series.index[5])
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def test_get_none():
|
| 223 |
+
# GH#5652
|
| 224 |
+
s1 = Series(dtype=object)
|
| 225 |
+
s2 = Series(dtype=object, index=list("abc"))
|
| 226 |
+
for s in [s1, s2]:
|
| 227 |
+
result = s.get(None)
|
| 228 |
+
assert result is None
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_getitem.py
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Series.__getitem__ test classes are organized by the type of key passed.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from datetime import (
|
| 6 |
+
date,
|
| 7 |
+
datetime,
|
| 8 |
+
time,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
import pytest
|
| 13 |
+
|
| 14 |
+
from pandas._libs.tslibs import (
|
| 15 |
+
conversion,
|
| 16 |
+
timezones,
|
| 17 |
+
)
|
| 18 |
+
from pandas.compat.numpy import np_version_gt2
|
| 19 |
+
from pandas.errors import Pandas4Warning
|
| 20 |
+
|
| 21 |
+
from pandas.core.dtypes.common import is_scalar
|
| 22 |
+
|
| 23 |
+
import pandas as pd
|
| 24 |
+
from pandas import (
|
| 25 |
+
Categorical,
|
| 26 |
+
DataFrame,
|
| 27 |
+
DatetimeIndex,
|
| 28 |
+
Index,
|
| 29 |
+
Series,
|
| 30 |
+
Timestamp,
|
| 31 |
+
date_range,
|
| 32 |
+
period_range,
|
| 33 |
+
timedelta_range,
|
| 34 |
+
)
|
| 35 |
+
import pandas._testing as tm
|
| 36 |
+
from pandas.core.indexing import IndexingError
|
| 37 |
+
|
| 38 |
+
from pandas.tseries.offsets import BDay
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class TestSeriesGetitemScalars:
|
| 42 |
+
def test_getitem_object_index_float_string(self):
|
| 43 |
+
# GH#17286
|
| 44 |
+
ser = Series([1] * 4, index=Index(["a", "b", "c", 1.0]))
|
| 45 |
+
assert ser["a"] == 1
|
| 46 |
+
assert ser[1.0] == 1
|
| 47 |
+
|
| 48 |
+
def test_getitem_float_keys_tuple_values(self):
|
| 49 |
+
# see GH#13509
|
| 50 |
+
|
| 51 |
+
# unique Index
|
| 52 |
+
ser = Series([(1, 1), (2, 2), (3, 3)], index=[0.0, 0.1, 0.2], name="foo")
|
| 53 |
+
result = ser[0.0]
|
| 54 |
+
assert result == (1, 1)
|
| 55 |
+
|
| 56 |
+
# non-unique Index
|
| 57 |
+
expected = Series([(1, 1), (2, 2)], index=[0.0, 0.0], name="foo")
|
| 58 |
+
ser = Series([(1, 1), (2, 2), (3, 3)], index=[0.0, 0.0, 0.2], name="foo")
|
| 59 |
+
|
| 60 |
+
result = ser[0.0]
|
| 61 |
+
tm.assert_series_equal(result, expected)
|
| 62 |
+
|
| 63 |
+
def test_getitem_unrecognized_scalar(self):
|
| 64 |
+
# GH#32684 a scalar key that is not recognized by lib.is_scalar
|
| 65 |
+
|
| 66 |
+
# a series that might be produced via `frame.dtypes`
|
| 67 |
+
ser = Series([1, 2], index=[np.dtype("O"), np.dtype("i8")])
|
| 68 |
+
|
| 69 |
+
key = ser.index[1]
|
| 70 |
+
|
| 71 |
+
result = ser[key]
|
| 72 |
+
assert result == 2
|
| 73 |
+
|
| 74 |
+
def test_getitem_negative_out_of_bounds(self):
|
| 75 |
+
ser = Series(["a"] * 10, index=["a"] * 10)
|
| 76 |
+
|
| 77 |
+
with pytest.raises(KeyError, match="^-11$"):
|
| 78 |
+
ser[-11]
|
| 79 |
+
|
| 80 |
+
def test_getitem_out_of_bounds_indexerror(self, datetime_series):
|
| 81 |
+
# don't segfault, GH#495
|
| 82 |
+
N = len(datetime_series)
|
| 83 |
+
with pytest.raises(KeyError, match=str(N)):
|
| 84 |
+
datetime_series[N]
|
| 85 |
+
|
| 86 |
+
def test_getitem_out_of_bounds_empty_rangeindex_keyerror(self):
|
| 87 |
+
# GH#917
|
| 88 |
+
# With a RangeIndex, an int key gives a KeyError
|
| 89 |
+
ser = Series([], dtype=object)
|
| 90 |
+
with pytest.raises(KeyError, match="-1"):
|
| 91 |
+
ser[-1]
|
| 92 |
+
|
| 93 |
+
def test_getitem_keyerror_with_integer_index(self, any_int_numpy_dtype):
|
| 94 |
+
dtype = any_int_numpy_dtype
|
| 95 |
+
ser = Series(
|
| 96 |
+
np.random.default_rng(2).standard_normal(6),
|
| 97 |
+
index=Index([0, 0, 1, 1, 2, 2], dtype=dtype),
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
with pytest.raises(KeyError, match=r"^5$"):
|
| 101 |
+
ser[5]
|
| 102 |
+
|
| 103 |
+
with pytest.raises(KeyError, match=r"^'c'$"):
|
| 104 |
+
ser["c"]
|
| 105 |
+
|
| 106 |
+
# not monotonic
|
| 107 |
+
ser = Series(
|
| 108 |
+
np.random.default_rng(2).standard_normal(6), index=[2, 2, 0, 0, 1, 1]
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
with pytest.raises(KeyError, match=r"^5$"):
|
| 112 |
+
ser[5]
|
| 113 |
+
|
| 114 |
+
with pytest.raises(KeyError, match=r"^'c'$"):
|
| 115 |
+
ser["c"]
|
| 116 |
+
|
| 117 |
+
def test_getitem_int64(self, datetime_series):
|
| 118 |
+
if np_version_gt2:
|
| 119 |
+
msg = r"^np.int64\(5\)$"
|
| 120 |
+
else:
|
| 121 |
+
msg = "^5$"
|
| 122 |
+
idx = np.int64(5)
|
| 123 |
+
with pytest.raises(KeyError, match=msg):
|
| 124 |
+
datetime_series[idx]
|
| 125 |
+
|
| 126 |
+
def test_getitem_full_range(self):
|
| 127 |
+
# github.com/pandas-dev/pandas/commit/4f433773141d2eb384325714a2776bcc5b2e20f7
|
| 128 |
+
ser = Series(range(5), index=list(range(5)))
|
| 129 |
+
result = ser[list(range(5))]
|
| 130 |
+
tm.assert_series_equal(result, ser)
|
| 131 |
+
|
| 132 |
+
# ------------------------------------------------------------------
|
| 133 |
+
# Series with DatetimeIndex
|
| 134 |
+
|
| 135 |
+
@pytest.mark.parametrize("tzstr", ["Europe/Berlin", "dateutil/Europe/Berlin"])
|
| 136 |
+
def test_getitem_pydatetime_tz(self, tzstr):
|
| 137 |
+
tz = timezones.maybe_get_tz(tzstr)
|
| 138 |
+
|
| 139 |
+
index = date_range(
|
| 140 |
+
start="2012-12-24 16:00", end="2012-12-24 18:00", freq="h", tz=tzstr
|
| 141 |
+
)
|
| 142 |
+
ts = Series(index=index, data=index.hour)
|
| 143 |
+
time_pandas = Timestamp("2012-12-24 17:00", tz=tzstr)
|
| 144 |
+
|
| 145 |
+
dt = datetime(2012, 12, 24, 17, 0)
|
| 146 |
+
time_datetime = conversion.localize_pydatetime(dt, tz)
|
| 147 |
+
assert ts[time_pandas] == ts[time_datetime]
|
| 148 |
+
|
| 149 |
+
@pytest.mark.parametrize("tz", ["US/Eastern", "dateutil/US/Eastern"])
|
| 150 |
+
def test_string_index_alias_tz_aware(self, tz):
|
| 151 |
+
rng = date_range("1/1/2000", periods=10, tz=tz)
|
| 152 |
+
ser = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
|
| 153 |
+
|
| 154 |
+
result = ser["1/3/2000"]
|
| 155 |
+
tm.assert_almost_equal(result, ser.iloc[2])
|
| 156 |
+
|
| 157 |
+
def test_getitem_time_object(self):
|
| 158 |
+
rng = date_range("1/1/2000", "1/5/2000", freq="5min")
|
| 159 |
+
ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
|
| 160 |
+
|
| 161 |
+
mask = (rng.hour == 9) & (rng.minute == 30)
|
| 162 |
+
result = ts[time(9, 30)]
|
| 163 |
+
expected = ts[mask]
|
| 164 |
+
result.index = result.index._with_freq(None)
|
| 165 |
+
tm.assert_series_equal(result, expected)
|
| 166 |
+
|
| 167 |
+
# ------------------------------------------------------------------
|
| 168 |
+
# Series with CategoricalIndex
|
| 169 |
+
|
| 170 |
+
def test_getitem_scalar_categorical_index(self):
|
| 171 |
+
cats = Categorical([Timestamp("12-31-1999"), Timestamp("12-31-2000")])
|
| 172 |
+
|
| 173 |
+
ser = Series([1, 2], index=cats)
|
| 174 |
+
|
| 175 |
+
expected = ser.iloc[0]
|
| 176 |
+
result = ser[cats[0]]
|
| 177 |
+
assert result == expected
|
| 178 |
+
|
| 179 |
+
def test_getitem_numeric_categorical_listlike_matches_scalar(self):
|
| 180 |
+
# GH#15470
|
| 181 |
+
ser = Series(["a", "b", "c"], index=pd.CategoricalIndex([2, 1, 0]))
|
| 182 |
+
|
| 183 |
+
# 0 is treated as a label
|
| 184 |
+
assert ser[0] == "c"
|
| 185 |
+
|
| 186 |
+
# the listlike analogue should also be treated as labels
|
| 187 |
+
res = ser[[0]]
|
| 188 |
+
expected = ser.iloc[-1:]
|
| 189 |
+
tm.assert_series_equal(res, expected)
|
| 190 |
+
|
| 191 |
+
res2 = ser[[0, 1, 2]]
|
| 192 |
+
tm.assert_series_equal(res2, ser.iloc[::-1])
|
| 193 |
+
|
| 194 |
+
def test_getitem_integer_categorical_not_positional(self):
|
| 195 |
+
# GH#14865
|
| 196 |
+
ser = Series(["a", "b", "c"], index=Index([1, 2, 3], dtype="category"))
|
| 197 |
+
assert ser.get(3) == "c"
|
| 198 |
+
assert ser[3] == "c"
|
| 199 |
+
|
| 200 |
+
def test_getitem_str_with_timedeltaindex(self):
|
| 201 |
+
rng = timedelta_range("1 day 10:11:12", freq="h", periods=500)
|
| 202 |
+
ser = Series(np.arange(len(rng)), index=rng)
|
| 203 |
+
|
| 204 |
+
key = "6 days, 23:11:12"
|
| 205 |
+
indexer = rng.get_loc(key)
|
| 206 |
+
assert indexer == 133
|
| 207 |
+
|
| 208 |
+
result = ser[key]
|
| 209 |
+
assert result == ser.iloc[133]
|
| 210 |
+
|
| 211 |
+
msg = r"^Timedelta\('50 days 00:00:00'\)$"
|
| 212 |
+
with pytest.raises(KeyError, match=msg):
|
| 213 |
+
rng.get_loc("50 days")
|
| 214 |
+
with pytest.raises(KeyError, match=msg):
|
| 215 |
+
ser["50 days"]
|
| 216 |
+
|
| 217 |
+
def test_getitem_bool_index_positional(self):
|
| 218 |
+
# GH#48653
|
| 219 |
+
ser = Series({True: 1, False: 0})
|
| 220 |
+
with pytest.raises(KeyError, match="^0$"):
|
| 221 |
+
ser[0]
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class TestSeriesGetitemSlices:
|
| 225 |
+
def test_getitem_partial_str_slice_with_datetimeindex(self):
|
| 226 |
+
# GH#34860
|
| 227 |
+
arr = date_range("1/1/2008", "1/1/2009")
|
| 228 |
+
ser = arr.to_series()
|
| 229 |
+
result = ser["2008"]
|
| 230 |
+
|
| 231 |
+
rng = date_range(start="2008-01-01", end="2008-12-31")
|
| 232 |
+
expected = Series(rng, index=rng)
|
| 233 |
+
|
| 234 |
+
tm.assert_series_equal(result, expected)
|
| 235 |
+
|
| 236 |
+
def test_getitem_slice_strings_with_datetimeindex(self):
|
| 237 |
+
idx = DatetimeIndex(
|
| 238 |
+
["1/1/2000", "1/2/2000", "1/2/2000", "1/3/2000", "1/4/2000"]
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
ts = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx)
|
| 242 |
+
|
| 243 |
+
result = ts["1/2/2000":]
|
| 244 |
+
expected = ts[1:]
|
| 245 |
+
tm.assert_series_equal(result, expected)
|
| 246 |
+
|
| 247 |
+
result = ts["1/2/2000":"1/3/2000"]
|
| 248 |
+
expected = ts[1:4]
|
| 249 |
+
tm.assert_series_equal(result, expected)
|
| 250 |
+
|
| 251 |
+
def test_getitem_partial_str_slice_with_timedeltaindex(self):
|
| 252 |
+
rng = timedelta_range("1 day 10:11:12", freq="h", periods=500)
|
| 253 |
+
ser = Series(np.arange(len(rng)), index=rng)
|
| 254 |
+
|
| 255 |
+
result = ser["5 day":"6 day"]
|
| 256 |
+
expected = ser.iloc[86:134]
|
| 257 |
+
tm.assert_series_equal(result, expected)
|
| 258 |
+
|
| 259 |
+
result = ser["5 day":]
|
| 260 |
+
expected = ser.iloc[86:]
|
| 261 |
+
tm.assert_series_equal(result, expected)
|
| 262 |
+
|
| 263 |
+
result = ser[:"6 day"]
|
| 264 |
+
expected = ser.iloc[:134]
|
| 265 |
+
tm.assert_series_equal(result, expected)
|
| 266 |
+
|
| 267 |
+
def test_getitem_partial_str_slice_high_reso_with_timedeltaindex(self):
|
| 268 |
+
# higher reso
|
| 269 |
+
rng = timedelta_range("1 day 10:11:12", freq="us", periods=2000)
|
| 270 |
+
ser = Series(np.arange(len(rng)), index=rng)
|
| 271 |
+
|
| 272 |
+
result = ser["1 day 10:11:12":]
|
| 273 |
+
expected = ser.iloc[0:]
|
| 274 |
+
tm.assert_series_equal(result, expected)
|
| 275 |
+
|
| 276 |
+
result = ser["1 day 10:11:12.001":]
|
| 277 |
+
expected = ser.iloc[1000:]
|
| 278 |
+
tm.assert_series_equal(result, expected)
|
| 279 |
+
|
| 280 |
+
result = ser["1 days, 10:11:12.001001"]
|
| 281 |
+
assert result == ser.iloc[1001]
|
| 282 |
+
|
| 283 |
+
def test_getitem_slice_2d(self, datetime_series):
|
| 284 |
+
# GH#30588 multi-dimensional indexing deprecated
|
| 285 |
+
with pytest.raises(ValueError, match="Multi-dimensional indexing"):
|
| 286 |
+
datetime_series[:, np.newaxis]
|
| 287 |
+
|
| 288 |
+
def test_getitem_median_slice_bug(self):
|
| 289 |
+
index = date_range("20090415", "20090519", freq="2B")
|
| 290 |
+
ser = Series(np.random.default_rng(2).standard_normal(13), index=index)
|
| 291 |
+
|
| 292 |
+
indexer = [slice(6, 7, None)]
|
| 293 |
+
msg = "Indexing with a single-item list"
|
| 294 |
+
with pytest.raises(ValueError, match=msg):
|
| 295 |
+
# GH#31299
|
| 296 |
+
ser[indexer]
|
| 297 |
+
# but we're OK with a single-element tuple
|
| 298 |
+
result = ser[(indexer[0],)]
|
| 299 |
+
expected = ser[indexer[0]]
|
| 300 |
+
tm.assert_series_equal(result, expected)
|
| 301 |
+
|
| 302 |
+
@pytest.mark.parametrize(
|
| 303 |
+
"slc, positions",
|
| 304 |
+
[
|
| 305 |
+
[slice(date(2018, 1, 1), None), [0, 1, 2]],
|
| 306 |
+
[slice(date(2019, 1, 2), None), [2]],
|
| 307 |
+
[slice(date(2020, 1, 1), None), []],
|
| 308 |
+
[slice(None, date(2020, 1, 1)), [0, 1, 2]],
|
| 309 |
+
[slice(None, date(2019, 1, 1)), [0]],
|
| 310 |
+
],
|
| 311 |
+
)
|
| 312 |
+
def test_getitem_slice_date(self, slc, positions):
|
| 313 |
+
# https://github.com/pandas-dev/pandas/issues/31501
|
| 314 |
+
ser = Series(
|
| 315 |
+
[0, 1, 2],
|
| 316 |
+
DatetimeIndex(["2019-01-01", "2019-01-01T06:00:00", "2019-01-02"]),
|
| 317 |
+
)
|
| 318 |
+
msg = "Slicing with a datetime.date object is deprecated"
|
| 319 |
+
with tm.assert_produces_warning(Pandas4Warning, match=msg):
|
| 320 |
+
result = ser[slc]
|
| 321 |
+
expected = ser.take(positions)
|
| 322 |
+
tm.assert_series_equal(result, expected)
|
| 323 |
+
|
| 324 |
+
def test_getitem_slice_float_raises(self, datetime_series):
|
| 325 |
+
msg = (
|
| 326 |
+
"cannot do slice indexing on DatetimeIndex with these indexers "
|
| 327 |
+
r"\[{key}\] of type float"
|
| 328 |
+
)
|
| 329 |
+
with pytest.raises(TypeError, match=msg.format(key=r"4\.0")):
|
| 330 |
+
datetime_series[4.0:10.0]
|
| 331 |
+
|
| 332 |
+
with pytest.raises(TypeError, match=msg.format(key=r"4\.5")):
|
| 333 |
+
datetime_series[4.5:10.0]
|
| 334 |
+
|
| 335 |
+
def test_getitem_slice_bug(self):
|
| 336 |
+
ser = Series(range(10), index=list(range(10)))
|
| 337 |
+
result = ser[-12:]
|
| 338 |
+
tm.assert_series_equal(result, ser)
|
| 339 |
+
|
| 340 |
+
result = ser[-7:]
|
| 341 |
+
tm.assert_series_equal(result, ser[3:])
|
| 342 |
+
|
| 343 |
+
result = ser[:-12]
|
| 344 |
+
tm.assert_series_equal(result, ser[:0])
|
| 345 |
+
|
| 346 |
+
def test_getitem_slice_integers(self):
|
| 347 |
+
ser = Series(
|
| 348 |
+
np.random.default_rng(2).standard_normal(8),
|
| 349 |
+
index=[2, 4, 6, 8, 10, 12, 14, 16],
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
result = ser[:4]
|
| 353 |
+
expected = Series(ser.values[:4], index=[2, 4, 6, 8])
|
| 354 |
+
tm.assert_series_equal(result, expected)
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
class TestSeriesGetitemListLike:
|
| 358 |
+
@pytest.mark.parametrize("box", [list, np.array, Index, Series])
|
| 359 |
+
def test_getitem_no_matches(self, box):
|
| 360 |
+
# GH#33462 we expect the same behavior for list/ndarray/Index/Series
|
| 361 |
+
ser = Series(["A", "B"])
|
| 362 |
+
|
| 363 |
+
key = Series(["C"], dtype=object)
|
| 364 |
+
key = box(key)
|
| 365 |
+
|
| 366 |
+
msg = r"None of \[Index\(\['C'\], dtype='object|str'\)\] are in the \[index\]"
|
| 367 |
+
with pytest.raises(KeyError, match=msg):
|
| 368 |
+
ser[key]
|
| 369 |
+
|
| 370 |
+
def test_getitem_intlist_intindex_periodvalues(self):
|
| 371 |
+
ser = Series(period_range("2000-01-01", periods=10, freq="D"))
|
| 372 |
+
|
| 373 |
+
result = ser[[2, 4]]
|
| 374 |
+
exp = Series(
|
| 375 |
+
[pd.Period("2000-01-03", freq="D"), pd.Period("2000-01-05", freq="D")],
|
| 376 |
+
index=[2, 4],
|
| 377 |
+
dtype="Period[D]",
|
| 378 |
+
)
|
| 379 |
+
tm.assert_series_equal(result, exp)
|
| 380 |
+
assert result.dtype == "Period[D]"
|
| 381 |
+
|
| 382 |
+
@pytest.mark.parametrize("box", [list, np.array, Index])
|
| 383 |
+
def test_getitem_intlist_intervalindex_non_int(self, box):
|
| 384 |
+
# GH#33404 fall back to positional since ints are unambiguous;
|
| 385 |
+
# changed in 3.0 to never fallback
|
| 386 |
+
dti = date_range("2000-01-03", periods=3)._with_freq(None)
|
| 387 |
+
ii = pd.IntervalIndex.from_breaks(dti)
|
| 388 |
+
ser = Series(range(len(ii)), index=ii)
|
| 389 |
+
|
| 390 |
+
key = box([0])
|
| 391 |
+
msg = r"None of \[Index\(\[0\], dtype='int(32|64)'\)\] are in the \[index\]"
|
| 392 |
+
with pytest.raises(KeyError, match=msg):
|
| 393 |
+
ser[key]
|
| 394 |
+
|
| 395 |
+
@pytest.mark.parametrize("box", [list, np.array, Index])
|
| 396 |
+
@pytest.mark.parametrize("dtype", [np.int64, np.float64, np.uint64])
|
| 397 |
+
def test_getitem_intlist_multiindex_numeric_level(self, dtype, box):
|
| 398 |
+
# GH#33404 do _not_ fall back to positional since ints are ambiguous
|
| 399 |
+
idx = Index(range(4)).astype(dtype)
|
| 400 |
+
dti = date_range("2000-01-03", periods=3)
|
| 401 |
+
mi = pd.MultiIndex.from_product([idx, dti])
|
| 402 |
+
ser = Series(range(len(mi))[::-1], index=mi)
|
| 403 |
+
|
| 404 |
+
key = box([5])
|
| 405 |
+
with pytest.raises(KeyError, match="5"):
|
| 406 |
+
ser[key]
|
| 407 |
+
|
| 408 |
+
def test_getitem_uint_array_key(self, any_unsigned_int_numpy_dtype):
|
| 409 |
+
# GH #37218
|
| 410 |
+
ser = Series([1, 2, 3])
|
| 411 |
+
key = np.array([4], dtype=any_unsigned_int_numpy_dtype)
|
| 412 |
+
|
| 413 |
+
with pytest.raises(KeyError, match="4"):
|
| 414 |
+
ser[key]
|
| 415 |
+
with pytest.raises(KeyError, match="4"):
|
| 416 |
+
ser.loc[key]
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
class TestGetitemBooleanMask:
|
| 420 |
+
def test_getitem_boolean(self, string_series):
|
| 421 |
+
ser = string_series
|
| 422 |
+
mask = ser > ser.median()
|
| 423 |
+
|
| 424 |
+
# passing list is OK
|
| 425 |
+
result = ser[list(mask)]
|
| 426 |
+
expected = ser[mask]
|
| 427 |
+
tm.assert_series_equal(result, expected)
|
| 428 |
+
tm.assert_index_equal(result.index, ser.index[mask])
|
| 429 |
+
|
| 430 |
+
def test_getitem_boolean_empty(self):
|
| 431 |
+
ser = Series([], dtype=np.int64)
|
| 432 |
+
ser.index.name = "index_name"
|
| 433 |
+
ser = ser[ser.isna()]
|
| 434 |
+
assert ser.index.name == "index_name"
|
| 435 |
+
assert ser.dtype == np.int64
|
| 436 |
+
|
| 437 |
+
# GH#5877
|
| 438 |
+
# indexing with empty series
|
| 439 |
+
ser = Series(["A", "B"], dtype=object)
|
| 440 |
+
expected = Series(dtype=object, index=Index([], dtype="int64"))
|
| 441 |
+
result = ser[Series([], dtype=object)]
|
| 442 |
+
tm.assert_series_equal(result, expected)
|
| 443 |
+
|
| 444 |
+
# invalid because of the boolean indexer
|
| 445 |
+
# that's empty or not-aligned
|
| 446 |
+
msg = (
|
| 447 |
+
r"Unalignable boolean Series provided as indexer \(index of "
|
| 448 |
+
r"the boolean Series and of the indexed object do not match"
|
| 449 |
+
)
|
| 450 |
+
with pytest.raises(IndexingError, match=msg):
|
| 451 |
+
ser[Series([], dtype=bool)]
|
| 452 |
+
|
| 453 |
+
with pytest.raises(IndexingError, match=msg):
|
| 454 |
+
ser[Series([True], dtype=bool)]
|
| 455 |
+
|
| 456 |
+
def test_getitem_boolean_object(self, string_series):
|
| 457 |
+
# using column from DataFrame
|
| 458 |
+
|
| 459 |
+
ser = string_series
|
| 460 |
+
mask = ser > ser.median()
|
| 461 |
+
omask = mask.astype(object)
|
| 462 |
+
|
| 463 |
+
# getitem
|
| 464 |
+
result = ser[omask]
|
| 465 |
+
expected = ser[mask]
|
| 466 |
+
tm.assert_series_equal(result, expected)
|
| 467 |
+
|
| 468 |
+
# setitem
|
| 469 |
+
s2 = ser.copy()
|
| 470 |
+
cop = ser.copy()
|
| 471 |
+
cop[omask] = 5
|
| 472 |
+
s2[mask] = 5
|
| 473 |
+
tm.assert_series_equal(cop, s2)
|
| 474 |
+
|
| 475 |
+
# nans raise exception
|
| 476 |
+
omask[5:10] = np.nan
|
| 477 |
+
msg = "Cannot mask with non-boolean array containing NA / NaN values"
|
| 478 |
+
with pytest.raises(ValueError, match=msg):
|
| 479 |
+
ser[omask]
|
| 480 |
+
with pytest.raises(ValueError, match=msg):
|
| 481 |
+
ser[omask] = 5
|
| 482 |
+
|
| 483 |
+
def test_getitem_boolean_dt64_copies(self):
|
| 484 |
+
# GH#36210
|
| 485 |
+
dti = date_range("2016-01-01", periods=4, tz="US/Pacific")
|
| 486 |
+
key = np.array([True, True, False, False])
|
| 487 |
+
|
| 488 |
+
ser = Series(dti._data)
|
| 489 |
+
|
| 490 |
+
res = ser[key]
|
| 491 |
+
assert res._values._ndarray.base is None
|
| 492 |
+
|
| 493 |
+
# compare with numeric case for reference
|
| 494 |
+
ser2 = Series(range(4))
|
| 495 |
+
res2 = ser2[key]
|
| 496 |
+
assert res2._values.base is None
|
| 497 |
+
|
| 498 |
+
def test_getitem_boolean_corner(self, datetime_series):
|
| 499 |
+
ts = datetime_series
|
| 500 |
+
mask_shifted = ts.shift(1, freq=BDay()) > ts.median()
|
| 501 |
+
|
| 502 |
+
msg = (
|
| 503 |
+
r"Unalignable boolean Series provided as indexer \(index of "
|
| 504 |
+
r"the boolean Series and of the indexed object do not match"
|
| 505 |
+
)
|
| 506 |
+
with pytest.raises(IndexingError, match=msg):
|
| 507 |
+
ts[mask_shifted]
|
| 508 |
+
|
| 509 |
+
with pytest.raises(IndexingError, match=msg):
|
| 510 |
+
ts.loc[mask_shifted]
|
| 511 |
+
|
| 512 |
+
def test_getitem_boolean_different_order(self, string_series):
|
| 513 |
+
ordered = string_series.sort_values()
|
| 514 |
+
|
| 515 |
+
sel = string_series[ordered > 0]
|
| 516 |
+
exp = string_series[string_series > 0]
|
| 517 |
+
tm.assert_series_equal(sel, exp)
|
| 518 |
+
|
| 519 |
+
def test_getitem_boolean_contiguous_preserve_freq(self):
|
| 520 |
+
rng = date_range("1/1/2000", "3/1/2000", freq="B")
|
| 521 |
+
|
| 522 |
+
mask = np.zeros(len(rng), dtype=bool)
|
| 523 |
+
mask[10:20] = True
|
| 524 |
+
|
| 525 |
+
masked = rng[mask]
|
| 526 |
+
expected = rng[10:20]
|
| 527 |
+
assert expected.freq == rng.freq
|
| 528 |
+
tm.assert_index_equal(masked, expected)
|
| 529 |
+
|
| 530 |
+
mask[22] = True
|
| 531 |
+
masked = rng[mask]
|
| 532 |
+
assert masked.freq is None
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
class TestGetitemCallable:
|
| 536 |
+
def test_getitem_callable(self):
|
| 537 |
+
# GH#12533
|
| 538 |
+
ser = Series(4, index=list("ABCD"))
|
| 539 |
+
result = ser[lambda x: "A"]
|
| 540 |
+
assert result == ser.loc["A"]
|
| 541 |
+
|
| 542 |
+
result = ser[lambda x: ["A", "B"]]
|
| 543 |
+
expected = ser.loc[["A", "B"]]
|
| 544 |
+
tm.assert_series_equal(result, expected)
|
| 545 |
+
|
| 546 |
+
result = ser[lambda x: [True, False, True, True]]
|
| 547 |
+
expected = ser.iloc[[0, 2, 3]]
|
| 548 |
+
tm.assert_series_equal(result, expected)
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
def test_getitem_generator(string_series):
|
| 552 |
+
gen = (x > 0 for x in string_series)
|
| 553 |
+
result = string_series[gen]
|
| 554 |
+
result2 = string_series[iter(string_series > 0)]
|
| 555 |
+
expected = string_series[string_series > 0]
|
| 556 |
+
tm.assert_series_equal(result, expected)
|
| 557 |
+
tm.assert_series_equal(result2, expected)
|
| 558 |
+
|
| 559 |
+
|
| 560 |
+
@pytest.mark.parametrize(
|
| 561 |
+
"data",
|
| 562 |
+
[
|
| 563 |
+
[0, 1],
|
| 564 |
+
date_range("2012-01-01", periods=2),
|
| 565 |
+
date_range("2012-01-01", periods=2, tz="CET"),
|
| 566 |
+
],
|
| 567 |
+
)
|
| 568 |
+
def test_getitem_ndim_deprecated(data):
|
| 569 |
+
series = Series(data)
|
| 570 |
+
with pytest.raises(ValueError, match="Multi-dimensional indexing"):
|
| 571 |
+
series[:, None]
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
def test_getitem_multilevel_scalar_slice_not_implemented(
|
| 575 |
+
multiindex_year_month_day_dataframe_random_data,
|
| 576 |
+
):
|
| 577 |
+
# not implementing this for now
|
| 578 |
+
df = multiindex_year_month_day_dataframe_random_data
|
| 579 |
+
ser = df["A"]
|
| 580 |
+
|
| 581 |
+
msg = r"\(2000, slice\(3, 4, None\)\)"
|
| 582 |
+
with pytest.raises(TypeError, match=msg):
|
| 583 |
+
ser[2000, 3:4]
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
def test_getitem_dataframe_raises():
|
| 587 |
+
rng = list(range(10))
|
| 588 |
+
ser = Series(10, index=rng)
|
| 589 |
+
df = DataFrame(rng, index=rng)
|
| 590 |
+
msg = (
|
| 591 |
+
"Indexing a Series with DataFrame is not supported, "
|
| 592 |
+
"use the appropriate DataFrame column"
|
| 593 |
+
)
|
| 594 |
+
with pytest.raises(TypeError, match=msg):
|
| 595 |
+
ser[df > 5]
|
| 596 |
+
|
| 597 |
+
|
| 598 |
+
def test_getitem_assignment_series_alignment():
|
| 599 |
+
# https://github.com/pandas-dev/pandas/issues/37427
|
| 600 |
+
# with getitem, when assigning with a Series, it is not first aligned
|
| 601 |
+
ser = Series(range(10))
|
| 602 |
+
idx = np.array([2, 4, 9])
|
| 603 |
+
ser[idx] = Series([10, 11, 12])
|
| 604 |
+
expected = Series([0, 1, 10, 3, 11, 5, 6, 7, 8, 12])
|
| 605 |
+
tm.assert_series_equal(ser, expected)
|
| 606 |
+
|
| 607 |
+
|
| 608 |
+
def test_getitem_duplicate_index_mistyped_key_raises_keyerror():
|
| 609 |
+
# GH#29189 float_index.get_loc(None) should raise KeyError, not TypeError
|
| 610 |
+
ser = Series([2, 5, 6, 8], index=[2.0, 4.0, 4.0, 5.0])
|
| 611 |
+
with pytest.raises(KeyError, match="None"):
|
| 612 |
+
ser[None]
|
| 613 |
+
|
| 614 |
+
with pytest.raises(KeyError, match="None"):
|
| 615 |
+
ser.index.get_loc(None)
|
| 616 |
+
|
| 617 |
+
with pytest.raises(KeyError, match="None"):
|
| 618 |
+
ser.index._engine.get_loc(None)
|
| 619 |
+
|
| 620 |
+
|
| 621 |
+
def test_getitem_1tuple_slice_without_multiindex():
|
| 622 |
+
ser = Series(range(5))
|
| 623 |
+
key = (slice(3),)
|
| 624 |
+
|
| 625 |
+
result = ser[key]
|
| 626 |
+
expected = ser[key[0]]
|
| 627 |
+
tm.assert_series_equal(result, expected)
|
| 628 |
+
|
| 629 |
+
|
| 630 |
+
def test_getitem_preserve_name(datetime_series):
|
| 631 |
+
result = datetime_series[datetime_series > 0]
|
| 632 |
+
assert result.name == datetime_series.name
|
| 633 |
+
|
| 634 |
+
result = datetime_series[5:10]
|
| 635 |
+
assert result.name == datetime_series.name
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
def test_getitem_with_integer_labels():
|
| 639 |
+
# integer indexes, be careful
|
| 640 |
+
ser = Series(
|
| 641 |
+
np.random.default_rng(2).standard_normal(10), index=list(range(0, 20, 2))
|
| 642 |
+
)
|
| 643 |
+
inds = [0, 2, 5, 7, 8]
|
| 644 |
+
arr_inds = np.array([0, 2, 5, 7, 8])
|
| 645 |
+
with pytest.raises(KeyError, match="not in index"):
|
| 646 |
+
ser[inds]
|
| 647 |
+
|
| 648 |
+
with pytest.raises(KeyError, match="not in index"):
|
| 649 |
+
ser[arr_inds]
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
def test_getitem_missing(datetime_series):
|
| 653 |
+
# missing
|
| 654 |
+
d = datetime_series.index[0] - BDay()
|
| 655 |
+
msg = r"Timestamp\('1999-12-31 00:00:00'\)"
|
| 656 |
+
with pytest.raises(KeyError, match=msg):
|
| 657 |
+
datetime_series[d]
|
| 658 |
+
|
| 659 |
+
|
| 660 |
+
def test_getitem_fancy(string_series, object_series):
|
| 661 |
+
msg = r"None of \[Index\(\[1, 2, 3\], dtype='int(32|64)'\)\] are in the \[index\]"
|
| 662 |
+
with pytest.raises(KeyError, match=msg):
|
| 663 |
+
string_series[[1, 2, 3]]
|
| 664 |
+
with pytest.raises(KeyError, match=msg):
|
| 665 |
+
object_series[[1, 2, 3]]
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
def test_getitem_box_float64(datetime_series):
|
| 669 |
+
with pytest.raises(KeyError, match="^5$"):
|
| 670 |
+
datetime_series[5]
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def test_getitem_unordered_dup():
|
| 674 |
+
obj = Series(range(5), index=["c", "a", "a", "b", "b"])
|
| 675 |
+
assert is_scalar(obj["c"])
|
| 676 |
+
assert obj["c"] == 0
|
| 677 |
+
|
| 678 |
+
|
| 679 |
+
def test_getitem_dups():
|
| 680 |
+
ser = Series(range(5), index=["A", "A", "B", "C", "C"], dtype=np.int64)
|
| 681 |
+
expected = Series([3, 4], index=["C", "C"], dtype=np.int64)
|
| 682 |
+
result = ser["C"]
|
| 683 |
+
tm.assert_series_equal(result, expected)
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
def test_getitem_categorical_str():
|
| 687 |
+
# GH#31765
|
| 688 |
+
ser = Series(range(5), index=Categorical(["a", "b", "c", "a", "b"]))
|
| 689 |
+
result = ser["a"]
|
| 690 |
+
expected = ser.iloc[[0, 3]]
|
| 691 |
+
tm.assert_series_equal(result, expected)
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
def test_slice_can_reorder_not_uniquely_indexed():
|
| 695 |
+
ser = Series(1, index=["a", "a", "b", "b", "c"])
|
| 696 |
+
ser[::-1] # it works!
|
| 697 |
+
|
| 698 |
+
|
| 699 |
+
@pytest.mark.parametrize("index_vals", ["aabcd", "aadcb"])
|
| 700 |
+
def test_duplicated_index_getitem_positional_indexer(index_vals):
|
| 701 |
+
# GH 11747; changed in 3.0 integers are treated as always-labels
|
| 702 |
+
s = Series(range(5), index=list(index_vals))
|
| 703 |
+
|
| 704 |
+
with pytest.raises(KeyError, match="^3$"):
|
| 705 |
+
s[3]
|
| 706 |
+
|
| 707 |
+
|
| 708 |
+
class TestGetitemDeprecatedIndexers:
|
| 709 |
+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
|
| 710 |
+
def test_getitem_dict_and_set_deprecated(self, key):
|
| 711 |
+
# GH#42825 enforced in 2.0
|
| 712 |
+
ser = Series([1, 2, 3])
|
| 713 |
+
with pytest.raises(TypeError, match="as an indexer is not supported"):
|
| 714 |
+
ser[key]
|
| 715 |
+
|
| 716 |
+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
|
| 717 |
+
def test_setitem_dict_and_set_disallowed(self, key):
|
| 718 |
+
# GH#42825 enforced in 2.0
|
| 719 |
+
ser = Series([1, 2, 3])
|
| 720 |
+
with pytest.raises(TypeError, match="as an indexer is not supported"):
|
| 721 |
+
ser[key] = 1
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_indexing.py
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""test get/set & misc"""
|
| 2 |
+
|
| 3 |
+
from datetime import timedelta
|
| 4 |
+
import re
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pytest
|
| 8 |
+
|
| 9 |
+
from pandas.errors import IndexingError
|
| 10 |
+
|
| 11 |
+
from pandas import (
|
| 12 |
+
NA,
|
| 13 |
+
DataFrame,
|
| 14 |
+
Index,
|
| 15 |
+
IndexSlice,
|
| 16 |
+
MultiIndex,
|
| 17 |
+
NaT,
|
| 18 |
+
Series,
|
| 19 |
+
Timedelta,
|
| 20 |
+
Timestamp,
|
| 21 |
+
concat,
|
| 22 |
+
date_range,
|
| 23 |
+
isna,
|
| 24 |
+
period_range,
|
| 25 |
+
timedelta_range,
|
| 26 |
+
)
|
| 27 |
+
import pandas._testing as tm
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_basic_indexing():
|
| 31 |
+
s = Series(
|
| 32 |
+
np.random.default_rng(2).standard_normal(5), index=["a", "b", "a", "a", "b"]
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
with pytest.raises(KeyError, match="^5$"):
|
| 36 |
+
s[5]
|
| 37 |
+
|
| 38 |
+
with pytest.raises(KeyError, match=r"^'c'$"):
|
| 39 |
+
s["c"]
|
| 40 |
+
|
| 41 |
+
s = s.sort_index()
|
| 42 |
+
|
| 43 |
+
with pytest.raises(KeyError, match="^5$"):
|
| 44 |
+
s[5]
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def test_getitem_numeric_should_not_fallback_to_positional(any_numeric_dtype):
|
| 48 |
+
# GH51053
|
| 49 |
+
dtype = any_numeric_dtype
|
| 50 |
+
idx = Index([1, 0, 1], dtype=dtype)
|
| 51 |
+
ser = Series(range(3), index=idx)
|
| 52 |
+
result = ser[1]
|
| 53 |
+
expected = Series([0, 2], index=Index([1, 1], dtype=dtype))
|
| 54 |
+
tm.assert_series_equal(result, expected, check_exact=True)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def test_setitem_numeric_should_not_fallback_to_positional(any_numeric_dtype):
|
| 58 |
+
# GH51053
|
| 59 |
+
dtype = any_numeric_dtype
|
| 60 |
+
idx = Index([1, 0, 1], dtype=dtype)
|
| 61 |
+
ser = Series(range(3), index=idx)
|
| 62 |
+
ser[1] = 10
|
| 63 |
+
expected = Series([10, 1, 10], index=idx)
|
| 64 |
+
tm.assert_series_equal(ser, expected, check_exact=True)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_basic_getitem_with_labels(datetime_series):
|
| 68 |
+
indices = datetime_series.index[[5, 10, 15]]
|
| 69 |
+
|
| 70 |
+
result = datetime_series[indices]
|
| 71 |
+
expected = datetime_series.reindex(indices)
|
| 72 |
+
tm.assert_series_equal(result, expected)
|
| 73 |
+
|
| 74 |
+
result = datetime_series[indices[0] : indices[2]]
|
| 75 |
+
expected = datetime_series.loc[indices[0] : indices[2]]
|
| 76 |
+
tm.assert_series_equal(result, expected)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def test_basic_getitem_dt64tz_values():
|
| 80 |
+
# GH12089
|
| 81 |
+
# with tz for values
|
| 82 |
+
ser = Series(
|
| 83 |
+
date_range("2011-01-01", periods=3, tz="US/Eastern"), index=["a", "b", "c"]
|
| 84 |
+
)
|
| 85 |
+
expected = Timestamp("2011-01-01", tz="US/Eastern")
|
| 86 |
+
result = ser.loc["a"]
|
| 87 |
+
assert result == expected
|
| 88 |
+
result = ser.iloc[0]
|
| 89 |
+
assert result == expected
|
| 90 |
+
result = ser["a"]
|
| 91 |
+
assert result == expected
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def test_getitem_setitem_ellipsis():
|
| 95 |
+
s = Series(np.random.default_rng(2).standard_normal(10))
|
| 96 |
+
|
| 97 |
+
result = s[...]
|
| 98 |
+
tm.assert_series_equal(result, s)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
@pytest.mark.parametrize(
|
| 102 |
+
"result_1, duplicate_item, expected_1",
|
| 103 |
+
[
|
| 104 |
+
[
|
| 105 |
+
{1: 12, 2: [1, 2, 2, 3]},
|
| 106 |
+
{1: 313},
|
| 107 |
+
Series({1: 12}, dtype=object),
|
| 108 |
+
],
|
| 109 |
+
[
|
| 110 |
+
{1: [1, 2, 3], 2: [1, 2, 2, 3]},
|
| 111 |
+
{1: [1, 2, 3]},
|
| 112 |
+
Series({1: [1, 2, 3]}),
|
| 113 |
+
],
|
| 114 |
+
],
|
| 115 |
+
)
|
| 116 |
+
def test_getitem_with_duplicates_indices(result_1, duplicate_item, expected_1):
|
| 117 |
+
# GH 17610
|
| 118 |
+
result_1 = Series(result_1)
|
| 119 |
+
duplicate_item = Series(duplicate_item)
|
| 120 |
+
result = result_1._append_internal(duplicate_item)
|
| 121 |
+
expected = expected_1._append_internal(duplicate_item)
|
| 122 |
+
tm.assert_series_equal(result[1], expected)
|
| 123 |
+
assert result[2] == result_1[2]
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def test_getitem_setitem_integers():
|
| 127 |
+
# caused bug without test
|
| 128 |
+
s = Series([1, 2, 3], ["a", "b", "c"])
|
| 129 |
+
|
| 130 |
+
assert s.iloc[0] == s["a"]
|
| 131 |
+
s.iloc[0] = 5
|
| 132 |
+
tm.assert_almost_equal(s["a"], 5)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def test_series_box_timestamp():
|
| 136 |
+
rng = date_range("20090415", "20090519", freq="B")
|
| 137 |
+
ser = Series(rng)
|
| 138 |
+
assert isinstance(ser[0], Timestamp)
|
| 139 |
+
assert isinstance(ser.at[1], Timestamp)
|
| 140 |
+
assert isinstance(ser.iat[2], Timestamp)
|
| 141 |
+
assert isinstance(ser.loc[3], Timestamp)
|
| 142 |
+
assert isinstance(ser.iloc[4], Timestamp)
|
| 143 |
+
|
| 144 |
+
ser = Series(rng, index=rng)
|
| 145 |
+
assert isinstance(ser[rng[0]], Timestamp)
|
| 146 |
+
assert isinstance(ser.at[rng[1]], Timestamp)
|
| 147 |
+
assert isinstance(ser.iat[2], Timestamp)
|
| 148 |
+
assert isinstance(ser.loc[rng[3]], Timestamp)
|
| 149 |
+
assert isinstance(ser.iloc[4], Timestamp)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def test_series_box_timedelta():
|
| 153 |
+
rng = timedelta_range("1 day 1 s", periods=5, freq="h")
|
| 154 |
+
ser = Series(rng)
|
| 155 |
+
assert isinstance(ser[0], Timedelta)
|
| 156 |
+
assert isinstance(ser.at[1], Timedelta)
|
| 157 |
+
assert isinstance(ser.iat[2], Timedelta)
|
| 158 |
+
assert isinstance(ser.loc[3], Timedelta)
|
| 159 |
+
assert isinstance(ser.iloc[4], Timedelta)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def test_getitem_ambiguous_keyerror(indexer_sl):
|
| 163 |
+
ser = Series(range(10), index=list(range(0, 20, 2)))
|
| 164 |
+
with pytest.raises(KeyError, match=r"^1$"):
|
| 165 |
+
indexer_sl(ser)[1]
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def test_getitem_dups_with_missing(indexer_sl):
|
| 169 |
+
# breaks reindex, so need to use .loc internally
|
| 170 |
+
# GH 4246
|
| 171 |
+
ser = Series([1, 2, 3, 4], ["foo", "bar", "foo", "bah"])
|
| 172 |
+
with pytest.raises(KeyError, match=re.escape("['bam'] not in index")):
|
| 173 |
+
indexer_sl(ser)[["foo", "bar", "bah", "bam"]]
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def test_setitem_ambiguous_keyerror(indexer_sl):
|
| 177 |
+
s = Series(range(10), index=list(range(0, 20, 2)))
|
| 178 |
+
|
| 179 |
+
# equivalent of an append
|
| 180 |
+
s2 = s.copy()
|
| 181 |
+
indexer_sl(s2)[1] = 5
|
| 182 |
+
expected = concat([s, Series([5], index=[1])])
|
| 183 |
+
tm.assert_series_equal(s2, expected)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def test_setitem(datetime_series):
|
| 187 |
+
datetime_series[datetime_series.index[5]] = np.nan
|
| 188 |
+
datetime_series.iloc[[1, 2, 17]] = np.nan
|
| 189 |
+
datetime_series.iloc[6] = np.nan
|
| 190 |
+
assert np.isnan(datetime_series.iloc[6])
|
| 191 |
+
assert np.isnan(datetime_series.iloc[2])
|
| 192 |
+
datetime_series[np.isnan(datetime_series)] = 5
|
| 193 |
+
assert not np.isnan(datetime_series.iloc[2])
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def test_setslice(datetime_series):
|
| 197 |
+
sl = datetime_series[5:20]
|
| 198 |
+
assert len(sl) == len(sl.index)
|
| 199 |
+
assert sl.index.is_unique is True
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def test_basic_getitem_setitem_corner(datetime_series):
|
| 203 |
+
# invalid tuples, e.g. td.ts[:, None] vs. td.ts[:, 2]
|
| 204 |
+
msg = "key of type tuple not found and not a MultiIndex"
|
| 205 |
+
with pytest.raises(KeyError, match=msg):
|
| 206 |
+
datetime_series[:, 2]
|
| 207 |
+
with pytest.raises(KeyError, match=msg):
|
| 208 |
+
datetime_series[:, 2] = 2
|
| 209 |
+
|
| 210 |
+
# weird lists. [slice(0, 5)] raises but not two slices
|
| 211 |
+
msg = "Indexing with a single-item list"
|
| 212 |
+
with pytest.raises(ValueError, match=msg):
|
| 213 |
+
# GH#31299
|
| 214 |
+
datetime_series[[slice(None, 5)]]
|
| 215 |
+
|
| 216 |
+
# but we're OK with a single-element tuple
|
| 217 |
+
result = datetime_series[(slice(None, 5),)]
|
| 218 |
+
expected = datetime_series[:5]
|
| 219 |
+
tm.assert_series_equal(result, expected)
|
| 220 |
+
|
| 221 |
+
# OK
|
| 222 |
+
msg = r"unhashable type(: 'slice')?"
|
| 223 |
+
with pytest.raises(TypeError, match=msg):
|
| 224 |
+
datetime_series[[5, [None, None]]]
|
| 225 |
+
with pytest.raises(TypeError, match=msg):
|
| 226 |
+
datetime_series[[5, [None, None]]] = 2
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def test_slice(string_series, object_series):
|
| 230 |
+
original = string_series.copy()
|
| 231 |
+
numSlice = string_series[10:20]
|
| 232 |
+
numSliceEnd = string_series[-10:]
|
| 233 |
+
objSlice = object_series[10:20]
|
| 234 |
+
|
| 235 |
+
assert string_series.index[9] not in numSlice.index
|
| 236 |
+
assert object_series.index[9] not in objSlice.index
|
| 237 |
+
|
| 238 |
+
assert len(numSlice) == len(numSlice.index)
|
| 239 |
+
assert string_series[numSlice.index[0]] == numSlice[numSlice.index[0]]
|
| 240 |
+
|
| 241 |
+
assert numSlice.index[1] == string_series.index[11]
|
| 242 |
+
tm.assert_numpy_array_equal(np.array(numSliceEnd), np.array(string_series)[-10:])
|
| 243 |
+
|
| 244 |
+
# Test return view.
|
| 245 |
+
sl = string_series[10:20]
|
| 246 |
+
sl[:] = 0
|
| 247 |
+
|
| 248 |
+
# Doesn't modify parent (CoW)
|
| 249 |
+
tm.assert_series_equal(string_series, original)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def test_timedelta_assignment():
|
| 253 |
+
# GH 8209
|
| 254 |
+
s = Series([], dtype=object)
|
| 255 |
+
s.loc["B"] = timedelta(1)
|
| 256 |
+
expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["B"])
|
| 257 |
+
tm.assert_series_equal(s, expected)
|
| 258 |
+
|
| 259 |
+
s = s.reindex(s.index.insert(0, "A"))
|
| 260 |
+
expected = Series(
|
| 261 |
+
[np.nan, Timedelta("1 days")], dtype="timedelta64[us]", index=["A", "B"]
|
| 262 |
+
)
|
| 263 |
+
tm.assert_series_equal(s, expected)
|
| 264 |
+
|
| 265 |
+
s.loc["A"] = timedelta(1)
|
| 266 |
+
expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["A", "B"])
|
| 267 |
+
tm.assert_series_equal(s, expected)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def test_underlying_data_conversion():
|
| 271 |
+
# GH 4080
|
| 272 |
+
df = DataFrame({c: [1, 2, 3] for c in ["a", "b", "c"]})
|
| 273 |
+
return_value = df.set_index(["a", "b", "c"], inplace=True)
|
| 274 |
+
assert return_value is None
|
| 275 |
+
s = Series([1], index=[(2, 2, 2)])
|
| 276 |
+
df["val"] = 0
|
| 277 |
+
df_original = df.copy()
|
| 278 |
+
df
|
| 279 |
+
|
| 280 |
+
with tm.raises_chained_assignment_error():
|
| 281 |
+
df["val"].update(s)
|
| 282 |
+
expected = df_original
|
| 283 |
+
tm.assert_frame_equal(df, expected)
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def test_preserve_refs(datetime_series):
|
| 287 |
+
seq = datetime_series.iloc[[5, 10, 15]]
|
| 288 |
+
seq.iloc[1] = np.nan
|
| 289 |
+
assert not np.isnan(datetime_series.iloc[10])
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def test_multilevel_preserve_name(lexsorted_two_level_string_multiindex, indexer_sl):
|
| 293 |
+
index = lexsorted_two_level_string_multiindex
|
| 294 |
+
ser = Series(
|
| 295 |
+
np.random.default_rng(2).standard_normal(len(index)), index=index, name="sth"
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
result = indexer_sl(ser)["foo"]
|
| 299 |
+
assert result.name == ser.name
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
# miscellaneous methods
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
@pytest.mark.parametrize(
|
| 306 |
+
"index",
|
| 307 |
+
[
|
| 308 |
+
date_range("2014-01-01", periods=20, freq="MS"),
|
| 309 |
+
period_range("2014-01", periods=20, freq="M"),
|
| 310 |
+
timedelta_range("0", periods=20, freq="h"),
|
| 311 |
+
],
|
| 312 |
+
)
|
| 313 |
+
def test_slice_with_negative_step(index):
|
| 314 |
+
keystr1 = str(index[9])
|
| 315 |
+
keystr2 = str(index[13])
|
| 316 |
+
|
| 317 |
+
ser = Series(np.arange(20), index)
|
| 318 |
+
SLC = IndexSlice
|
| 319 |
+
|
| 320 |
+
for key in [keystr1, index[9]]:
|
| 321 |
+
tm.assert_indexing_slices_equivalent(ser, SLC[key::-1], SLC[9::-1])
|
| 322 |
+
tm.assert_indexing_slices_equivalent(ser, SLC[:key:-1], SLC[:8:-1])
|
| 323 |
+
|
| 324 |
+
for key2 in [keystr2, index[13]]:
|
| 325 |
+
tm.assert_indexing_slices_equivalent(ser, SLC[key2:key:-1], SLC[13:8:-1])
|
| 326 |
+
tm.assert_indexing_slices_equivalent(ser, SLC[key:key2:-1], SLC[0:0:-1])
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def test_tuple_index():
|
| 330 |
+
# GH 35534 - Selecting values when a Series has an Index of tuples
|
| 331 |
+
s = Series([1, 2], index=[("a",), ("b",)])
|
| 332 |
+
assert s[("a",)] == 1
|
| 333 |
+
assert s[("b",)] == 2
|
| 334 |
+
s[("b",)] = 3
|
| 335 |
+
assert s[("b",)] == 3
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def test_frozenset_index():
|
| 339 |
+
# GH35747 - Selecting values when a Series has an Index of frozenset
|
| 340 |
+
idx0, idx1 = frozenset("a"), frozenset("b")
|
| 341 |
+
s = Series([1, 2], index=[idx0, idx1])
|
| 342 |
+
assert s[idx0] == 1
|
| 343 |
+
assert s[idx1] == 2
|
| 344 |
+
s[idx1] = 3
|
| 345 |
+
assert s[idx1] == 3
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def test_loc_setitem_all_false_indexer():
|
| 349 |
+
# GH#45778
|
| 350 |
+
ser = Series([1, 2], index=["a", "b"])
|
| 351 |
+
expected = ser.copy()
|
| 352 |
+
rhs = Series([6, 7], index=["a", "b"])
|
| 353 |
+
ser.loc[ser > 100] = rhs
|
| 354 |
+
tm.assert_series_equal(ser, expected)
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def test_loc_boolean_indexer_non_matching_index():
|
| 358 |
+
# GH#46551
|
| 359 |
+
ser = Series([1])
|
| 360 |
+
result = ser.loc[Series([NA, False], dtype="boolean")]
|
| 361 |
+
expected = Series([], dtype="int64")
|
| 362 |
+
tm.assert_series_equal(result, expected)
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def test_loc_boolean_indexer_miss_matching_index():
|
| 366 |
+
# GH#46551
|
| 367 |
+
ser = Series([1])
|
| 368 |
+
indexer = Series([NA, False], dtype="boolean", index=[1, 2])
|
| 369 |
+
with pytest.raises(IndexingError, match="Unalignable"):
|
| 370 |
+
ser.loc[indexer]
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
def test_loc_setitem_nested_data_enlargement():
|
| 374 |
+
# GH#48614
|
| 375 |
+
df = DataFrame({"a": [1]})
|
| 376 |
+
ser = Series({"label": df})
|
| 377 |
+
ser.loc["new_label"] = df
|
| 378 |
+
expected = Series({"label": df, "new_label": df})
|
| 379 |
+
tm.assert_series_equal(ser, expected)
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def test_loc_ea_numeric_index_oob_slice_end():
|
| 383 |
+
# GH#50161
|
| 384 |
+
ser = Series(1, index=Index([0, 1, 2], dtype="Int64"))
|
| 385 |
+
result = ser.loc[2:3]
|
| 386 |
+
expected = Series(1, index=Index([2], dtype="Int64"))
|
| 387 |
+
tm.assert_series_equal(result, expected)
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def test_getitem_bool_int_key():
|
| 391 |
+
# GH#48653
|
| 392 |
+
ser = Series({True: 1, False: 0})
|
| 393 |
+
with pytest.raises(KeyError, match="0"):
|
| 394 |
+
ser.loc[0]
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
@pytest.mark.parametrize("val", [{}, {"b": "x"}])
|
| 398 |
+
@pytest.mark.parametrize("indexer", [[], [False, False], slice(0, -1), np.array([])])
|
| 399 |
+
def test_setitem_empty_indexer(indexer, val):
|
| 400 |
+
# GH#45981
|
| 401 |
+
df = DataFrame({"a": [1, 2], **val})
|
| 402 |
+
expected = df.copy()
|
| 403 |
+
df.loc[indexer] = 1.5
|
| 404 |
+
tm.assert_frame_equal(df, expected)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def test_loc_non_monotonic_index_with_a_missing_label():
|
| 408 |
+
msg = "Cannot get left slice bound for non-monotonic index with a missing label 4"
|
| 409 |
+
ser = Series([3, 6, 7, 6], index=[3, 8, 7, 6])
|
| 410 |
+
with pytest.raises(KeyError, match=msg):
|
| 411 |
+
ser.loc[4:7]
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
class TestDeprecatedIndexers:
|
| 415 |
+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
|
| 416 |
+
def test_getitem_dict_and_set_deprecated(self, key):
|
| 417 |
+
# GH#42825 enforced in 2.0
|
| 418 |
+
ser = Series([1, 2])
|
| 419 |
+
with pytest.raises(TypeError, match="as an indexer is not supported"):
|
| 420 |
+
ser.loc[key]
|
| 421 |
+
|
| 422 |
+
@pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)])
|
| 423 |
+
def test_getitem_dict_and_set_deprecated_multiindex(self, key):
|
| 424 |
+
# GH#42825 enforced in 2.0
|
| 425 |
+
ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)]))
|
| 426 |
+
with pytest.raises(TypeError, match="as an indexer is not supported"):
|
| 427 |
+
ser.loc[key]
|
| 428 |
+
|
| 429 |
+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
|
| 430 |
+
def test_setitem_dict_and_set_disallowed(self, key):
|
| 431 |
+
# GH#42825 enforced in 2.0
|
| 432 |
+
ser = Series([1, 2])
|
| 433 |
+
with pytest.raises(TypeError, match="as an indexer is not supported"):
|
| 434 |
+
ser.loc[key] = 1
|
| 435 |
+
|
| 436 |
+
@pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)])
|
| 437 |
+
def test_setitem_dict_and_set_disallowed_multiindex(self, key):
|
| 438 |
+
# GH#42825 enforced in 2.0
|
| 439 |
+
ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)]))
|
| 440 |
+
with pytest.raises(TypeError, match="as an indexer is not supported"):
|
| 441 |
+
ser.loc[key] = 1
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
class TestSetitemValidation:
|
| 445 |
+
# This is adapted from pandas/tests/arrays/masked/test_indexing.py
|
| 446 |
+
def _check_setitem_invalid(self, ser, invalid, indexer):
|
| 447 |
+
orig_ser = ser.copy()
|
| 448 |
+
|
| 449 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 450 |
+
ser[indexer] = invalid
|
| 451 |
+
ser = orig_ser.copy()
|
| 452 |
+
|
| 453 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 454 |
+
ser.iloc[indexer] = invalid
|
| 455 |
+
ser = orig_ser.copy()
|
| 456 |
+
|
| 457 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 458 |
+
ser.loc[indexer] = invalid
|
| 459 |
+
ser = orig_ser.copy()
|
| 460 |
+
|
| 461 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 462 |
+
ser[:] = invalid
|
| 463 |
+
|
| 464 |
+
def _check_setitem_valid(self, ser, value, indexer):
|
| 465 |
+
orig_ser = ser.copy()
|
| 466 |
+
|
| 467 |
+
ser[indexer] = value
|
| 468 |
+
ser = orig_ser.copy()
|
| 469 |
+
|
| 470 |
+
ser.iloc[indexer] = value
|
| 471 |
+
ser = orig_ser.copy()
|
| 472 |
+
|
| 473 |
+
ser.loc[indexer] = value
|
| 474 |
+
ser = orig_ser.copy()
|
| 475 |
+
|
| 476 |
+
ser[:] = value
|
| 477 |
+
|
| 478 |
+
_invalid_scalars = [
|
| 479 |
+
1 + 2j,
|
| 480 |
+
"True",
|
| 481 |
+
"1",
|
| 482 |
+
"1.0",
|
| 483 |
+
NaT,
|
| 484 |
+
np.datetime64("NaT", "ns"),
|
| 485 |
+
np.timedelta64("NaT", "ns"),
|
| 486 |
+
]
|
| 487 |
+
_indexers = [0, [0], slice(0, 1), [True, False, False], slice(None, None, None)]
|
| 488 |
+
|
| 489 |
+
@pytest.mark.parametrize(
|
| 490 |
+
"invalid", [*_invalid_scalars, 1, 1.0, np.int64(1), np.float64(1)]
|
| 491 |
+
)
|
| 492 |
+
@pytest.mark.parametrize("indexer", _indexers)
|
| 493 |
+
def test_setitem_validation_scalar_bool(self, invalid, indexer):
|
| 494 |
+
ser = Series([True, False, False], dtype="bool")
|
| 495 |
+
self._check_setitem_invalid(ser, invalid, indexer)
|
| 496 |
+
|
| 497 |
+
@pytest.mark.parametrize("invalid", [*_invalid_scalars, True, 1.5, np.float64(1.5)])
|
| 498 |
+
@pytest.mark.parametrize("indexer", _indexers)
|
| 499 |
+
def test_setitem_validation_scalar_int(self, invalid, any_int_numpy_dtype, indexer):
|
| 500 |
+
ser = Series([1, 2, 3], dtype=any_int_numpy_dtype)
|
| 501 |
+
if isna(invalid) and invalid is not NaT and not np.isnat(invalid):
|
| 502 |
+
self._check_setitem_valid(ser, invalid, indexer)
|
| 503 |
+
else:
|
| 504 |
+
self._check_setitem_invalid(ser, invalid, indexer)
|
| 505 |
+
|
| 506 |
+
@pytest.mark.parametrize("invalid", [*_invalid_scalars, True])
|
| 507 |
+
@pytest.mark.parametrize("indexer", _indexers)
|
| 508 |
+
def test_setitem_validation_scalar_float(self, invalid, float_numpy_dtype, indexer):
|
| 509 |
+
ser = Series([1, 2, None], dtype=float_numpy_dtype)
|
| 510 |
+
self._check_setitem_invalid(ser, invalid, indexer)
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_mask.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import Series
|
| 5 |
+
import pandas._testing as tm
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_mask():
|
| 9 |
+
# compare with tested results in test_where
|
| 10 |
+
s = Series(np.random.default_rng(2).standard_normal(5))
|
| 11 |
+
cond = s > 0
|
| 12 |
+
|
| 13 |
+
rs = s.where(~cond, np.nan)
|
| 14 |
+
tm.assert_series_equal(rs, s.mask(cond))
|
| 15 |
+
|
| 16 |
+
rs = s.where(~cond)
|
| 17 |
+
rs2 = s.mask(cond)
|
| 18 |
+
tm.assert_series_equal(rs, rs2)
|
| 19 |
+
|
| 20 |
+
rs = s.where(~cond, -s)
|
| 21 |
+
rs2 = s.mask(cond, -s)
|
| 22 |
+
tm.assert_series_equal(rs, rs2)
|
| 23 |
+
|
| 24 |
+
cond = Series([True, False, False, True, False], index=s.index)
|
| 25 |
+
s2 = -(s.abs())
|
| 26 |
+
rs = s2.where(~cond[:3])
|
| 27 |
+
rs2 = s2.mask(cond[:3])
|
| 28 |
+
tm.assert_series_equal(rs, rs2)
|
| 29 |
+
|
| 30 |
+
rs = s2.where(~cond[:3], -s2)
|
| 31 |
+
rs2 = s2.mask(cond[:3], -s2)
|
| 32 |
+
tm.assert_series_equal(rs, rs2)
|
| 33 |
+
|
| 34 |
+
msg = "Array conditional must be same shape as self"
|
| 35 |
+
with pytest.raises(ValueError, match=msg):
|
| 36 |
+
s.mask(1)
|
| 37 |
+
with pytest.raises(ValueError, match=msg):
|
| 38 |
+
s.mask(cond[:3].values, -s)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def test_mask_casts():
|
| 42 |
+
# dtype changes
|
| 43 |
+
ser = Series([1, 2, 3, 4])
|
| 44 |
+
result = ser.mask(ser > 2, np.nan)
|
| 45 |
+
expected = Series([1, 2, np.nan, np.nan])
|
| 46 |
+
tm.assert_series_equal(result, expected)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def test_mask_casts2():
|
| 50 |
+
# see gh-21891
|
| 51 |
+
ser = Series([1, 2])
|
| 52 |
+
res = ser.mask([True, False])
|
| 53 |
+
|
| 54 |
+
exp = Series([np.nan, 2])
|
| 55 |
+
tm.assert_series_equal(res, exp)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def test_mask_inplace():
|
| 59 |
+
s = Series(np.random.default_rng(2).standard_normal(5))
|
| 60 |
+
cond = s > 0
|
| 61 |
+
|
| 62 |
+
rs = s.copy()
|
| 63 |
+
rs.mask(cond, inplace=True)
|
| 64 |
+
tm.assert_series_equal(rs.dropna(), s[~cond])
|
| 65 |
+
tm.assert_series_equal(rs, s.mask(cond))
|
| 66 |
+
|
| 67 |
+
rs = s.copy()
|
| 68 |
+
rs.mask(cond, -s, inplace=True)
|
| 69 |
+
tm.assert_series_equal(rs, s.mask(cond, -s))
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_set_value.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from pandas import (
|
| 6 |
+
DatetimeIndex,
|
| 7 |
+
Index,
|
| 8 |
+
Series,
|
| 9 |
+
)
|
| 10 |
+
import pandas._testing as tm
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def test_series_set_value(using_infer_string):
|
| 14 |
+
# GH#1561, GH#51363 as of 3.0 we do not do inference in Index.insert
|
| 15 |
+
|
| 16 |
+
dates = [datetime(2001, 1, 1), datetime(2001, 1, 2)]
|
| 17 |
+
if using_infer_string:
|
| 18 |
+
index = DatetimeIndex(dates)
|
| 19 |
+
else:
|
| 20 |
+
index = Index(dates, dtype=object)
|
| 21 |
+
|
| 22 |
+
s = Series(dtype=object)
|
| 23 |
+
s._set_value(dates[0], 1.0)
|
| 24 |
+
s._set_value(dates[1], np.nan)
|
| 25 |
+
|
| 26 |
+
expected = Series([1.0, np.nan], index=index)
|
| 27 |
+
|
| 28 |
+
tm.assert_series_equal(s, expected)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_set_value_dt64(datetime_series):
|
| 32 |
+
idx = datetime_series.index[10]
|
| 33 |
+
res = datetime_series._set_value(idx, 0)
|
| 34 |
+
assert res is None
|
| 35 |
+
assert datetime_series[idx] == 0
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def test_set_value_str_index(string_series):
|
| 39 |
+
# equiv
|
| 40 |
+
ser = string_series.copy()
|
| 41 |
+
res = ser._set_value("foobar", 0)
|
| 42 |
+
assert res is None
|
| 43 |
+
assert ser.index[-1] == "foobar"
|
| 44 |
+
assert ser["foobar"] == 0
|
| 45 |
+
|
| 46 |
+
ser2 = string_series.copy()
|
| 47 |
+
ser2.loc["foobar"] = 0
|
| 48 |
+
assert ser2.index[-1] == "foobar"
|
| 49 |
+
assert ser2["foobar"] == 0
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_setitem.py
ADDED
|
@@ -0,0 +1,1843 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
from datetime import (
|
| 3 |
+
date,
|
| 4 |
+
datetime,
|
| 5 |
+
)
|
| 6 |
+
from decimal import Decimal
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pytest
|
| 10 |
+
|
| 11 |
+
from pandas.compat.numpy import np_version_gt2
|
| 12 |
+
from pandas.errors import IndexingError
|
| 13 |
+
|
| 14 |
+
from pandas.core.dtypes.common import is_list_like
|
| 15 |
+
|
| 16 |
+
from pandas import (
|
| 17 |
+
NA,
|
| 18 |
+
Categorical,
|
| 19 |
+
DataFrame,
|
| 20 |
+
DatetimeIndex,
|
| 21 |
+
Index,
|
| 22 |
+
Interval,
|
| 23 |
+
IntervalIndex,
|
| 24 |
+
MultiIndex,
|
| 25 |
+
NaT,
|
| 26 |
+
Period,
|
| 27 |
+
Series,
|
| 28 |
+
StringDtype,
|
| 29 |
+
Timedelta,
|
| 30 |
+
Timestamp,
|
| 31 |
+
array,
|
| 32 |
+
concat,
|
| 33 |
+
date_range,
|
| 34 |
+
interval_range,
|
| 35 |
+
period_range,
|
| 36 |
+
timedelta_range,
|
| 37 |
+
)
|
| 38 |
+
import pandas._testing as tm
|
| 39 |
+
|
| 40 |
+
from pandas.tseries.offsets import BDay
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class TestSetitemDT64Values:
|
| 44 |
+
def test_setitem_none_nan(self):
|
| 45 |
+
series = Series(date_range("1/1/2000", periods=10, unit="ns"))
|
| 46 |
+
series[3] = None
|
| 47 |
+
assert series[3] is NaT
|
| 48 |
+
|
| 49 |
+
series[3:5] = None
|
| 50 |
+
assert series[4] is NaT
|
| 51 |
+
|
| 52 |
+
series[5] = np.nan
|
| 53 |
+
assert series[5] is NaT
|
| 54 |
+
|
| 55 |
+
series[5:7] = np.nan
|
| 56 |
+
assert series[6] is NaT
|
| 57 |
+
|
| 58 |
+
def test_setitem_multiindex_empty_slice(self):
|
| 59 |
+
# https://github.com/pandas-dev/pandas/issues/35878
|
| 60 |
+
idx = MultiIndex.from_tuples([("a", 1), ("b", 2)])
|
| 61 |
+
result = Series([1, 2], index=idx)
|
| 62 |
+
expected = result.copy()
|
| 63 |
+
result.loc[[]] = 0
|
| 64 |
+
tm.assert_series_equal(result, expected)
|
| 65 |
+
|
| 66 |
+
def test_setitem_with_string_index(self):
|
| 67 |
+
# GH#23451
|
| 68 |
+
# Set object dtype to avoid upcast when setting date.today()
|
| 69 |
+
ser = Series([1, 2, 3], index=["Date", "b", "other"], dtype=object)
|
| 70 |
+
ser["Date"] = date.today()
|
| 71 |
+
assert ser.Date == date.today()
|
| 72 |
+
assert ser["Date"] == date.today()
|
| 73 |
+
|
| 74 |
+
def test_setitem_tuple_with_datetimetz_values(self):
|
| 75 |
+
# GH#20441
|
| 76 |
+
arr = date_range("2017", periods=4, tz="US/Eastern", unit="ns")
|
| 77 |
+
index = [(0, 1), (0, 2), (0, 3), (0, 4)]
|
| 78 |
+
result = Series(arr, index=index)
|
| 79 |
+
expected = result.copy()
|
| 80 |
+
result[(0, 1)] = np.nan
|
| 81 |
+
expected.iloc[0] = np.nan
|
| 82 |
+
tm.assert_series_equal(result, expected)
|
| 83 |
+
|
| 84 |
+
@pytest.mark.parametrize("tz", ["US/Eastern", "UTC", "Asia/Tokyo"])
|
| 85 |
+
def test_setitem_with_tz(self, tz, indexer_sli):
|
| 86 |
+
orig = Series(date_range("2016-01-01", freq="h", periods=3, tz=tz, unit="ns"))
|
| 87 |
+
assert orig.dtype == f"datetime64[ns, {tz}]"
|
| 88 |
+
|
| 89 |
+
exp = Series(
|
| 90 |
+
[
|
| 91 |
+
Timestamp("2016-01-01 00:00", tz=tz),
|
| 92 |
+
Timestamp("2011-01-01 00:00", tz=tz),
|
| 93 |
+
Timestamp("2016-01-01 02:00", tz=tz),
|
| 94 |
+
],
|
| 95 |
+
dtype=orig.dtype,
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
# scalar
|
| 99 |
+
ser = orig.copy()
|
| 100 |
+
indexer_sli(ser)[1] = Timestamp("2011-01-01", tz=tz)
|
| 101 |
+
tm.assert_series_equal(ser, exp)
|
| 102 |
+
|
| 103 |
+
# vector
|
| 104 |
+
vals = Series(
|
| 105 |
+
[Timestamp("2011-01-01", tz=tz), Timestamp("2012-01-01", tz=tz)],
|
| 106 |
+
index=[1, 2],
|
| 107 |
+
dtype=orig.dtype,
|
| 108 |
+
)
|
| 109 |
+
assert vals.dtype == f"datetime64[ns, {tz}]"
|
| 110 |
+
|
| 111 |
+
exp = Series(
|
| 112 |
+
[
|
| 113 |
+
Timestamp("2016-01-01 00:00", tz=tz),
|
| 114 |
+
Timestamp("2011-01-01 00:00", tz=tz),
|
| 115 |
+
Timestamp("2012-01-01 00:00", tz=tz),
|
| 116 |
+
],
|
| 117 |
+
dtype=orig.dtype,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
ser = orig.copy()
|
| 121 |
+
indexer_sli(ser)[[1, 2]] = vals
|
| 122 |
+
tm.assert_series_equal(ser, exp)
|
| 123 |
+
|
| 124 |
+
def test_setitem_with_tz_dst(self, indexer_sli):
|
| 125 |
+
# GH#14146 trouble setting values near DST boundary
|
| 126 |
+
tz = "US/Eastern"
|
| 127 |
+
orig = Series(date_range("2016-11-06", freq="h", periods=3, tz=tz, unit="ns"))
|
| 128 |
+
assert orig.dtype == f"datetime64[ns, {tz}]"
|
| 129 |
+
|
| 130 |
+
exp = Series(
|
| 131 |
+
[
|
| 132 |
+
Timestamp("2016-11-06 00:00-04:00", tz=tz),
|
| 133 |
+
Timestamp("2011-01-01 00:00-05:00", tz=tz),
|
| 134 |
+
Timestamp("2016-11-06 01:00-05:00", tz=tz),
|
| 135 |
+
],
|
| 136 |
+
dtype=orig.dtype,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# scalar
|
| 140 |
+
ser = orig.copy()
|
| 141 |
+
indexer_sli(ser)[1] = Timestamp("2011-01-01", tz=tz)
|
| 142 |
+
tm.assert_series_equal(ser, exp)
|
| 143 |
+
|
| 144 |
+
# vector
|
| 145 |
+
vals = Series(
|
| 146 |
+
[Timestamp("2011-01-01", tz=tz), Timestamp("2012-01-01", tz=tz)],
|
| 147 |
+
index=[1, 2],
|
| 148 |
+
dtype=orig.dtype,
|
| 149 |
+
)
|
| 150 |
+
assert vals.dtype == f"datetime64[ns, {tz}]"
|
| 151 |
+
|
| 152 |
+
exp = Series(
|
| 153 |
+
[
|
| 154 |
+
Timestamp("2016-11-06 00:00", tz=tz),
|
| 155 |
+
Timestamp("2011-01-01 00:00", tz=tz),
|
| 156 |
+
Timestamp("2012-01-01 00:00", tz=tz),
|
| 157 |
+
],
|
| 158 |
+
dtype=orig.dtype,
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
ser = orig.copy()
|
| 162 |
+
indexer_sli(ser)[[1, 2]] = vals
|
| 163 |
+
tm.assert_series_equal(ser, exp)
|
| 164 |
+
|
| 165 |
+
def test_object_series_setitem_dt64array_exact_match(self):
|
| 166 |
+
# make sure the dt64 isn't cast by numpy to integers
|
| 167 |
+
# https://github.com/numpy/numpy/issues/12550
|
| 168 |
+
|
| 169 |
+
ser = Series({"X": np.nan}, dtype=object)
|
| 170 |
+
|
| 171 |
+
indexer = [True]
|
| 172 |
+
|
| 173 |
+
# "exact_match" -> size of array being set matches size of ser
|
| 174 |
+
value = np.array([4], dtype="M8[ns]")
|
| 175 |
+
|
| 176 |
+
ser.iloc[indexer] = value
|
| 177 |
+
|
| 178 |
+
expected = Series([value[0]], index=["X"], dtype=object)
|
| 179 |
+
assert all(isinstance(x, np.datetime64) for x in expected.values)
|
| 180 |
+
|
| 181 |
+
tm.assert_series_equal(ser, expected)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
class TestSetitemScalarIndexer:
|
| 185 |
+
def test_setitem_negative_out_of_bounds(self):
|
| 186 |
+
# As of 3.0, int keys are treated as labels, so this becomes
|
| 187 |
+
# setitem-with-expansion
|
| 188 |
+
ser = Series(["a"] * 10, index=["a"] * 10)
|
| 189 |
+
ser[-11] = "foo"
|
| 190 |
+
exp = Series(["a"] * 10 + ["foo"], index=["a"] * 10 + [-11])
|
| 191 |
+
tm.assert_series_equal(ser, exp)
|
| 192 |
+
|
| 193 |
+
@pytest.mark.parametrize("indexer", [tm.loc, tm.at])
|
| 194 |
+
@pytest.mark.parametrize("ser_index", [0, 1])
|
| 195 |
+
def test_setitem_series_object_dtype(self, indexer, ser_index):
|
| 196 |
+
# GH#38303
|
| 197 |
+
ser = Series([0, 0], dtype="object")
|
| 198 |
+
idxr = indexer(ser)
|
| 199 |
+
idxr[0] = Series([42], index=[ser_index])
|
| 200 |
+
expected = Series([Series([42], index=[ser_index]), 0], dtype="object")
|
| 201 |
+
tm.assert_series_equal(ser, expected)
|
| 202 |
+
|
| 203 |
+
@pytest.mark.parametrize("index, exp_value", [(0, 42), (1, np.nan)])
|
| 204 |
+
def test_setitem_series(self, index, exp_value):
|
| 205 |
+
# GH#38303
|
| 206 |
+
ser = Series([0, 0])
|
| 207 |
+
ser.loc[0] = Series([42], index=[index])
|
| 208 |
+
expected = Series([exp_value, 0])
|
| 209 |
+
tm.assert_series_equal(ser, expected)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class TestSetitemSlices:
|
| 213 |
+
def test_setitem_slice_float_raises(self, datetime_series):
|
| 214 |
+
msg = (
|
| 215 |
+
"cannot do slice indexing on DatetimeIndex with these indexers "
|
| 216 |
+
r"\[{key}\] of type float"
|
| 217 |
+
)
|
| 218 |
+
with pytest.raises(TypeError, match=msg.format(key=r"4\.0")):
|
| 219 |
+
datetime_series[4.0:10.0] = 0
|
| 220 |
+
|
| 221 |
+
with pytest.raises(TypeError, match=msg.format(key=r"4\.5")):
|
| 222 |
+
datetime_series[4.5:10.0] = 0
|
| 223 |
+
|
| 224 |
+
def test_setitem_slice(self):
|
| 225 |
+
ser = Series(range(10), index=list(range(10)))
|
| 226 |
+
ser[-12:] = 0
|
| 227 |
+
assert (ser == 0).all()
|
| 228 |
+
|
| 229 |
+
ser[:-12] = 5
|
| 230 |
+
assert (ser == 0).all()
|
| 231 |
+
|
| 232 |
+
def test_setitem_slice_integers(self):
|
| 233 |
+
ser = Series(
|
| 234 |
+
np.random.default_rng(2).standard_normal(8),
|
| 235 |
+
index=[2, 4, 6, 8, 10, 12, 14, 16],
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
ser[:4] = 0
|
| 239 |
+
assert (ser[:4] == 0).all()
|
| 240 |
+
assert not (ser[4:] == 0).any()
|
| 241 |
+
|
| 242 |
+
def test_setitem_slicestep(self):
|
| 243 |
+
# caught this bug when writing tests
|
| 244 |
+
series = Series(
|
| 245 |
+
np.arange(20, dtype=np.float64), index=np.arange(20, dtype=np.int64)
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
series[::2] = 0
|
| 249 |
+
assert (series[::2] == 0).all()
|
| 250 |
+
|
| 251 |
+
def test_setitem_multiindex_slice(self, indexer_sli):
|
| 252 |
+
# GH 8856
|
| 253 |
+
mi = MultiIndex.from_product(([0, 1], list("abcde")))
|
| 254 |
+
result = Series(np.arange(10, dtype=np.int64), mi)
|
| 255 |
+
indexer_sli(result)[::4] = 100
|
| 256 |
+
expected = Series([100, 1, 2, 3, 100, 5, 6, 7, 100, 9], mi)
|
| 257 |
+
tm.assert_series_equal(result, expected)
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
class TestSetitemBooleanMask:
|
| 261 |
+
def test_setitem_mask_cast(self):
|
| 262 |
+
# GH#2746
|
| 263 |
+
# need to upcast
|
| 264 |
+
ser = Series([1, 2], index=[1, 2], dtype="int64")
|
| 265 |
+
ser[[True, False]] = Series([0], index=[1], dtype="int64")
|
| 266 |
+
expected = Series([0, 2], index=[1, 2], dtype="int64")
|
| 267 |
+
|
| 268 |
+
tm.assert_series_equal(ser, expected)
|
| 269 |
+
|
| 270 |
+
def test_setitem_mask_align_and_promote(self):
|
| 271 |
+
# GH#8387: test that changing types does not break alignment
|
| 272 |
+
ts = Series(
|
| 273 |
+
np.random.default_rng(2).standard_normal(100), index=np.arange(100, 0, -1)
|
| 274 |
+
).round(5)
|
| 275 |
+
mask = ts > 0
|
| 276 |
+
left = ts.copy()
|
| 277 |
+
right = ts[mask].copy().map(str)
|
| 278 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 279 |
+
left[mask] = right
|
| 280 |
+
|
| 281 |
+
def test_setitem_mask_promote_strs(self):
|
| 282 |
+
ser = Series([0, 1, 2, 0])
|
| 283 |
+
mask = ser > 0
|
| 284 |
+
ser2 = ser[mask].map(str)
|
| 285 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 286 |
+
ser[mask] = ser2
|
| 287 |
+
|
| 288 |
+
def test_setitem_mask_promote(self):
|
| 289 |
+
ser = Series([0, "foo", "bar", 0])
|
| 290 |
+
mask = Series([False, True, True, False])
|
| 291 |
+
ser2 = ser[mask]
|
| 292 |
+
ser[mask] = ser2
|
| 293 |
+
|
| 294 |
+
expected = Series([0, "foo", "bar", 0])
|
| 295 |
+
tm.assert_series_equal(ser, expected)
|
| 296 |
+
|
| 297 |
+
def test_setitem_boolean(self, string_series):
|
| 298 |
+
mask = string_series > string_series.median()
|
| 299 |
+
|
| 300 |
+
# similar indexed series
|
| 301 |
+
result = string_series.copy()
|
| 302 |
+
result[mask] = string_series * 2
|
| 303 |
+
expected = string_series * 2
|
| 304 |
+
tm.assert_series_equal(result[mask], expected[mask])
|
| 305 |
+
|
| 306 |
+
# needs alignment
|
| 307 |
+
result = string_series.copy()
|
| 308 |
+
result[mask] = (string_series * 2)[0:5]
|
| 309 |
+
expected = (string_series * 2)[0:5].reindex_like(string_series)
|
| 310 |
+
expected[-mask] = string_series[mask]
|
| 311 |
+
tm.assert_series_equal(result[mask], expected[mask])
|
| 312 |
+
|
| 313 |
+
def test_setitem_boolean_corner(self, datetime_series):
|
| 314 |
+
ts = datetime_series
|
| 315 |
+
mask_shifted = ts.shift(1, freq=BDay()) > ts.median()
|
| 316 |
+
|
| 317 |
+
msg = (
|
| 318 |
+
r"Unalignable boolean Series provided as indexer \(index of "
|
| 319 |
+
r"the boolean Series and of the indexed object do not match"
|
| 320 |
+
)
|
| 321 |
+
with pytest.raises(IndexingError, match=msg):
|
| 322 |
+
ts[mask_shifted] = 1
|
| 323 |
+
|
| 324 |
+
with pytest.raises(IndexingError, match=msg):
|
| 325 |
+
ts.loc[mask_shifted] = 1
|
| 326 |
+
|
| 327 |
+
def test_setitem_boolean_different_order(self, string_series):
|
| 328 |
+
ordered = string_series.sort_values()
|
| 329 |
+
|
| 330 |
+
copy = string_series.copy()
|
| 331 |
+
copy[ordered > 0] = 0
|
| 332 |
+
|
| 333 |
+
expected = string_series.copy()
|
| 334 |
+
expected[expected > 0] = 0
|
| 335 |
+
|
| 336 |
+
tm.assert_series_equal(copy, expected)
|
| 337 |
+
|
| 338 |
+
@pytest.mark.parametrize("func", [list, np.array, Series])
|
| 339 |
+
def test_setitem_boolean_python_list(self, func):
|
| 340 |
+
# GH19406
|
| 341 |
+
ser = Series([None, "b", None])
|
| 342 |
+
mask = func([True, False, True])
|
| 343 |
+
ser[mask] = ["a", "c"]
|
| 344 |
+
expected = Series(["a", "b", "c"])
|
| 345 |
+
tm.assert_series_equal(ser, expected)
|
| 346 |
+
|
| 347 |
+
def test_setitem_boolean_nullable_int_types(self, any_numeric_ea_dtype):
|
| 348 |
+
# GH: 26468
|
| 349 |
+
ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype)
|
| 350 |
+
ser[ser > 6] = Series(range(4), dtype=any_numeric_ea_dtype)
|
| 351 |
+
expected = Series([5, 6, 2, 3], dtype=any_numeric_ea_dtype)
|
| 352 |
+
tm.assert_series_equal(ser, expected)
|
| 353 |
+
|
| 354 |
+
ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype)
|
| 355 |
+
ser.loc[ser > 6] = Series(range(4), dtype=any_numeric_ea_dtype)
|
| 356 |
+
tm.assert_series_equal(ser, expected)
|
| 357 |
+
|
| 358 |
+
ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype)
|
| 359 |
+
loc_ser = Series(range(4), dtype=any_numeric_ea_dtype)
|
| 360 |
+
ser.loc[ser > 6] = loc_ser.loc[loc_ser > 1]
|
| 361 |
+
tm.assert_series_equal(ser, expected)
|
| 362 |
+
|
| 363 |
+
def test_setitem_with_bool_mask_and_values_matching_n_trues_in_length(self):
|
| 364 |
+
# GH#30567
|
| 365 |
+
ser = Series([None] * 10)
|
| 366 |
+
mask = [False] * 3 + [True] * 5 + [False] * 2
|
| 367 |
+
ser[mask] = range(5)
|
| 368 |
+
result = ser
|
| 369 |
+
expected = Series([None] * 3 + list(range(5)) + [None] * 2, dtype=object)
|
| 370 |
+
tm.assert_series_equal(result, expected)
|
| 371 |
+
|
| 372 |
+
def test_setitem_nan_with_bool(self):
|
| 373 |
+
# GH 13034
|
| 374 |
+
result = Series([True, False, True])
|
| 375 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 376 |
+
result[0] = np.nan
|
| 377 |
+
|
| 378 |
+
def test_setitem_mask_smallint_upcast(self):
|
| 379 |
+
orig = Series([1, 2, 3], dtype="int8")
|
| 380 |
+
alt = np.array([999, 1000, 1001], dtype=np.int64)
|
| 381 |
+
|
| 382 |
+
mask = np.array([True, False, True])
|
| 383 |
+
|
| 384 |
+
ser = orig.copy()
|
| 385 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 386 |
+
ser[mask] = Series(alt)
|
| 387 |
+
|
| 388 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 389 |
+
ser.mask(mask, alt, inplace=True)
|
| 390 |
+
|
| 391 |
+
res = ser.where(~mask, Series(alt))
|
| 392 |
+
expected = Series([999, 2, 1001])
|
| 393 |
+
tm.assert_series_equal(res, expected)
|
| 394 |
+
|
| 395 |
+
def test_setitem_mask_smallint_no_upcast(self):
|
| 396 |
+
# like test_setitem_mask_smallint_upcast, but while we can't hold 'alt',
|
| 397 |
+
# we *can* hold alt[mask] without casting
|
| 398 |
+
orig = Series([1, 2, 3], dtype="uint8")
|
| 399 |
+
alt = Series([245, 1000, 246], dtype=np.int64)
|
| 400 |
+
|
| 401 |
+
mask = np.array([True, False, True])
|
| 402 |
+
|
| 403 |
+
ser = orig.copy()
|
| 404 |
+
ser[mask] = alt
|
| 405 |
+
expected = Series([245, 2, 246], dtype="uint8")
|
| 406 |
+
tm.assert_series_equal(ser, expected)
|
| 407 |
+
|
| 408 |
+
ser2 = orig.copy()
|
| 409 |
+
ser2.mask(mask, alt, inplace=True)
|
| 410 |
+
tm.assert_series_equal(ser2, expected)
|
| 411 |
+
|
| 412 |
+
# TODO: ser.where(~mask, alt) unnecessarily upcasts to int64
|
| 413 |
+
ser3 = orig.copy()
|
| 414 |
+
res = ser3.where(~mask, alt)
|
| 415 |
+
tm.assert_series_equal(res, expected, check_dtype=False)
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
class TestSetitemViewCopySemantics:
|
| 419 |
+
def test_setitem_invalidates_datetime_index_freq(self):
|
| 420 |
+
# GH#24096 altering a datetime64tz Series inplace invalidates the
|
| 421 |
+
# `freq` attribute on the underlying DatetimeIndex
|
| 422 |
+
|
| 423 |
+
dti = date_range("20130101", periods=3, tz="US/Eastern")
|
| 424 |
+
ts = dti[1]
|
| 425 |
+
ser = Series(dti)
|
| 426 |
+
assert ser._values is not dti
|
| 427 |
+
assert ser._values._ndarray.base is dti._data._ndarray.base
|
| 428 |
+
assert dti.freq == "D"
|
| 429 |
+
ser.iloc[1] = NaT
|
| 430 |
+
assert ser._values.freq is None
|
| 431 |
+
|
| 432 |
+
# check that the DatetimeIndex was not altered in place
|
| 433 |
+
assert ser._values is not dti
|
| 434 |
+
assert ser._values._ndarray.base is not dti._data._ndarray.base
|
| 435 |
+
assert dti[1] == ts
|
| 436 |
+
assert dti.freq == "D"
|
| 437 |
+
|
| 438 |
+
def test_dt64tz_setitem_does_not_mutate_dti(self):
|
| 439 |
+
# GH#21907, GH#24096
|
| 440 |
+
dti = date_range("2016-01-01", periods=10, tz="US/Pacific")
|
| 441 |
+
ts = dti[0]
|
| 442 |
+
ser = Series(dti)
|
| 443 |
+
assert ser._values is not dti
|
| 444 |
+
assert ser._values._ndarray.base is dti._data._ndarray.base
|
| 445 |
+
assert ser._mgr.blocks[0].values._ndarray.base is dti._data._ndarray.base
|
| 446 |
+
|
| 447 |
+
assert ser._mgr.blocks[0].values is not dti
|
| 448 |
+
|
| 449 |
+
ser[::3] = NaT
|
| 450 |
+
assert ser[0] is NaT
|
| 451 |
+
assert dti[0] == ts
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
class TestSetitemCallable:
|
| 455 |
+
def test_setitem_callable_key(self):
|
| 456 |
+
# GH#12533
|
| 457 |
+
ser = Series([1, 2, 3, 4], index=list("ABCD"))
|
| 458 |
+
ser[lambda x: "A"] = -1
|
| 459 |
+
|
| 460 |
+
expected = Series([-1, 2, 3, 4], index=list("ABCD"))
|
| 461 |
+
tm.assert_series_equal(ser, expected)
|
| 462 |
+
|
| 463 |
+
def test_setitem_callable_other(self):
|
| 464 |
+
# GH#13299
|
| 465 |
+
inc = lambda x: x + 1
|
| 466 |
+
|
| 467 |
+
# set object dtype to avoid upcast when setting inc
|
| 468 |
+
ser = Series([1, 2, -1, 4], dtype=object)
|
| 469 |
+
ser[ser < 0] = inc
|
| 470 |
+
|
| 471 |
+
expected = Series([1, 2, inc, 4])
|
| 472 |
+
tm.assert_series_equal(ser, expected)
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
class TestSetitemWithExpansion:
|
| 476 |
+
def test_setitem_empty_series(self, using_infer_string):
|
| 477 |
+
# GH#10193
|
| 478 |
+
key = Timestamp("2012-01-01")
|
| 479 |
+
series = Series(dtype=object)
|
| 480 |
+
series[key] = 47
|
| 481 |
+
expected = Series(
|
| 482 |
+
47, index=[key] if using_infer_string else Index([key], dtype=object)
|
| 483 |
+
)
|
| 484 |
+
tm.assert_series_equal(series, expected)
|
| 485 |
+
|
| 486 |
+
def test_setitem_empty_series_datetimeindex_preserves_freq(self):
|
| 487 |
+
# GH#33573 our index should retain its freq
|
| 488 |
+
dti = DatetimeIndex([], freq="D", dtype="M8[ns]")
|
| 489 |
+
series = Series([], index=dti, dtype=object)
|
| 490 |
+
key = Timestamp("2012-01-01")
|
| 491 |
+
series[key] = 47
|
| 492 |
+
expected = Series(47, DatetimeIndex([key], freq="D").as_unit("ns"))
|
| 493 |
+
tm.assert_series_equal(series, expected)
|
| 494 |
+
assert series.index.freq == expected.index.freq
|
| 495 |
+
|
| 496 |
+
def test_setitem_empty_series_timestamp_preserves_dtype(self):
|
| 497 |
+
# GH 21881
|
| 498 |
+
timestamp = Timestamp(1412526600000000000)
|
| 499 |
+
series = Series([timestamp], index=["timestamp"], dtype=object)
|
| 500 |
+
expected = series["timestamp"]
|
| 501 |
+
|
| 502 |
+
series = Series([], dtype=object)
|
| 503 |
+
series["anything"] = 300.0
|
| 504 |
+
series["timestamp"] = timestamp
|
| 505 |
+
result = series["timestamp"]
|
| 506 |
+
assert result == expected
|
| 507 |
+
|
| 508 |
+
@pytest.mark.parametrize(
|
| 509 |
+
"td",
|
| 510 |
+
[
|
| 511 |
+
Timedelta("9 days"),
|
| 512 |
+
Timedelta("9 days").to_timedelta64(),
|
| 513 |
+
Timedelta("9 days").to_pytimedelta(),
|
| 514 |
+
],
|
| 515 |
+
)
|
| 516 |
+
def test_append_timedelta_does_not_cast(self, td, using_infer_string, request):
|
| 517 |
+
# GH#22717 inserting a Timedelta should _not_ cast to int64
|
| 518 |
+
if using_infer_string and not isinstance(td, Timedelta):
|
| 519 |
+
# TODO: GH#56010
|
| 520 |
+
request.applymarker(pytest.mark.xfail(reason="inferred as string"))
|
| 521 |
+
|
| 522 |
+
expected = Series(["x", td], index=[0, "td"], dtype=object)
|
| 523 |
+
|
| 524 |
+
ser = Series(["x"])
|
| 525 |
+
ser["td"] = td
|
| 526 |
+
tm.assert_series_equal(ser, expected)
|
| 527 |
+
assert isinstance(ser["td"], Timedelta)
|
| 528 |
+
|
| 529 |
+
ser = Series(["x"])
|
| 530 |
+
ser.loc["td"] = Timedelta("9 days")
|
| 531 |
+
tm.assert_series_equal(ser, expected)
|
| 532 |
+
assert isinstance(ser["td"], Timedelta)
|
| 533 |
+
|
| 534 |
+
def test_setitem_with_expansion_type_promotion(self):
|
| 535 |
+
# GH#12599
|
| 536 |
+
ser = Series(dtype=object)
|
| 537 |
+
ser["a"] = Timestamp("2016-01-01")
|
| 538 |
+
ser["b"] = 3.0
|
| 539 |
+
ser["c"] = "foo"
|
| 540 |
+
expected = Series([Timestamp("2016-01-01"), 3.0, "foo"], index=["a", "b", "c"])
|
| 541 |
+
tm.assert_series_equal(ser, expected)
|
| 542 |
+
|
| 543 |
+
def test_setitem_not_contained(self, string_series):
|
| 544 |
+
# set item that's not contained
|
| 545 |
+
ser = string_series.copy()
|
| 546 |
+
assert "foobar" not in ser.index
|
| 547 |
+
ser["foobar"] = 1
|
| 548 |
+
|
| 549 |
+
app = Series([1], index=["foobar"], name="series")
|
| 550 |
+
expected = concat([string_series, app])
|
| 551 |
+
tm.assert_series_equal(ser, expected)
|
| 552 |
+
|
| 553 |
+
def test_setitem_keep_precision(self, any_numeric_ea_dtype):
|
| 554 |
+
# GH#32346
|
| 555 |
+
ser = Series([1, 2], dtype=any_numeric_ea_dtype)
|
| 556 |
+
ser[2] = 10
|
| 557 |
+
expected = Series([1, 2, 10], dtype=any_numeric_ea_dtype)
|
| 558 |
+
tm.assert_series_equal(ser, expected)
|
| 559 |
+
|
| 560 |
+
@pytest.mark.parametrize(
|
| 561 |
+
"na, target_na, dtype, target_dtype, indexer, raises",
|
| 562 |
+
[
|
| 563 |
+
(NA, NA, "Int64", "Int64", 1, False),
|
| 564 |
+
(NA, NA, "Int64", "Int64", 2, False),
|
| 565 |
+
(NA, np.nan, "int64", "float64", 1, False),
|
| 566 |
+
(NA, np.nan, "int64", "float64", 2, False),
|
| 567 |
+
(NaT, NaT, "int64", "object", 1, True),
|
| 568 |
+
(NaT, NaT, "int64", "object", 2, False),
|
| 569 |
+
(np.nan, NA, "Int64", "Int64", 1, False),
|
| 570 |
+
(np.nan, NA, "Int64", "Int64", 2, False),
|
| 571 |
+
(np.nan, NA, "Float64", "Float64", 1, False),
|
| 572 |
+
(np.nan, NA, "Float64", "Float64", 2, False),
|
| 573 |
+
(np.nan, np.nan, "int64", "float64", 1, False),
|
| 574 |
+
(np.nan, np.nan, "int64", "float64", 2, False),
|
| 575 |
+
],
|
| 576 |
+
)
|
| 577 |
+
def test_setitem_enlarge_with_na(
|
| 578 |
+
self, na, target_na, dtype, target_dtype, indexer, raises
|
| 579 |
+
):
|
| 580 |
+
# GH#32346
|
| 581 |
+
ser = Series([1, 2], dtype=dtype)
|
| 582 |
+
if raises:
|
| 583 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 584 |
+
ser[indexer] = na
|
| 585 |
+
else:
|
| 586 |
+
ser[indexer] = na
|
| 587 |
+
expected_values = [1, target_na] if indexer == 1 else [1, 2, target_na]
|
| 588 |
+
expected = Series(expected_values, dtype=target_dtype)
|
| 589 |
+
tm.assert_series_equal(ser, expected)
|
| 590 |
+
|
| 591 |
+
def test_setitem_enlargement_object_none(self, nulls_fixture, using_infer_string):
|
| 592 |
+
# GH#48665
|
| 593 |
+
ser = Series(["a", "b"])
|
| 594 |
+
ser[3] = nulls_fixture
|
| 595 |
+
dtype = (
|
| 596 |
+
"str"
|
| 597 |
+
if using_infer_string and not isinstance(nulls_fixture, Decimal)
|
| 598 |
+
else object
|
| 599 |
+
)
|
| 600 |
+
expected = Series(["a", "b", nulls_fixture], index=[0, 1, 3], dtype=dtype)
|
| 601 |
+
tm.assert_series_equal(ser, expected)
|
| 602 |
+
if using_infer_string:
|
| 603 |
+
ser[3] is np.nan
|
| 604 |
+
else:
|
| 605 |
+
assert ser[3] is nulls_fixture
|
| 606 |
+
|
| 607 |
+
|
| 608 |
+
def test_setitem_scalar_into_readonly_backing_data():
|
| 609 |
+
# GH#14359: test that you cannot mutate a read only buffer
|
| 610 |
+
|
| 611 |
+
array = np.zeros(5)
|
| 612 |
+
array.flags.writeable = False # make the array immutable
|
| 613 |
+
series = Series(array, copy=False)
|
| 614 |
+
|
| 615 |
+
for n in series.index:
|
| 616 |
+
msg = "assignment destination is read-only"
|
| 617 |
+
with pytest.raises(ValueError, match=msg):
|
| 618 |
+
series[n] = 1
|
| 619 |
+
|
| 620 |
+
assert array[n] == 0
|
| 621 |
+
|
| 622 |
+
|
| 623 |
+
def test_setitem_slice_into_readonly_backing_data():
|
| 624 |
+
# GH#14359: test that you cannot mutate a read only buffer
|
| 625 |
+
|
| 626 |
+
array = np.zeros(5)
|
| 627 |
+
array.flags.writeable = False # make the array immutable
|
| 628 |
+
series = Series(array, copy=False)
|
| 629 |
+
|
| 630 |
+
msg = "assignment destination is read-only"
|
| 631 |
+
with pytest.raises(ValueError, match=msg):
|
| 632 |
+
series[1:3] = 1
|
| 633 |
+
|
| 634 |
+
assert not array.any()
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
def test_setitem_categorical_assigning_ops():
|
| 638 |
+
orig = Series(Categorical(["b", "b"], categories=["a", "b"]))
|
| 639 |
+
ser = orig.copy()
|
| 640 |
+
ser[:] = "a"
|
| 641 |
+
exp = Series(Categorical(["a", "a"], categories=["a", "b"]))
|
| 642 |
+
tm.assert_series_equal(ser, exp)
|
| 643 |
+
|
| 644 |
+
ser = orig.copy()
|
| 645 |
+
ser[1] = "a"
|
| 646 |
+
exp = Series(Categorical(["b", "a"], categories=["a", "b"]))
|
| 647 |
+
tm.assert_series_equal(ser, exp)
|
| 648 |
+
|
| 649 |
+
ser = orig.copy()
|
| 650 |
+
ser[ser.index > 0] = "a"
|
| 651 |
+
exp = Series(Categorical(["b", "a"], categories=["a", "b"]))
|
| 652 |
+
tm.assert_series_equal(ser, exp)
|
| 653 |
+
|
| 654 |
+
ser = orig.copy()
|
| 655 |
+
ser[[False, True]] = "a"
|
| 656 |
+
exp = Series(Categorical(["b", "a"], categories=["a", "b"]))
|
| 657 |
+
tm.assert_series_equal(ser, exp)
|
| 658 |
+
|
| 659 |
+
ser = orig.copy()
|
| 660 |
+
ser.index = ["x", "y"]
|
| 661 |
+
ser["y"] = "a"
|
| 662 |
+
exp = Series(Categorical(["b", "a"], categories=["a", "b"]), index=["x", "y"])
|
| 663 |
+
tm.assert_series_equal(ser, exp)
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
def test_setitem_nan_into_categorical():
|
| 667 |
+
# ensure that one can set something to np.nan
|
| 668 |
+
ser = Series(Categorical([1, 2, 3]))
|
| 669 |
+
exp = Series(Categorical([1, np.nan, 3], categories=[1, 2, 3]))
|
| 670 |
+
ser[1] = np.nan
|
| 671 |
+
tm.assert_series_equal(ser, exp)
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
class TestSetitemCasting:
|
| 675 |
+
@pytest.mark.parametrize("unique", [True, False])
|
| 676 |
+
@pytest.mark.parametrize("val", [3, 3.0, "3"], ids=type)
|
| 677 |
+
def test_setitem_non_bool_into_bool(self, val, indexer_sli, unique):
|
| 678 |
+
# dont cast these 3-like values to bool
|
| 679 |
+
ser = Series([True, False])
|
| 680 |
+
if not unique:
|
| 681 |
+
ser.index = [1, 1]
|
| 682 |
+
|
| 683 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 684 |
+
indexer_sli(ser)[1] = val
|
| 685 |
+
|
| 686 |
+
def test_setitem_boolean_array_into_npbool(self):
|
| 687 |
+
# GH#45462
|
| 688 |
+
ser = Series([True, False, True])
|
| 689 |
+
values = ser._values
|
| 690 |
+
arr = array([True, False, None])
|
| 691 |
+
|
| 692 |
+
ser[:2] = arr[:2] # no NAs -> can set inplace
|
| 693 |
+
assert ser._values is values
|
| 694 |
+
|
| 695 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 696 |
+
ser[1:] = arr[1:] # has an NA -> cast to boolean dtype
|
| 697 |
+
|
| 698 |
+
|
| 699 |
+
class SetitemCastingEquivalents:
|
| 700 |
+
"""
|
| 701 |
+
Check each of several methods that _should_ be equivalent to `obj[key] = val`
|
| 702 |
+
|
| 703 |
+
We assume that
|
| 704 |
+
- obj.index is the default Index(range(len(obj)))
|
| 705 |
+
- the setitem does not expand the obj
|
| 706 |
+
"""
|
| 707 |
+
|
| 708 |
+
@pytest.fixture
|
| 709 |
+
def is_inplace(self, obj, expected):
|
| 710 |
+
"""
|
| 711 |
+
Whether we expect the setting to be in-place or not.
|
| 712 |
+
"""
|
| 713 |
+
return expected.dtype == obj.dtype
|
| 714 |
+
|
| 715 |
+
def check_indexer(self, obj, key, expected, val, indexer, is_inplace):
|
| 716 |
+
orig = obj
|
| 717 |
+
obj = obj.copy()
|
| 718 |
+
arr = obj._values
|
| 719 |
+
|
| 720 |
+
indexer(obj)[key] = val
|
| 721 |
+
tm.assert_series_equal(obj, expected)
|
| 722 |
+
|
| 723 |
+
self._check_inplace(is_inplace, orig, arr, obj)
|
| 724 |
+
|
| 725 |
+
def _check_inplace(self, is_inplace, orig, arr, obj):
|
| 726 |
+
if is_inplace is None:
|
| 727 |
+
# We are not (yet) checking whether setting is inplace or not
|
| 728 |
+
pass
|
| 729 |
+
elif is_inplace:
|
| 730 |
+
if arr.dtype.kind in ["m", "M"]:
|
| 731 |
+
# We may not have the same DTA/TDA, but will have the same
|
| 732 |
+
# underlying data
|
| 733 |
+
assert arr._ndarray is obj._values._ndarray
|
| 734 |
+
else:
|
| 735 |
+
assert obj._values is arr
|
| 736 |
+
else:
|
| 737 |
+
# otherwise original array should be unchanged
|
| 738 |
+
tm.assert_equal(arr, orig._values)
|
| 739 |
+
|
| 740 |
+
def test_int_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace):
|
| 741 |
+
if not isinstance(key, int):
|
| 742 |
+
pytest.skip("Not relevant for int key")
|
| 743 |
+
if raises:
|
| 744 |
+
ctx = pytest.raises(TypeError, match="Invalid value")
|
| 745 |
+
else:
|
| 746 |
+
ctx = contextlib.nullcontext()
|
| 747 |
+
|
| 748 |
+
with ctx:
|
| 749 |
+
self.check_indexer(obj, key, expected, val, indexer_sli, is_inplace)
|
| 750 |
+
|
| 751 |
+
if indexer_sli is tm.loc:
|
| 752 |
+
with ctx:
|
| 753 |
+
self.check_indexer(obj, key, expected, val, tm.at, is_inplace)
|
| 754 |
+
elif indexer_sli is tm.iloc:
|
| 755 |
+
with ctx:
|
| 756 |
+
self.check_indexer(obj, key, expected, val, tm.iat, is_inplace)
|
| 757 |
+
|
| 758 |
+
rng = range(key, key + 1)
|
| 759 |
+
with ctx:
|
| 760 |
+
self.check_indexer(obj, rng, expected, val, indexer_sli, is_inplace)
|
| 761 |
+
|
| 762 |
+
if indexer_sli is not tm.loc:
|
| 763 |
+
# Note: no .loc because that handles slice edges differently
|
| 764 |
+
slc = slice(key, key + 1)
|
| 765 |
+
with ctx:
|
| 766 |
+
self.check_indexer(obj, slc, expected, val, indexer_sli, is_inplace)
|
| 767 |
+
|
| 768 |
+
ilkey = [key]
|
| 769 |
+
with ctx:
|
| 770 |
+
self.check_indexer(obj, ilkey, expected, val, indexer_sli, is_inplace)
|
| 771 |
+
|
| 772 |
+
indkey = np.array(ilkey)
|
| 773 |
+
with ctx:
|
| 774 |
+
self.check_indexer(obj, indkey, expected, val, indexer_sli, is_inplace)
|
| 775 |
+
|
| 776 |
+
genkey = (x for x in [key])
|
| 777 |
+
with ctx:
|
| 778 |
+
self.check_indexer(obj, genkey, expected, val, indexer_sli, is_inplace)
|
| 779 |
+
|
| 780 |
+
def test_slice_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace):
|
| 781 |
+
if not isinstance(key, slice):
|
| 782 |
+
pytest.skip("Not relevant for slice key")
|
| 783 |
+
if raises:
|
| 784 |
+
ctx = pytest.raises(TypeError, match="Invalid value")
|
| 785 |
+
else:
|
| 786 |
+
ctx = contextlib.nullcontext()
|
| 787 |
+
|
| 788 |
+
if indexer_sli is not tm.loc:
|
| 789 |
+
# Note: no .loc because that handles slice edges differently
|
| 790 |
+
with ctx:
|
| 791 |
+
self.check_indexer(obj, key, expected, val, indexer_sli, is_inplace)
|
| 792 |
+
|
| 793 |
+
ilkey = list(range(len(obj)))[key]
|
| 794 |
+
with ctx:
|
| 795 |
+
self.check_indexer(obj, ilkey, expected, val, indexer_sli, is_inplace)
|
| 796 |
+
|
| 797 |
+
indkey = np.array(ilkey)
|
| 798 |
+
with ctx:
|
| 799 |
+
self.check_indexer(obj, indkey, expected, val, indexer_sli, is_inplace)
|
| 800 |
+
|
| 801 |
+
genkey = (x for x in indkey)
|
| 802 |
+
with ctx:
|
| 803 |
+
self.check_indexer(obj, genkey, expected, val, indexer_sli, is_inplace)
|
| 804 |
+
|
| 805 |
+
def test_mask_key(self, obj, key, expected, raises, val, indexer_sli):
|
| 806 |
+
# setitem with boolean mask
|
| 807 |
+
mask = np.zeros(obj.shape, dtype=bool)
|
| 808 |
+
mask[key] = True
|
| 809 |
+
|
| 810 |
+
obj = obj.copy()
|
| 811 |
+
|
| 812 |
+
if is_list_like(val) and len(val) < mask.sum():
|
| 813 |
+
msg = "boolean index did not match indexed array along dimension"
|
| 814 |
+
with pytest.raises(IndexError, match=msg):
|
| 815 |
+
indexer_sli(obj)[mask] = val
|
| 816 |
+
return
|
| 817 |
+
|
| 818 |
+
if raises:
|
| 819 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 820 |
+
indexer_sli(obj)[mask] = val
|
| 821 |
+
else:
|
| 822 |
+
indexer_sli(obj)[mask] = val
|
| 823 |
+
|
| 824 |
+
def test_series_where(self, obj, key, expected, raises, val, is_inplace):
|
| 825 |
+
mask = np.zeros(obj.shape, dtype=bool)
|
| 826 |
+
mask[key] = True
|
| 827 |
+
|
| 828 |
+
if is_list_like(val) and len(val) < len(obj):
|
| 829 |
+
# Series.where is not valid here
|
| 830 |
+
msg = "operands could not be broadcast together with shapes"
|
| 831 |
+
with pytest.raises(ValueError, match=msg):
|
| 832 |
+
obj.where(~mask, val)
|
| 833 |
+
return
|
| 834 |
+
|
| 835 |
+
orig = obj
|
| 836 |
+
obj = obj.copy()
|
| 837 |
+
arr = obj._values
|
| 838 |
+
|
| 839 |
+
res = obj.where(~mask, val)
|
| 840 |
+
|
| 841 |
+
if val is NA and res.dtype == object:
|
| 842 |
+
expected = expected.fillna(NA)
|
| 843 |
+
elif val is None and res.dtype == object:
|
| 844 |
+
assert expected.dtype == object
|
| 845 |
+
expected = expected.copy()
|
| 846 |
+
expected[expected.isna()] = None
|
| 847 |
+
tm.assert_series_equal(res, expected)
|
| 848 |
+
|
| 849 |
+
self._check_inplace(is_inplace, orig, arr, obj)
|
| 850 |
+
|
| 851 |
+
def test_index_where(self, obj, key, expected, raises, val):
|
| 852 |
+
mask = np.zeros(obj.shape, dtype=bool)
|
| 853 |
+
mask[key] = True
|
| 854 |
+
|
| 855 |
+
res = Index(obj).where(~mask, val)
|
| 856 |
+
expected_idx = Index(expected, dtype=expected.dtype)
|
| 857 |
+
tm.assert_index_equal(res, expected_idx)
|
| 858 |
+
|
| 859 |
+
def test_index_putmask(self, obj, key, expected, raises, val):
|
| 860 |
+
mask = np.zeros(obj.shape, dtype=bool)
|
| 861 |
+
mask[key] = True
|
| 862 |
+
|
| 863 |
+
res = Index(obj).putmask(mask, val)
|
| 864 |
+
tm.assert_index_equal(res, Index(expected, dtype=expected.dtype))
|
| 865 |
+
|
| 866 |
+
|
| 867 |
+
@pytest.mark.parametrize(
|
| 868 |
+
"obj,expected,key,raises",
|
| 869 |
+
[
|
| 870 |
+
pytest.param(
|
| 871 |
+
# GH#45568 setting a valid NA value into IntervalDtype[int] should
|
| 872 |
+
# cast to IntervalDtype[float]
|
| 873 |
+
Series(interval_range(1, 5)),
|
| 874 |
+
Series(
|
| 875 |
+
[Interval(1, 2), np.nan, Interval(3, 4), Interval(4, 5)],
|
| 876 |
+
dtype="interval[float64]",
|
| 877 |
+
),
|
| 878 |
+
1,
|
| 879 |
+
True,
|
| 880 |
+
id="interval_int_na_value",
|
| 881 |
+
),
|
| 882 |
+
pytest.param(
|
| 883 |
+
# these induce dtype changes
|
| 884 |
+
Series([2, 3, 4, 5, 6, 7, 8, 9, 10]),
|
| 885 |
+
Series([np.nan, 3, np.nan, 5, np.nan, 7, np.nan, 9, np.nan]),
|
| 886 |
+
slice(None, None, 2),
|
| 887 |
+
False,
|
| 888 |
+
id="int_series_slice_key_step",
|
| 889 |
+
),
|
| 890 |
+
pytest.param(
|
| 891 |
+
Series([True, True, False, False]),
|
| 892 |
+
Series([np.nan, True, np.nan, False], dtype=object),
|
| 893 |
+
slice(None, None, 2),
|
| 894 |
+
True,
|
| 895 |
+
id="bool_series_slice_key_step",
|
| 896 |
+
),
|
| 897 |
+
pytest.param(
|
| 898 |
+
# these induce dtype changes
|
| 899 |
+
Series(np.arange(10)),
|
| 900 |
+
Series([np.nan, np.nan, np.nan, np.nan, np.nan, 5, 6, 7, 8, 9]),
|
| 901 |
+
slice(None, 5),
|
| 902 |
+
False,
|
| 903 |
+
id="int_series_slice_key",
|
| 904 |
+
),
|
| 905 |
+
pytest.param(
|
| 906 |
+
# changes dtype GH#4463
|
| 907 |
+
Series([1, 2, 3]),
|
| 908 |
+
Series([np.nan, 2, 3]),
|
| 909 |
+
0,
|
| 910 |
+
False,
|
| 911 |
+
id="int_series_int_key",
|
| 912 |
+
),
|
| 913 |
+
pytest.param(
|
| 914 |
+
# changes dtype GH#4463
|
| 915 |
+
Series([False]),
|
| 916 |
+
Series([np.nan], dtype=object),
|
| 917 |
+
# TODO: maybe go to float64 since we are changing the _whole_ Series?
|
| 918 |
+
0,
|
| 919 |
+
True,
|
| 920 |
+
id="bool_series_int_key_change_all",
|
| 921 |
+
),
|
| 922 |
+
pytest.param(
|
| 923 |
+
# changes dtype GH#4463
|
| 924 |
+
Series([False, True]),
|
| 925 |
+
Series([np.nan, True], dtype=object),
|
| 926 |
+
0,
|
| 927 |
+
True,
|
| 928 |
+
id="bool_series_int_key",
|
| 929 |
+
),
|
| 930 |
+
],
|
| 931 |
+
)
|
| 932 |
+
class TestSetitemCastingEquivalents(SetitemCastingEquivalents):
|
| 933 |
+
@pytest.fixture(params=[np.nan, np.float64("NaN"), None, NA])
|
| 934 |
+
def val(self, request):
|
| 935 |
+
"""
|
| 936 |
+
NA values that should generally be valid_na for *all* dtypes.
|
| 937 |
+
|
| 938 |
+
Include both python float NaN and np.float64; only np.float64 has a
|
| 939 |
+
`dtype` attribute.
|
| 940 |
+
"""
|
| 941 |
+
return request.param
|
| 942 |
+
|
| 943 |
+
|
| 944 |
+
class TestSetitemTimedelta64IntoNumeric(SetitemCastingEquivalents):
|
| 945 |
+
# timedelta64 should not be treated as integers when setting into
|
| 946 |
+
# numeric Series
|
| 947 |
+
|
| 948 |
+
@pytest.fixture
|
| 949 |
+
def val(self):
|
| 950 |
+
td = np.timedelta64(4, "ns")
|
| 951 |
+
return td
|
| 952 |
+
# TODO: could also try np.full((1,), td)
|
| 953 |
+
|
| 954 |
+
@pytest.fixture(params=[complex, int, float])
|
| 955 |
+
def dtype(self, request):
|
| 956 |
+
return request.param
|
| 957 |
+
|
| 958 |
+
@pytest.fixture
|
| 959 |
+
def obj(self, dtype):
|
| 960 |
+
arr = np.arange(5).astype(dtype)
|
| 961 |
+
ser = Series(arr)
|
| 962 |
+
return ser
|
| 963 |
+
|
| 964 |
+
@pytest.fixture
|
| 965 |
+
def expected(self, dtype):
|
| 966 |
+
arr = np.arange(5).astype(dtype)
|
| 967 |
+
ser = Series(arr)
|
| 968 |
+
ser = ser.astype(object)
|
| 969 |
+
ser.iloc[0] = np.timedelta64(4, "ns")
|
| 970 |
+
return ser
|
| 971 |
+
|
| 972 |
+
@pytest.fixture
|
| 973 |
+
def key(self):
|
| 974 |
+
return 0
|
| 975 |
+
|
| 976 |
+
@pytest.fixture
|
| 977 |
+
def raises(self):
|
| 978 |
+
return True
|
| 979 |
+
|
| 980 |
+
|
| 981 |
+
class TestSetitemDT64IntoInt(SetitemCastingEquivalents):
|
| 982 |
+
# GH#39619 dont cast dt64 to int when doing this setitem
|
| 983 |
+
|
| 984 |
+
@pytest.fixture(params=["M8[ns]", "m8[ns]"])
|
| 985 |
+
def dtype(self, request):
|
| 986 |
+
return request.param
|
| 987 |
+
|
| 988 |
+
@pytest.fixture
|
| 989 |
+
def scalar(self, dtype):
|
| 990 |
+
val = np.datetime64("2021-01-18 13:25:00", "ns")
|
| 991 |
+
if dtype == "m8[ns]":
|
| 992 |
+
val = val - val
|
| 993 |
+
return val
|
| 994 |
+
|
| 995 |
+
@pytest.fixture
|
| 996 |
+
def expected(self, scalar):
|
| 997 |
+
expected = Series([scalar, scalar, 3], dtype=object)
|
| 998 |
+
assert isinstance(expected[0], type(scalar))
|
| 999 |
+
return expected
|
| 1000 |
+
|
| 1001 |
+
@pytest.fixture
|
| 1002 |
+
def obj(self):
|
| 1003 |
+
return Series([1, 2, 3])
|
| 1004 |
+
|
| 1005 |
+
@pytest.fixture
|
| 1006 |
+
def key(self):
|
| 1007 |
+
return slice(None, -1)
|
| 1008 |
+
|
| 1009 |
+
@pytest.fixture(params=[None, list, np.array])
|
| 1010 |
+
def val(self, scalar, request):
|
| 1011 |
+
box = request.param
|
| 1012 |
+
if box is None:
|
| 1013 |
+
return scalar
|
| 1014 |
+
return box([scalar, scalar])
|
| 1015 |
+
|
| 1016 |
+
@pytest.fixture
|
| 1017 |
+
def raises(self):
|
| 1018 |
+
return True
|
| 1019 |
+
|
| 1020 |
+
|
| 1021 |
+
class TestSetitemNAPeriodDtype(SetitemCastingEquivalents):
|
| 1022 |
+
# Setting compatible NA values into Series with PeriodDtype
|
| 1023 |
+
|
| 1024 |
+
@pytest.fixture
|
| 1025 |
+
def expected(self, key):
|
| 1026 |
+
exp = Series(period_range("2000-01-01", periods=10, freq="D"))
|
| 1027 |
+
exp._values.view("i8")[key] = NaT._value
|
| 1028 |
+
assert exp[key] is NaT or all(x is NaT for x in exp[key])
|
| 1029 |
+
return exp
|
| 1030 |
+
|
| 1031 |
+
@pytest.fixture
|
| 1032 |
+
def obj(self):
|
| 1033 |
+
return Series(period_range("2000-01-01", periods=10, freq="D"))
|
| 1034 |
+
|
| 1035 |
+
@pytest.fixture(params=[3, slice(3, 5)])
|
| 1036 |
+
def key(self, request):
|
| 1037 |
+
return request.param
|
| 1038 |
+
|
| 1039 |
+
@pytest.fixture(params=[None, np.nan])
|
| 1040 |
+
def val(self, request):
|
| 1041 |
+
return request.param
|
| 1042 |
+
|
| 1043 |
+
@pytest.fixture
|
| 1044 |
+
def raises(self):
|
| 1045 |
+
return False
|
| 1046 |
+
|
| 1047 |
+
|
| 1048 |
+
class TestSetitemNADatetimeLikeDtype(SetitemCastingEquivalents):
|
| 1049 |
+
# some nat-like values should be cast to datetime64/timedelta64 when
|
| 1050 |
+
# inserting into a datetime64/timedelta64 series. Others should coerce
|
| 1051 |
+
# to object and retain their dtypes.
|
| 1052 |
+
# GH#18586 for td64 and boolean mask case
|
| 1053 |
+
|
| 1054 |
+
@pytest.fixture(
|
| 1055 |
+
params=["m8[ns]", "M8[ns]", "datetime64[ns, UTC]", "datetime64[ns, US/Central]"]
|
| 1056 |
+
)
|
| 1057 |
+
def dtype(self, request):
|
| 1058 |
+
return request.param
|
| 1059 |
+
|
| 1060 |
+
@pytest.fixture
|
| 1061 |
+
def obj(self, dtype):
|
| 1062 |
+
i8vals = date_range("2016-01-01", periods=3).asi8
|
| 1063 |
+
idx = Index(i8vals, dtype=dtype)
|
| 1064 |
+
assert idx.dtype == dtype
|
| 1065 |
+
return Series(idx)
|
| 1066 |
+
|
| 1067 |
+
@pytest.fixture(
|
| 1068 |
+
params=[
|
| 1069 |
+
None,
|
| 1070 |
+
np.nan,
|
| 1071 |
+
NaT,
|
| 1072 |
+
np.timedelta64("NaT", "ns"),
|
| 1073 |
+
np.datetime64("NaT", "ns"),
|
| 1074 |
+
]
|
| 1075 |
+
)
|
| 1076 |
+
def val(self, request):
|
| 1077 |
+
return request.param
|
| 1078 |
+
|
| 1079 |
+
@pytest.fixture
|
| 1080 |
+
def is_inplace(self, val, obj):
|
| 1081 |
+
# td64 -> cast to object iff val is datetime64("NaT")
|
| 1082 |
+
# dt64 -> cast to object iff val is timedelta64("NaT")
|
| 1083 |
+
# dt64tz -> cast to object with anything _but_ NaT
|
| 1084 |
+
return val is NaT or val is None or val is np.nan or obj.dtype == val.dtype
|
| 1085 |
+
|
| 1086 |
+
@pytest.fixture
|
| 1087 |
+
def expected(self, obj, val, is_inplace):
|
| 1088 |
+
dtype = obj.dtype if is_inplace else object
|
| 1089 |
+
expected = Series([val, *list(obj[1:])], dtype=dtype)
|
| 1090 |
+
return expected
|
| 1091 |
+
|
| 1092 |
+
@pytest.fixture
|
| 1093 |
+
def key(self):
|
| 1094 |
+
return 0
|
| 1095 |
+
|
| 1096 |
+
@pytest.fixture
|
| 1097 |
+
def raises(self, is_inplace):
|
| 1098 |
+
return False if is_inplace else True
|
| 1099 |
+
|
| 1100 |
+
|
| 1101 |
+
class TestSetitemMismatchedTZCastsToObject(SetitemCastingEquivalents):
|
| 1102 |
+
# GH#24024
|
| 1103 |
+
@pytest.fixture
|
| 1104 |
+
def obj(self):
|
| 1105 |
+
return Series(date_range("2000", periods=2, tz="US/Central"))
|
| 1106 |
+
|
| 1107 |
+
@pytest.fixture
|
| 1108 |
+
def val(self):
|
| 1109 |
+
return Timestamp("2000", tz="US/Eastern")
|
| 1110 |
+
|
| 1111 |
+
@pytest.fixture
|
| 1112 |
+
def key(self):
|
| 1113 |
+
return 0
|
| 1114 |
+
|
| 1115 |
+
@pytest.fixture
|
| 1116 |
+
def expected(self, obj, val):
|
| 1117 |
+
# pre-2.0 this would cast to object, in 2.0 we cast the val to
|
| 1118 |
+
# the target tz
|
| 1119 |
+
expected = Series(
|
| 1120 |
+
[
|
| 1121 |
+
val.tz_convert("US/Central"),
|
| 1122 |
+
Timestamp("2000-01-02 00:00:00-06:00", tz="US/Central"),
|
| 1123 |
+
],
|
| 1124 |
+
dtype=obj.dtype,
|
| 1125 |
+
)
|
| 1126 |
+
return expected
|
| 1127 |
+
|
| 1128 |
+
@pytest.fixture
|
| 1129 |
+
def raises(self):
|
| 1130 |
+
return False
|
| 1131 |
+
|
| 1132 |
+
|
| 1133 |
+
@pytest.mark.parametrize(
|
| 1134 |
+
"obj,expected",
|
| 1135 |
+
[
|
| 1136 |
+
# For numeric series, we should coerce to NaN.
|
| 1137 |
+
(Series([1, 2, 3]), Series([np.nan, 2, 3])),
|
| 1138 |
+
(Series([1.0, 2.0, 3.0]), Series([np.nan, 2.0, 3.0])),
|
| 1139 |
+
# For datetime series, we should coerce to NaT.
|
| 1140 |
+
(
|
| 1141 |
+
Series([datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)]),
|
| 1142 |
+
Series([NaT, datetime(2000, 1, 2), datetime(2000, 1, 3)]),
|
| 1143 |
+
),
|
| 1144 |
+
# For objects, we should preserve the None value.
|
| 1145 |
+
(Series(["foo", "bar", "baz"]), Series([None, "bar", "baz"])),
|
| 1146 |
+
],
|
| 1147 |
+
)
|
| 1148 |
+
class TestSeriesNoneCoercion(SetitemCastingEquivalents):
|
| 1149 |
+
@pytest.fixture
|
| 1150 |
+
def key(self):
|
| 1151 |
+
return 0
|
| 1152 |
+
|
| 1153 |
+
@pytest.fixture
|
| 1154 |
+
def val(self):
|
| 1155 |
+
return None
|
| 1156 |
+
|
| 1157 |
+
@pytest.fixture
|
| 1158 |
+
def raises(self):
|
| 1159 |
+
return False
|
| 1160 |
+
|
| 1161 |
+
|
| 1162 |
+
class TestSetitemFloatIntervalWithIntIntervalValues(SetitemCastingEquivalents):
|
| 1163 |
+
# GH#44201 Cast to shared IntervalDtype rather than object
|
| 1164 |
+
|
| 1165 |
+
def test_setitem_example(self):
|
| 1166 |
+
# Just a case here to make obvious what this test class is aimed at
|
| 1167 |
+
idx = IntervalIndex.from_breaks(range(4))
|
| 1168 |
+
obj = Series(idx)
|
| 1169 |
+
val = Interval(0.5, 1.5)
|
| 1170 |
+
|
| 1171 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1172 |
+
obj[0] = val
|
| 1173 |
+
|
| 1174 |
+
@pytest.fixture
|
| 1175 |
+
def obj(self):
|
| 1176 |
+
"""
|
| 1177 |
+
Fixture to create a Series [(0, 1], (1, 2], (2, 3]]
|
| 1178 |
+
"""
|
| 1179 |
+
idx = IntervalIndex.from_breaks(range(4))
|
| 1180 |
+
return Series(idx)
|
| 1181 |
+
|
| 1182 |
+
@pytest.fixture
|
| 1183 |
+
def val(self):
|
| 1184 |
+
"""
|
| 1185 |
+
Fixture to get an interval (0.5, 1.5]
|
| 1186 |
+
"""
|
| 1187 |
+
return Interval(0.5, 1.5)
|
| 1188 |
+
|
| 1189 |
+
@pytest.fixture
|
| 1190 |
+
def key(self):
|
| 1191 |
+
"""
|
| 1192 |
+
Fixture to get a key 0
|
| 1193 |
+
"""
|
| 1194 |
+
return 0
|
| 1195 |
+
|
| 1196 |
+
@pytest.fixture
|
| 1197 |
+
def expected(self, obj, val):
|
| 1198 |
+
"""
|
| 1199 |
+
Fixture to get a Series [(0.5, 1.5], (1.0, 2.0], (2.0, 3.0]]
|
| 1200 |
+
"""
|
| 1201 |
+
data = [val, *list(obj[1:])]
|
| 1202 |
+
idx = IntervalIndex(data, dtype="Interval[float64]")
|
| 1203 |
+
return Series(idx)
|
| 1204 |
+
|
| 1205 |
+
@pytest.fixture
|
| 1206 |
+
def raises(self):
|
| 1207 |
+
"""
|
| 1208 |
+
Fixture to enable raising pytest exceptions
|
| 1209 |
+
"""
|
| 1210 |
+
return True
|
| 1211 |
+
|
| 1212 |
+
|
| 1213 |
+
class TestSetitemRangeIntoIntegerSeries(SetitemCastingEquivalents):
|
| 1214 |
+
# GH#44261 Setting a range with sufficiently-small integers into
|
| 1215 |
+
# small-itemsize integer dtypes should not need to upcast
|
| 1216 |
+
|
| 1217 |
+
@pytest.fixture
|
| 1218 |
+
def obj(self, any_int_numpy_dtype):
|
| 1219 |
+
dtype = np.dtype(any_int_numpy_dtype)
|
| 1220 |
+
ser = Series(range(5), dtype=dtype)
|
| 1221 |
+
return ser
|
| 1222 |
+
|
| 1223 |
+
@pytest.fixture
|
| 1224 |
+
def val(self):
|
| 1225 |
+
return range(2, 4)
|
| 1226 |
+
|
| 1227 |
+
@pytest.fixture
|
| 1228 |
+
def key(self):
|
| 1229 |
+
return slice(0, 2)
|
| 1230 |
+
|
| 1231 |
+
@pytest.fixture
|
| 1232 |
+
def expected(self, any_int_numpy_dtype):
|
| 1233 |
+
dtype = np.dtype(any_int_numpy_dtype)
|
| 1234 |
+
exp = Series([2, 3, 2, 3, 4], dtype=dtype)
|
| 1235 |
+
return exp
|
| 1236 |
+
|
| 1237 |
+
@pytest.fixture
|
| 1238 |
+
def raises(self):
|
| 1239 |
+
return False
|
| 1240 |
+
|
| 1241 |
+
|
| 1242 |
+
@pytest.mark.parametrize(
|
| 1243 |
+
"val, raises",
|
| 1244 |
+
[
|
| 1245 |
+
(np.array([2.0, 3.0]), False),
|
| 1246 |
+
(np.array([2.5, 3.5]), True),
|
| 1247 |
+
(
|
| 1248 |
+
np.array([2**65, 2**65 + 1], dtype=np.float64),
|
| 1249 |
+
True,
|
| 1250 |
+
), # all ints, but can't cast
|
| 1251 |
+
],
|
| 1252 |
+
)
|
| 1253 |
+
class TestSetitemFloatNDarrayIntoIntegerSeries(SetitemCastingEquivalents):
|
| 1254 |
+
@pytest.fixture
|
| 1255 |
+
def obj(self):
|
| 1256 |
+
return Series(range(5), dtype=np.int64)
|
| 1257 |
+
|
| 1258 |
+
@pytest.fixture
|
| 1259 |
+
def key(self):
|
| 1260 |
+
return slice(0, 2)
|
| 1261 |
+
|
| 1262 |
+
@pytest.fixture
|
| 1263 |
+
def expected(self, val):
|
| 1264 |
+
if val[0] == 2:
|
| 1265 |
+
# NB: this condition is based on currently-hardcoded "val" cases
|
| 1266 |
+
dtype = np.int64
|
| 1267 |
+
else:
|
| 1268 |
+
dtype = np.float64
|
| 1269 |
+
res_values = np.array(range(5), dtype=dtype)
|
| 1270 |
+
res_values[:2] = val
|
| 1271 |
+
return Series(res_values)
|
| 1272 |
+
|
| 1273 |
+
|
| 1274 |
+
@pytest.mark.parametrize("val", [512, np.int16(512)])
|
| 1275 |
+
class TestSetitemIntoIntegerSeriesNeedsUpcast(SetitemCastingEquivalents):
|
| 1276 |
+
@pytest.fixture
|
| 1277 |
+
def obj(self):
|
| 1278 |
+
return Series([1, 2, 3], dtype=np.int8)
|
| 1279 |
+
|
| 1280 |
+
@pytest.fixture
|
| 1281 |
+
def key(self):
|
| 1282 |
+
return 1
|
| 1283 |
+
|
| 1284 |
+
@pytest.fixture
|
| 1285 |
+
def expected(self):
|
| 1286 |
+
return Series([1, 512, 3], dtype=np.int16)
|
| 1287 |
+
|
| 1288 |
+
@pytest.fixture
|
| 1289 |
+
def raises(self):
|
| 1290 |
+
return True
|
| 1291 |
+
|
| 1292 |
+
|
| 1293 |
+
@pytest.mark.parametrize("val", [2**33 + 1.0, 2**33 + 1.1, 2**62])
|
| 1294 |
+
class TestSmallIntegerSetitemUpcast(SetitemCastingEquivalents):
|
| 1295 |
+
# https://github.com/pandas-dev/pandas/issues/39584#issuecomment-941212124
|
| 1296 |
+
@pytest.fixture
|
| 1297 |
+
def obj(self):
|
| 1298 |
+
return Series([1, 2, 3], dtype="i4")
|
| 1299 |
+
|
| 1300 |
+
@pytest.fixture
|
| 1301 |
+
def key(self):
|
| 1302 |
+
return 0
|
| 1303 |
+
|
| 1304 |
+
@pytest.fixture
|
| 1305 |
+
def expected(self, val):
|
| 1306 |
+
if val % 1 != 0:
|
| 1307 |
+
dtype = "f8"
|
| 1308 |
+
else:
|
| 1309 |
+
dtype = "i8"
|
| 1310 |
+
return Series([val, 2, 3], dtype=dtype)
|
| 1311 |
+
|
| 1312 |
+
@pytest.fixture
|
| 1313 |
+
def raises(self):
|
| 1314 |
+
return True
|
| 1315 |
+
|
| 1316 |
+
|
| 1317 |
+
class CoercionTest(SetitemCastingEquivalents):
|
| 1318 |
+
# Tests ported from tests.indexing.test_coercion
|
| 1319 |
+
|
| 1320 |
+
@pytest.fixture
|
| 1321 |
+
def key(self):
|
| 1322 |
+
return 1
|
| 1323 |
+
|
| 1324 |
+
@pytest.fixture
|
| 1325 |
+
def expected(self, obj, key, val, exp_dtype):
|
| 1326 |
+
vals = list(obj)
|
| 1327 |
+
vals[key] = val
|
| 1328 |
+
return Series(vals, dtype=exp_dtype)
|
| 1329 |
+
|
| 1330 |
+
|
| 1331 |
+
@pytest.mark.parametrize(
|
| 1332 |
+
"val,exp_dtype,raises",
|
| 1333 |
+
[(np.int32(1), np.int8, None), (np.int16(2**9), np.int16, True)],
|
| 1334 |
+
)
|
| 1335 |
+
class TestCoercionInt8(CoercionTest):
|
| 1336 |
+
# previously test_setitem_series_int8 in tests.indexing.test_coercion
|
| 1337 |
+
@pytest.fixture
|
| 1338 |
+
def obj(self):
|
| 1339 |
+
return Series([1, 2, 3, 4], dtype=np.int8)
|
| 1340 |
+
|
| 1341 |
+
|
| 1342 |
+
@pytest.mark.parametrize("val", [1, 1.1, 1 + 1j, True])
|
| 1343 |
+
@pytest.mark.parametrize("exp_dtype", [object])
|
| 1344 |
+
class TestCoercionObject(CoercionTest):
|
| 1345 |
+
# previously test_setitem_series_object in tests.indexing.test_coercion
|
| 1346 |
+
@pytest.fixture
|
| 1347 |
+
def obj(self):
|
| 1348 |
+
return Series(["a", "b", "c", "d"], dtype=object)
|
| 1349 |
+
|
| 1350 |
+
@pytest.fixture
|
| 1351 |
+
def raises(self):
|
| 1352 |
+
return False
|
| 1353 |
+
|
| 1354 |
+
|
| 1355 |
+
@pytest.mark.parametrize(
|
| 1356 |
+
"val,exp_dtype,raises",
|
| 1357 |
+
[
|
| 1358 |
+
(1, object, True),
|
| 1359 |
+
("e", StringDtype(na_value=np.nan), False),
|
| 1360 |
+
],
|
| 1361 |
+
)
|
| 1362 |
+
class TestCoercionString(CoercionTest):
|
| 1363 |
+
@pytest.fixture
|
| 1364 |
+
def obj(self):
|
| 1365 |
+
return Series(["a", "b", "c", "d"], dtype=StringDtype(na_value=np.nan))
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
@pytest.mark.parametrize(
|
| 1369 |
+
"val,exp_dtype,raises",
|
| 1370 |
+
[
|
| 1371 |
+
(1, np.complex128, False),
|
| 1372 |
+
(1.1, np.complex128, False),
|
| 1373 |
+
(1 + 1j, np.complex128, False),
|
| 1374 |
+
(True, object, True),
|
| 1375 |
+
],
|
| 1376 |
+
)
|
| 1377 |
+
class TestCoercionComplex(CoercionTest):
|
| 1378 |
+
# previously test_setitem_series_complex128 in tests.indexing.test_coercion
|
| 1379 |
+
@pytest.fixture
|
| 1380 |
+
def obj(self):
|
| 1381 |
+
return Series([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j])
|
| 1382 |
+
|
| 1383 |
+
|
| 1384 |
+
@pytest.mark.parametrize(
|
| 1385 |
+
"val,exp_dtype,raises",
|
| 1386 |
+
[
|
| 1387 |
+
(1, object, True),
|
| 1388 |
+
("3", object, True),
|
| 1389 |
+
(3, object, True),
|
| 1390 |
+
(1.1, object, True),
|
| 1391 |
+
(1 + 1j, object, True),
|
| 1392 |
+
(True, bool, False),
|
| 1393 |
+
],
|
| 1394 |
+
)
|
| 1395 |
+
class TestCoercionBool(CoercionTest):
|
| 1396 |
+
# previously test_setitem_series_bool in tests.indexing.test_coercion
|
| 1397 |
+
@pytest.fixture
|
| 1398 |
+
def obj(self):
|
| 1399 |
+
return Series([True, False, True, False], dtype=bool)
|
| 1400 |
+
|
| 1401 |
+
|
| 1402 |
+
@pytest.mark.parametrize(
|
| 1403 |
+
"val,exp_dtype,raises",
|
| 1404 |
+
[
|
| 1405 |
+
(1, np.int64, False),
|
| 1406 |
+
(1.1, np.float64, True),
|
| 1407 |
+
(1 + 1j, np.complex128, True),
|
| 1408 |
+
(True, object, True),
|
| 1409 |
+
],
|
| 1410 |
+
)
|
| 1411 |
+
class TestCoercionInt64(CoercionTest):
|
| 1412 |
+
# previously test_setitem_series_int64 in tests.indexing.test_coercion
|
| 1413 |
+
@pytest.fixture
|
| 1414 |
+
def obj(self):
|
| 1415 |
+
return Series([1, 2, 3, 4])
|
| 1416 |
+
|
| 1417 |
+
|
| 1418 |
+
@pytest.mark.parametrize(
|
| 1419 |
+
"val,exp_dtype,raises",
|
| 1420 |
+
[
|
| 1421 |
+
(1, np.float64, False),
|
| 1422 |
+
(1.1, np.float64, False),
|
| 1423 |
+
(1 + 1j, np.complex128, True),
|
| 1424 |
+
(True, object, True),
|
| 1425 |
+
],
|
| 1426 |
+
)
|
| 1427 |
+
class TestCoercionFloat64(CoercionTest):
|
| 1428 |
+
# previously test_setitem_series_float64 in tests.indexing.test_coercion
|
| 1429 |
+
@pytest.fixture
|
| 1430 |
+
def obj(self):
|
| 1431 |
+
return Series([1.1, 2.2, 3.3, 4.4])
|
| 1432 |
+
|
| 1433 |
+
|
| 1434 |
+
@pytest.mark.parametrize(
|
| 1435 |
+
"val,exp_dtype,raises",
|
| 1436 |
+
[
|
| 1437 |
+
(1, np.float32, False),
|
| 1438 |
+
pytest.param(
|
| 1439 |
+
1.1,
|
| 1440 |
+
np.float32,
|
| 1441 |
+
False,
|
| 1442 |
+
marks=pytest.mark.xfail(
|
| 1443 |
+
not np_version_gt2,
|
| 1444 |
+
reason="np.float32(1.1) ends up as 1.100000023841858, so "
|
| 1445 |
+
"np_can_hold_element raises and we cast to float64",
|
| 1446 |
+
),
|
| 1447 |
+
),
|
| 1448 |
+
(1 + 1j, np.complex128, True),
|
| 1449 |
+
(True, object, True),
|
| 1450 |
+
(np.uint8(2), np.float32, False),
|
| 1451 |
+
(np.uint32(2), np.float32, False),
|
| 1452 |
+
# float32 cannot hold np.iinfo(np.uint32).max exactly
|
| 1453 |
+
# (closest it can hold is 4294967300.0 which off by 5.0), so
|
| 1454 |
+
# we cast to float64
|
| 1455 |
+
(np.uint32(np.iinfo(np.uint32).max), np.float64, True),
|
| 1456 |
+
(np.uint64(2), np.float32, False),
|
| 1457 |
+
(np.int64(2), np.float32, False),
|
| 1458 |
+
],
|
| 1459 |
+
)
|
| 1460 |
+
class TestCoercionFloat32(CoercionTest):
|
| 1461 |
+
@pytest.fixture
|
| 1462 |
+
def obj(self):
|
| 1463 |
+
return Series([1.1, 2.2, 3.3, 4.4], dtype=np.float32)
|
| 1464 |
+
|
| 1465 |
+
def test_slice_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace):
|
| 1466 |
+
super().test_slice_key(obj, key, expected, raises, val, indexer_sli, is_inplace)
|
| 1467 |
+
|
| 1468 |
+
if isinstance(val, float):
|
| 1469 |
+
# the xfail would xpass bc test_slice_key short-circuits
|
| 1470 |
+
raise AssertionError("xfail not relevant for this test.")
|
| 1471 |
+
|
| 1472 |
+
|
| 1473 |
+
@pytest.mark.parametrize(
|
| 1474 |
+
"exp_dtype",
|
| 1475 |
+
[
|
| 1476 |
+
"M8[ms]",
|
| 1477 |
+
"M8[ms, UTC]",
|
| 1478 |
+
"m8[ms]",
|
| 1479 |
+
],
|
| 1480 |
+
)
|
| 1481 |
+
class TestCoercionDatetime64HigherReso(CoercionTest):
|
| 1482 |
+
@pytest.fixture
|
| 1483 |
+
def obj(self, exp_dtype):
|
| 1484 |
+
idx = date_range("2011-01-01", freq="D", periods=4, unit="s")
|
| 1485 |
+
if exp_dtype == "m8[ms]":
|
| 1486 |
+
idx = idx - Timestamp("1970-01-01").as_unit("s")
|
| 1487 |
+
assert idx.dtype == "m8[s]"
|
| 1488 |
+
elif exp_dtype == "M8[ms, UTC]":
|
| 1489 |
+
idx = idx.tz_localize("UTC")
|
| 1490 |
+
return Series(idx)
|
| 1491 |
+
|
| 1492 |
+
@pytest.fixture
|
| 1493 |
+
def val(self, exp_dtype):
|
| 1494 |
+
ts = Timestamp("2011-01-02 03:04:05.678").as_unit("ms")
|
| 1495 |
+
if exp_dtype == "m8[ms]":
|
| 1496 |
+
return ts - Timestamp("1970-01-01").as_unit("s")
|
| 1497 |
+
elif exp_dtype == "M8[ms, UTC]":
|
| 1498 |
+
return ts.tz_localize("UTC")
|
| 1499 |
+
return ts
|
| 1500 |
+
|
| 1501 |
+
@pytest.fixture
|
| 1502 |
+
def raises(self):
|
| 1503 |
+
return True
|
| 1504 |
+
|
| 1505 |
+
|
| 1506 |
+
@pytest.mark.parametrize(
|
| 1507 |
+
"val,exp_dtype,raises",
|
| 1508 |
+
[
|
| 1509 |
+
(Timestamp("2012-01-01"), "datetime64[ns]", False),
|
| 1510 |
+
(1, object, True),
|
| 1511 |
+
("x", object, True),
|
| 1512 |
+
],
|
| 1513 |
+
)
|
| 1514 |
+
class TestCoercionDatetime64(CoercionTest):
|
| 1515 |
+
# previously test_setitem_series_datetime64 in tests.indexing.test_coercion
|
| 1516 |
+
|
| 1517 |
+
@pytest.fixture
|
| 1518 |
+
def obj(self):
|
| 1519 |
+
return Series(date_range("2011-01-01", freq="D", periods=4, unit="ns"))
|
| 1520 |
+
|
| 1521 |
+
@pytest.fixture
|
| 1522 |
+
def raises(self):
|
| 1523 |
+
return False
|
| 1524 |
+
|
| 1525 |
+
|
| 1526 |
+
@pytest.mark.parametrize(
|
| 1527 |
+
"val,exp_dtype,raises",
|
| 1528 |
+
[
|
| 1529 |
+
(Timestamp("2012-01-01", tz="US/Eastern"), "datetime64[ns, US/Eastern]", False),
|
| 1530 |
+
# pre-2.0, a mis-matched tz would end up casting to object
|
| 1531 |
+
(Timestamp("2012-01-01", tz="US/Pacific"), "datetime64[ns, US/Eastern]", False),
|
| 1532 |
+
(Timestamp("2012-01-01"), object, True),
|
| 1533 |
+
(1, object, True),
|
| 1534 |
+
],
|
| 1535 |
+
)
|
| 1536 |
+
class TestCoercionDatetime64TZ(CoercionTest):
|
| 1537 |
+
# previously test_setitem_series_datetime64tz in tests.indexing.test_coercion
|
| 1538 |
+
@pytest.fixture
|
| 1539 |
+
def obj(self):
|
| 1540 |
+
tz = "US/Eastern"
|
| 1541 |
+
return Series(date_range("2011-01-01", freq="D", periods=4, tz=tz, unit="ns"))
|
| 1542 |
+
|
| 1543 |
+
@pytest.fixture
|
| 1544 |
+
def raises(self):
|
| 1545 |
+
return False
|
| 1546 |
+
|
| 1547 |
+
|
| 1548 |
+
@pytest.mark.parametrize(
|
| 1549 |
+
"val,exp_dtype,raises",
|
| 1550 |
+
[
|
| 1551 |
+
(Timedelta("12 day"), "timedelta64[us]", False),
|
| 1552 |
+
(1, object, True),
|
| 1553 |
+
("x", object, True),
|
| 1554 |
+
],
|
| 1555 |
+
)
|
| 1556 |
+
class TestCoercionTimedelta64(CoercionTest):
|
| 1557 |
+
# previously test_setitem_series_timedelta64 in tests.indexing.test_coercion
|
| 1558 |
+
@pytest.fixture
|
| 1559 |
+
def obj(self):
|
| 1560 |
+
return Series(timedelta_range("1 day", periods=4))
|
| 1561 |
+
|
| 1562 |
+
@pytest.fixture
|
| 1563 |
+
def raises(self):
|
| 1564 |
+
return False
|
| 1565 |
+
|
| 1566 |
+
|
| 1567 |
+
@pytest.mark.parametrize(
|
| 1568 |
+
"val", ["foo", Period("2016", freq="Y"), Interval(1, 2, closed="both")]
|
| 1569 |
+
)
|
| 1570 |
+
@pytest.mark.parametrize("exp_dtype", [object])
|
| 1571 |
+
class TestPeriodIntervalCoercion(CoercionTest):
|
| 1572 |
+
# GH#45768
|
| 1573 |
+
@pytest.fixture(
|
| 1574 |
+
params=[
|
| 1575 |
+
period_range("2016-01-01", periods=3, freq="D"),
|
| 1576 |
+
interval_range(1, 5),
|
| 1577 |
+
]
|
| 1578 |
+
)
|
| 1579 |
+
def obj(self, request):
|
| 1580 |
+
return Series(request.param)
|
| 1581 |
+
|
| 1582 |
+
@pytest.fixture
|
| 1583 |
+
def raises(self):
|
| 1584 |
+
return True
|
| 1585 |
+
|
| 1586 |
+
|
| 1587 |
+
def test_20643():
|
| 1588 |
+
# closed by GH#45121
|
| 1589 |
+
orig = Series([0, 1, 2], index=["a", "b", "c"])
|
| 1590 |
+
|
| 1591 |
+
ser = orig.copy()
|
| 1592 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1593 |
+
ser.at["b"] = 2.7
|
| 1594 |
+
|
| 1595 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1596 |
+
ser.loc["b"] = 2.7
|
| 1597 |
+
|
| 1598 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1599 |
+
ser["b"] = 2.7
|
| 1600 |
+
|
| 1601 |
+
ser = orig.copy()
|
| 1602 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1603 |
+
ser.iat[1] = 2.7
|
| 1604 |
+
|
| 1605 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1606 |
+
ser.iloc[1] = 2.7
|
| 1607 |
+
|
| 1608 |
+
orig_df = orig.to_frame("A")
|
| 1609 |
+
|
| 1610 |
+
df = orig_df.copy()
|
| 1611 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1612 |
+
df.at["b", "A"] = 2.7
|
| 1613 |
+
|
| 1614 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1615 |
+
df.loc["b", "A"] = 2.7
|
| 1616 |
+
|
| 1617 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1618 |
+
df.iloc[1, 0] = 2.7
|
| 1619 |
+
|
| 1620 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1621 |
+
df.iat[1, 0] = 2.7
|
| 1622 |
+
|
| 1623 |
+
|
| 1624 |
+
def test_20643_comment():
|
| 1625 |
+
# https://github.com/pandas-dev/pandas/issues/20643#issuecomment-431244590
|
| 1626 |
+
# fixed sometime prior to GH#45121
|
| 1627 |
+
orig = Series([0, 1, 2], index=["a", "b", "c"])
|
| 1628 |
+
expected = Series([np.nan, 1, 2], index=["a", "b", "c"])
|
| 1629 |
+
|
| 1630 |
+
ser = orig.copy()
|
| 1631 |
+
ser.iat[0] = None
|
| 1632 |
+
tm.assert_series_equal(ser, expected)
|
| 1633 |
+
|
| 1634 |
+
ser = orig.copy()
|
| 1635 |
+
ser.iloc[0] = None
|
| 1636 |
+
tm.assert_series_equal(ser, expected)
|
| 1637 |
+
|
| 1638 |
+
|
| 1639 |
+
def test_15413():
|
| 1640 |
+
# fixed by GH#45121
|
| 1641 |
+
ser = Series([1, 2, 3])
|
| 1642 |
+
|
| 1643 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1644 |
+
ser[ser == 2] += 0.5
|
| 1645 |
+
|
| 1646 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1647 |
+
ser[1] += 0.5
|
| 1648 |
+
|
| 1649 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1650 |
+
ser.loc[1] += 0.5
|
| 1651 |
+
|
| 1652 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1653 |
+
ser.iloc[1] += 0.5
|
| 1654 |
+
|
| 1655 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1656 |
+
ser.iat[1] += 0.5
|
| 1657 |
+
|
| 1658 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1659 |
+
ser.at[1] += 0.5
|
| 1660 |
+
|
| 1661 |
+
|
| 1662 |
+
def test_32878_int_itemsize():
|
| 1663 |
+
# Fixed by GH#45121
|
| 1664 |
+
arr = np.arange(5).astype("i4")
|
| 1665 |
+
ser = Series(arr)
|
| 1666 |
+
val = np.int64(np.iinfo(np.int64).max)
|
| 1667 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1668 |
+
ser[0] = val
|
| 1669 |
+
|
| 1670 |
+
|
| 1671 |
+
def test_32878_complex_itemsize():
|
| 1672 |
+
arr = np.arange(5).astype("c8")
|
| 1673 |
+
ser = Series(arr)
|
| 1674 |
+
val = np.finfo(np.float64).max
|
| 1675 |
+
val = val.astype("c16")
|
| 1676 |
+
|
| 1677 |
+
# GH#32878 used to coerce val to inf+0.000000e+00j
|
| 1678 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1679 |
+
ser[0] = val
|
| 1680 |
+
|
| 1681 |
+
|
| 1682 |
+
def test_37692(indexer_al):
|
| 1683 |
+
# GH#37692
|
| 1684 |
+
ser = Series([1, 2, 3], index=["a", "b", "c"])
|
| 1685 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1686 |
+
indexer_al(ser)["b"] = "test"
|
| 1687 |
+
|
| 1688 |
+
|
| 1689 |
+
def test_setitem_bool_int_float_consistency(indexer_sli):
|
| 1690 |
+
# GH#21513
|
| 1691 |
+
# bool-with-int and bool-with-float both upcast to object
|
| 1692 |
+
# int-with-float and float-with-int are both non-casting so long
|
| 1693 |
+
# as the setitem can be done losslessly
|
| 1694 |
+
for dtype in [np.float64, np.int64]:
|
| 1695 |
+
ser = Series(0, index=range(3), dtype=dtype)
|
| 1696 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1697 |
+
indexer_sli(ser)[0] = True
|
| 1698 |
+
|
| 1699 |
+
ser = Series(0, index=range(3), dtype=bool)
|
| 1700 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 1701 |
+
ser[0] = dtype(1)
|
| 1702 |
+
|
| 1703 |
+
# 1.0 can be held losslessly, so no casting
|
| 1704 |
+
ser = Series(0, index=range(3), dtype=np.int64)
|
| 1705 |
+
indexer_sli(ser)[0] = np.float64(1.0)
|
| 1706 |
+
assert ser.dtype == np.int64
|
| 1707 |
+
|
| 1708 |
+
# 1 can be held losslessly, so no casting
|
| 1709 |
+
ser = Series(0, index=range(3), dtype=np.float64)
|
| 1710 |
+
indexer_sli(ser)[0] = np.int64(1)
|
| 1711 |
+
|
| 1712 |
+
|
| 1713 |
+
def test_setitem_positional_with_casting():
|
| 1714 |
+
# GH#45070 case where in __setitem__ we get a KeyError, then when
|
| 1715 |
+
# we fallback we *also* get a ValueError if we try to set inplace.
|
| 1716 |
+
# As of 3.0 we always treat int keys as labels, so this becomes
|
| 1717 |
+
# setitem-with-expansion
|
| 1718 |
+
ser = Series([1, 2, 3], index=["a", "b", "c"])
|
| 1719 |
+
|
| 1720 |
+
ser[0] = "X"
|
| 1721 |
+
expected = Series([1, 2, 3, "X"], index=["a", "b", "c", 0], dtype=object)
|
| 1722 |
+
tm.assert_series_equal(ser, expected)
|
| 1723 |
+
|
| 1724 |
+
|
| 1725 |
+
def test_setitem_positional_float_into_int_coerces():
|
| 1726 |
+
# Case where we hit a KeyError and then trying to set in-place incorrectly
|
| 1727 |
+
# casts a float to an int;
|
| 1728 |
+
# As of 3.0 we always treat int keys as labels, so this becomes
|
| 1729 |
+
# setitem-with-expansion
|
| 1730 |
+
ser = Series([1, 2, 3], index=["a", "b", "c"])
|
| 1731 |
+
|
| 1732 |
+
ser[0] = 1.5
|
| 1733 |
+
expected = Series([1, 2, 3, 1.5], index=["a", "b", "c", 0])
|
| 1734 |
+
tm.assert_series_equal(ser, expected)
|
| 1735 |
+
|
| 1736 |
+
|
| 1737 |
+
def test_setitem_int_not_positional():
|
| 1738 |
+
# GH#42215 deprecated falling back to positional on __setitem__ with an
|
| 1739 |
+
# int not contained in the index; enforced in 2.0
|
| 1740 |
+
ser = Series([1, 2, 3, 4], index=[1.1, 2.1, 3.0, 4.1])
|
| 1741 |
+
assert not ser.index._should_fallback_to_positional
|
| 1742 |
+
# assert not ser.index.astype(object)._should_fallback_to_positional
|
| 1743 |
+
|
| 1744 |
+
# 3.0 is in our index, so post-enforcement behavior is unchanged
|
| 1745 |
+
ser[3] = 10
|
| 1746 |
+
expected = Series([1, 2, 10, 4], index=ser.index)
|
| 1747 |
+
tm.assert_series_equal(ser, expected)
|
| 1748 |
+
|
| 1749 |
+
# pre-enforcement `ser[5] = 5` raised IndexError
|
| 1750 |
+
ser[5] = 5
|
| 1751 |
+
expected = Series([1, 2, 10, 4, 5], index=[1.1, 2.1, 3.0, 4.1, 5.0])
|
| 1752 |
+
tm.assert_series_equal(ser, expected)
|
| 1753 |
+
|
| 1754 |
+
ii = IntervalIndex.from_breaks(range(10))[::2]
|
| 1755 |
+
ser2 = Series(range(len(ii)), index=ii)
|
| 1756 |
+
exp_index = ii.astype(object).append(Index([4]))
|
| 1757 |
+
expected2 = Series([0, 1, 2, 3, 4, 9], index=exp_index)
|
| 1758 |
+
# pre-enforcement `ser2[4] = 9` interpreted 4 as positional
|
| 1759 |
+
ser2[4] = 9
|
| 1760 |
+
tm.assert_series_equal(ser2, expected2)
|
| 1761 |
+
|
| 1762 |
+
mi = MultiIndex.from_product([ser.index, ["A", "B"]])
|
| 1763 |
+
ser3 = Series(range(len(mi)), index=mi)
|
| 1764 |
+
expected3 = ser3.copy()
|
| 1765 |
+
expected3.loc[4] = 99
|
| 1766 |
+
# pre-enforcement `ser3[4] = 99` interpreted 4 as positional
|
| 1767 |
+
ser3[4] = 99
|
| 1768 |
+
tm.assert_series_equal(ser3, expected3)
|
| 1769 |
+
|
| 1770 |
+
|
| 1771 |
+
def test_setitem_with_bool_indexer():
|
| 1772 |
+
# GH#42530
|
| 1773 |
+
|
| 1774 |
+
df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
|
| 1775 |
+
result = df.pop("b").copy()
|
| 1776 |
+
result[[True, False, False]] = 9
|
| 1777 |
+
expected = Series(data=[9, 5, 6], name="b")
|
| 1778 |
+
tm.assert_series_equal(result, expected)
|
| 1779 |
+
|
| 1780 |
+
df.loc[[True, False, False], "a"] = 10
|
| 1781 |
+
expected = DataFrame({"a": [10, 2, 3]})
|
| 1782 |
+
tm.assert_frame_equal(df, expected)
|
| 1783 |
+
|
| 1784 |
+
|
| 1785 |
+
@pytest.mark.parametrize("size", range(2, 6))
|
| 1786 |
+
@pytest.mark.parametrize(
|
| 1787 |
+
"mask", [[True, False, False, False, False], [True, False], [False]]
|
| 1788 |
+
)
|
| 1789 |
+
@pytest.mark.parametrize(
|
| 1790 |
+
"item", [2.0, np.nan, np.finfo(float).max, np.finfo(float).min]
|
| 1791 |
+
)
|
| 1792 |
+
@pytest.mark.parametrize("box", [np.array, list, tuple])
|
| 1793 |
+
def test_setitem_bool_indexer_dont_broadcast_length1_values(size, mask, item, box):
|
| 1794 |
+
# GH#44265
|
| 1795 |
+
# see also tests.series.indexing.test_where.test_broadcast
|
| 1796 |
+
|
| 1797 |
+
selection = np.resize(mask, size)
|
| 1798 |
+
|
| 1799 |
+
data = np.arange(size, dtype=float)
|
| 1800 |
+
|
| 1801 |
+
ser = Series(data)
|
| 1802 |
+
|
| 1803 |
+
if selection.sum() != 1:
|
| 1804 |
+
msg = (
|
| 1805 |
+
"cannot set using a list-like indexer with a different "
|
| 1806 |
+
"length than the value"
|
| 1807 |
+
)
|
| 1808 |
+
with pytest.raises(ValueError, match=msg):
|
| 1809 |
+
# GH#44265
|
| 1810 |
+
ser[selection] = box([item])
|
| 1811 |
+
else:
|
| 1812 |
+
# In this corner case setting is equivalent to setting with the unboxed
|
| 1813 |
+
# item
|
| 1814 |
+
ser[selection] = box([item])
|
| 1815 |
+
|
| 1816 |
+
expected = Series(np.arange(size, dtype=float))
|
| 1817 |
+
expected[selection] = item
|
| 1818 |
+
tm.assert_series_equal(ser, expected)
|
| 1819 |
+
|
| 1820 |
+
|
| 1821 |
+
def test_setitem_empty_mask_dont_upcast_dt64():
|
| 1822 |
+
dti = date_range("2016-01-01", periods=3)
|
| 1823 |
+
ser = Series(dti)
|
| 1824 |
+
orig = ser.copy()
|
| 1825 |
+
mask = np.zeros(3, dtype=bool)
|
| 1826 |
+
|
| 1827 |
+
ser[mask] = "foo"
|
| 1828 |
+
assert ser.dtype == dti.dtype # no-op -> dont upcast
|
| 1829 |
+
tm.assert_series_equal(ser, orig)
|
| 1830 |
+
|
| 1831 |
+
ser.mask(mask, "foo", inplace=True)
|
| 1832 |
+
assert ser.dtype == dti.dtype # no-op -> dont upcast
|
| 1833 |
+
tm.assert_series_equal(ser, orig)
|
| 1834 |
+
|
| 1835 |
+
|
| 1836 |
+
def test_setitem_bool_dtype_with_boolean_indexer():
|
| 1837 |
+
# GH 57338
|
| 1838 |
+
s1 = Series([True, True, True], dtype=bool)
|
| 1839 |
+
s2 = Series([False, False, False], dtype=bool)
|
| 1840 |
+
condition = [False, True, False]
|
| 1841 |
+
s1[condition] = s2[condition]
|
| 1842 |
+
expected = Series([True, False, True], dtype=bool)
|
| 1843 |
+
tm.assert_series_equal(s1, expected)
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_take.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from pandas import Series
|
| 5 |
+
import pandas._testing as tm
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_take_validate_axis():
|
| 9 |
+
# GH#51022
|
| 10 |
+
ser = Series([-1, 5, 6, 2, 4])
|
| 11 |
+
|
| 12 |
+
msg = "No axis named foo for object type Series"
|
| 13 |
+
with pytest.raises(ValueError, match=msg):
|
| 14 |
+
ser.take([1, 2], axis="foo")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_take():
|
| 18 |
+
ser = Series([-1, 5, 6, 2, 4])
|
| 19 |
+
|
| 20 |
+
actual = ser.take([1, 3, 4])
|
| 21 |
+
expected = Series([5, 2, 4], index=[1, 3, 4])
|
| 22 |
+
tm.assert_series_equal(actual, expected)
|
| 23 |
+
|
| 24 |
+
actual = ser.take([-1, 3, 4])
|
| 25 |
+
expected = Series([4, 2, 4], index=[4, 3, 4])
|
| 26 |
+
tm.assert_series_equal(actual, expected)
|
| 27 |
+
|
| 28 |
+
msg = "indices are out-of-bounds"
|
| 29 |
+
with pytest.raises(IndexError, match=msg):
|
| 30 |
+
ser.take([1, 10])
|
| 31 |
+
with pytest.raises(IndexError, match=msg):
|
| 32 |
+
ser.take([2, 5])
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_take_categorical():
|
| 36 |
+
# https://github.com/pandas-dev/pandas/issues/20664
|
| 37 |
+
ser = Series(pd.Categorical(["a", "b", "c"]))
|
| 38 |
+
result = ser.take([-2, -2, 0])
|
| 39 |
+
expected = Series(
|
| 40 |
+
pd.Categorical(["b", "b", "a"], categories=["a", "b", "c"]), index=[1, 1, 0]
|
| 41 |
+
)
|
| 42 |
+
tm.assert_series_equal(result, expected)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def test_take_slice_raises():
|
| 46 |
+
ser = Series([-1, 5, 6, 2, 4])
|
| 47 |
+
|
| 48 |
+
msg = "Series.take requires a sequence of integers, not slice"
|
| 49 |
+
with pytest.raises(TypeError, match=msg):
|
| 50 |
+
ser.take(slice(0, 3, 1))
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_where.py
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas.core.dtypes.common import is_integer
|
| 5 |
+
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from pandas import (
|
| 8 |
+
Series,
|
| 9 |
+
Timestamp,
|
| 10 |
+
date_range,
|
| 11 |
+
isna,
|
| 12 |
+
)
|
| 13 |
+
import pandas._testing as tm
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def test_where_unsafe_int(any_signed_int_numpy_dtype):
|
| 17 |
+
s = Series(np.arange(10), dtype=any_signed_int_numpy_dtype)
|
| 18 |
+
mask = s < 5
|
| 19 |
+
|
| 20 |
+
s[mask] = range(2, 7)
|
| 21 |
+
expected = Series(
|
| 22 |
+
list(range(2, 7)) + list(range(5, 10)),
|
| 23 |
+
dtype=any_signed_int_numpy_dtype,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
tm.assert_series_equal(s, expected)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_where_unsafe_float(float_numpy_dtype):
|
| 30 |
+
s = Series(np.arange(10), dtype=float_numpy_dtype)
|
| 31 |
+
mask = s < 5
|
| 32 |
+
|
| 33 |
+
s[mask] = range(2, 7)
|
| 34 |
+
data = list(range(2, 7)) + list(range(5, 10))
|
| 35 |
+
expected = Series(data, dtype=float_numpy_dtype)
|
| 36 |
+
|
| 37 |
+
tm.assert_series_equal(s, expected)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@pytest.mark.parametrize(
|
| 41 |
+
"dtype,expected_dtype",
|
| 42 |
+
[
|
| 43 |
+
(np.int8, np.float64),
|
| 44 |
+
(np.int16, np.float64),
|
| 45 |
+
(np.int32, np.float64),
|
| 46 |
+
(np.int64, np.float64),
|
| 47 |
+
(np.float32, np.float32),
|
| 48 |
+
(np.float64, np.float64),
|
| 49 |
+
],
|
| 50 |
+
)
|
| 51 |
+
def test_where_unsafe_upcast(dtype, expected_dtype):
|
| 52 |
+
# see gh-9743
|
| 53 |
+
s = Series(np.arange(10), dtype=dtype)
|
| 54 |
+
values = [2.5, 3.5, 4.5, 5.5, 6.5]
|
| 55 |
+
mask = s < 5
|
| 56 |
+
if np.dtype(dtype).kind == np.dtype(expected_dtype).kind == "f":
|
| 57 |
+
s[mask] = values
|
| 58 |
+
expected = Series(values + list(range(5, 10)), dtype=expected_dtype)
|
| 59 |
+
tm.assert_series_equal(s, expected)
|
| 60 |
+
else:
|
| 61 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 62 |
+
s[mask] = values
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def test_where_unsafe():
|
| 66 |
+
# see gh-9731
|
| 67 |
+
s = Series(np.arange(10), dtype="int64")
|
| 68 |
+
values = [2.5, 3.5, 4.5, 5.5]
|
| 69 |
+
|
| 70 |
+
mask = s > 5
|
| 71 |
+
expected = Series(list(range(6)) + values, dtype="float64")
|
| 72 |
+
|
| 73 |
+
with pytest.raises(TypeError, match="Invalid value"):
|
| 74 |
+
s[mask] = values
|
| 75 |
+
s = s.astype("float64")
|
| 76 |
+
s[mask] = values
|
| 77 |
+
|
| 78 |
+
# see gh-3235
|
| 79 |
+
s = Series(np.arange(10), dtype="int64")
|
| 80 |
+
mask = s < 5
|
| 81 |
+
s[mask] = range(2, 7)
|
| 82 |
+
expected = Series(list(range(2, 7)) + list(range(5, 10)), dtype="int64")
|
| 83 |
+
tm.assert_series_equal(s, expected)
|
| 84 |
+
assert s.dtype == expected.dtype
|
| 85 |
+
|
| 86 |
+
s = Series(np.arange(10), dtype="int64")
|
| 87 |
+
mask = s > 5
|
| 88 |
+
s[mask] = [0] * 4
|
| 89 |
+
expected = Series([0, 1, 2, 3, 4, 5] + [0] * 4, dtype="int64")
|
| 90 |
+
tm.assert_series_equal(s, expected)
|
| 91 |
+
|
| 92 |
+
s = Series(np.arange(10))
|
| 93 |
+
mask = s > 5
|
| 94 |
+
|
| 95 |
+
msg = "cannot set using a list-like indexer with a different length than the value"
|
| 96 |
+
with pytest.raises(ValueError, match=msg):
|
| 97 |
+
s[mask] = [5, 4, 3, 2, 1]
|
| 98 |
+
|
| 99 |
+
with pytest.raises(ValueError, match=msg):
|
| 100 |
+
s[mask] = [0] * 5
|
| 101 |
+
|
| 102 |
+
# dtype changes
|
| 103 |
+
s = Series([1, 2, 3, 4])
|
| 104 |
+
result = s.where(s > 2, np.nan)
|
| 105 |
+
expected = Series([np.nan, np.nan, 3, 4])
|
| 106 |
+
tm.assert_series_equal(result, expected)
|
| 107 |
+
|
| 108 |
+
# GH 4667
|
| 109 |
+
# setting with None changes dtype
|
| 110 |
+
s = Series(range(10)).astype(float)
|
| 111 |
+
s[8] = None
|
| 112 |
+
result = s[8]
|
| 113 |
+
assert isna(result)
|
| 114 |
+
|
| 115 |
+
s = Series(range(10)).astype(float)
|
| 116 |
+
s[s > 8] = None
|
| 117 |
+
result = s[isna(s)]
|
| 118 |
+
expected = Series(np.nan, index=[9])
|
| 119 |
+
tm.assert_series_equal(result, expected)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def test_where():
|
| 123 |
+
s = Series(np.random.default_rng(2).standard_normal(5))
|
| 124 |
+
cond = s > 0
|
| 125 |
+
|
| 126 |
+
rs = s.where(cond).dropna()
|
| 127 |
+
rs2 = s[cond]
|
| 128 |
+
tm.assert_series_equal(rs, rs2)
|
| 129 |
+
|
| 130 |
+
rs = s.where(cond, -s)
|
| 131 |
+
tm.assert_series_equal(rs, s.abs())
|
| 132 |
+
|
| 133 |
+
rs = s.where(cond)
|
| 134 |
+
assert s.shape == rs.shape
|
| 135 |
+
assert rs is not s
|
| 136 |
+
|
| 137 |
+
# test alignment
|
| 138 |
+
cond = Series([True, False, False, True, False], index=s.index)
|
| 139 |
+
s2 = -(s.abs())
|
| 140 |
+
|
| 141 |
+
expected = s2[cond].reindex(s2.index[:3]).reindex(s2.index)
|
| 142 |
+
rs = s2.where(cond[:3])
|
| 143 |
+
tm.assert_series_equal(rs, expected)
|
| 144 |
+
|
| 145 |
+
expected = s2.abs()
|
| 146 |
+
expected.iloc[0] = s2[0]
|
| 147 |
+
rs = s2.where(cond[:3], -s2)
|
| 148 |
+
tm.assert_series_equal(rs, expected)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def test_where_error():
|
| 152 |
+
s = Series(np.random.default_rng(2).standard_normal(5))
|
| 153 |
+
cond = s > 0
|
| 154 |
+
|
| 155 |
+
msg = "Array conditional must be same shape as self"
|
| 156 |
+
with pytest.raises(ValueError, match=msg):
|
| 157 |
+
s.where(1)
|
| 158 |
+
with pytest.raises(ValueError, match=msg):
|
| 159 |
+
s.where(cond[:3].values, -s)
|
| 160 |
+
|
| 161 |
+
# GH 2745
|
| 162 |
+
s = Series([1, 2])
|
| 163 |
+
s[[True, False]] = [0, 1]
|
| 164 |
+
expected = Series([0, 2])
|
| 165 |
+
tm.assert_series_equal(s, expected)
|
| 166 |
+
|
| 167 |
+
# failures
|
| 168 |
+
msg = "cannot set using a list-like indexer with a different length than the value"
|
| 169 |
+
with pytest.raises(ValueError, match=msg):
|
| 170 |
+
s[[True, False]] = [0, 2, 3]
|
| 171 |
+
|
| 172 |
+
with pytest.raises(ValueError, match=msg):
|
| 173 |
+
s[[True, False]] = []
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@pytest.mark.parametrize("klass", [list, tuple, np.array, Series])
|
| 177 |
+
def test_where_array_like(klass):
|
| 178 |
+
# see gh-15414
|
| 179 |
+
s = Series([1, 2, 3])
|
| 180 |
+
cond = [False, True, True]
|
| 181 |
+
expected = Series([np.nan, 2, 3])
|
| 182 |
+
|
| 183 |
+
result = s.where(klass(cond))
|
| 184 |
+
tm.assert_series_equal(result, expected)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
@pytest.mark.parametrize(
|
| 188 |
+
"cond",
|
| 189 |
+
[
|
| 190 |
+
[1, 0, 1],
|
| 191 |
+
Series([2, 5, 7]),
|
| 192 |
+
["True", "False", "True"],
|
| 193 |
+
[Timestamp("2017-01-01"), pd.NaT, Timestamp("2017-01-02")],
|
| 194 |
+
],
|
| 195 |
+
)
|
| 196 |
+
def test_where_invalid_input(cond):
|
| 197 |
+
# see gh-15414: only boolean arrays accepted
|
| 198 |
+
s = Series([1, 2, 3])
|
| 199 |
+
msg = "Boolean array expected for the condition"
|
| 200 |
+
|
| 201 |
+
with pytest.raises(TypeError, match=msg):
|
| 202 |
+
s.where(cond)
|
| 203 |
+
|
| 204 |
+
msg = "Array conditional must be same shape as self"
|
| 205 |
+
with pytest.raises(ValueError, match=msg):
|
| 206 |
+
s.where([True])
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def test_where_ndframe_align():
|
| 210 |
+
msg = "Array conditional must be same shape as self"
|
| 211 |
+
s = Series([1, 2, 3])
|
| 212 |
+
|
| 213 |
+
cond = [True]
|
| 214 |
+
with pytest.raises(ValueError, match=msg):
|
| 215 |
+
s.where(cond)
|
| 216 |
+
|
| 217 |
+
expected = Series([1, np.nan, np.nan])
|
| 218 |
+
|
| 219 |
+
out = s.where(Series(cond))
|
| 220 |
+
tm.assert_series_equal(out, expected)
|
| 221 |
+
|
| 222 |
+
cond = np.array([False, True, False, True])
|
| 223 |
+
with pytest.raises(ValueError, match=msg):
|
| 224 |
+
s.where(cond)
|
| 225 |
+
|
| 226 |
+
expected = Series([np.nan, 2, np.nan])
|
| 227 |
+
|
| 228 |
+
out = s.where(Series(cond))
|
| 229 |
+
tm.assert_series_equal(out, expected)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def test_where_setitem_invalid():
|
| 233 |
+
# GH 2702
|
| 234 |
+
# make sure correct exceptions are raised on invalid list assignment
|
| 235 |
+
|
| 236 |
+
msg = lambda x: (
|
| 237 |
+
f"cannot set using a {x} indexer with a different length than the value"
|
| 238 |
+
)
|
| 239 |
+
# slice
|
| 240 |
+
s = Series(list("abc"), dtype=object)
|
| 241 |
+
|
| 242 |
+
with pytest.raises(ValueError, match=msg("slice")):
|
| 243 |
+
s[0:3] = list(range(27))
|
| 244 |
+
|
| 245 |
+
s[0:3] = list(range(3))
|
| 246 |
+
expected = Series([0, 1, 2])
|
| 247 |
+
tm.assert_series_equal(s.astype(np.int64), expected)
|
| 248 |
+
|
| 249 |
+
# slice with step
|
| 250 |
+
s = Series(list("abcdef"), dtype=object)
|
| 251 |
+
|
| 252 |
+
with pytest.raises(ValueError, match=msg("slice")):
|
| 253 |
+
s[0:4:2] = list(range(27))
|
| 254 |
+
|
| 255 |
+
s = Series(list("abcdef"), dtype=object)
|
| 256 |
+
s[0:4:2] = list(range(2))
|
| 257 |
+
expected = Series([0, "b", 1, "d", "e", "f"])
|
| 258 |
+
tm.assert_series_equal(s, expected)
|
| 259 |
+
|
| 260 |
+
# neg slices
|
| 261 |
+
s = Series(list("abcdef"), dtype=object)
|
| 262 |
+
|
| 263 |
+
with pytest.raises(ValueError, match=msg("slice")):
|
| 264 |
+
s[:-1] = list(range(27))
|
| 265 |
+
|
| 266 |
+
s[-3:-1] = list(range(2))
|
| 267 |
+
expected = Series(["a", "b", "c", 0, 1, "f"])
|
| 268 |
+
tm.assert_series_equal(s, expected)
|
| 269 |
+
|
| 270 |
+
# list
|
| 271 |
+
s = Series(list("abc"), dtype=object)
|
| 272 |
+
|
| 273 |
+
with pytest.raises(ValueError, match=msg("list-like")):
|
| 274 |
+
s[[0, 1, 2]] = list(range(27))
|
| 275 |
+
|
| 276 |
+
s = Series(list("abc"), dtype=object)
|
| 277 |
+
|
| 278 |
+
with pytest.raises(ValueError, match=msg("list-like")):
|
| 279 |
+
s[[0, 1, 2]] = list(range(2))
|
| 280 |
+
|
| 281 |
+
# scalar
|
| 282 |
+
s = Series(list("abc"), dtype=object)
|
| 283 |
+
s[0] = list(range(10))
|
| 284 |
+
expected = Series([list(range(10)), "b", "c"])
|
| 285 |
+
tm.assert_series_equal(s, expected)
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
@pytest.mark.parametrize("size", range(2, 6))
|
| 289 |
+
@pytest.mark.parametrize(
|
| 290 |
+
"mask", [[True, False, False, False, False], [True, False], [False]]
|
| 291 |
+
)
|
| 292 |
+
@pytest.mark.parametrize(
|
| 293 |
+
"item", [2.0, np.nan, np.finfo(float).max, np.finfo(float).min]
|
| 294 |
+
)
|
| 295 |
+
@pytest.mark.parametrize("box", [np.array, list, tuple])
|
| 296 |
+
def test_broadcast(size, mask, item, box):
|
| 297 |
+
# GH#8801, GH#4195
|
| 298 |
+
selection = np.resize(mask, size)
|
| 299 |
+
|
| 300 |
+
data = np.arange(size, dtype=float)
|
| 301 |
+
|
| 302 |
+
# Construct the expected series by taking the source
|
| 303 |
+
# data or item based on the selection
|
| 304 |
+
expected = Series(
|
| 305 |
+
[item if use_item else data[i] for i, use_item in enumerate(selection)]
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
s = Series(data)
|
| 309 |
+
|
| 310 |
+
s[selection] = item
|
| 311 |
+
tm.assert_series_equal(s, expected)
|
| 312 |
+
|
| 313 |
+
s = Series(data)
|
| 314 |
+
result = s.where(~selection, box([item]))
|
| 315 |
+
tm.assert_series_equal(result, expected)
|
| 316 |
+
|
| 317 |
+
s = Series(data)
|
| 318 |
+
result = s.mask(selection, box([item]))
|
| 319 |
+
tm.assert_series_equal(result, expected)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def test_where_inplace():
|
| 323 |
+
s = Series(np.random.default_rng(2).standard_normal(5))
|
| 324 |
+
cond = s > 0
|
| 325 |
+
|
| 326 |
+
rs = s.copy()
|
| 327 |
+
|
| 328 |
+
result = rs.where(cond, inplace=True)
|
| 329 |
+
assert result is rs
|
| 330 |
+
tm.assert_series_equal(rs.dropna(), s[cond])
|
| 331 |
+
tm.assert_series_equal(rs, s.where(cond))
|
| 332 |
+
|
| 333 |
+
rs = s.copy()
|
| 334 |
+
result = rs.where(cond, -s, inplace=True)
|
| 335 |
+
assert result is rs
|
| 336 |
+
tm.assert_series_equal(rs, s.where(cond, -s))
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def test_where_dups():
|
| 340 |
+
# GH 4550
|
| 341 |
+
# where crashes with dups in index
|
| 342 |
+
s1 = Series(list(range(3)))
|
| 343 |
+
s2 = Series(list(range(3)))
|
| 344 |
+
comb = pd.concat([s1, s2])
|
| 345 |
+
result = comb.where(comb < 2)
|
| 346 |
+
expected = Series([0, 1, np.nan, 0, 1, np.nan], index=[0, 1, 2, 0, 1, 2])
|
| 347 |
+
tm.assert_series_equal(result, expected)
|
| 348 |
+
|
| 349 |
+
# GH 4548
|
| 350 |
+
# inplace updating not working with dups
|
| 351 |
+
comb[comb < 1] = 5
|
| 352 |
+
expected = Series([5, 1, 2, 5, 1, 2], index=[0, 1, 2, 0, 1, 2])
|
| 353 |
+
tm.assert_series_equal(comb, expected)
|
| 354 |
+
|
| 355 |
+
comb[comb < 2] += 10
|
| 356 |
+
expected = Series([5, 11, 2, 5, 11, 2], index=[0, 1, 2, 0, 1, 2])
|
| 357 |
+
tm.assert_series_equal(comb, expected)
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
def test_where_numeric_with_string():
|
| 361 |
+
# GH 9280
|
| 362 |
+
s = Series([1, 2, 3])
|
| 363 |
+
w = s.where(s > 1, "X")
|
| 364 |
+
|
| 365 |
+
assert not is_integer(w[0])
|
| 366 |
+
assert is_integer(w[1])
|
| 367 |
+
assert is_integer(w[2])
|
| 368 |
+
assert isinstance(w[0], str)
|
| 369 |
+
assert w.dtype == "object"
|
| 370 |
+
|
| 371 |
+
w = s.where(s > 1, ["X", "Y", "Z"])
|
| 372 |
+
assert not is_integer(w[0])
|
| 373 |
+
assert is_integer(w[1])
|
| 374 |
+
assert is_integer(w[2])
|
| 375 |
+
assert isinstance(w[0], str)
|
| 376 |
+
assert w.dtype == "object"
|
| 377 |
+
|
| 378 |
+
w = s.where(s > 1, np.array(["X", "Y", "Z"]))
|
| 379 |
+
assert not is_integer(w[0])
|
| 380 |
+
assert is_integer(w[1])
|
| 381 |
+
assert is_integer(w[2])
|
| 382 |
+
assert isinstance(w[0], str)
|
| 383 |
+
assert w.dtype == "object"
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def test_where_datetimetz():
|
| 387 |
+
# GH 15701
|
| 388 |
+
timestamps = ["2016-12-31 12:00:04+00:00", "2016-12-31 12:00:04.010000+00:00"]
|
| 389 |
+
ser = Series([Timestamp(t) for t in timestamps], dtype="datetime64[ns, UTC]")
|
| 390 |
+
rs = ser.where(Series([False, True]))
|
| 391 |
+
expected = Series([pd.NaT, ser[1]], dtype="datetime64[ns, UTC]")
|
| 392 |
+
tm.assert_series_equal(rs, expected)
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def test_where_sparse():
|
| 396 |
+
# GH#17198 make sure we dont get an AttributeError for sp_index
|
| 397 |
+
ser = Series(pd.arrays.SparseArray([1, 2]))
|
| 398 |
+
result = ser.where(ser >= 2, 0)
|
| 399 |
+
expected = Series(pd.arrays.SparseArray([0, 2]))
|
| 400 |
+
tm.assert_series_equal(result, expected)
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
def test_where_empty_series_and_empty_cond_having_non_bool_dtypes():
|
| 404 |
+
# https://github.com/pandas-dev/pandas/issues/34592
|
| 405 |
+
ser = Series([], dtype=float)
|
| 406 |
+
result = ser.where([])
|
| 407 |
+
tm.assert_series_equal(result, ser)
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
def test_where_categorical(frame_or_series):
|
| 411 |
+
# https://github.com/pandas-dev/pandas/issues/18888
|
| 412 |
+
exp = frame_or_series(
|
| 413 |
+
pd.Categorical(["A", "A", "B", "B", np.nan], categories=["A", "B", "C"]),
|
| 414 |
+
dtype="category",
|
| 415 |
+
)
|
| 416 |
+
df = frame_or_series(["A", "A", "B", "B", "C"], dtype="category")
|
| 417 |
+
res = df.where(df != "C")
|
| 418 |
+
tm.assert_equal(exp, res)
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def test_where_datetimelike_categorical(tz_naive_fixture):
|
| 422 |
+
# GH#37682
|
| 423 |
+
tz = tz_naive_fixture
|
| 424 |
+
|
| 425 |
+
dr = date_range("2001-01-01", periods=3, tz=tz)._with_freq(None)
|
| 426 |
+
lvals = pd.DatetimeIndex([dr[0], dr[1], pd.NaT])
|
| 427 |
+
rvals = pd.Categorical([dr[0], pd.NaT, dr[2]])
|
| 428 |
+
|
| 429 |
+
mask = np.array([True, True, False])
|
| 430 |
+
|
| 431 |
+
# DatetimeIndex.where
|
| 432 |
+
res = lvals.where(mask, rvals)
|
| 433 |
+
tm.assert_index_equal(res, dr)
|
| 434 |
+
|
| 435 |
+
# DatetimeArray.where
|
| 436 |
+
res = lvals._data._where(mask, rvals)
|
| 437 |
+
tm.assert_datetime_array_equal(res, dr._data)
|
| 438 |
+
|
| 439 |
+
# Series.where
|
| 440 |
+
res = Series(lvals).where(mask, rvals)
|
| 441 |
+
tm.assert_series_equal(res, Series(dr))
|
| 442 |
+
|
| 443 |
+
# DataFrame.where
|
| 444 |
+
res = pd.DataFrame(lvals).where(mask[:, None], pd.DataFrame(rvals))
|
| 445 |
+
|
| 446 |
+
tm.assert_frame_equal(res, pd.DataFrame(dr))
|
.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_xs.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import pytest
|
| 3 |
+
|
| 4 |
+
from pandas import (
|
| 5 |
+
MultiIndex,
|
| 6 |
+
Series,
|
| 7 |
+
date_range,
|
| 8 |
+
)
|
| 9 |
+
import pandas._testing as tm
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def test_xs_datetimelike_wrapping():
|
| 13 |
+
# GH#31630 a case where we shouldn't wrap datetime64 in Timestamp
|
| 14 |
+
arr = date_range("2016-01-01", periods=3)._data._ndarray
|
| 15 |
+
|
| 16 |
+
ser = Series(arr, dtype=object)
|
| 17 |
+
for i in range(len(ser)):
|
| 18 |
+
ser.iloc[i] = arr[i]
|
| 19 |
+
assert ser.dtype == object
|
| 20 |
+
assert isinstance(ser[0], np.datetime64)
|
| 21 |
+
|
| 22 |
+
result = ser.xs(0)
|
| 23 |
+
assert isinstance(result, np.datetime64)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TestXSWithMultiIndex:
|
| 27 |
+
def test_xs_level_series(self, multiindex_dataframe_random_data):
|
| 28 |
+
df = multiindex_dataframe_random_data
|
| 29 |
+
ser = df["A"]
|
| 30 |
+
expected = ser[:, "two"]
|
| 31 |
+
result = df.xs("two", level=1)["A"]
|
| 32 |
+
tm.assert_series_equal(result, expected)
|
| 33 |
+
|
| 34 |
+
def test_series_getitem_multiindex_xs_by_label(self):
|
| 35 |
+
# GH#5684
|
| 36 |
+
idx = MultiIndex.from_tuples(
|
| 37 |
+
[("a", "one"), ("a", "two"), ("b", "one"), ("b", "two")]
|
| 38 |
+
)
|
| 39 |
+
ser = Series([1, 2, 3, 4], index=idx)
|
| 40 |
+
return_value = ser.index.set_names(["L1", "L2"], inplace=True)
|
| 41 |
+
assert return_value is None
|
| 42 |
+
expected = Series([1, 3], index=["a", "b"])
|
| 43 |
+
return_value = expected.index.set_names(["L1"], inplace=True)
|
| 44 |
+
assert return_value is None
|
| 45 |
+
|
| 46 |
+
result = ser.xs("one", level="L2")
|
| 47 |
+
tm.assert_series_equal(result, expected)
|
| 48 |
+
|
| 49 |
+
def test_series_getitem_multiindex_xs(self):
|
| 50 |
+
# GH#6258
|
| 51 |
+
dt = list(date_range("20130903", periods=3))
|
| 52 |
+
idx = MultiIndex.from_product([list("AB"), dt])
|
| 53 |
+
ser = Series([1, 3, 4, 1, 3, 4], index=idx)
|
| 54 |
+
expected = Series([1, 1], index=list("AB"))
|
| 55 |
+
|
| 56 |
+
result = ser.xs("20130903", level=1)
|
| 57 |
+
tm.assert_series_equal(result, expected)
|
| 58 |
+
|
| 59 |
+
def test_series_xs_droplevel_false(self):
|
| 60 |
+
# GH: 19056
|
| 61 |
+
mi = MultiIndex.from_tuples(
|
| 62 |
+
[("a", "x"), ("a", "y"), ("b", "x")], names=["level1", "level2"]
|
| 63 |
+
)
|
| 64 |
+
ser = Series([1, 1, 1], index=mi)
|
| 65 |
+
result = ser.xs("a", axis=0, drop_level=False)
|
| 66 |
+
expected = Series(
|
| 67 |
+
[1, 1],
|
| 68 |
+
index=MultiIndex.from_tuples(
|
| 69 |
+
[("a", "x"), ("a", "y")], names=["level1", "level2"]
|
| 70 |
+
),
|
| 71 |
+
)
|
| 72 |
+
tm.assert_series_equal(result, expected)
|
| 73 |
+
|
| 74 |
+
def test_xs_key_as_list(self):
|
| 75 |
+
# GH#41760
|
| 76 |
+
mi = MultiIndex.from_tuples([("a", "x")], names=["level1", "level2"])
|
| 77 |
+
ser = Series([1], index=mi)
|
| 78 |
+
with pytest.raises(TypeError, match="list keys are not supported"):
|
| 79 |
+
ser.xs(["a", "x"], axis=0, drop_level=False)
|
| 80 |
+
|
| 81 |
+
with pytest.raises(TypeError, match="list keys are not supported"):
|
| 82 |
+
ser.xs(["a"], axis=0, drop_level=False)
|
.venv/lib/python3.13/site-packages/pandas/tests/series/methods/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test files dedicated to individual (stand-alone) Series methods
|
| 3 |
+
|
| 4 |
+
Ideally these files/tests should correspond 1-to-1 with tests.frame.methods
|
| 5 |
+
|
| 6 |
+
These may also present opportunities for sharing/de-duplicating test code.
|
| 7 |
+
"""
|