123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- from datetime import (
- datetime,
- timedelta,
- timezone,
- )
- import numpy as np
- import pytest
- from pandas._libs.tslibs import (
- OutOfBoundsDatetime,
- OutOfBoundsTimedelta,
- Timedelta,
- Timestamp,
- offsets,
- to_offset,
- )
- import pandas._testing as tm
- class TestTimestampArithmetic:
- def test_overflow_offset(self):
- # no overflow expected
- stamp = Timestamp("2000/1/1")
- offset_no_overflow = to_offset("D") * 100
- expected = Timestamp("2000/04/10")
- assert stamp + offset_no_overflow == expected
- assert offset_no_overflow + stamp == expected
- expected = Timestamp("1999/09/23")
- assert stamp - offset_no_overflow == expected
- def test_overflow_offset_raises(self):
- # xref https://github.com/statsmodels/statsmodels/issues/3374
- # ends up multiplying really large numbers which overflow
- stamp = Timestamp("2017-01-13 00:00:00").as_unit("ns")
- offset_overflow = 20169940 * offsets.Day(1)
- msg = (
- "the add operation between "
- r"\<-?\d+ \* Days\> and \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} "
- "will overflow"
- )
- lmsg2 = r"Cannot cast -?20169940 days \+?00:00:00 to unit='ns' without overflow"
- with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
- stamp + offset_overflow
- with pytest.raises(OverflowError, match=msg):
- offset_overflow + stamp
- with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
- stamp - offset_overflow
- # xref https://github.com/pandas-dev/pandas/issues/14080
- # used to crash, so check for proper overflow exception
- stamp = Timestamp("2000/1/1").as_unit("ns")
- offset_overflow = to_offset("D") * 100**5
- lmsg3 = (
- r"Cannot cast -?10000000000 days \+?00:00:00 to unit='ns' without overflow"
- )
- with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):
- stamp + offset_overflow
- with pytest.raises(OverflowError, match=msg):
- offset_overflow + stamp
- with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):
- stamp - offset_overflow
- def test_overflow_timestamp_raises(self):
- # https://github.com/pandas-dev/pandas/issues/31774
- msg = "Result is too large"
- a = Timestamp("2101-01-01 00:00:00").as_unit("ns")
- b = Timestamp("1688-01-01 00:00:00").as_unit("ns")
- with pytest.raises(OutOfBoundsDatetime, match=msg):
- a - b
- # but we're OK for timestamp and datetime.datetime
- assert (a - b.to_pydatetime()) == (a.to_pydatetime() - b)
- def test_delta_preserve_nanos(self):
- val = Timestamp(1337299200000000123)
- result = val + timedelta(1)
- assert result.nanosecond == val.nanosecond
- def test_rsub_dtscalars(self, tz_naive_fixture):
- # In particular, check that datetime64 - Timestamp works GH#28286
- td = Timedelta(1235345642000)
- ts = Timestamp("2021-01-01", tz=tz_naive_fixture)
- other = ts + td
- assert other - ts == td
- assert other.to_pydatetime() - ts == td
- if tz_naive_fixture is None:
- assert other.to_datetime64() - ts == td
- else:
- msg = "Cannot subtract tz-naive and tz-aware datetime-like objects"
- with pytest.raises(TypeError, match=msg):
- other.to_datetime64() - ts
- def test_timestamp_sub_datetime(self):
- dt = datetime(2013, 10, 12)
- ts = Timestamp(datetime(2013, 10, 13))
- assert (ts - dt).days == 1
- assert (dt - ts).days == -1
- def test_subtract_tzaware_datetime(self):
- t1 = Timestamp("2020-10-22T22:00:00+00:00")
- t2 = datetime(2020, 10, 22, 22, tzinfo=timezone.utc)
- result = t1 - t2
- assert isinstance(result, Timedelta)
- assert result == Timedelta("0 days")
- def test_subtract_timestamp_from_different_timezone(self):
- t1 = Timestamp("20130101").tz_localize("US/Eastern")
- t2 = Timestamp("20130101").tz_localize("CET")
- result = t1 - t2
- assert isinstance(result, Timedelta)
- assert result == Timedelta("0 days 06:00:00")
- def test_subtracting_involving_datetime_with_different_tz(self):
- t1 = datetime(2013, 1, 1, tzinfo=timezone(timedelta(hours=-5)))
- t2 = Timestamp("20130101").tz_localize("CET")
- result = t1 - t2
- assert isinstance(result, Timedelta)
- assert result == Timedelta("0 days 06:00:00")
- result = t2 - t1
- assert isinstance(result, Timedelta)
- assert result == Timedelta("-1 days +18:00:00")
- def test_subtracting_different_timezones(self, tz_aware_fixture):
- t_raw = Timestamp("20130101")
- t_UTC = t_raw.tz_localize("UTC")
- t_diff = t_UTC.tz_convert(tz_aware_fixture) + Timedelta("0 days 05:00:00")
- result = t_diff - t_UTC
- assert isinstance(result, Timedelta)
- assert result == Timedelta("0 days 05:00:00")
- def test_addition_subtraction_types(self):
- # Assert on the types resulting from Timestamp +/- various date/time
- # objects
- dt = datetime(2014, 3, 4)
- td = timedelta(seconds=1)
- ts = Timestamp(dt)
- msg = "Addition/subtraction of integers"
- with pytest.raises(TypeError, match=msg):
- # GH#22535 add/sub with integers is deprecated
- ts + 1
- with pytest.raises(TypeError, match=msg):
- ts - 1
- # Timestamp + datetime not supported, though subtraction is supported
- # and yields timedelta more tests in tseries/base/tests/test_base.py
- assert type(ts - dt) == Timedelta
- assert type(ts + td) == Timestamp
- assert type(ts - td) == Timestamp
- # Timestamp +/- datetime64 not supported, so not tested (could possibly
- # assert error raised?)
- td64 = np.timedelta64(1, "D")
- assert type(ts + td64) == Timestamp
- assert type(ts - td64) == Timestamp
- @pytest.mark.parametrize(
- "td", [Timedelta(hours=3), np.timedelta64(3, "h"), timedelta(hours=3)]
- )
- def test_radd_tdscalar(self, td, fixed_now_ts):
- # GH#24775 timedelta64+Timestamp should not raise
- ts = fixed_now_ts
- assert td + ts == ts + td
- @pytest.mark.parametrize(
- "other,expected_difference",
- [
- (np.timedelta64(-123, "ns"), -123),
- (np.timedelta64(1234567898, "ns"), 1234567898),
- (np.timedelta64(-123, "us"), -123000),
- (np.timedelta64(-123, "ms"), -123000000),
- ],
- )
- def test_timestamp_add_timedelta64_unit(self, other, expected_difference):
- now = datetime.utcnow()
- ts = Timestamp(now).as_unit("ns")
- result = ts + other
- valdiff = result._value - ts._value
- assert valdiff == expected_difference
- ts2 = Timestamp(now)
- assert ts2 + other == result
- @pytest.mark.parametrize(
- "ts",
- [
- Timestamp("1776-07-04"),
- Timestamp("1776-07-04", tz="UTC"),
- ],
- )
- @pytest.mark.parametrize(
- "other",
- [
- 1,
- np.int64(1),
- np.array([1, 2], dtype=np.int32),
- np.array([3, 4], dtype=np.uint64),
- ],
- )
- def test_add_int_with_freq(self, ts, other):
- msg = "Addition/subtraction of integers and integer-arrays"
- with pytest.raises(TypeError, match=msg):
- ts + other
- with pytest.raises(TypeError, match=msg):
- other + ts
- with pytest.raises(TypeError, match=msg):
- ts - other
- msg = "unsupported operand type"
- with pytest.raises(TypeError, match=msg):
- other - ts
- @pytest.mark.parametrize("shape", [(6,), (2, 3)])
- def test_addsub_m8ndarray(self, shape):
- # GH#33296
- ts = Timestamp("2020-04-04 15:45").as_unit("ns")
- other = np.arange(6).astype("m8[h]").reshape(shape)
- result = ts + other
- ex_stamps = [ts + Timedelta(hours=n) for n in range(6)]
- expected = np.array([x.asm8 for x in ex_stamps], dtype="M8[ns]").reshape(shape)
- tm.assert_numpy_array_equal(result, expected)
- result = other + ts
- tm.assert_numpy_array_equal(result, expected)
- result = ts - other
- ex_stamps = [ts - Timedelta(hours=n) for n in range(6)]
- expected = np.array([x.asm8 for x in ex_stamps], dtype="M8[ns]").reshape(shape)
- tm.assert_numpy_array_equal(result, expected)
- msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timestamp'"
- with pytest.raises(TypeError, match=msg):
- other - ts
- @pytest.mark.parametrize("shape", [(6,), (2, 3)])
- def test_addsub_m8ndarray_tzaware(self, shape):
- # GH#33296
- ts = Timestamp("2020-04-04 15:45", tz="US/Pacific")
- other = np.arange(6).astype("m8[h]").reshape(shape)
- result = ts + other
- ex_stamps = [ts + Timedelta(hours=n) for n in range(6)]
- expected = np.array(ex_stamps).reshape(shape)
- tm.assert_numpy_array_equal(result, expected)
- result = other + ts
- tm.assert_numpy_array_equal(result, expected)
- result = ts - other
- ex_stamps = [ts - Timedelta(hours=n) for n in range(6)]
- expected = np.array(ex_stamps).reshape(shape)
- tm.assert_numpy_array_equal(result, expected)
- msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timestamp'"
- with pytest.raises(TypeError, match=msg):
- other - ts
- def test_subtract_different_utc_objects(self, utc_fixture, utc_fixture2):
- # GH 32619
- dt = datetime(2021, 1, 1)
- ts1 = Timestamp(dt, tz=utc_fixture)
- ts2 = Timestamp(dt, tz=utc_fixture2)
- result = ts1 - ts2
- expected = Timedelta(0)
- assert result == expected
|