1069 lines
39 KiB
Python
1069 lines
39 KiB
Python
import calendar
|
|
from datetime import (
|
|
date,
|
|
datetime,
|
|
timedelta,
|
|
timezone,
|
|
)
|
|
import zoneinfo
|
|
|
|
import dateutil.tz
|
|
from dateutil.tz import (
|
|
gettz,
|
|
tzoffset,
|
|
tzutc,
|
|
)
|
|
import numpy as np
|
|
import pytest
|
|
import pytz
|
|
|
|
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
|
|
from pandas.compat import PY310
|
|
from pandas.errors import OutOfBoundsDatetime
|
|
|
|
from pandas import (
|
|
NA,
|
|
NaT,
|
|
Period,
|
|
Timedelta,
|
|
Timestamp,
|
|
)
|
|
|
|
|
|
class TestTimestampConstructorUnitKeyword:
|
|
@pytest.mark.parametrize("typ", [int, float])
|
|
def test_constructor_int_float_with_YM_unit(self, typ):
|
|
# GH#47266 avoid the conversions in cast_from_unit
|
|
val = typ(150)
|
|
|
|
ts = Timestamp(val, unit="Y")
|
|
expected = Timestamp("2120-01-01")
|
|
assert ts == expected
|
|
|
|
ts = Timestamp(val, unit="M")
|
|
expected = Timestamp("1982-07-01")
|
|
assert ts == expected
|
|
|
|
@pytest.mark.parametrize("typ", [int, float])
|
|
def test_construct_from_int_float_with_unit_out_of_bound_raises(self, typ):
|
|
# GH#50870 make sure we get a OutOfBoundsDatetime instead of OverflowError
|
|
val = typ(150000000000000)
|
|
|
|
msg = f"cannot convert input {val} with the unit 'D'"
|
|
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
|
Timestamp(val, unit="D")
|
|
|
|
def test_constructor_float_not_round_with_YM_unit_raises(self):
|
|
# GH#47267 avoid the conversions in cast_from-unit
|
|
|
|
msg = "Conversion of non-round float with unit=[MY] is ambiguous"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(150.5, unit="Y")
|
|
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(150.5, unit="M")
|
|
|
|
@pytest.mark.parametrize(
|
|
"value, check_kwargs",
|
|
[
|
|
[946688461000000000, {}],
|
|
[946688461000000000 / 1000, {"unit": "us"}],
|
|
[946688461000000000 / 1_000_000, {"unit": "ms"}],
|
|
[946688461000000000 / 1_000_000_000, {"unit": "s"}],
|
|
[10957, {"unit": "D", "h": 0}],
|
|
[
|
|
(946688461000000000 + 500000) / 1000000000,
|
|
{"unit": "s", "us": 499, "ns": 964},
|
|
],
|
|
[
|
|
(946688461000000000 + 500000000) / 1000000000,
|
|
{"unit": "s", "us": 500000},
|
|
],
|
|
[(946688461000000000 + 500000) / 1000000, {"unit": "ms", "us": 500}],
|
|
[(946688461000000000 + 500000) / 1000, {"unit": "us", "us": 500}],
|
|
[(946688461000000000 + 500000000) / 1000000, {"unit": "ms", "us": 500000}],
|
|
[946688461000000000 / 1000.0 + 5, {"unit": "us", "us": 5}],
|
|
[946688461000000000 / 1000.0 + 5000, {"unit": "us", "us": 5000}],
|
|
[946688461000000000 / 1000000.0 + 0.5, {"unit": "ms", "us": 500}],
|
|
[946688461000000000 / 1000000.0 + 0.005, {"unit": "ms", "us": 5, "ns": 5}],
|
|
[946688461000000000 / 1000000000.0 + 0.5, {"unit": "s", "us": 500000}],
|
|
[10957 + 0.5, {"unit": "D", "h": 12}],
|
|
],
|
|
)
|
|
def test_construct_with_unit(self, value, check_kwargs):
|
|
def check(value, unit=None, h=1, s=1, us=0, ns=0):
|
|
stamp = Timestamp(value, unit=unit)
|
|
assert stamp.year == 2000
|
|
assert stamp.month == 1
|
|
assert stamp.day == 1
|
|
assert stamp.hour == h
|
|
if unit != "D":
|
|
assert stamp.minute == 1
|
|
assert stamp.second == s
|
|
assert stamp.microsecond == us
|
|
else:
|
|
assert stamp.minute == 0
|
|
assert stamp.second == 0
|
|
assert stamp.microsecond == 0
|
|
assert stamp.nanosecond == ns
|
|
|
|
check(value, **check_kwargs)
|
|
|
|
|
|
class TestTimestampConstructorFoldKeyword:
|
|
def test_timestamp_constructor_invalid_fold_raise(self):
|
|
# Test for GH#25057
|
|
# Valid fold values are only [None, 0, 1]
|
|
msg = "Valid values for the fold argument are None, 0, or 1."
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(123, fold=2)
|
|
|
|
def test_timestamp_constructor_pytz_fold_raise(self):
|
|
# Test for GH#25057
|
|
# pytz doesn't support fold. Check that we raise
|
|
# if fold is passed with pytz
|
|
msg = "pytz timezones do not support fold. Please use dateutil timezones."
|
|
tz = pytz.timezone("Europe/London")
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(datetime(2019, 10, 27, 0, 30, 0, 0), tz=tz, fold=0)
|
|
|
|
@pytest.mark.parametrize("fold", [0, 1])
|
|
@pytest.mark.parametrize(
|
|
"ts_input",
|
|
[
|
|
1572136200000000000,
|
|
1572136200000000000.0,
|
|
np.datetime64(1572136200000000000, "ns"),
|
|
"2019-10-27 01:30:00+01:00",
|
|
datetime(2019, 10, 27, 0, 30, 0, 0, tzinfo=timezone.utc),
|
|
],
|
|
)
|
|
def test_timestamp_constructor_fold_conflict(self, ts_input, fold):
|
|
# Test for GH#25057
|
|
# Check that we raise on fold conflict
|
|
msg = (
|
|
"Cannot pass fold with possibly unambiguous input: int, float, "
|
|
"numpy.datetime64, str, or timezone-aware datetime-like. "
|
|
"Pass naive datetime-like or build Timestamp from components."
|
|
)
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(ts_input=ts_input, fold=fold)
|
|
|
|
@pytest.mark.parametrize("tz", ["dateutil/Europe/London", None])
|
|
@pytest.mark.parametrize("fold", [0, 1])
|
|
def test_timestamp_constructor_retain_fold(self, tz, fold):
|
|
# Test for GH#25057
|
|
# Check that we retain fold
|
|
ts = Timestamp(year=2019, month=10, day=27, hour=1, minute=30, tz=tz, fold=fold)
|
|
result = ts.fold
|
|
expected = fold
|
|
assert result == expected
|
|
|
|
try:
|
|
_tzs = [
|
|
"dateutil/Europe/London",
|
|
zoneinfo.ZoneInfo("Europe/London"),
|
|
]
|
|
except zoneinfo.ZoneInfoNotFoundError:
|
|
_tzs = ["dateutil/Europe/London"]
|
|
|
|
@pytest.mark.parametrize("tz", _tzs)
|
|
@pytest.mark.parametrize(
|
|
"ts_input,fold_out",
|
|
[
|
|
(1572136200000000000, 0),
|
|
(1572139800000000000, 1),
|
|
("2019-10-27 01:30:00+01:00", 0),
|
|
("2019-10-27 01:30:00+00:00", 1),
|
|
(datetime(2019, 10, 27, 1, 30, 0, 0, fold=0), 0),
|
|
(datetime(2019, 10, 27, 1, 30, 0, 0, fold=1), 1),
|
|
],
|
|
)
|
|
def test_timestamp_constructor_infer_fold_from_value(self, tz, ts_input, fold_out):
|
|
# Test for GH#25057
|
|
# Check that we infer fold correctly based on timestamps since utc
|
|
# or strings
|
|
ts = Timestamp(ts_input, tz=tz)
|
|
result = ts.fold
|
|
expected = fold_out
|
|
assert result == expected
|
|
|
|
@pytest.mark.parametrize("tz", ["dateutil/Europe/London"])
|
|
@pytest.mark.parametrize(
|
|
"ts_input,fold,value_out",
|
|
[
|
|
(datetime(2019, 10, 27, 1, 30, 0, 0), 0, 1572136200000000),
|
|
(datetime(2019, 10, 27, 1, 30, 0, 0), 1, 1572139800000000),
|
|
],
|
|
)
|
|
def test_timestamp_constructor_adjust_value_for_fold(
|
|
self, tz, ts_input, fold, value_out
|
|
):
|
|
# Test for GH#25057
|
|
# Check that we adjust value for fold correctly
|
|
# based on timestamps since utc
|
|
ts = Timestamp(ts_input, tz=tz, fold=fold)
|
|
result = ts._value
|
|
expected = value_out
|
|
assert result == expected
|
|
|
|
|
|
class TestTimestampConstructorPositionalAndKeywordSupport:
|
|
def test_constructor_positional(self):
|
|
# see GH#10758
|
|
msg = (
|
|
"'NoneType' object cannot be interpreted as an integer"
|
|
if PY310
|
|
else "an integer is required"
|
|
)
|
|
with pytest.raises(TypeError, match=msg):
|
|
Timestamp(2000, 1)
|
|
|
|
msg = "month must be in 1..12"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(2000, 0, 1)
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(2000, 13, 1)
|
|
|
|
msg = "day is out of range for month"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(2000, 1, 0)
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(2000, 1, 32)
|
|
|
|
# see gh-11630
|
|
assert repr(Timestamp(2015, 11, 12)) == repr(Timestamp("20151112"))
|
|
assert repr(Timestamp(2015, 11, 12, 1, 2, 3, 999999)) == repr(
|
|
Timestamp("2015-11-12 01:02:03.999999")
|
|
)
|
|
|
|
def test_constructor_keyword(self):
|
|
# GH#10758
|
|
msg = "function missing required argument 'day'|Required argument 'day'"
|
|
with pytest.raises(TypeError, match=msg):
|
|
Timestamp(year=2000, month=1)
|
|
|
|
msg = "month must be in 1..12"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(year=2000, month=0, day=1)
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(year=2000, month=13, day=1)
|
|
|
|
msg = "day is out of range for month"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(year=2000, month=1, day=0)
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(year=2000, month=1, day=32)
|
|
|
|
assert repr(Timestamp(year=2015, month=11, day=12)) == repr(
|
|
Timestamp("20151112")
|
|
)
|
|
|
|
assert repr(
|
|
Timestamp(
|
|
year=2015,
|
|
month=11,
|
|
day=12,
|
|
hour=1,
|
|
minute=2,
|
|
second=3,
|
|
microsecond=999999,
|
|
)
|
|
) == repr(Timestamp("2015-11-12 01:02:03.999999"))
|
|
|
|
@pytest.mark.parametrize(
|
|
"arg",
|
|
[
|
|
"year",
|
|
"month",
|
|
"day",
|
|
"hour",
|
|
"minute",
|
|
"second",
|
|
"microsecond",
|
|
"nanosecond",
|
|
],
|
|
)
|
|
def test_invalid_date_kwarg_with_string_input(self, arg):
|
|
kwarg = {arg: 1}
|
|
msg = "Cannot pass a date attribute keyword argument"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp("2010-10-10 12:59:59.999999999", **kwarg)
|
|
|
|
@pytest.mark.parametrize("kwargs", [{}, {"year": 2020}, {"year": 2020, "month": 1}])
|
|
def test_constructor_missing_keyword(self, kwargs):
|
|
# GH#31200
|
|
|
|
# The exact error message of datetime() depends on its version
|
|
msg1 = r"function missing required argument '(year|month|day)' \(pos [123]\)"
|
|
msg2 = r"Required argument '(year|month|day)' \(pos [123]\) not found"
|
|
msg = "|".join([msg1, msg2])
|
|
|
|
with pytest.raises(TypeError, match=msg):
|
|
Timestamp(**kwargs)
|
|
|
|
def test_constructor_positional_with_tzinfo(self):
|
|
# GH#31929
|
|
ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc)
|
|
expected = Timestamp("2020-12-31", tzinfo=timezone.utc)
|
|
assert ts == expected
|
|
|
|
@pytest.mark.parametrize("kwd", ["nanosecond", "microsecond", "second", "minute"])
|
|
def test_constructor_positional_keyword_mixed_with_tzinfo(self, kwd, request):
|
|
# TODO: if we passed microsecond with a keyword we would mess up
|
|
# xref GH#45307
|
|
if kwd != "nanosecond":
|
|
# nanosecond is keyword-only as of 2.0, others are not
|
|
mark = pytest.mark.xfail(reason="GH#45307")
|
|
request.applymarker(mark)
|
|
|
|
kwargs = {kwd: 4}
|
|
ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc, **kwargs)
|
|
|
|
td_kwargs = {kwd + "s": 4}
|
|
td = Timedelta(**td_kwargs)
|
|
expected = Timestamp("2020-12-31", tz=timezone.utc) + td
|
|
assert ts == expected
|
|
|
|
|
|
class TestTimestampClassMethodConstructors:
|
|
# Timestamp constructors other than __new__
|
|
|
|
def test_constructor_strptime(self):
|
|
# GH#25016
|
|
# Test support for Timestamp.strptime
|
|
fmt = "%Y%m%d-%H%M%S-%f%z"
|
|
ts = "20190129-235348-000001+0000"
|
|
msg = r"Timestamp.strptime\(\) is not implemented"
|
|
with pytest.raises(NotImplementedError, match=msg):
|
|
Timestamp.strptime(ts, fmt)
|
|
|
|
def test_constructor_fromisocalendar(self):
|
|
# GH#30395
|
|
expected_timestamp = Timestamp("2000-01-03 00:00:00")
|
|
expected_stdlib = datetime.fromisocalendar(2000, 1, 1)
|
|
result = Timestamp.fromisocalendar(2000, 1, 1)
|
|
assert result == expected_timestamp
|
|
assert result == expected_stdlib
|
|
assert isinstance(result, Timestamp)
|
|
|
|
def test_constructor_fromordinal(self):
|
|
base = datetime(2000, 1, 1)
|
|
|
|
ts = Timestamp.fromordinal(base.toordinal())
|
|
assert base == ts
|
|
assert base.toordinal() == ts.toordinal()
|
|
|
|
ts = Timestamp.fromordinal(base.toordinal(), tz="US/Eastern")
|
|
assert Timestamp("2000-01-01", tz="US/Eastern") == ts
|
|
assert base.toordinal() == ts.toordinal()
|
|
|
|
# GH#3042
|
|
dt = datetime(2011, 4, 16, 0, 0)
|
|
ts = Timestamp.fromordinal(dt.toordinal())
|
|
assert ts.to_pydatetime() == dt
|
|
|
|
# with a tzinfo
|
|
stamp = Timestamp("2011-4-16", tz="US/Eastern")
|
|
dt_tz = stamp.to_pydatetime()
|
|
ts = Timestamp.fromordinal(dt_tz.toordinal(), tz="US/Eastern")
|
|
assert ts.to_pydatetime() == dt_tz
|
|
|
|
def test_now(self):
|
|
# GH#9000
|
|
ts_from_string = Timestamp("now")
|
|
ts_from_method = Timestamp.now()
|
|
ts_datetime = datetime.now()
|
|
|
|
ts_from_string_tz = Timestamp("now", tz="US/Eastern")
|
|
ts_from_method_tz = Timestamp.now(tz="US/Eastern")
|
|
|
|
# Check that the delta between the times is less than 1s (arbitrarily
|
|
# small)
|
|
delta = Timedelta(seconds=1)
|
|
assert abs(ts_from_method - ts_from_string) < delta
|
|
assert abs(ts_datetime - ts_from_method) < delta
|
|
assert abs(ts_from_method_tz - ts_from_string_tz) < delta
|
|
assert (
|
|
abs(
|
|
ts_from_string_tz.tz_localize(None)
|
|
- ts_from_method_tz.tz_localize(None)
|
|
)
|
|
< delta
|
|
)
|
|
|
|
def test_today(self):
|
|
ts_from_string = Timestamp("today")
|
|
ts_from_method = Timestamp.today()
|
|
ts_datetime = datetime.today()
|
|
|
|
ts_from_string_tz = Timestamp("today", tz="US/Eastern")
|
|
ts_from_method_tz = Timestamp.today(tz="US/Eastern")
|
|
|
|
# Check that the delta between the times is less than 1s (arbitrarily
|
|
# small)
|
|
delta = Timedelta(seconds=1)
|
|
assert abs(ts_from_method - ts_from_string) < delta
|
|
assert abs(ts_datetime - ts_from_method) < delta
|
|
assert abs(ts_from_method_tz - ts_from_string_tz) < delta
|
|
assert (
|
|
abs(
|
|
ts_from_string_tz.tz_localize(None)
|
|
- ts_from_method_tz.tz_localize(None)
|
|
)
|
|
< delta
|
|
)
|
|
|
|
|
|
class TestTimestampResolutionInference:
|
|
def test_construct_from_time_unit(self):
|
|
# GH#54097 only passing a time component, no date
|
|
ts = Timestamp("01:01:01.111")
|
|
assert ts.unit == "ms"
|
|
|
|
def test_constructor_str_infer_reso(self):
|
|
# non-iso8601 path
|
|
|
|
# _parse_delimited_date path
|
|
ts = Timestamp("01/30/2023")
|
|
assert ts.unit == "s"
|
|
|
|
# _parse_dateabbr_string path
|
|
ts = Timestamp("2015Q1")
|
|
assert ts.unit == "s"
|
|
|
|
# dateutil_parse path
|
|
ts = Timestamp("2016-01-01 1:30:01 PM")
|
|
assert ts.unit == "s"
|
|
|
|
ts = Timestamp("2016 June 3 15:25:01.345")
|
|
assert ts.unit == "ms"
|
|
|
|
ts = Timestamp("300-01-01")
|
|
assert ts.unit == "s"
|
|
|
|
ts = Timestamp("300 June 1:30:01.300")
|
|
assert ts.unit == "ms"
|
|
|
|
# dateutil path -> don't drop trailing zeros
|
|
ts = Timestamp("01-01-2013T00:00:00.000000000+0000")
|
|
assert ts.unit == "ns"
|
|
|
|
ts = Timestamp("2016/01/02 03:04:05.001000 UTC")
|
|
assert ts.unit == "us"
|
|
|
|
# higher-than-nanosecond -> we drop the trailing bits
|
|
ts = Timestamp("01-01-2013T00:00:00.000000002100+0000")
|
|
assert ts == Timestamp("01-01-2013T00:00:00.000000002+0000")
|
|
assert ts.unit == "ns"
|
|
|
|
# GH#56208 minute reso through the ISO8601 path with tz offset
|
|
ts = Timestamp("2020-01-01 00:00+00:00")
|
|
assert ts.unit == "s"
|
|
|
|
ts = Timestamp("2020-01-01 00+00:00")
|
|
assert ts.unit == "s"
|
|
|
|
@pytest.mark.parametrize("method", ["now", "today"])
|
|
def test_now_today_unit(self, method):
|
|
# GH#55879
|
|
ts_from_method = getattr(Timestamp, method)()
|
|
ts_from_string = Timestamp(method)
|
|
assert ts_from_method.unit == ts_from_string.unit == "us"
|
|
|
|
|
|
class TestTimestampConstructors:
|
|
def test_weekday_but_no_day_raises(self):
|
|
# GH#52659
|
|
msg = "Parsing datetimes with weekday but no day information is not supported"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp("2023 Sept Thu")
|
|
|
|
def test_construct_from_string_invalid_raises(self):
|
|
# dateutil (weirdly) parses "200622-12-31" as
|
|
# datetime(2022, 6, 20, 12, 0, tzinfo=tzoffset(None, -111600)
|
|
# which besides being mis-parsed, is a tzoffset that will cause
|
|
# str(ts) to raise ValueError. Ensure we raise in the constructor
|
|
# instead.
|
|
# see test_to_datetime_malformed_raise for analogous to_datetime test
|
|
with pytest.raises(ValueError, match="gives an invalid tzoffset"):
|
|
Timestamp("200622-12-31")
|
|
|
|
def test_constructor_from_iso8601_str_with_offset_reso(self):
|
|
# GH#49737
|
|
ts = Timestamp("2016-01-01 04:05:06-01:00")
|
|
assert ts.unit == "s"
|
|
|
|
ts = Timestamp("2016-01-01 04:05:06.000-01:00")
|
|
assert ts.unit == "ms"
|
|
|
|
ts = Timestamp("2016-01-01 04:05:06.000000-01:00")
|
|
assert ts.unit == "us"
|
|
|
|
ts = Timestamp("2016-01-01 04:05:06.000000001-01:00")
|
|
assert ts.unit == "ns"
|
|
|
|
def test_constructor_from_date_second_reso(self):
|
|
# GH#49034 constructing from a pydate object gets lowest supported
|
|
# reso, i.e. seconds
|
|
obj = date(2012, 9, 1)
|
|
ts = Timestamp(obj)
|
|
assert ts.unit == "s"
|
|
|
|
def test_constructor_datetime64_with_tz(self):
|
|
# GH#42288, GH#24559
|
|
dt = np.datetime64("1970-01-01 05:00:00")
|
|
tzstr = "UTC+05:00"
|
|
|
|
# pre-2.0 this interpreted dt as a UTC time. in 2.0 this is treated
|
|
# as a wall-time, consistent with DatetimeIndex behavior
|
|
ts = Timestamp(dt, tz=tzstr)
|
|
|
|
alt = Timestamp(dt).tz_localize(tzstr)
|
|
assert ts == alt
|
|
assert ts.hour == 5
|
|
|
|
def test_constructor(self):
|
|
base_str = "2014-07-01 09:00"
|
|
base_dt = datetime(2014, 7, 1, 9)
|
|
base_expected = 1_404_205_200_000_000_000
|
|
|
|
# confirm base representation is correct
|
|
assert calendar.timegm(base_dt.timetuple()) * 1_000_000_000 == base_expected
|
|
|
|
tests = [
|
|
(base_str, base_dt, base_expected),
|
|
(
|
|
"2014-07-01 10:00",
|
|
datetime(2014, 7, 1, 10),
|
|
base_expected + 3600 * 1_000_000_000,
|
|
),
|
|
(
|
|
"2014-07-01 09:00:00.000008000",
|
|
datetime(2014, 7, 1, 9, 0, 0, 8),
|
|
base_expected + 8000,
|
|
),
|
|
(
|
|
"2014-07-01 09:00:00.000000005",
|
|
Timestamp("2014-07-01 09:00:00.000000005"),
|
|
base_expected + 5,
|
|
),
|
|
]
|
|
|
|
timezones = [
|
|
(None, 0),
|
|
("UTC", 0),
|
|
(pytz.utc, 0),
|
|
("Asia/Tokyo", 9),
|
|
("US/Eastern", -4),
|
|
("dateutil/US/Pacific", -7),
|
|
(pytz.FixedOffset(-180), -3),
|
|
(dateutil.tz.tzoffset(None, 18000), 5),
|
|
]
|
|
|
|
for date_str, date_obj, expected in tests:
|
|
for result in [Timestamp(date_str), Timestamp(date_obj)]:
|
|
result = result.as_unit("ns") # test originally written before non-nano
|
|
# only with timestring
|
|
assert result.as_unit("ns")._value == expected
|
|
|
|
# re-creation shouldn't affect to internal value
|
|
result = Timestamp(result)
|
|
assert result.as_unit("ns")._value == expected
|
|
|
|
# with timezone
|
|
for tz, offset in timezones:
|
|
for result in [Timestamp(date_str, tz=tz), Timestamp(date_obj, tz=tz)]:
|
|
result = result.as_unit(
|
|
"ns"
|
|
) # test originally written before non-nano
|
|
expected_tz = expected - offset * 3600 * 1_000_000_000
|
|
assert result.as_unit("ns")._value == expected_tz
|
|
|
|
# should preserve tz
|
|
result = Timestamp(result)
|
|
assert result.as_unit("ns")._value == expected_tz
|
|
|
|
# should convert to UTC
|
|
if tz is not None:
|
|
result = Timestamp(result).tz_convert("UTC")
|
|
else:
|
|
result = Timestamp(result, tz="UTC")
|
|
expected_utc = expected - offset * 3600 * 1_000_000_000
|
|
assert result.as_unit("ns")._value == expected_utc
|
|
|
|
def test_constructor_with_stringoffset(self):
|
|
# GH 7833
|
|
base_str = "2014-07-01 11:00:00+02:00"
|
|
base_dt = datetime(2014, 7, 1, 9)
|
|
base_expected = 1_404_205_200_000_000_000
|
|
|
|
# confirm base representation is correct
|
|
assert calendar.timegm(base_dt.timetuple()) * 1_000_000_000 == base_expected
|
|
|
|
tests = [
|
|
(base_str, base_expected),
|
|
("2014-07-01 12:00:00+02:00", base_expected + 3600 * 1_000_000_000),
|
|
("2014-07-01 11:00:00.000008000+02:00", base_expected + 8000),
|
|
("2014-07-01 11:00:00.000000005+02:00", base_expected + 5),
|
|
]
|
|
|
|
timezones = [
|
|
(None, 0),
|
|
("UTC", 0),
|
|
(pytz.utc, 0),
|
|
("Asia/Tokyo", 9),
|
|
("US/Eastern", -4),
|
|
("dateutil/US/Pacific", -7),
|
|
(pytz.FixedOffset(-180), -3),
|
|
(dateutil.tz.tzoffset(None, 18000), 5),
|
|
]
|
|
|
|
for date_str, expected in tests:
|
|
for result in [Timestamp(date_str)]:
|
|
# only with timestring
|
|
assert result.as_unit("ns")._value == expected
|
|
|
|
# re-creation shouldn't affect to internal value
|
|
result = Timestamp(result)
|
|
assert result.as_unit("ns")._value == expected
|
|
|
|
# with timezone
|
|
for tz, offset in timezones:
|
|
result = Timestamp(date_str, tz=tz)
|
|
expected_tz = expected
|
|
assert result.as_unit("ns")._value == expected_tz
|
|
|
|
# should preserve tz
|
|
result = Timestamp(result)
|
|
assert result.as_unit("ns")._value == expected_tz
|
|
|
|
# should convert to UTC
|
|
result = Timestamp(result).tz_convert("UTC")
|
|
expected_utc = expected
|
|
assert result.as_unit("ns")._value == expected_utc
|
|
|
|
# This should be 2013-11-01 05:00 in UTC
|
|
# converted to Chicago tz
|
|
result = Timestamp("2013-11-01 00:00:00-0500", tz="America/Chicago")
|
|
assert result._value == Timestamp("2013-11-01 05:00")._value
|
|
expected = "Timestamp('2013-11-01 00:00:00-0500', tz='America/Chicago')"
|
|
assert repr(result) == expected
|
|
assert result == eval(repr(result))
|
|
|
|
# This should be 2013-11-01 05:00 in UTC
|
|
# converted to Tokyo tz (+09:00)
|
|
result = Timestamp("2013-11-01 00:00:00-0500", tz="Asia/Tokyo")
|
|
assert result._value == Timestamp("2013-11-01 05:00")._value
|
|
expected = "Timestamp('2013-11-01 14:00:00+0900', tz='Asia/Tokyo')"
|
|
assert repr(result) == expected
|
|
assert result == eval(repr(result))
|
|
|
|
# GH11708
|
|
# This should be 2015-11-18 10:00 in UTC
|
|
# converted to Asia/Katmandu
|
|
result = Timestamp("2015-11-18 15:45:00+05:45", tz="Asia/Katmandu")
|
|
assert result._value == Timestamp("2015-11-18 10:00")._value
|
|
expected = "Timestamp('2015-11-18 15:45:00+0545', tz='Asia/Katmandu')"
|
|
assert repr(result) == expected
|
|
assert result == eval(repr(result))
|
|
|
|
# This should be 2015-11-18 10:00 in UTC
|
|
# converted to Asia/Kolkata
|
|
result = Timestamp("2015-11-18 15:30:00+05:30", tz="Asia/Kolkata")
|
|
assert result._value == Timestamp("2015-11-18 10:00")._value
|
|
expected = "Timestamp('2015-11-18 15:30:00+0530', tz='Asia/Kolkata')"
|
|
assert repr(result) == expected
|
|
assert result == eval(repr(result))
|
|
|
|
def test_constructor_invalid(self):
|
|
msg = "Cannot convert input"
|
|
with pytest.raises(TypeError, match=msg):
|
|
Timestamp(slice(2))
|
|
msg = "Cannot convert Period"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(Period("1000-01-01"))
|
|
|
|
def test_constructor_invalid_tz(self):
|
|
# GH#17690
|
|
msg = (
|
|
"Argument 'tzinfo' has incorrect type "
|
|
r"\(expected datetime.tzinfo, got str\)"
|
|
)
|
|
with pytest.raises(TypeError, match=msg):
|
|
Timestamp("2017-10-22", tzinfo="US/Eastern")
|
|
|
|
msg = "at most one of"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp("2017-10-22", tzinfo=pytz.utc, tz="UTC")
|
|
|
|
msg = "Cannot pass a date attribute keyword argument when passing a date string"
|
|
with pytest.raises(ValueError, match=msg):
|
|
# GH#5168
|
|
# case where user tries to pass tz as an arg, not kwarg, gets
|
|
# interpreted as `year`
|
|
Timestamp("2012-01-01", "US/Pacific")
|
|
|
|
def test_constructor_tz_or_tzinfo(self):
|
|
# GH#17943, GH#17690, GH#5168
|
|
stamps = [
|
|
Timestamp(year=2017, month=10, day=22, tz="UTC"),
|
|
Timestamp(year=2017, month=10, day=22, tzinfo=pytz.utc),
|
|
Timestamp(year=2017, month=10, day=22, tz=pytz.utc),
|
|
Timestamp(datetime(2017, 10, 22), tzinfo=pytz.utc),
|
|
Timestamp(datetime(2017, 10, 22), tz="UTC"),
|
|
Timestamp(datetime(2017, 10, 22), tz=pytz.utc),
|
|
]
|
|
assert all(ts == stamps[0] for ts in stamps)
|
|
|
|
@pytest.mark.parametrize(
|
|
"result",
|
|
[
|
|
Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), nanosecond=1),
|
|
Timestamp(
|
|
year=2000,
|
|
month=1,
|
|
day=2,
|
|
hour=3,
|
|
minute=4,
|
|
second=5,
|
|
microsecond=6,
|
|
nanosecond=1,
|
|
),
|
|
Timestamp(
|
|
year=2000,
|
|
month=1,
|
|
day=2,
|
|
hour=3,
|
|
minute=4,
|
|
second=5,
|
|
microsecond=6,
|
|
nanosecond=1,
|
|
tz="UTC",
|
|
),
|
|
Timestamp(2000, 1, 2, 3, 4, 5, 6, None, nanosecond=1),
|
|
Timestamp(2000, 1, 2, 3, 4, 5, 6, tz=pytz.UTC, nanosecond=1),
|
|
],
|
|
)
|
|
def test_constructor_nanosecond(self, result):
|
|
# GH 18898
|
|
# As of 2.0 (GH 49416), nanosecond should not be accepted positionally
|
|
expected = Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), tz=result.tz)
|
|
expected = expected + Timedelta(nanoseconds=1)
|
|
assert result == expected
|
|
|
|
@pytest.mark.parametrize("z", ["Z0", "Z00"])
|
|
def test_constructor_invalid_Z0_isostring(self, z):
|
|
# GH 8910
|
|
msg = f"Unknown datetime string format, unable to parse: 2014-11-02 01:00{z}"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(f"2014-11-02 01:00{z}")
|
|
|
|
def test_out_of_bounds_integer_value(self):
|
|
# GH#26651 check that we raise OutOfBoundsDatetime, not OverflowError
|
|
msg = str(Timestamp.max._value * 2)
|
|
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
|
Timestamp(Timestamp.max._value * 2)
|
|
msg = str(Timestamp.min._value * 2)
|
|
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
|
Timestamp(Timestamp.min._value * 2)
|
|
|
|
def test_out_of_bounds_value(self):
|
|
one_us = np.timedelta64(1).astype("timedelta64[us]")
|
|
|
|
# By definition we can't go out of bounds in [ns], so we
|
|
# convert the datetime64s to [us] so we can go out of bounds
|
|
min_ts_us = np.datetime64(Timestamp.min).astype("M8[us]") + one_us
|
|
max_ts_us = np.datetime64(Timestamp.max).astype("M8[us]")
|
|
|
|
# No error for the min/max datetimes
|
|
Timestamp(min_ts_us)
|
|
Timestamp(max_ts_us)
|
|
|
|
# We used to raise on these before supporting non-nano
|
|
us_val = NpyDatetimeUnit.NPY_FR_us.value
|
|
assert Timestamp(min_ts_us - one_us)._creso == us_val
|
|
assert Timestamp(max_ts_us + one_us)._creso == us_val
|
|
|
|
# https://github.com/numpy/numpy/issues/22346 for why
|
|
# we can't use the same construction as above with minute resolution
|
|
|
|
# too_low, too_high are the _just_ outside the range of M8[s]
|
|
too_low = np.datetime64("-292277022657-01-27T08:29", "m")
|
|
too_high = np.datetime64("292277026596-12-04T15:31", "m")
|
|
|
|
msg = "Out of bounds"
|
|
# One us less than the minimum is an error
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(too_low)
|
|
|
|
# One us more than the maximum is an error
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(too_high)
|
|
|
|
def test_out_of_bounds_string(self):
|
|
msg = "Cannot cast .* to unit='ns' without overflow"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp("1676-01-01").as_unit("ns")
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp("2263-01-01").as_unit("ns")
|
|
|
|
ts = Timestamp("2263-01-01")
|
|
assert ts.unit == "s"
|
|
|
|
ts = Timestamp("1676-01-01")
|
|
assert ts.unit == "s"
|
|
|
|
def test_barely_out_of_bounds(self):
|
|
# GH#19529
|
|
# GH#19382 close enough to bounds that dropping nanos would result
|
|
# in an in-bounds datetime
|
|
msg = "Out of bounds nanosecond timestamp: 2262-04-11 23:47:16"
|
|
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
|
Timestamp("2262-04-11 23:47:16.854775808")
|
|
|
|
@pytest.mark.skip_ubsan
|
|
def test_bounds_with_different_units(self):
|
|
out_of_bounds_dates = ("1677-09-21", "2262-04-12")
|
|
|
|
time_units = ("D", "h", "m", "s", "ms", "us")
|
|
|
|
for date_string in out_of_bounds_dates:
|
|
for unit in time_units:
|
|
dt64 = np.datetime64(date_string, unit)
|
|
ts = Timestamp(dt64)
|
|
if unit in ["s", "ms", "us"]:
|
|
# We can preserve the input unit
|
|
assert ts._value == dt64.view("i8")
|
|
else:
|
|
# we chose the closest unit that we _do_ support
|
|
assert ts._creso == NpyDatetimeUnit.NPY_FR_s.value
|
|
|
|
# With more extreme cases, we can't even fit inside second resolution
|
|
info = np.iinfo(np.int64)
|
|
msg = "Out of bounds second timestamp:"
|
|
for value in [info.min + 1, info.max]:
|
|
for unit in ["D", "h", "m"]:
|
|
dt64 = np.datetime64(value, unit)
|
|
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
|
Timestamp(dt64)
|
|
|
|
in_bounds_dates = ("1677-09-23", "2262-04-11")
|
|
|
|
for date_string in in_bounds_dates:
|
|
for unit in time_units:
|
|
dt64 = np.datetime64(date_string, unit)
|
|
Timestamp(dt64)
|
|
|
|
@pytest.mark.parametrize("arg", ["001-01-01", "0001-01-01"])
|
|
def test_out_of_bounds_string_consistency(self, arg):
|
|
# GH 15829
|
|
msg = "Cannot cast 0001-01-01 00:00:00 to unit='ns' without overflow"
|
|
with pytest.raises(OutOfBoundsDatetime, match=msg):
|
|
Timestamp(arg).as_unit("ns")
|
|
|
|
ts = Timestamp(arg)
|
|
assert ts.unit == "s"
|
|
assert ts.year == ts.month == ts.day == 1
|
|
|
|
def test_min_valid(self):
|
|
# Ensure that Timestamp.min is a valid Timestamp
|
|
Timestamp(Timestamp.min)
|
|
|
|
def test_max_valid(self):
|
|
# Ensure that Timestamp.max is a valid Timestamp
|
|
Timestamp(Timestamp.max)
|
|
|
|
@pytest.mark.parametrize("offset", ["+0300", "+0200"])
|
|
def test_construct_timestamp_near_dst(self, offset):
|
|
# GH 20854
|
|
expected = Timestamp(f"2016-10-30 03:00:00{offset}", tz="Europe/Helsinki")
|
|
result = Timestamp(expected).tz_convert("Europe/Helsinki")
|
|
assert result == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"arg", ["2013/01/01 00:00:00+09:00", "2013-01-01 00:00:00+09:00"]
|
|
)
|
|
def test_construct_with_different_string_format(self, arg):
|
|
# GH 12064
|
|
result = Timestamp(arg)
|
|
expected = Timestamp(datetime(2013, 1, 1), tz=pytz.FixedOffset(540))
|
|
assert result == expected
|
|
|
|
@pytest.mark.parametrize("box", [datetime, Timestamp])
|
|
def test_raise_tz_and_tzinfo_in_datetime_input(self, box):
|
|
# GH 23579
|
|
kwargs = {"year": 2018, "month": 1, "day": 1, "tzinfo": pytz.utc}
|
|
msg = "Cannot pass a datetime or Timestamp"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(box(**kwargs), tz="US/Pacific")
|
|
msg = "Cannot pass a datetime or Timestamp"
|
|
with pytest.raises(ValueError, match=msg):
|
|
Timestamp(box(**kwargs), tzinfo=pytz.timezone("US/Pacific"))
|
|
|
|
def test_dont_convert_dateutil_utc_to_pytz_utc(self):
|
|
result = Timestamp(datetime(2018, 1, 1), tz=tzutc())
|
|
expected = Timestamp(datetime(2018, 1, 1)).tz_localize(tzutc())
|
|
assert result == expected
|
|
|
|
def test_constructor_subclassed_datetime(self):
|
|
# GH 25851
|
|
# ensure that subclassed datetime works for
|
|
# Timestamp creation
|
|
class SubDatetime(datetime):
|
|
pass
|
|
|
|
data = SubDatetime(2000, 1, 1)
|
|
result = Timestamp(data)
|
|
expected = Timestamp(2000, 1, 1)
|
|
assert result == expected
|
|
|
|
def test_timestamp_constructor_tz_utc(self):
|
|
utc_stamp = Timestamp("3/11/2012 05:00", tz="utc")
|
|
assert utc_stamp.tzinfo is timezone.utc
|
|
assert utc_stamp.hour == 5
|
|
|
|
utc_stamp = Timestamp("3/11/2012 05:00").tz_localize("utc")
|
|
assert utc_stamp.hour == 5
|
|
|
|
def test_timestamp_to_datetime_tzoffset(self):
|
|
tzinfo = tzoffset(None, 7200)
|
|
expected = Timestamp("3/11/2012 04:00", tz=tzinfo)
|
|
result = Timestamp(expected.to_pydatetime())
|
|
assert expected == result
|
|
|
|
def test_timestamp_constructor_near_dst_boundary(self):
|
|
# GH#11481 & GH#15777
|
|
# Naive string timestamps were being localized incorrectly
|
|
# with tz_convert_from_utc_single instead of tz_localize_to_utc
|
|
|
|
for tz in ["Europe/Brussels", "Europe/Prague"]:
|
|
result = Timestamp("2015-10-25 01:00", tz=tz)
|
|
expected = Timestamp("2015-10-25 01:00").tz_localize(tz)
|
|
assert result == expected
|
|
|
|
msg = "Cannot infer dst time from 2015-10-25 02:00:00"
|
|
with pytest.raises(pytz.AmbiguousTimeError, match=msg):
|
|
Timestamp("2015-10-25 02:00", tz=tz)
|
|
|
|
result = Timestamp("2017-03-26 01:00", tz="Europe/Paris")
|
|
expected = Timestamp("2017-03-26 01:00").tz_localize("Europe/Paris")
|
|
assert result == expected
|
|
|
|
msg = "2017-03-26 02:00"
|
|
with pytest.raises(pytz.NonExistentTimeError, match=msg):
|
|
Timestamp("2017-03-26 02:00", tz="Europe/Paris")
|
|
|
|
# GH#11708
|
|
naive = Timestamp("2015-11-18 10:00:00")
|
|
result = naive.tz_localize("UTC").tz_convert("Asia/Kolkata")
|
|
expected = Timestamp("2015-11-18 15:30:00+0530", tz="Asia/Kolkata")
|
|
assert result == expected
|
|
|
|
# GH#15823
|
|
result = Timestamp("2017-03-26 00:00", tz="Europe/Paris")
|
|
expected = Timestamp("2017-03-26 00:00:00+0100", tz="Europe/Paris")
|
|
assert result == expected
|
|
|
|
result = Timestamp("2017-03-26 01:00", tz="Europe/Paris")
|
|
expected = Timestamp("2017-03-26 01:00:00+0100", tz="Europe/Paris")
|
|
assert result == expected
|
|
|
|
msg = "2017-03-26 02:00"
|
|
with pytest.raises(pytz.NonExistentTimeError, match=msg):
|
|
Timestamp("2017-03-26 02:00", tz="Europe/Paris")
|
|
|
|
result = Timestamp("2017-03-26 02:00:00+0100", tz="Europe/Paris")
|
|
naive = Timestamp(result.as_unit("ns")._value)
|
|
expected = naive.tz_localize("UTC").tz_convert("Europe/Paris")
|
|
assert result == expected
|
|
|
|
result = Timestamp("2017-03-26 03:00", tz="Europe/Paris")
|
|
expected = Timestamp("2017-03-26 03:00:00+0200", tz="Europe/Paris")
|
|
assert result == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"tz",
|
|
[
|
|
pytz.timezone("US/Eastern"),
|
|
gettz("US/Eastern"),
|
|
"US/Eastern",
|
|
"dateutil/US/Eastern",
|
|
],
|
|
)
|
|
def test_timestamp_constructed_by_date_and_tz(self, tz):
|
|
# GH#2993, Timestamp cannot be constructed by datetime.date
|
|
# and tz correctly
|
|
|
|
result = Timestamp(date(2012, 3, 11), tz=tz)
|
|
|
|
expected = Timestamp("3/11/2012", tz=tz)
|
|
assert result.hour == expected.hour
|
|
assert result == expected
|
|
|
|
|
|
def test_constructor_ambiguous_dst():
|
|
# GH 24329
|
|
# Make sure that calling Timestamp constructor
|
|
# on Timestamp created from ambiguous time
|
|
# doesn't change Timestamp.value
|
|
ts = Timestamp(1382835600000000000, tz="dateutil/Europe/London")
|
|
expected = ts._value
|
|
result = Timestamp(ts)._value
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize("epoch", [1552211999999999872, 1552211999999999999])
|
|
def test_constructor_before_dst_switch(epoch):
|
|
# GH 31043
|
|
# Make sure that calling Timestamp constructor
|
|
# on time just before DST switch doesn't lead to
|
|
# nonexistent time or value change
|
|
ts = Timestamp(epoch, tz="dateutil/America/Los_Angeles")
|
|
result = ts.tz.dst(ts)
|
|
expected = timedelta(seconds=0)
|
|
assert Timestamp(ts)._value == epoch
|
|
assert result == expected
|
|
|
|
|
|
def test_timestamp_constructor_identity():
|
|
# Test for #30543
|
|
expected = Timestamp("2017-01-01T12")
|
|
result = Timestamp(expected)
|
|
assert result is expected
|
|
|
|
|
|
@pytest.mark.parametrize("nano", [-1, 1000])
|
|
def test_timestamp_nano_range(nano):
|
|
# GH 48255
|
|
with pytest.raises(ValueError, match="nanosecond must be in 0..999"):
|
|
Timestamp(year=2022, month=1, day=1, nanosecond=nano)
|
|
|
|
|
|
def test_non_nano_value():
|
|
# https://github.com/pandas-dev/pandas/issues/49076
|
|
result = Timestamp("1800-01-01", unit="s").value
|
|
# `.value` shows nanoseconds, even though unit is 's'
|
|
assert result == -5364662400000000000
|
|
|
|
# out-of-nanoseconds-bounds `.value` raises informative message
|
|
msg = (
|
|
r"Cannot convert Timestamp to nanoseconds without overflow. "
|
|
r"Use `.asm8.view\('i8'\)` to cast represent Timestamp in its "
|
|
r"own unit \(here, s\).$"
|
|
)
|
|
ts = Timestamp("0300-01-01")
|
|
with pytest.raises(OverflowError, match=msg):
|
|
ts.value
|
|
# check that the suggested workaround actually works
|
|
result = ts.asm8.view("i8")
|
|
assert result == -52700112000
|
|
|
|
|
|
@pytest.mark.parametrize("na_value", [None, np.nan, np.datetime64("NaT"), NaT, NA])
|
|
def test_timestamp_constructor_na_value(na_value):
|
|
# GH45481
|
|
result = Timestamp(na_value)
|
|
expected = NaT
|
|
assert result is expected
|