123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- from datetime import timedelta
- from itertools import product
- import numpy as np
- import pytest
- from pandas._libs.tslibs import OutOfBoundsTimedelta
- from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
- from pandas import (
- NaT,
- Timedelta,
- offsets,
- to_timedelta,
- )
- def test_construct_with_weeks_unit_overflow():
- # GH#47268 don't silently wrap around
- with pytest.raises(OutOfBoundsTimedelta, match="without overflow"):
- Timedelta(1000000000000000000, unit="W")
- with pytest.raises(OutOfBoundsTimedelta, match="without overflow"):
- Timedelta(1000000000000000000.0, unit="W")
- def test_construct_from_td64_with_unit():
- # ignore the unit, as it may cause silently overflows leading to incorrect
- # results, and in non-overflow cases is irrelevant GH#46827
- obj = np.timedelta64(123456789000000000, "h")
- with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
- Timedelta(obj, unit="ps")
- with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
- Timedelta(obj, unit="ns")
- with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"):
- Timedelta(obj)
- def test_from_td64_retain_resolution():
- # case where we retain millisecond resolution
- obj = np.timedelta64(12345, "ms")
- td = Timedelta(obj)
- assert td._value == obj.view("i8")
- assert td._creso == NpyDatetimeUnit.NPY_FR_ms.value
- # Case where we cast to nearest-supported reso
- obj2 = np.timedelta64(1234, "D")
- td2 = Timedelta(obj2)
- assert td2._creso == NpyDatetimeUnit.NPY_FR_s.value
- assert td2 == obj2
- assert td2.days == 1234
- # Case that _would_ overflow if we didn't support non-nano
- obj3 = np.timedelta64(1000000000000000000, "us")
- td3 = Timedelta(obj3)
- assert td3.total_seconds() == 1000000000000
- assert td3._creso == NpyDatetimeUnit.NPY_FR_us.value
- def test_from_pytimedelta_us_reso():
- # pytimedelta has microsecond resolution, so Timedelta(pytd) inherits that
- td = timedelta(days=4, minutes=3)
- result = Timedelta(td)
- assert result.to_pytimedelta() == td
- assert result._creso == NpyDatetimeUnit.NPY_FR_us.value
- def test_from_tick_reso():
- tick = offsets.Nano()
- assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ns.value
- tick = offsets.Micro()
- assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_us.value
- tick = offsets.Milli()
- assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ms.value
- tick = offsets.Second()
- assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
- # everything above Second gets cast to the closest supported reso: second
- tick = offsets.Minute()
- assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
- tick = offsets.Hour()
- assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
- tick = offsets.Day()
- assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value
- def test_construction():
- expected = np.timedelta64(10, "D").astype("m8[ns]").view("i8")
- assert Timedelta(10, unit="d")._value == expected
- assert Timedelta(10.0, unit="d")._value == expected
- assert Timedelta("10 days")._value == expected
- assert Timedelta(days=10)._value == expected
- assert Timedelta(days=10.0)._value == expected
- expected += np.timedelta64(10, "s").astype("m8[ns]").view("i8")
- assert Timedelta("10 days 00:00:10")._value == expected
- assert Timedelta(days=10, seconds=10)._value == expected
- assert Timedelta(days=10, milliseconds=10 * 1000)._value == expected
- assert Timedelta(days=10, microseconds=10 * 1000 * 1000)._value == expected
- # rounding cases
- assert Timedelta(82739999850000)._value == 82739999850000
- assert "0 days 22:58:59.999850" in str(Timedelta(82739999850000))
- assert Timedelta(123072001000000)._value == 123072001000000
- assert "1 days 10:11:12.001" in str(Timedelta(123072001000000))
- # string conversion with/without leading zero
- # GH#9570
- assert Timedelta("0:00:00") == timedelta(hours=0)
- assert Timedelta("00:00:00") == timedelta(hours=0)
- assert Timedelta("-1:00:00") == -timedelta(hours=1)
- assert Timedelta("-01:00:00") == -timedelta(hours=1)
- # more strings & abbrevs
- # GH#8190
- assert Timedelta("1 h") == timedelta(hours=1)
- assert Timedelta("1 hour") == timedelta(hours=1)
- assert Timedelta("1 hr") == timedelta(hours=1)
- assert Timedelta("1 hours") == timedelta(hours=1)
- assert Timedelta("-1 hours") == -timedelta(hours=1)
- assert Timedelta("1 m") == timedelta(minutes=1)
- assert Timedelta("1.5 m") == timedelta(seconds=90)
- assert Timedelta("1 minute") == timedelta(minutes=1)
- assert Timedelta("1 minutes") == timedelta(minutes=1)
- assert Timedelta("1 s") == timedelta(seconds=1)
- assert Timedelta("1 second") == timedelta(seconds=1)
- assert Timedelta("1 seconds") == timedelta(seconds=1)
- assert Timedelta("1 ms") == timedelta(milliseconds=1)
- assert Timedelta("1 milli") == timedelta(milliseconds=1)
- assert Timedelta("1 millisecond") == timedelta(milliseconds=1)
- assert Timedelta("1 us") == timedelta(microseconds=1)
- assert Timedelta("1 µs") == timedelta(microseconds=1)
- assert Timedelta("1 micros") == timedelta(microseconds=1)
- assert Timedelta("1 microsecond") == timedelta(microseconds=1)
- assert Timedelta("1.5 microsecond") == Timedelta("00:00:00.000001500")
- assert Timedelta("1 ns") == Timedelta("00:00:00.000000001")
- assert Timedelta("1 nano") == Timedelta("00:00:00.000000001")
- assert Timedelta("1 nanosecond") == Timedelta("00:00:00.000000001")
- # combos
- assert Timedelta("10 days 1 hour") == timedelta(days=10, hours=1)
- assert Timedelta("10 days 1 h") == timedelta(days=10, hours=1)
- assert Timedelta("10 days 1 h 1m 1s") == timedelta(
- days=10, hours=1, minutes=1, seconds=1
- )
- assert Timedelta("-10 days 1 h 1m 1s") == -timedelta(
- days=10, hours=1, minutes=1, seconds=1
- )
- assert Timedelta("-10 days 1 h 1m 1s") == -timedelta(
- days=10, hours=1, minutes=1, seconds=1
- )
- assert Timedelta("-10 days 1 h 1m 1s 3us") == -timedelta(
- days=10, hours=1, minutes=1, seconds=1, microseconds=3
- )
- assert Timedelta("-10 days 1 h 1.5m 1s 3us") == -timedelta(
- days=10, hours=1, minutes=1, seconds=31, microseconds=3
- )
- # Currently invalid as it has a - on the hh:mm:dd part
- # (only allowed on the days)
- msg = "only leading negative signs are allowed"
- with pytest.raises(ValueError, match=msg):
- Timedelta("-10 days -1 h 1.5m 1s 3us")
- # only leading neg signs are allowed
- with pytest.raises(ValueError, match=msg):
- Timedelta("10 days -1 h 1.5m 1s 3us")
- # no units specified
- msg = "no units specified"
- with pytest.raises(ValueError, match=msg):
- Timedelta("3.1415")
- # invalid construction
- msg = "cannot construct a Timedelta"
- with pytest.raises(ValueError, match=msg):
- Timedelta()
- msg = "unit abbreviation w/o a number"
- with pytest.raises(ValueError, match=msg):
- Timedelta("foo")
- msg = (
- "cannot construct a Timedelta from "
- "the passed arguments, allowed keywords are "
- )
- with pytest.raises(ValueError, match=msg):
- Timedelta(day=10)
- # floats
- expected = np.timedelta64(10, "s").astype("m8[ns]").view("i8") + np.timedelta64(
- 500, "ms"
- ).astype("m8[ns]").view("i8")
- assert Timedelta(10.5, unit="s")._value == expected
- # offset
- assert to_timedelta(offsets.Hour(2)) == Timedelta(hours=2)
- assert Timedelta(offsets.Hour(2)) == Timedelta(hours=2)
- assert Timedelta(offsets.Second(2)) == Timedelta(seconds=2)
- # GH#11995: unicode
- expected = Timedelta("1H")
- result = Timedelta("1H")
- assert result == expected
- assert to_timedelta(offsets.Hour(2)) == Timedelta("0 days, 02:00:00")
- msg = "unit abbreviation w/o a number"
- with pytest.raises(ValueError, match=msg):
- Timedelta("foo bar")
- @pytest.mark.parametrize(
- "item",
- list(
- {
- "days": "D",
- "seconds": "s",
- "microseconds": "us",
- "milliseconds": "ms",
- "minutes": "m",
- "hours": "h",
- "weeks": "W",
- }.items()
- ),
- )
- @pytest.mark.parametrize(
- "npdtype", [np.int64, np.int32, np.int16, np.float64, np.float32, np.float16]
- )
- def test_td_construction_with_np_dtypes(npdtype, item):
- # GH#8757: test construction with np dtypes
- pykwarg, npkwarg = item
- expected = np.timedelta64(1, npkwarg).astype("m8[ns]").view("i8")
- assert Timedelta(**{pykwarg: npdtype(1)})._value == expected
- @pytest.mark.parametrize(
- "val",
- [
- "1s",
- "-1s",
- "1us",
- "-1us",
- "1 day",
- "-1 day",
- "-23:59:59.999999",
- "-1 days +23:59:59.999999",
- "-1ns",
- "1ns",
- "-23:59:59.999999999",
- ],
- )
- def test_td_from_repr_roundtrip(val):
- # round-trip both for string and value
- td = Timedelta(val)
- assert Timedelta(td._value) == td
- assert Timedelta(str(td)) == td
- assert Timedelta(td._repr_base(format="all")) == td
- assert Timedelta(td._repr_base()) == td
- def test_overflow_on_construction():
- # GH#3374
- value = Timedelta("1day")._value * 20169940
- msg = "Cannot cast 1742682816000000000000 from ns to 'ns' without overflow"
- with pytest.raises(OutOfBoundsTimedelta, match=msg):
- Timedelta(value)
- # xref GH#17637
- msg = "Cannot cast 139993 from D to 'ns' without overflow"
- with pytest.raises(OutOfBoundsTimedelta, match=msg):
- Timedelta(7 * 19999, unit="D")
- # used to overflow before non-ns support
- td = Timedelta(timedelta(days=13 * 19999))
- assert td._creso == NpyDatetimeUnit.NPY_FR_us.value
- assert td.days == 13 * 19999
- @pytest.mark.parametrize(
- "val, unit",
- [
- (3508, "M"),
- (15251, "W"), # 1
- (106752, "D"), # change from previous:
- (2562048, "h"), # 0 hours
- (153722868, "m"), # 13 minutes
- (9223372037, "s"), # 44 seconds
- ],
- )
- def test_construction_out_of_bounds_td64ns(val, unit):
- # TODO: parametrize over units just above/below the implementation bounds
- # once GH#38964 is resolved
- # Timedelta.max is just under 106752 days
- td64 = np.timedelta64(val, unit)
- assert td64.astype("m8[ns]").view("i8") < 0 # i.e. naive astype will be wrong
- td = Timedelta(td64)
- if unit != "M":
- # with unit="M" the conversion to "s" is poorly defined
- # (and numpy issues DeprecationWarning)
- assert td.asm8 == td64
- assert td.asm8.dtype == "m8[s]"
- msg = r"Cannot cast 1067\d\d days .* to unit='ns' without overflow"
- with pytest.raises(OutOfBoundsTimedelta, match=msg):
- td.as_unit("ns")
- # But just back in bounds and we are OK
- assert Timedelta(td64 - 1) == td64 - 1
- td64 *= -1
- assert td64.astype("m8[ns]").view("i8") > 0 # i.e. naive astype will be wrong
- td2 = Timedelta(td64)
- msg = r"Cannot cast -1067\d\d days .* to unit='ns' without overflow"
- with pytest.raises(OutOfBoundsTimedelta, match=msg):
- td2.as_unit("ns")
- # But just back in bounds and we are OK
- assert Timedelta(td64 + 1) == td64 + 1
- @pytest.mark.parametrize(
- "val, unit",
- [
- (3508 * 10**9, "M"),
- (15251 * 10**9, "W"),
- (106752 * 10**9, "D"),
- (2562048 * 10**9, "h"),
- (153722868 * 10**9, "m"),
- ],
- )
- def test_construction_out_of_bounds_td64s(val, unit):
- td64 = np.timedelta64(val, unit)
- with pytest.raises(OutOfBoundsTimedelta, match=str(td64)):
- Timedelta(td64)
- # But just back in bounds and we are OK
- assert Timedelta(td64 - 10**9) == td64 - 10**9
- @pytest.mark.parametrize(
- "fmt,exp",
- [
- (
- "P6DT0H50M3.010010012S",
- Timedelta(
- days=6,
- minutes=50,
- seconds=3,
- milliseconds=10,
- microseconds=10,
- nanoseconds=12,
- ),
- ),
- (
- "P-6DT0H50M3.010010012S",
- Timedelta(
- days=-6,
- minutes=50,
- seconds=3,
- milliseconds=10,
- microseconds=10,
- nanoseconds=12,
- ),
- ),
- ("P4DT12H30M5S", Timedelta(days=4, hours=12, minutes=30, seconds=5)),
- ("P0DT0H0M0.000000123S", Timedelta(nanoseconds=123)),
- ("P0DT0H0M0.00001S", Timedelta(microseconds=10)),
- ("P0DT0H0M0.001S", Timedelta(milliseconds=1)),
- ("P0DT0H1M0S", Timedelta(minutes=1)),
- ("P1DT25H61M61S", Timedelta(days=1, hours=25, minutes=61, seconds=61)),
- ("PT1S", Timedelta(seconds=1)),
- ("PT0S", Timedelta(seconds=0)),
- ("P1WT0S", Timedelta(days=7, seconds=0)),
- ("P1D", Timedelta(days=1)),
- ("P1DT1H", Timedelta(days=1, hours=1)),
- ("P1W", Timedelta(days=7)),
- ("PT300S", Timedelta(seconds=300)),
- ("P1DT0H0M00000000000S", Timedelta(days=1)),
- ("PT-6H3M", Timedelta(hours=-6, minutes=3)),
- ("-PT6H3M", Timedelta(hours=-6, minutes=-3)),
- ("-PT-6H+3M", Timedelta(hours=6, minutes=-3)),
- ],
- )
- def test_iso_constructor(fmt, exp):
- assert Timedelta(fmt) == exp
- @pytest.mark.parametrize(
- "fmt",
- [
- "PPPPPPPPPPPP",
- "PDTHMS",
- "P0DT999H999M999S",
- "P1DT0H0M0.0000000000000S",
- "P1DT0H0M0.S",
- "P",
- "-P",
- ],
- )
- def test_iso_constructor_raises(fmt):
- msg = f"Invalid ISO 8601 Duration format - {fmt}"
- with pytest.raises(ValueError, match=msg):
- Timedelta(fmt)
- @pytest.mark.parametrize(
- "constructed_td, conversion",
- [
- (Timedelta(nanoseconds=100), "100ns"),
- (
- Timedelta(
- days=1,
- hours=1,
- minutes=1,
- weeks=1,
- seconds=1,
- milliseconds=1,
- microseconds=1,
- nanoseconds=1,
- ),
- 694861001001001,
- ),
- (Timedelta(microseconds=1) + Timedelta(nanoseconds=1), "1us1ns"),
- (Timedelta(microseconds=1) - Timedelta(nanoseconds=1), "999ns"),
- (Timedelta(microseconds=1) + 5 * Timedelta(nanoseconds=-2), "990ns"),
- ],
- )
- def test_td_constructor_on_nanoseconds(constructed_td, conversion):
- # GH#9273
- assert constructed_td == Timedelta(conversion)
- def test_td_constructor_value_error():
- msg = "Invalid type <class 'str'>. Must be int or float."
- with pytest.raises(TypeError, match=msg):
- Timedelta(nanoseconds="abc")
- def test_timedelta_constructor_identity():
- # Test for #30543
- expected = Timedelta(np.timedelta64(1, "s"))
- result = Timedelta(expected)
- assert result is expected
- def test_timedelta_pass_td_and_kwargs_raises():
- # don't silently ignore the kwargs GH#48898
- td = Timedelta(days=1)
- msg = (
- "Cannot pass both a Timedelta input and timedelta keyword arguments, "
- r"got \['days'\]"
- )
- with pytest.raises(ValueError, match=msg):
- Timedelta(td, days=2)
- @pytest.mark.parametrize(
- "constructor, value, unit, expectation",
- [
- (Timedelta, "10s", "ms", (ValueError, "unit must not be specified")),
- (to_timedelta, "10s", "ms", (ValueError, "unit must not be specified")),
- (to_timedelta, ["1", 2, 3], "s", (ValueError, "unit must not be specified")),
- ],
- )
- def test_string_with_unit(constructor, value, unit, expectation):
- exp, match = expectation
- with pytest.raises(exp, match=match):
- _ = constructor(value, unit=unit)
- @pytest.mark.parametrize(
- "value",
- [
- "".join(elements)
- for repetition in (1, 2)
- for elements in product("+-, ", repeat=repetition)
- ],
- )
- def test_string_without_numbers(value):
- # GH39710 Timedelta input string with only symbols and no digits raises an error
- msg = (
- "symbols w/o a number"
- if value != "--"
- else "only leading negative signs are allowed"
- )
- with pytest.raises(ValueError, match=msg):
- Timedelta(value)
- def test_timedelta_new_npnat():
- # GH#48898
- nat = np.timedelta64("NaT", "h")
- assert Timedelta(nat) is NaT
- def test_subclass_respected():
- # GH#49579
- class MyCustomTimedelta(Timedelta):
- pass
- td = MyCustomTimedelta("1 minute")
- assert isinstance(td, MyCustomTimedelta)
- def test_non_nano_value():
- # https://github.com/pandas-dev/pandas/issues/49076
- result = Timedelta(10, unit="D").as_unit("s").value
- # `.value` shows nanoseconds, even though unit is 's'
- assert result == 864000000000000
- # out-of-nanoseconds-bounds `.value` raises informative message
- msg = (
- r"Cannot convert Timedelta to nanoseconds without overflow. "
- r"Use `.asm8.view\('i8'\)` to cast represent Timedelta in its "
- r"own unit \(here, s\).$"
- )
- td = Timedelta(1_000, "D").as_unit("s") * 1_000
- with pytest.raises(OverflowError, match=msg):
- td.value
- # check that the suggested workaround actually works
- result = td.asm8.view("i8")
- assert result == 86400000000
|