From 9521ad2d2695f80e9897c04723ed2f3b10c15596 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 15:47:26 -0700 Subject: [PATCH 01/12] TST: move test files to methods/ directory --- pandas/tests/indexes/datetimes/{ => methods}/test_delete.py | 0 pandas/tests/indexes/datetimes/{ => methods}/test_map.py | 0 pandas/tests/indexes/datetimes/{ => methods}/test_unique.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename pandas/tests/indexes/datetimes/{ => methods}/test_delete.py (100%) rename pandas/tests/indexes/datetimes/{ => methods}/test_map.py (100%) rename pandas/tests/indexes/datetimes/{ => methods}/test_unique.py (100%) diff --git a/pandas/tests/indexes/datetimes/test_delete.py b/pandas/tests/indexes/datetimes/methods/test_delete.py similarity index 100% rename from pandas/tests/indexes/datetimes/test_delete.py rename to pandas/tests/indexes/datetimes/methods/test_delete.py diff --git a/pandas/tests/indexes/datetimes/test_map.py b/pandas/tests/indexes/datetimes/methods/test_map.py similarity index 100% rename from pandas/tests/indexes/datetimes/test_map.py rename to pandas/tests/indexes/datetimes/methods/test_map.py diff --git a/pandas/tests/indexes/datetimes/test_unique.py b/pandas/tests/indexes/datetimes/methods/test_unique.py similarity index 100% rename from pandas/tests/indexes/datetimes/test_unique.py rename to pandas/tests/indexes/datetimes/methods/test_unique.py From d62a483226453889120b7a24106bb7369adec9c3 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 15:53:20 -0700 Subject: [PATCH 02/12] REF: tz_localize tests --- .../datetimes/methods/test_tz_localize.py | 388 ++++++++++++++++++ .../tests/indexes/datetimes/test_timezones.py | 374 ----------------- 2 files changed, 388 insertions(+), 374 deletions(-) create mode 100644 pandas/tests/indexes/datetimes/methods/test_tz_localize.py diff --git a/pandas/tests/indexes/datetimes/methods/test_tz_localize.py b/pandas/tests/indexes/datetimes/methods/test_tz_localize.py new file mode 100644 index 0000000000000..ca71dde3e6c9e --- /dev/null +++ b/pandas/tests/indexes/datetimes/methods/test_tz_localize.py @@ -0,0 +1,388 @@ +from datetime import ( + datetime, + timedelta, +) + +import dateutil.tz +from dateutil.tz import gettz +import numpy as np +import pytest +import pytz + +from pandas import ( + DatetimeIndex, + Timestamp, + bdate_range, + date_range, + offsets, + to_datetime, +) +import pandas._testing as tm + +try: + from zoneinfo import ZoneInfo +except ImportError: + # Cannot assign to a type [misc] + ZoneInfo = None # type: ignore[misc, assignment] + + +class TestTZLocalize: + def test_tz_localize_invalidates_freq(self): + # we only preserve freq in unambiguous cases + + # if localized to US/Eastern, this crosses a DST transition + dti = date_range("2014-03-08 23:00", "2014-03-09 09:00", freq="h") + assert dti.freq == "h" + + result = dti.tz_localize(None) # no-op + assert result.freq == "h" + + result = dti.tz_localize("UTC") # unambiguous freq preservation + assert result.freq == "h" + + result = dti.tz_localize("US/Eastern", nonexistent="shift_forward") + assert result.freq is None + assert result.inferred_freq is None # i.e. we are not _too_ strict here + + # Case where we _can_ keep freq because we're length==1 + dti2 = dti[:1] + result = dti2.tz_localize("US/Eastern") + assert result.freq == "h" + + def test_tz_localize_utc_copies(self, utc_fixture): + # GH#46460 + times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"] + index = DatetimeIndex(times) + + res = index.tz_localize(utc_fixture) + assert not tm.shares_memory(res, index) + + res2 = index._data.tz_localize(utc_fixture) + assert not tm.shares_memory(index._data, res2) + + def test_dti_tz_localize_nonexistent_raise_coerce(self): + # GH#13057 + times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"] + index = DatetimeIndex(times) + tz = "US/Eastern" + with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)): + index.tz_localize(tz=tz) + + with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)): + index.tz_localize(tz=tz, nonexistent="raise") + + result = index.tz_localize(tz=tz, nonexistent="NaT") + test_times = ["2015-03-08 01:00-05:00", "NaT", "2015-03-08 03:00-04:00"] + dti = to_datetime(test_times, utc=True) + expected = dti.tz_convert("US/Eastern") + tm.assert_index_equal(result, expected) + + easts = [pytz.timezone("US/Eastern"), gettz("US/Eastern")] + if ZoneInfo is not None: + try: + tz = ZoneInfo("US/Eastern") + except KeyError: + # no tzdata + pass + else: + easts.append(tz) + + @pytest.mark.parametrize("tz", easts) + def test_dti_tz_localize_ambiguous_infer(self, tz): + # November 6, 2011, fall back, repeat 2 AM hour + # With no repeated hours, we cannot infer the transition + dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour()) + with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"): + dr.tz_localize(tz) + + # With repeated hours, we can infer the transition + dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour(), tz=tz) + times = [ + "11/06/2011 00:00", + "11/06/2011 01:00", + "11/06/2011 01:00", + "11/06/2011 02:00", + "11/06/2011 03:00", + ] + di = DatetimeIndex(times) + localized = di.tz_localize(tz, ambiguous="infer") + expected = dr._with_freq(None) + tm.assert_index_equal(expected, localized) + tm.assert_index_equal(expected, DatetimeIndex(times, tz=tz, ambiguous="infer")) + + # When there is no dst transition, nothing special happens + dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=offsets.Hour()) + localized = dr.tz_localize(tz) + localized_infer = dr.tz_localize(tz, ambiguous="infer") + tm.assert_index_equal(localized, localized_infer) + + @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) + def test_dti_tz_localize_ambiguous_times(self, tz): + # March 13, 2011, spring forward, skip from 2 AM to 3 AM + dr = date_range(datetime(2011, 3, 13, 1, 30), periods=3, freq=offsets.Hour()) + with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:30:00"): + dr.tz_localize(tz) + + # after dst transition, it works + dr = date_range( + datetime(2011, 3, 13, 3, 30), periods=3, freq=offsets.Hour(), tz=tz + ) + + # November 6, 2011, fall back, repeat 2 AM hour + dr = date_range(datetime(2011, 11, 6, 1, 30), periods=3, freq=offsets.Hour()) + with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"): + dr.tz_localize(tz) + + # UTC is OK + dr = date_range( + datetime(2011, 3, 13), periods=48, freq=offsets.Minute(30), tz=pytz.utc + ) + + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_dti_tz_localize_pass_dates_to_utc(self, tzstr): + strdates = ["1/1/2012", "3/1/2012", "4/1/2012"] + + idx = DatetimeIndex(strdates) + conv = idx.tz_localize(tzstr) + + fromdates = DatetimeIndex(strdates, tz=tzstr) + + assert conv.tz == fromdates.tz + tm.assert_numpy_array_equal(conv.values, fromdates.values) + + @pytest.mark.parametrize("prefix", ["", "dateutil/"]) + def test_dti_tz_localize(self, prefix): + tzstr = prefix + "US/Eastern" + dti = date_range(start="1/1/2005", end="1/1/2005 0:00:30.256", freq="ms") + dti2 = dti.tz_localize(tzstr) + + dti_utc = date_range( + start="1/1/2005 05:00", end="1/1/2005 5:00:30.256", freq="ms", tz="utc" + ) + + tm.assert_numpy_array_equal(dti2.values, dti_utc.values) + + dti3 = dti2.tz_convert(prefix + "US/Pacific") + tm.assert_numpy_array_equal(dti3.values, dti_utc.values) + + dti = date_range(start="11/6/2011 1:59", end="11/6/2011 2:00", freq="ms") + with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"): + dti.tz_localize(tzstr) + + dti = date_range(start="3/13/2011 1:59", end="3/13/2011 2:00", freq="ms") + with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:00:00"): + dti.tz_localize(tzstr) + + @pytest.mark.parametrize( + "tz", + [ + "US/Eastern", + "dateutil/US/Eastern", + pytz.timezone("US/Eastern"), + gettz("US/Eastern"), + ], + ) + def test_dti_tz_localize_utc_conversion(self, tz): + # Localizing to time zone should: + # 1) check for DST ambiguities + # 2) convert to UTC + + rng = date_range("3/10/2012", "3/11/2012", freq="30min") + + converted = rng.tz_localize(tz) + expected_naive = rng + offsets.Hour(5) + tm.assert_numpy_array_equal(converted.asi8, expected_naive.asi8) + + # DST ambiguity, this should fail + rng = date_range("3/11/2012", "3/12/2012", freq="30min") + # Is this really how it should fail?? + with pytest.raises(pytz.NonExistentTimeError, match="2012-03-11 02:00:00"): + rng.tz_localize(tz) + + def test_dti_tz_localize_roundtrip(self, tz_aware_fixture): + # note: this tz tests that a tz-naive index can be localized + # and de-localized successfully, when there are no DST transitions + # in the range. + idx = date_range(start="2014-06-01", end="2014-08-30", freq="15min") + tz = tz_aware_fixture + localized = idx.tz_localize(tz) + # can't localize a tz-aware object + with pytest.raises( + TypeError, match="Already tz-aware, use tz_convert to convert" + ): + localized.tz_localize(tz) + reset = localized.tz_localize(None) + assert reset.tzinfo is None + expected = idx._with_freq(None) + tm.assert_index_equal(reset, expected) + + def test_dti_tz_localize_naive(self): + rng = date_range("1/1/2011", periods=100, freq="h") + + conv = rng.tz_localize("US/Pacific") + exp = date_range("1/1/2011", periods=100, freq="h", tz="US/Pacific") + + tm.assert_index_equal(conv, exp._with_freq(None)) + + def test_dti_tz_localize_tzlocal(self): + # GH#13583 + offset = dateutil.tz.tzlocal().utcoffset(datetime(2011, 1, 1)) + offset = int(offset.total_seconds() * 1000000000) + + dti = date_range(start="2001-01-01", end="2001-03-01") + dti2 = dti.tz_localize(dateutil.tz.tzlocal()) + tm.assert_numpy_array_equal(dti2.asi8 + offset, dti.asi8) + + dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal()) + dti2 = dti.tz_localize(None) + tm.assert_numpy_array_equal(dti2.asi8 - offset, dti.asi8) + + @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) + def test_dti_tz_localize_ambiguous_nat(self, tz): + times = [ + "11/06/2011 00:00", + "11/06/2011 01:00", + "11/06/2011 01:00", + "11/06/2011 02:00", + "11/06/2011 03:00", + ] + di = DatetimeIndex(times) + localized = di.tz_localize(tz, ambiguous="NaT") + + times = [ + "11/06/2011 00:00", + np.nan, + np.nan, + "11/06/2011 02:00", + "11/06/2011 03:00", + ] + di_test = DatetimeIndex(times, tz="US/Eastern") + + # left dtype is datetime64[ns, US/Eastern] + # right is datetime64[ns, tzfile('/usr/share/zoneinfo/US/Eastern')] + tm.assert_numpy_array_equal(di_test.values, localized.values) + + @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) + def test_dti_tz_localize_ambiguous_flags(self, tz): + # November 6, 2011, fall back, repeat 2 AM hour + + # Pass in flags to determine right dst transition + dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour(), tz=tz) + times = [ + "11/06/2011 00:00", + "11/06/2011 01:00", + "11/06/2011 01:00", + "11/06/2011 02:00", + "11/06/2011 03:00", + ] + + # Test tz_localize + di = DatetimeIndex(times) + is_dst = [1, 1, 0, 0, 0] + localized = di.tz_localize(tz, ambiguous=is_dst) + expected = dr._with_freq(None) + tm.assert_index_equal(expected, localized) + tm.assert_index_equal(expected, DatetimeIndex(times, tz=tz, ambiguous=is_dst)) + + localized = di.tz_localize(tz, ambiguous=np.array(is_dst)) + tm.assert_index_equal(dr, localized) + + localized = di.tz_localize(tz, ambiguous=np.array(is_dst).astype("bool")) + tm.assert_index_equal(dr, localized) + + # Test constructor + localized = DatetimeIndex(times, tz=tz, ambiguous=is_dst) + tm.assert_index_equal(dr, localized) + + # Test duplicate times where inferring the dst fails + times += times + di = DatetimeIndex(times) + + # When the sizes are incompatible, make sure error is raised + msg = "Length of ambiguous bool-array must be the same size as vals" + with pytest.raises(Exception, match=msg): + di.tz_localize(tz, ambiguous=is_dst) + + # When sizes are compatible and there are repeats ('infer' won't work) + is_dst = np.hstack((is_dst, is_dst)) + localized = di.tz_localize(tz, ambiguous=is_dst) + dr = dr.append(dr) + tm.assert_index_equal(dr, localized) + + # When there is no dst transition, nothing special happens + dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=offsets.Hour()) + is_dst = np.array([1] * 10) + localized = dr.tz_localize(tz) + localized_is_dst = dr.tz_localize(tz, ambiguous=is_dst) + tm.assert_index_equal(localized, localized_is_dst) + + def test_dti_tz_localize_bdate_range(self): + dr = bdate_range("1/1/2009", "1/1/2010") + dr_utc = bdate_range("1/1/2009", "1/1/2010", tz=pytz.utc) + localized = dr.tz_localize(pytz.utc) + tm.assert_index_equal(dr_utc, localized) + + @pytest.mark.parametrize( + "start_ts, tz, end_ts, shift", + [ + ["2015-03-29 02:20:00", "Europe/Warsaw", "2015-03-29 03:00:00", "forward"], + [ + "2015-03-29 02:20:00", + "Europe/Warsaw", + "2015-03-29 01:59:59.999999999", + "backward", + ], + [ + "2015-03-29 02:20:00", + "Europe/Warsaw", + "2015-03-29 03:20:00", + timedelta(hours=1), + ], + [ + "2015-03-29 02:20:00", + "Europe/Warsaw", + "2015-03-29 01:20:00", + timedelta(hours=-1), + ], + ["2018-03-11 02:33:00", "US/Pacific", "2018-03-11 03:00:00", "forward"], + [ + "2018-03-11 02:33:00", + "US/Pacific", + "2018-03-11 01:59:59.999999999", + "backward", + ], + [ + "2018-03-11 02:33:00", + "US/Pacific", + "2018-03-11 03:33:00", + timedelta(hours=1), + ], + [ + "2018-03-11 02:33:00", + "US/Pacific", + "2018-03-11 01:33:00", + timedelta(hours=-1), + ], + ], + ) + @pytest.mark.parametrize("tz_type", ["", "dateutil/"]) + def test_dti_tz_localize_nonexistent_shift( + self, start_ts, tz, end_ts, shift, tz_type + ): + # GH#8917 + tz = tz_type + tz + if isinstance(shift, str): + shift = "shift_" + shift + dti = DatetimeIndex([Timestamp(start_ts)]) + result = dti.tz_localize(tz, nonexistent=shift) + expected = DatetimeIndex([Timestamp(end_ts)]).tz_localize(tz) + tm.assert_index_equal(result, expected) + + @pytest.mark.parametrize("offset", [-1, 1]) + def test_dti_tz_localize_nonexistent_shift_invalid(self, offset, warsaw): + # GH#8917 + tz = warsaw + dti = DatetimeIndex([Timestamp("2015-03-29 02:20:00")]) + msg = "The provided timedelta will relocalize on a nonexistent time" + with pytest.raises(ValueError, match=msg): + dti.tz_localize(tz, nonexistent=timedelta(seconds=offset)) diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 8590a2542a31d..4b5adf5fb3e6b 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -19,12 +19,6 @@ import pytest import pytz -try: - from zoneinfo import ZoneInfo -except ImportError: - # Cannot assign to a type [misc] - ZoneInfo = None # type: ignore[misc, assignment] - from pandas._libs.tslibs import ( conversion, timezones, @@ -331,351 +325,6 @@ def test_tz_convert_unsorted(self, tzstr): exp = dr.hour[::-1] tm.assert_almost_equal(result, exp) - # ------------------------------------------------------------- - # DatetimeIndex.tz_localize - - def test_tz_localize_utc_copies(self, utc_fixture): - # GH#46460 - times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"] - index = DatetimeIndex(times) - - res = index.tz_localize(utc_fixture) - assert not tm.shares_memory(res, index) - - res2 = index._data.tz_localize(utc_fixture) - assert not tm.shares_memory(index._data, res2) - - def test_dti_tz_localize_nonexistent_raise_coerce(self): - # GH#13057 - times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"] - index = DatetimeIndex(times) - tz = "US/Eastern" - with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)): - index.tz_localize(tz=tz) - - with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)): - index.tz_localize(tz=tz, nonexistent="raise") - - result = index.tz_localize(tz=tz, nonexistent="NaT") - test_times = ["2015-03-08 01:00-05:00", "NaT", "2015-03-08 03:00-04:00"] - dti = to_datetime(test_times, utc=True) - expected = dti.tz_convert("US/Eastern") - tm.assert_index_equal(result, expected) - - easts = [pytz.timezone("US/Eastern"), gettz("US/Eastern")] - if ZoneInfo is not None: - try: - tz = ZoneInfo("US/Eastern") - except KeyError: - # no tzdata - pass - else: - easts.append(tz) - - @pytest.mark.parametrize("tz", easts) - def test_dti_tz_localize_ambiguous_infer(self, tz): - # November 6, 2011, fall back, repeat 2 AM hour - # With no repeated hours, we cannot infer the transition - dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=pd.offsets.Hour()) - with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"): - dr.tz_localize(tz) - - # With repeated hours, we can infer the transition - dr = date_range( - datetime(2011, 11, 6, 0), periods=5, freq=pd.offsets.Hour(), tz=tz - ) - times = [ - "11/06/2011 00:00", - "11/06/2011 01:00", - "11/06/2011 01:00", - "11/06/2011 02:00", - "11/06/2011 03:00", - ] - di = DatetimeIndex(times) - localized = di.tz_localize(tz, ambiguous="infer") - expected = dr._with_freq(None) - tm.assert_index_equal(expected, localized) - tm.assert_index_equal(expected, DatetimeIndex(times, tz=tz, ambiguous="infer")) - - # When there is no dst transition, nothing special happens - dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=pd.offsets.Hour()) - localized = dr.tz_localize(tz) - localized_infer = dr.tz_localize(tz, ambiguous="infer") - tm.assert_index_equal(localized, localized_infer) - - @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) - def test_dti_tz_localize_ambiguous_times(self, tz): - # March 13, 2011, spring forward, skip from 2 AM to 3 AM - dr = date_range(datetime(2011, 3, 13, 1, 30), periods=3, freq=pd.offsets.Hour()) - with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:30:00"): - dr.tz_localize(tz) - - # after dst transition, it works - dr = date_range( - datetime(2011, 3, 13, 3, 30), periods=3, freq=pd.offsets.Hour(), tz=tz - ) - - # November 6, 2011, fall back, repeat 2 AM hour - dr = date_range(datetime(2011, 11, 6, 1, 30), periods=3, freq=pd.offsets.Hour()) - with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"): - dr.tz_localize(tz) - - # UTC is OK - dr = date_range( - datetime(2011, 3, 13), periods=48, freq=pd.offsets.Minute(30), tz=pytz.utc - ) - - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_tz_localize_pass_dates_to_utc(self, tzstr): - strdates = ["1/1/2012", "3/1/2012", "4/1/2012"] - - idx = DatetimeIndex(strdates) - conv = idx.tz_localize(tzstr) - - fromdates = DatetimeIndex(strdates, tz=tzstr) - - assert conv.tz == fromdates.tz - tm.assert_numpy_array_equal(conv.values, fromdates.values) - - @pytest.mark.parametrize("prefix", ["", "dateutil/"]) - def test_dti_tz_localize(self, prefix): - tzstr = prefix + "US/Eastern" - dti = date_range(start="1/1/2005", end="1/1/2005 0:00:30.256", freq="ms") - dti2 = dti.tz_localize(tzstr) - - dti_utc = date_range( - start="1/1/2005 05:00", end="1/1/2005 5:00:30.256", freq="ms", tz="utc" - ) - - tm.assert_numpy_array_equal(dti2.values, dti_utc.values) - - dti3 = dti2.tz_convert(prefix + "US/Pacific") - tm.assert_numpy_array_equal(dti3.values, dti_utc.values) - - dti = date_range(start="11/6/2011 1:59", end="11/6/2011 2:00", freq="ms") - with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"): - dti.tz_localize(tzstr) - - dti = date_range(start="3/13/2011 1:59", end="3/13/2011 2:00", freq="ms") - with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:00:00"): - dti.tz_localize(tzstr) - - @pytest.mark.parametrize( - "tz", - [ - "US/Eastern", - "dateutil/US/Eastern", - pytz.timezone("US/Eastern"), - gettz("US/Eastern"), - ], - ) - def test_dti_tz_localize_utc_conversion(self, tz): - # Localizing to time zone should: - # 1) check for DST ambiguities - # 2) convert to UTC - - rng = date_range("3/10/2012", "3/11/2012", freq="30min") - - converted = rng.tz_localize(tz) - expected_naive = rng + pd.offsets.Hour(5) - tm.assert_numpy_array_equal(converted.asi8, expected_naive.asi8) - - # DST ambiguity, this should fail - rng = date_range("3/11/2012", "3/12/2012", freq="30min") - # Is this really how it should fail?? - with pytest.raises(pytz.NonExistentTimeError, match="2012-03-11 02:00:00"): - rng.tz_localize(tz) - - def test_dti_tz_localize_roundtrip(self, tz_aware_fixture): - # note: this tz tests that a tz-naive index can be localized - # and de-localized successfully, when there are no DST transitions - # in the range. - idx = date_range(start="2014-06-01", end="2014-08-30", freq="15min") - tz = tz_aware_fixture - localized = idx.tz_localize(tz) - # can't localize a tz-aware object - with pytest.raises( - TypeError, match="Already tz-aware, use tz_convert to convert" - ): - localized.tz_localize(tz) - reset = localized.tz_localize(None) - assert reset.tzinfo is None - expected = idx._with_freq(None) - tm.assert_index_equal(reset, expected) - - def test_dti_tz_localize_naive(self): - rng = date_range("1/1/2011", periods=100, freq="h") - - conv = rng.tz_localize("US/Pacific") - exp = date_range("1/1/2011", periods=100, freq="h", tz="US/Pacific") - - tm.assert_index_equal(conv, exp._with_freq(None)) - - def test_dti_tz_localize_tzlocal(self): - # GH#13583 - offset = dateutil.tz.tzlocal().utcoffset(datetime(2011, 1, 1)) - offset = int(offset.total_seconds() * 1000000000) - - dti = date_range(start="2001-01-01", end="2001-03-01") - dti2 = dti.tz_localize(dateutil.tz.tzlocal()) - tm.assert_numpy_array_equal(dti2.asi8 + offset, dti.asi8) - - dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal()) - dti2 = dti.tz_localize(None) - tm.assert_numpy_array_equal(dti2.asi8 - offset, dti.asi8) - - @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) - def test_dti_tz_localize_ambiguous_nat(self, tz): - times = [ - "11/06/2011 00:00", - "11/06/2011 01:00", - "11/06/2011 01:00", - "11/06/2011 02:00", - "11/06/2011 03:00", - ] - di = DatetimeIndex(times) - localized = di.tz_localize(tz, ambiguous="NaT") - - times = [ - "11/06/2011 00:00", - np.nan, - np.nan, - "11/06/2011 02:00", - "11/06/2011 03:00", - ] - di_test = DatetimeIndex(times, tz="US/Eastern") - - # left dtype is datetime64[ns, US/Eastern] - # right is datetime64[ns, tzfile('/usr/share/zoneinfo/US/Eastern')] - tm.assert_numpy_array_equal(di_test.values, localized.values) - - @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) - def test_dti_tz_localize_ambiguous_flags(self, tz): - # November 6, 2011, fall back, repeat 2 AM hour - - # Pass in flags to determine right dst transition - dr = date_range( - datetime(2011, 11, 6, 0), periods=5, freq=pd.offsets.Hour(), tz=tz - ) - times = [ - "11/06/2011 00:00", - "11/06/2011 01:00", - "11/06/2011 01:00", - "11/06/2011 02:00", - "11/06/2011 03:00", - ] - - # Test tz_localize - di = DatetimeIndex(times) - is_dst = [1, 1, 0, 0, 0] - localized = di.tz_localize(tz, ambiguous=is_dst) - expected = dr._with_freq(None) - tm.assert_index_equal(expected, localized) - tm.assert_index_equal(expected, DatetimeIndex(times, tz=tz, ambiguous=is_dst)) - - localized = di.tz_localize(tz, ambiguous=np.array(is_dst)) - tm.assert_index_equal(dr, localized) - - localized = di.tz_localize(tz, ambiguous=np.array(is_dst).astype("bool")) - tm.assert_index_equal(dr, localized) - - # Test constructor - localized = DatetimeIndex(times, tz=tz, ambiguous=is_dst) - tm.assert_index_equal(dr, localized) - - # Test duplicate times where inferring the dst fails - times += times - di = DatetimeIndex(times) - - # When the sizes are incompatible, make sure error is raised - msg = "Length of ambiguous bool-array must be the same size as vals" - with pytest.raises(Exception, match=msg): - di.tz_localize(tz, ambiguous=is_dst) - - # When sizes are compatible and there are repeats ('infer' won't work) - is_dst = np.hstack((is_dst, is_dst)) - localized = di.tz_localize(tz, ambiguous=is_dst) - dr = dr.append(dr) - tm.assert_index_equal(dr, localized) - - # When there is no dst transition, nothing special happens - dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=pd.offsets.Hour()) - is_dst = np.array([1] * 10) - localized = dr.tz_localize(tz) - localized_is_dst = dr.tz_localize(tz, ambiguous=is_dst) - tm.assert_index_equal(localized, localized_is_dst) - - def test_dti_tz_localize_bdate_range(self): - dr = bdate_range("1/1/2009", "1/1/2010") - dr_utc = bdate_range("1/1/2009", "1/1/2010", tz=pytz.utc) - localized = dr.tz_localize(pytz.utc) - tm.assert_index_equal(dr_utc, localized) - - @pytest.mark.parametrize( - "start_ts, tz, end_ts, shift", - [ - ["2015-03-29 02:20:00", "Europe/Warsaw", "2015-03-29 03:00:00", "forward"], - [ - "2015-03-29 02:20:00", - "Europe/Warsaw", - "2015-03-29 01:59:59.999999999", - "backward", - ], - [ - "2015-03-29 02:20:00", - "Europe/Warsaw", - "2015-03-29 03:20:00", - timedelta(hours=1), - ], - [ - "2015-03-29 02:20:00", - "Europe/Warsaw", - "2015-03-29 01:20:00", - timedelta(hours=-1), - ], - ["2018-03-11 02:33:00", "US/Pacific", "2018-03-11 03:00:00", "forward"], - [ - "2018-03-11 02:33:00", - "US/Pacific", - "2018-03-11 01:59:59.999999999", - "backward", - ], - [ - "2018-03-11 02:33:00", - "US/Pacific", - "2018-03-11 03:33:00", - timedelta(hours=1), - ], - [ - "2018-03-11 02:33:00", - "US/Pacific", - "2018-03-11 01:33:00", - timedelta(hours=-1), - ], - ], - ) - @pytest.mark.parametrize("tz_type", ["", "dateutil/"]) - def test_dti_tz_localize_nonexistent_shift( - self, start_ts, tz, end_ts, shift, tz_type - ): - # GH 8917 - tz = tz_type + tz - if isinstance(shift, str): - shift = "shift_" + shift - dti = DatetimeIndex([Timestamp(start_ts)]) - result = dti.tz_localize(tz, nonexistent=shift) - expected = DatetimeIndex([Timestamp(end_ts)]).tz_localize(tz) - tm.assert_index_equal(result, expected) - - @pytest.mark.parametrize("offset", [-1, 1]) - def test_dti_tz_localize_nonexistent_shift_invalid(self, offset, warsaw): - # GH 8917 - tz = warsaw - dti = DatetimeIndex([Timestamp("2015-03-29 02:20:00")]) - msg = "The provided timedelta will relocalize on a nonexistent time" - with pytest.raises(ValueError, match=msg): - dti.tz_localize(tz, nonexistent=timedelta(seconds=offset)) - # ------------------------------------------------------------- # DatetimeIndex.normalize @@ -1103,26 +752,3 @@ def test_iteration_preserves_nanoseconds(self, tz): ) for i, ts in enumerate(index): assert ts == index[i] # pylint: disable=unnecessary-list-index-lookup - - -def test_tz_localize_invalidates_freq(): - # we only preserve freq in unambiguous cases - - # if localized to US/Eastern, this crosses a DST transition - dti = date_range("2014-03-08 23:00", "2014-03-09 09:00", freq="h") - assert dti.freq == "h" - - result = dti.tz_localize(None) # no-op - assert result.freq == "h" - - result = dti.tz_localize("UTC") # unambiguous freq preservation - assert result.freq == "h" - - result = dti.tz_localize("US/Eastern", nonexistent="shift_forward") - assert result.freq is None - assert result.inferred_freq is None # i.e. we are not _too_ strict here - - # Case where we _can_ keep freq because we're length==1 - dti2 = dti[:1] - result = dti2.tz_localize("US/Eastern") - assert result.freq == "h" From d40a9356fb115542fadd106ed0338b1d82599536 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 15:56:34 -0700 Subject: [PATCH 03/12] implement test_round.py --- .../indexes/datetimes/methods/test_round.py | 213 ++++++++++++++++++ .../indexes/datetimes/test_scalar_compat.py | 209 +---------------- 2 files changed, 214 insertions(+), 208 deletions(-) create mode 100644 pandas/tests/indexes/datetimes/methods/test_round.py diff --git a/pandas/tests/indexes/datetimes/methods/test_round.py b/pandas/tests/indexes/datetimes/methods/test_round.py new file mode 100644 index 0000000000000..6c9fb8ded0650 --- /dev/null +++ b/pandas/tests/indexes/datetimes/methods/test_round.py @@ -0,0 +1,213 @@ +import pytest + +from pandas._libs.tslibs import to_offset +from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG + +from pandas import ( + DatetimeIndex, + Timestamp, + date_range, +) +import pandas._testing as tm + + +class TestDatetimeIndexRound: + def test_round_daily(self): + dti = date_range("20130101 09:10:11", periods=5) + result = dti.round("D") + expected = date_range("20130101", periods=5) + tm.assert_index_equal(result, expected) + + dti = dti.tz_localize("UTC").tz_convert("US/Eastern") + result = dti.round("D") + expected = date_range("20130101", periods=5).tz_localize("US/Eastern") + tm.assert_index_equal(result, expected) + + result = dti.round("s") + tm.assert_index_equal(result, dti) + + @pytest.mark.parametrize( + "freq, error_msg", + [ + ("Y", " is a non-fixed frequency"), + ("ME", " is a non-fixed frequency"), + ("foobar", "Invalid frequency: foobar"), + ], + ) + def test_round_invalid(self, freq, error_msg): + dti = date_range("20130101 09:10:11", periods=5) + dti = dti.tz_localize("UTC").tz_convert("US/Eastern") + with pytest.raises(ValueError, match=error_msg): + dti.round(freq) + + def test_round(self, tz_naive_fixture): + tz = tz_naive_fixture + rng = date_range(start="2016-01-01", periods=5, freq="30Min", tz=tz) + elt = rng[1] + + expected_rng = DatetimeIndex( + [ + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 01:00:00", tz=tz), + Timestamp("2016-01-01 02:00:00", tz=tz), + Timestamp("2016-01-01 02:00:00", tz=tz), + ] + ) + expected_elt = expected_rng[1] + + tm.assert_index_equal(rng.round(freq="h"), expected_rng) + assert elt.round(freq="h") == expected_elt + + msg = INVALID_FREQ_ERR_MSG + with pytest.raises(ValueError, match=msg): + rng.round(freq="foo") + with pytest.raises(ValueError, match=msg): + elt.round(freq="foo") + + msg = " is a non-fixed frequency" + with pytest.raises(ValueError, match=msg): + rng.round(freq="ME") + with pytest.raises(ValueError, match=msg): + elt.round(freq="ME") + + # GH#14440 & GH#15578 + index = DatetimeIndex(["2016-10-17 12:00:00.0015"], tz=tz) + result = index.round("ms") + expected = DatetimeIndex(["2016-10-17 12:00:00.002000"], tz=tz) + tm.assert_index_equal(result, expected) + + for freq in ["us", "ns"]: + tm.assert_index_equal(index, index.round(freq)) + + index = DatetimeIndex(["2016-10-17 12:00:00.00149"], tz=tz) + result = index.round("ms") + expected = DatetimeIndex(["2016-10-17 12:00:00.001000"], tz=tz) + tm.assert_index_equal(result, expected) + + index = DatetimeIndex(["2016-10-17 12:00:00.001501031"]) + result = index.round("10ns") + expected = DatetimeIndex(["2016-10-17 12:00:00.001501030"]) + tm.assert_index_equal(result, expected) + + with tm.assert_produces_warning(False): + ts = "2016-10-17 12:00:00.001501031" + DatetimeIndex([ts]).round("1010ns") + + def test_no_rounding_occurs(self, tz_naive_fixture): + # GH 21262 + tz = tz_naive_fixture + rng = date_range(start="2016-01-01", periods=5, freq="2Min", tz=tz) + + expected_rng = DatetimeIndex( + [ + Timestamp("2016-01-01 00:00:00", tz=tz), + Timestamp("2016-01-01 00:02:00", tz=tz), + Timestamp("2016-01-01 00:04:00", tz=tz), + Timestamp("2016-01-01 00:06:00", tz=tz), + Timestamp("2016-01-01 00:08:00", tz=tz), + ] + ) + + tm.assert_index_equal(rng.round(freq="2min"), expected_rng) + + @pytest.mark.parametrize( + "test_input, rounder, freq, expected", + [ + (["2117-01-01 00:00:45"], "floor", "15s", ["2117-01-01 00:00:45"]), + (["2117-01-01 00:00:45"], "ceil", "15s", ["2117-01-01 00:00:45"]), + ( + ["2117-01-01 00:00:45.000000012"], + "floor", + "10ns", + ["2117-01-01 00:00:45.000000010"], + ), + ( + ["1823-01-01 00:00:01.000000012"], + "ceil", + "10ns", + ["1823-01-01 00:00:01.000000020"], + ), + (["1823-01-01 00:00:01"], "floor", "1s", ["1823-01-01 00:00:01"]), + (["1823-01-01 00:00:01"], "ceil", "1s", ["1823-01-01 00:00:01"]), + (["2018-01-01 00:15:00"], "ceil", "15min", ["2018-01-01 00:15:00"]), + (["2018-01-01 00:15:00"], "floor", "15min", ["2018-01-01 00:15:00"]), + (["1823-01-01 03:00:00"], "ceil", "3h", ["1823-01-01 03:00:00"]), + (["1823-01-01 03:00:00"], "floor", "3h", ["1823-01-01 03:00:00"]), + ( + ("NaT", "1823-01-01 00:00:01"), + "floor", + "1s", + ("NaT", "1823-01-01 00:00:01"), + ), + ( + ("NaT", "1823-01-01 00:00:01"), + "ceil", + "1s", + ("NaT", "1823-01-01 00:00:01"), + ), + ], + ) + def test_ceil_floor_edge(self, test_input, rounder, freq, expected): + dt = DatetimeIndex(list(test_input)) + func = getattr(dt, rounder) + result = func(freq) + expected = DatetimeIndex(list(expected)) + assert expected.equals(result) + + @pytest.mark.parametrize( + "start, index_freq, periods", + [("2018-01-01", "12h", 25), ("2018-01-01 0:0:0.124999", "1ns", 1000)], + ) + @pytest.mark.parametrize( + "round_freq", + [ + "2ns", + "3ns", + "4ns", + "5ns", + "6ns", + "7ns", + "250ns", + "500ns", + "750ns", + "1us", + "19us", + "250us", + "500us", + "750us", + "1s", + "2s", + "3s", + "12h", + "1D", + ], + ) + def test_round_int64(self, start, index_freq, periods, round_freq): + dt = date_range(start=start, freq=index_freq, periods=periods) + unit = to_offset(round_freq).nanos + + # test floor + result = dt.floor(round_freq) + diff = dt.asi8 - result.asi8 + mod = result.asi8 % unit + assert (mod == 0).all(), f"floor not a {round_freq} multiple" + assert (0 <= diff).all() and (diff < unit).all(), "floor error" + + # test ceil + result = dt.ceil(round_freq) + diff = result.asi8 - dt.asi8 + mod = result.asi8 % unit + assert (mod == 0).all(), f"ceil not a {round_freq} multiple" + assert (0 <= diff).all() and (diff < unit).all(), "ceil error" + + # test round + result = dt.round(round_freq) + diff = abs(result.asi8 - dt.asi8) + mod = result.asi8 % unit + assert (mod == 0).all(), f"round not a {round_freq} multiple" + assert (diff <= unit // 2).all(), "round error" + if unit % 2 == 0: + assert ( + result.asi8[diff == unit // 2] % 2 == 0 + ).all(), "round half to even error" diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index 7c7e57b51ccc0..b4539939e9926 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -6,11 +6,7 @@ import numpy as np import pytest -from pandas._libs.tslibs import ( - OutOfBoundsDatetime, - to_offset, -) -from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG +from pandas._libs.tslibs import OutOfBoundsDatetime import pandas as pd from pandas import ( @@ -75,209 +71,6 @@ def test_dti_timestamp_isocalendar_fields(self): result = idx[-1].isocalendar() assert result == expected - # ---------------------------------------------------------------- - # DatetimeIndex.round - - def test_round_daily(self): - dti = date_range("20130101 09:10:11", periods=5) - result = dti.round("D") - expected = date_range("20130101", periods=5) - tm.assert_index_equal(result, expected) - - dti = dti.tz_localize("UTC").tz_convert("US/Eastern") - result = dti.round("D") - expected = date_range("20130101", periods=5).tz_localize("US/Eastern") - tm.assert_index_equal(result, expected) - - result = dti.round("s") - tm.assert_index_equal(result, dti) - - @pytest.mark.parametrize( - "freq, error_msg", - [ - ("Y", " is a non-fixed frequency"), - ("ME", " is a non-fixed frequency"), - ("foobar", "Invalid frequency: foobar"), - ], - ) - def test_round_invalid(self, freq, error_msg): - dti = date_range("20130101 09:10:11", periods=5) - dti = dti.tz_localize("UTC").tz_convert("US/Eastern") - with pytest.raises(ValueError, match=error_msg): - dti.round(freq) - - def test_round(self, tz_naive_fixture): - tz = tz_naive_fixture - rng = date_range(start="2016-01-01", periods=5, freq="30Min", tz=tz) - elt = rng[1] - - expected_rng = DatetimeIndex( - [ - Timestamp("2016-01-01 00:00:00", tz=tz), - Timestamp("2016-01-01 00:00:00", tz=tz), - Timestamp("2016-01-01 01:00:00", tz=tz), - Timestamp("2016-01-01 02:00:00", tz=tz), - Timestamp("2016-01-01 02:00:00", tz=tz), - ] - ) - expected_elt = expected_rng[1] - - tm.assert_index_equal(rng.round(freq="h"), expected_rng) - assert elt.round(freq="h") == expected_elt - - msg = INVALID_FREQ_ERR_MSG - with pytest.raises(ValueError, match=msg): - rng.round(freq="foo") - with pytest.raises(ValueError, match=msg): - elt.round(freq="foo") - - msg = " is a non-fixed frequency" - with pytest.raises(ValueError, match=msg): - rng.round(freq="ME") - with pytest.raises(ValueError, match=msg): - elt.round(freq="ME") - - # GH#14440 & GH#15578 - index = DatetimeIndex(["2016-10-17 12:00:00.0015"], tz=tz) - result = index.round("ms") - expected = DatetimeIndex(["2016-10-17 12:00:00.002000"], tz=tz) - tm.assert_index_equal(result, expected) - - for freq in ["us", "ns"]: - tm.assert_index_equal(index, index.round(freq)) - - index = DatetimeIndex(["2016-10-17 12:00:00.00149"], tz=tz) - result = index.round("ms") - expected = DatetimeIndex(["2016-10-17 12:00:00.001000"], tz=tz) - tm.assert_index_equal(result, expected) - - index = DatetimeIndex(["2016-10-17 12:00:00.001501031"]) - result = index.round("10ns") - expected = DatetimeIndex(["2016-10-17 12:00:00.001501030"]) - tm.assert_index_equal(result, expected) - - with tm.assert_produces_warning(False): - ts = "2016-10-17 12:00:00.001501031" - DatetimeIndex([ts]).round("1010ns") - - def test_no_rounding_occurs(self, tz_naive_fixture): - # GH 21262 - tz = tz_naive_fixture - rng = date_range(start="2016-01-01", periods=5, freq="2Min", tz=tz) - - expected_rng = DatetimeIndex( - [ - Timestamp("2016-01-01 00:00:00", tz=tz), - Timestamp("2016-01-01 00:02:00", tz=tz), - Timestamp("2016-01-01 00:04:00", tz=tz), - Timestamp("2016-01-01 00:06:00", tz=tz), - Timestamp("2016-01-01 00:08:00", tz=tz), - ] - ) - - tm.assert_index_equal(rng.round(freq="2min"), expected_rng) - - @pytest.mark.parametrize( - "test_input, rounder, freq, expected", - [ - (["2117-01-01 00:00:45"], "floor", "15s", ["2117-01-01 00:00:45"]), - (["2117-01-01 00:00:45"], "ceil", "15s", ["2117-01-01 00:00:45"]), - ( - ["2117-01-01 00:00:45.000000012"], - "floor", - "10ns", - ["2117-01-01 00:00:45.000000010"], - ), - ( - ["1823-01-01 00:00:01.000000012"], - "ceil", - "10ns", - ["1823-01-01 00:00:01.000000020"], - ), - (["1823-01-01 00:00:01"], "floor", "1s", ["1823-01-01 00:00:01"]), - (["1823-01-01 00:00:01"], "ceil", "1s", ["1823-01-01 00:00:01"]), - (["2018-01-01 00:15:00"], "ceil", "15min", ["2018-01-01 00:15:00"]), - (["2018-01-01 00:15:00"], "floor", "15min", ["2018-01-01 00:15:00"]), - (["1823-01-01 03:00:00"], "ceil", "3h", ["1823-01-01 03:00:00"]), - (["1823-01-01 03:00:00"], "floor", "3h", ["1823-01-01 03:00:00"]), - ( - ("NaT", "1823-01-01 00:00:01"), - "floor", - "1s", - ("NaT", "1823-01-01 00:00:01"), - ), - ( - ("NaT", "1823-01-01 00:00:01"), - "ceil", - "1s", - ("NaT", "1823-01-01 00:00:01"), - ), - ], - ) - def test_ceil_floor_edge(self, test_input, rounder, freq, expected): - dt = DatetimeIndex(list(test_input)) - func = getattr(dt, rounder) - result = func(freq) - expected = DatetimeIndex(list(expected)) - assert expected.equals(result) - - @pytest.mark.parametrize( - "start, index_freq, periods", - [("2018-01-01", "12h", 25), ("2018-01-01 0:0:0.124999", "1ns", 1000)], - ) - @pytest.mark.parametrize( - "round_freq", - [ - "2ns", - "3ns", - "4ns", - "5ns", - "6ns", - "7ns", - "250ns", - "500ns", - "750ns", - "1us", - "19us", - "250us", - "500us", - "750us", - "1s", - "2s", - "3s", - "12h", - "1D", - ], - ) - def test_round_int64(self, start, index_freq, periods, round_freq): - dt = date_range(start=start, freq=index_freq, periods=periods) - unit = to_offset(round_freq).nanos - - # test floor - result = dt.floor(round_freq) - diff = dt.asi8 - result.asi8 - mod = result.asi8 % unit - assert (mod == 0).all(), f"floor not a {round_freq} multiple" - assert (0 <= diff).all() and (diff < unit).all(), "floor error" - - # test ceil - result = dt.ceil(round_freq) - diff = result.asi8 - dt.asi8 - mod = result.asi8 % unit - assert (mod == 0).all(), f"ceil not a {round_freq} multiple" - assert (0 <= diff).all() and (diff < unit).all(), "ceil error" - - # test round - result = dt.round(round_freq) - diff = abs(result.asi8 - dt.asi8) - mod = result.asi8 % unit - assert (mod == 0).all(), f"round not a {round_freq} multiple" - assert (diff <= unit // 2).all(), "round error" - if unit % 2 == 0: - assert ( - result.asi8[diff == unit // 2] % 2 == 0 - ).all(), "round half to even error" - # ---------------------------------------------------------------- # DatetimeIndex.normalize From 927fd1b6292388141e072f6b8d9eb038ca267b20 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 16:07:06 -0700 Subject: [PATCH 04/12] organize DatetimeIndex tests --- .../datetimes/methods/test_normalize.py | 39 ++++ .../datetimes/methods/test_to_julian_date.py | 45 +++++ .../indexes/datetimes/test_arithmetic.py | 56 ++++++ .../tests/indexes/datetimes/test_datetime.py | 71 ------- pandas/tests/indexes/datetimes/test_iter.py | 71 +++++++ pandas/tests/indexes/datetimes/test_misc.py | 188 ++++++++---------- .../indexes/datetimes/test_scalar_compat.py | 69 ------- .../tests/indexes/datetimes/test_timezones.py | 11 - 8 files changed, 293 insertions(+), 257 deletions(-) create mode 100644 pandas/tests/indexes/datetimes/methods/test_normalize.py create mode 100644 pandas/tests/indexes/datetimes/methods/test_to_julian_date.py create mode 100644 pandas/tests/indexes/datetimes/test_arithmetic.py create mode 100644 pandas/tests/indexes/datetimes/test_iter.py diff --git a/pandas/tests/indexes/datetimes/methods/test_normalize.py b/pandas/tests/indexes/datetimes/methods/test_normalize.py new file mode 100644 index 0000000000000..e9d1b1a81fc6c --- /dev/null +++ b/pandas/tests/indexes/datetimes/methods/test_normalize.py @@ -0,0 +1,39 @@ +import numpy as np + +from pandas import ( + DatetimeIndex, + NaT, + Timestamp, + date_range, +) +import pandas._testing as tm + + +class TestNormalize: + def test_normalize(self): + rng = date_range("1/1/2000 9:30", periods=10, freq="D") + + result = rng.normalize() + expected = date_range("1/1/2000", periods=10, freq="D") + tm.assert_index_equal(result, expected) + + arr_ns = np.array([1380585623454345752, 1380585612343234312]).astype( + "datetime64[ns]" + ) + rng_ns = DatetimeIndex(arr_ns) + rng_ns_normalized = rng_ns.normalize() + + arr_ns = np.array([1380585600000000000, 1380585600000000000]).astype( + "datetime64[ns]" + ) + expected = DatetimeIndex(arr_ns) + tm.assert_index_equal(rng_ns_normalized, expected) + + assert result.is_normalized + assert not rng.is_normalized + + def test_normalize_nat(self): + dti = DatetimeIndex([NaT, Timestamp("2018-01-01 01:00:00")]) + result = dti.normalize() + expected = DatetimeIndex([NaT, Timestamp("2018-01-01")]) + tm.assert_index_equal(result, expected) diff --git a/pandas/tests/indexes/datetimes/methods/test_to_julian_date.py b/pandas/tests/indexes/datetimes/methods/test_to_julian_date.py new file mode 100644 index 0000000000000..fc1f0595c21c5 --- /dev/null +++ b/pandas/tests/indexes/datetimes/methods/test_to_julian_date.py @@ -0,0 +1,45 @@ +import numpy as np + +from pandas import ( + Index, + Timestamp, + date_range, +) +import pandas._testing as tm + + +class TestDateTimeIndexToJulianDate: + def test_1700(self): + dr = date_range(start=Timestamp("1710-10-01"), periods=5, freq="D") + r1 = Index([x.to_julian_date() for x in dr]) + r2 = dr.to_julian_date() + assert isinstance(r2, Index) and r2.dtype == np.float64 + tm.assert_index_equal(r1, r2) + + def test_2000(self): + dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="D") + r1 = Index([x.to_julian_date() for x in dr]) + r2 = dr.to_julian_date() + assert isinstance(r2, Index) and r2.dtype == np.float64 + tm.assert_index_equal(r1, r2) + + def test_hour(self): + dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="h") + r1 = Index([x.to_julian_date() for x in dr]) + r2 = dr.to_julian_date() + assert isinstance(r2, Index) and r2.dtype == np.float64 + tm.assert_index_equal(r1, r2) + + def test_minute(self): + dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="min") + r1 = Index([x.to_julian_date() for x in dr]) + r2 = dr.to_julian_date() + assert isinstance(r2, Index) and r2.dtype == np.float64 + tm.assert_index_equal(r1, r2) + + def test_second(self): + dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="s") + r1 = Index([x.to_julian_date() for x in dr]) + r2 = dr.to_julian_date() + assert isinstance(r2, Index) and r2.dtype == np.float64 + tm.assert_index_equal(r1, r2) diff --git a/pandas/tests/indexes/datetimes/test_arithmetic.py b/pandas/tests/indexes/datetimes/test_arithmetic.py new file mode 100644 index 0000000000000..3a7c418b27de6 --- /dev/null +++ b/pandas/tests/indexes/datetimes/test_arithmetic.py @@ -0,0 +1,56 @@ +# Arithmetic tests specific to DatetimeIndex are generally about `freq` +# rentention or inference. Other arithmetic tests belong in +# tests/arithmetic/test_datetime64.py +import pytest + +from pandas import ( + Timedelta, + TimedeltaIndex, + Timestamp, + date_range, + timedelta_range, +) +import pandas._testing as tm + + +class TestDatetimeIndexArithmetic: + def test_add_timedelta_preserves_freq(self): + # GH#37295 should hold for any DTI with freq=None or Tick freq + tz = "Canada/Eastern" + dti = date_range( + start=Timestamp("2019-03-26 00:00:00-0400", tz=tz), + end=Timestamp("2020-10-17 00:00:00-0400", tz=tz), + freq="D", + ) + result = dti + Timedelta(days=1) + assert result.freq == dti.freq + + def test_sub_datetime_preserves_freq(self, tz_naive_fixture): + # GH#48818 + dti = date_range("2016-01-01", periods=12, tz=tz_naive_fixture) + + res = dti - dti[0] + expected = timedelta_range("0 Days", "11 Days") + tm.assert_index_equal(res, expected) + assert res.freq == expected.freq + + @pytest.mark.xfail( + reason="The inherited freq is incorrect bc dti.freq is incorrect " + "https://github.com/pandas-dev/pandas/pull/48818/files#r982793461" + ) + def test_sub_datetime_preserves_freq_across_dst(self): + # GH#48818 + ts = Timestamp("2016-03-11", tz="US/Pacific") + dti = date_range(ts, periods=4) + + res = dti - dti[0] + expected = TimedeltaIndex( + [ + Timedelta(days=0), + Timedelta(days=1), + Timedelta(days=2), + Timedelta(days=2, hours=23), + ] + ) + tm.assert_index_equal(res, expected) + assert res.freq == expected.freq diff --git a/pandas/tests/indexes/datetimes/test_datetime.py b/pandas/tests/indexes/datetimes/test_datetime.py index 3b0b856d07673..d5999436b915d 100644 --- a/pandas/tests/indexes/datetimes/test_datetime.py +++ b/pandas/tests/indexes/datetimes/test_datetime.py @@ -2,7 +2,6 @@ from datetime import date import re -import dateutil import numpy as np import pytest @@ -21,36 +20,6 @@ class TestDatetimeIndex: - def test_sub_datetime_preserves_freq(self, tz_naive_fixture): - # GH#48818 - dti = date_range("2016-01-01", periods=12, tz=tz_naive_fixture) - - res = dti - dti[0] - expected = pd.timedelta_range("0 Days", "11 Days") - tm.assert_index_equal(res, expected) - assert res.freq == expected.freq - - @pytest.mark.xfail( - reason="The inherited freq is incorrect bc dti.freq is incorrect " - "https://github.com/pandas-dev/pandas/pull/48818/files#r982793461" - ) - def test_sub_datetime_preserves_freq_across_dst(self): - # GH#48818 - ts = Timestamp("2016-03-11", tz="US/Pacific") - dti = date_range(ts, periods=4) - - res = dti - dti[0] - expected = pd.TimedeltaIndex( - [ - pd.Timedelta(days=0), - pd.Timedelta(days=1), - pd.Timedelta(days=2), - pd.Timedelta(days=2, hours=23), - ] - ) - tm.assert_index_equal(res, expected) - assert res.freq == expected.freq - def test_time_overflow_for_32bit_machines(self): # GH8943. On some machines NumPy defaults to np.int32 (for example, # 32-bit Linux machines). In the function _generate_regular_range @@ -96,46 +65,6 @@ def test_append_nondatetimeindex(self): result = rng.append(idx) assert isinstance(result[0], Timestamp) - def test_iteration_preserves_tz(self): - # see gh-8890 - index = date_range("2012-01-01", periods=3, freq="h", tz="US/Eastern") - - for i, ts in enumerate(index): - result = ts - expected = index[i] # pylint: disable=unnecessary-list-index-lookup - assert result == expected - - index = date_range( - "2012-01-01", periods=3, freq="h", tz=dateutil.tz.tzoffset(None, -28800) - ) - - for i, ts in enumerate(index): - result = ts - expected = index[i] # pylint: disable=unnecessary-list-index-lookup - assert result._repr_base == expected._repr_base - assert result == expected - - # 9100 - index = DatetimeIndex( - ["2014-12-01 03:32:39.987000-08:00", "2014-12-01 04:12:34.987000-08:00"] - ) - for i, ts in enumerate(index): - result = ts - expected = index[i] # pylint: disable=unnecessary-list-index-lookup - assert result._repr_base == expected._repr_base - assert result == expected - - @pytest.mark.parametrize("periods", [0, 9999, 10000, 10001]) - def test_iteration_over_chunksize(self, periods): - # GH21012 - - index = date_range("2000-01-01 00:00:00", periods=periods, freq="min") - num = 0 - for stamp in index: - assert index[num] == stamp - num += 1 - assert num == len(index) - def test_misc_coverage(self): rng = date_range("1/1/2000", periods=5) result = rng.groupby(rng.day) diff --git a/pandas/tests/indexes/datetimes/test_iter.py b/pandas/tests/indexes/datetimes/test_iter.py new file mode 100644 index 0000000000000..a1861e6e9447e --- /dev/null +++ b/pandas/tests/indexes/datetimes/test_iter.py @@ -0,0 +1,71 @@ +import dateutil.tz +import numpy as np +import pytest + +from pandas import ( + DatetimeIndex, + date_range, + to_datetime, +) + + +class TestDatetimeIndexIteration: + @pytest.mark.parametrize( + "tz", [None, "UTC", "US/Central", dateutil.tz.tzoffset(None, -28800)] + ) + def test_iteration_preserves_nanoseconds(self, tz): + # GH#19603 + index = DatetimeIndex( + ["2018-02-08 15:00:00.168456358", "2018-02-08 15:00:00.168456359"], tz=tz + ) + for i, ts in enumerate(index): + assert ts == index[i] # pylint: disable=unnecessary-list-index-lookup + + def test_iter_readonly(self): + # GH#28055 ints_to_pydatetime with readonly array + arr = np.array([np.datetime64("2012-02-15T12:00:00.000000000")]) + arr.setflags(write=False) + dti = to_datetime(arr) + list(dti) + + def test_iteration_preserves_tz(self): + # see GH#8890 + index = date_range("2012-01-01", periods=3, freq="h", tz="US/Eastern") + + for i, ts in enumerate(index): + result = ts + expected = index[i] # pylint: disable=unnecessary-list-index-lookup + assert result == expected + + def test_iteration_preserves_tz2(self): + index = date_range( + "2012-01-01", periods=3, freq="h", tz=dateutil.tz.tzoffset(None, -28800) + ) + + for i, ts in enumerate(index): + result = ts + expected = index[i] # pylint: disable=unnecessary-list-index-lookup + assert result._repr_base == expected._repr_base + assert result == expected + + def test_iteration_preserves_tz3(self): + # GH#9100 + index = DatetimeIndex( + ["2014-12-01 03:32:39.987000-08:00", "2014-12-01 04:12:34.987000-08:00"] + ) + for i, ts in enumerate(index): + result = ts + expected = index[i] # pylint: disable=unnecessary-list-index-lookup + assert result._repr_base == expected._repr_base + assert result == expected + + @pytest.mark.parametrize("periods", [0, 9999, 10000, 10001]) + def test_iteration_over_chunksize(self, periods): + # GH#21012 + + index = date_range("2000-01-01 00:00:00", periods=periods, freq="min") + num = 0 + for stamp in index: + assert index[num] == stamp + num += 1 + assert num == len(index) diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index d86d78ba47c5b..bdefdbbe5d5c9 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -10,7 +10,6 @@ from pandas import ( DatetimeIndex, Index, - Timedelta, Timestamp, date_range, offsets, @@ -31,92 +30,89 @@ def test_no_millisecond_field(self): with pytest.raises(AttributeError, match=msg): DatetimeIndex([]).millisecond - def test_datetimeindex_accessors(self): - dti_naive = date_range(freq="D", start=datetime(1998, 1, 1), periods=365) + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_datetimeindex_accessors(self, tz): # GH#13303 - dti_tz = date_range( - freq="D", start=datetime(1998, 1, 1), periods=365, tz="US/Eastern" - ) - for dti in [dti_naive, dti_tz]: - assert dti.year[0] == 1998 - assert dti.month[0] == 1 - assert dti.day[0] == 1 - assert dti.hour[0] == 0 - assert dti.minute[0] == 0 - assert dti.second[0] == 0 - assert dti.microsecond[0] == 0 - assert dti.dayofweek[0] == 3 - - assert dti.dayofyear[0] == 1 - assert dti.dayofyear[120] == 121 - - assert dti.isocalendar().week.iloc[0] == 1 - assert dti.isocalendar().week.iloc[120] == 18 - - assert dti.quarter[0] == 1 - assert dti.quarter[120] == 2 - - assert dti.days_in_month[0] == 31 - assert dti.days_in_month[90] == 30 - - assert dti.is_month_start[0] - assert not dti.is_month_start[1] - assert dti.is_month_start[31] - assert dti.is_quarter_start[0] - assert dti.is_quarter_start[90] - assert dti.is_year_start[0] - assert not dti.is_year_start[364] - assert not dti.is_month_end[0] - assert dti.is_month_end[30] - assert not dti.is_month_end[31] - assert dti.is_month_end[364] - assert not dti.is_quarter_end[0] - assert not dti.is_quarter_end[30] - assert dti.is_quarter_end[89] - assert dti.is_quarter_end[364] - assert not dti.is_year_end[0] - assert dti.is_year_end[364] - - assert len(dti.year) == 365 - assert len(dti.month) == 365 - assert len(dti.day) == 365 - assert len(dti.hour) == 365 - assert len(dti.minute) == 365 - assert len(dti.second) == 365 - assert len(dti.microsecond) == 365 - assert len(dti.dayofweek) == 365 - assert len(dti.dayofyear) == 365 - assert len(dti.isocalendar()) == 365 - assert len(dti.quarter) == 365 - assert len(dti.is_month_start) == 365 - assert len(dti.is_month_end) == 365 - assert len(dti.is_quarter_start) == 365 - assert len(dti.is_quarter_end) == 365 - assert len(dti.is_year_start) == 365 - assert len(dti.is_year_end) == 365 - - dti.name = "name" - - # non boolean accessors -> return Index - for accessor in DatetimeArray._field_ops: - res = getattr(dti, accessor) - assert len(res) == 365 - assert isinstance(res, Index) - assert res.name == "name" - - # boolean accessors -> return array - for accessor in DatetimeArray._bool_ops: - res = getattr(dti, accessor) - assert len(res) == 365 - assert isinstance(res, np.ndarray) - - # test boolean indexing - res = dti[dti.is_quarter_start] - exp = dti[[0, 90, 181, 273]] - tm.assert_index_equal(res, exp) - res = dti[dti.is_leap_year] - exp = DatetimeIndex([], freq="D", tz=dti.tz, name="name") - tm.assert_index_equal(res, exp) + dti = date_range(freq="D", start=datetime(1998, 1, 1), periods=365, tz=tz) + assert dti.year[0] == 1998 + assert dti.month[0] == 1 + assert dti.day[0] == 1 + assert dti.hour[0] == 0 + assert dti.minute[0] == 0 + assert dti.second[0] == 0 + assert dti.microsecond[0] == 0 + assert dti.dayofweek[0] == 3 + + assert dti.dayofyear[0] == 1 + assert dti.dayofyear[120] == 121 + + assert dti.isocalendar().week.iloc[0] == 1 + assert dti.isocalendar().week.iloc[120] == 18 + + assert dti.quarter[0] == 1 + assert dti.quarter[120] == 2 + + assert dti.days_in_month[0] == 31 + assert dti.days_in_month[90] == 30 + + assert dti.is_month_start[0] + assert not dti.is_month_start[1] + assert dti.is_month_start[31] + assert dti.is_quarter_start[0] + assert dti.is_quarter_start[90] + assert dti.is_year_start[0] + assert not dti.is_year_start[364] + assert not dti.is_month_end[0] + assert dti.is_month_end[30] + assert not dti.is_month_end[31] + assert dti.is_month_end[364] + assert not dti.is_quarter_end[0] + assert not dti.is_quarter_end[30] + assert dti.is_quarter_end[89] + assert dti.is_quarter_end[364] + assert not dti.is_year_end[0] + assert dti.is_year_end[364] + + assert len(dti.year) == 365 + assert len(dti.month) == 365 + assert len(dti.day) == 365 + assert len(dti.hour) == 365 + assert len(dti.minute) == 365 + assert len(dti.second) == 365 + assert len(dti.microsecond) == 365 + assert len(dti.dayofweek) == 365 + assert len(dti.dayofyear) == 365 + assert len(dti.isocalendar()) == 365 + assert len(dti.quarter) == 365 + assert len(dti.is_month_start) == 365 + assert len(dti.is_month_end) == 365 + assert len(dti.is_quarter_start) == 365 + assert len(dti.is_quarter_end) == 365 + assert len(dti.is_year_start) == 365 + assert len(dti.is_year_end) == 365 + + dti.name = "name" + + # non boolean accessors -> return Index + for accessor in DatetimeArray._field_ops: + res = getattr(dti, accessor) + assert len(res) == 365 + assert isinstance(res, Index) + assert res.name == "name" + + # boolean accessors -> return array + for accessor in DatetimeArray._bool_ops: + res = getattr(dti, accessor) + assert len(res) == 365 + assert isinstance(res, np.ndarray) + + # test boolean indexing + res = dti[dti.is_quarter_start] + exp = dti[[0, 90, 181, 273]] + tm.assert_index_equal(res, exp) + res = dti[dti.is_leap_year] + exp = DatetimeIndex([], freq="D", tz=dti.tz, name="name") + tm.assert_index_equal(res, exp) def test_datetimeindex_accessors2(self): dti = date_range(freq="BQ-FEB", start=datetime(1998, 1, 1), periods=4) @@ -283,23 +279,3 @@ def test_nanosecond_field(self): expected = Index(np.arange(10, dtype=np.int32)) tm.assert_index_equal(dti.nanosecond, expected) - - -def test_iter_readonly(): - # GH#28055 ints_to_pydatetime with readonly array - arr = np.array([np.datetime64("2012-02-15T12:00:00.000000000")]) - arr.setflags(write=False) - dti = pd.to_datetime(arr) - list(dti) - - -def test_add_timedelta_preserves_freq(): - # GH#37295 should hold for any DTI with freq=None or Tick freq - tz = "Canada/Eastern" - dti = date_range( - start=Timestamp("2019-03-26 00:00:00-0400", tz=tz), - end=Timestamp("2020-10-17 00:00:00-0400", tz=tz), - freq="D", - ) - result = dti + Timedelta(days=1) - assert result.freq == dti.freq diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index b4539939e9926..4818134b7c35a 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -3,7 +3,6 @@ """ from datetime import datetime -import numpy as np import pytest from pandas._libs.tslibs import OutOfBoundsDatetime @@ -70,71 +69,3 @@ def test_dti_timestamp_isocalendar_fields(self): expected = tuple(idx.isocalendar().iloc[-1].to_list()) result = idx[-1].isocalendar() assert result == expected - - # ---------------------------------------------------------------- - # DatetimeIndex.normalize - - def test_normalize(self): - rng = date_range("1/1/2000 9:30", periods=10, freq="D") - - result = rng.normalize() - expected = date_range("1/1/2000", periods=10, freq="D") - tm.assert_index_equal(result, expected) - - arr_ns = np.array([1380585623454345752, 1380585612343234312]).astype( - "datetime64[ns]" - ) - rng_ns = DatetimeIndex(arr_ns) - rng_ns_normalized = rng_ns.normalize() - - arr_ns = np.array([1380585600000000000, 1380585600000000000]).astype( - "datetime64[ns]" - ) - expected = DatetimeIndex(arr_ns) - tm.assert_index_equal(rng_ns_normalized, expected) - - assert result.is_normalized - assert not rng.is_normalized - - def test_normalize_nat(self): - dti = DatetimeIndex([pd.NaT, Timestamp("2018-01-01 01:00:00")]) - result = dti.normalize() - expected = DatetimeIndex([pd.NaT, Timestamp("2018-01-01")]) - tm.assert_index_equal(result, expected) - - -class TestDateTimeIndexToJulianDate: - def test_1700(self): - dr = date_range(start=Timestamp("1710-10-01"), periods=5, freq="D") - r1 = pd.Index([x.to_julian_date() for x in dr]) - r2 = dr.to_julian_date() - assert isinstance(r2, pd.Index) and r2.dtype == np.float64 - tm.assert_index_equal(r1, r2) - - def test_2000(self): - dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="D") - r1 = pd.Index([x.to_julian_date() for x in dr]) - r2 = dr.to_julian_date() - assert isinstance(r2, pd.Index) and r2.dtype == np.float64 - tm.assert_index_equal(r1, r2) - - def test_hour(self): - dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="h") - r1 = pd.Index([x.to_julian_date() for x in dr]) - r2 = dr.to_julian_date() - assert isinstance(r2, pd.Index) and r2.dtype == np.float64 - tm.assert_index_equal(r1, r2) - - def test_minute(self): - dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="min") - r1 = pd.Index([x.to_julian_date() for x in dr]) - r2 = dr.to_julian_date() - assert isinstance(r2, pd.Index) and r2.dtype == np.float64 - tm.assert_index_equal(r1, r2) - - def test_second(self): - dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="s") - r1 = pd.Index([x.to_julian_date() for x in dr]) - r2 = dr.to_julian_date() - assert isinstance(r2, pd.Index) and r2.dtype == np.float64 - tm.assert_index_equal(r1, r2) diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 4b5adf5fb3e6b..387993d59d3cc 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -741,14 +741,3 @@ def test_dti_convert_tz_aware_datetime_datetime(self, tz): ex_vals = np.array([Timestamp(x).as_unit("ns")._value for x in dates_aware]) tm.assert_numpy_array_equal(converted.asi8, ex_vals) assert converted.tz is timezone.utc - - @pytest.mark.parametrize( - "tz", [None, "UTC", "US/Central", dateutil.tz.tzoffset(None, -28800)] - ) - def test_iteration_preserves_nanoseconds(self, tz): - # GH 19603 - index = DatetimeIndex( - ["2018-02-08 15:00:00.168456358", "2018-02-08 15:00:00.168456359"], tz=tz - ) - for i, ts in enumerate(index): - assert ts == index[i] # pylint: disable=unnecessary-list-index-lookup From c97c551e932a2442e2165df3c0f5da7ef0bb525a Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 16:13:06 -0700 Subject: [PATCH 05/12] REF: DTI tests --- .../indexes/datetimes/methods/test_astype.py | 19 ++++++++++++ .../datetimes/methods/test_resolution.py | 31 +++++++++++++++++++ .../indexes/datetimes/test_constructors.py | 10 ++++++ pandas/tests/indexes/datetimes/test_ops.py | 27 ---------------- .../indexes/datetimes/test_scalar_compat.py | 14 --------- .../tests/indexes/datetimes/test_timezones.py | 19 ------------ 6 files changed, 60 insertions(+), 60 deletions(-) create mode 100644 pandas/tests/indexes/datetimes/methods/test_resolution.py diff --git a/pandas/tests/indexes/datetimes/methods/test_astype.py b/pandas/tests/indexes/datetimes/methods/test_astype.py index 94390acb35624..a35d2624276be 100644 --- a/pandas/tests/indexes/datetimes/methods/test_astype.py +++ b/pandas/tests/indexes/datetimes/methods/test_astype.py @@ -18,6 +18,25 @@ class TestDatetimeIndex: + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_dti_astype_asobject_tzinfos(self, tzstr): + # GH#1345 + + # dates around a dst transition + rng = date_range("2/13/2010", "5/6/2010", tz=tzstr) + + objs = rng.astype(object) + for i, x in enumerate(objs): + exval = rng[i] + assert x == exval + assert x.tzinfo == exval.tzinfo + + objs = rng.astype(object) + for i, x in enumerate(objs): + exval = rng[i] + assert x == exval + assert x.tzinfo == exval.tzinfo + def test_astype(self): # GH 13149, GH 13209 idx = DatetimeIndex(["2016-05-16", "NaT", NaT, np.nan], name="idx") diff --git a/pandas/tests/indexes/datetimes/methods/test_resolution.py b/pandas/tests/indexes/datetimes/methods/test_resolution.py new file mode 100644 index 0000000000000..6336fc8277947 --- /dev/null +++ b/pandas/tests/indexes/datetimes/methods/test_resolution.py @@ -0,0 +1,31 @@ +from dateutil.tz import tzlocal +import pytest + +from pandas.compat import IS64 + +from pandas import date_range + + +@pytest.mark.parametrize( + "freq,expected", + [ + ("Y", "day"), + ("Q", "day"), + ("ME", "day"), + ("D", "day"), + ("h", "hour"), + ("min", "minute"), + ("s", "second"), + ("ms", "millisecond"), + ("us", "microsecond"), + ], +) +def test_dti_resolution(request, tz_naive_fixture, freq, expected): + tz = tz_naive_fixture + if freq == "Y" and not IS64 and isinstance(tz, tzlocal): + request.applymarker( + pytest.mark.xfail(reason="OverflowError inside tzlocal past 2038") + ) + + idx = date_range(start="2013-04-01", periods=30, freq=freq, tz=tz) + assert idx.resolution == expected diff --git a/pandas/tests/indexes/datetimes/test_constructors.py b/pandas/tests/indexes/datetimes/test_constructors.py index 598845471046f..e16ca1b183705 100644 --- a/pandas/tests/indexes/datetimes/test_constructors.py +++ b/pandas/tests/indexes/datetimes/test_constructors.py @@ -567,6 +567,16 @@ def test_construction_outofbounds(self): # can't create DatetimeIndex DatetimeIndex(dates) + @pytest.mark.parametrize("data", [["1400-01-01"], [datetime(1400, 1, 1)]]) + def test_dti_date_out_of_range(self, data): + # GH#1475 + msg = ( + "^Out of bounds nanosecond timestamp: " + "1400-01-01( 00:00:00)?, at position 0$" + ) + with pytest.raises(OutOfBoundsDatetime, match=msg): + DatetimeIndex(data) + def test_construction_with_ndarray(self): # GH 5152 dates = [datetime(2013, 10, 7), datetime(2013, 10, 8), datetime(2013, 10, 9)] diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index c58d55ad6371b..30c510864ce68 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -1,10 +1,7 @@ from datetime import datetime -from dateutil.tz import tzlocal import pytest -from pandas.compat import IS64 - from pandas import ( DatetimeIndex, Index, @@ -17,30 +14,6 @@ class TestDatetimeIndexOps: - @pytest.mark.parametrize( - "freq,expected", - [ - ("Y", "day"), - ("Q", "day"), - ("ME", "day"), - ("D", "day"), - ("h", "hour"), - ("min", "minute"), - ("s", "second"), - ("ms", "millisecond"), - ("us", "microsecond"), - ], - ) - def test_resolution(self, request, tz_naive_fixture, freq, expected): - tz = tz_naive_fixture - if freq == "Y" and not IS64 and isinstance(tz, tzlocal): - request.applymarker( - pytest.mark.xfail(reason="OverflowError inside tzlocal past 2038") - ) - - idx = date_range(start="2013-04-01", periods=30, freq=freq, tz=tz) - assert idx.resolution == expected - def test_infer_freq(self, freq_sample): # GH 11018 idx = date_range("2011-01-01 09:00:00", freq=freq_sample, periods=10) diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index 4818134b7c35a..8545e0506210e 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -1,15 +1,11 @@ """ Tests for DatetimeIndex methods behaving like their Timestamp counterparts """ -from datetime import datetime import pytest -from pandas._libs.tslibs import OutOfBoundsDatetime - import pandas as pd from pandas import ( - DatetimeIndex, Timestamp, date_range, ) @@ -29,16 +25,6 @@ def test_dti_date(self): expected = [t.date() for t in rng] assert (result == expected).all() - @pytest.mark.parametrize("data", [["1400-01-01"], [datetime(1400, 1, 1)]]) - def test_dti_date_out_of_range(self, data): - # GH#1475 - msg = ( - "^Out of bounds nanosecond timestamp: " - "1400-01-01( 00:00:00)?, at position 0$" - ) - with pytest.raises(OutOfBoundsDatetime, match=msg): - DatetimeIndex(data) - @pytest.mark.parametrize( "field", [ diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 387993d59d3cc..4434f2b85d9c0 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -595,25 +595,6 @@ def test_dti_tz_nat(self, tzstr): assert isna(idx[1]) assert idx[0].tzinfo is not None - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_astype_asobject_tzinfos(self, tzstr): - # GH#1345 - - # dates around a dst transition - rng = date_range("2/13/2010", "5/6/2010", tz=tzstr) - - objs = rng.astype(object) - for i, x in enumerate(objs): - exval = rng[i] - assert x == exval - assert x.tzinfo == exval.tzinfo - - objs = rng.astype(object) - for i, x in enumerate(objs): - exval = rng[i] - assert x == exval - assert x.tzinfo == exval.tzinfo - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) def test_dti_with_timezone_repr(self, tzstr): rng = date_range("4/13/2010", "5/6/2010") From dc46c8d1e98201d0cd0fb33d3087f00afc79970d Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 16:13:42 -0700 Subject: [PATCH 06/12] mv test_asof.py --- pandas/tests/indexes/datetimes/{ => methods}/test_asof.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pandas/tests/indexes/datetimes/{ => methods}/test_asof.py (100%) diff --git a/pandas/tests/indexes/datetimes/test_asof.py b/pandas/tests/indexes/datetimes/methods/test_asof.py similarity index 100% rename from pandas/tests/indexes/datetimes/test_asof.py rename to pandas/tests/indexes/datetimes/methods/test_asof.py From dff27d1a557524858d09d71fcea026fe037a02e0 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 16:23:48 -0700 Subject: [PATCH 07/12] DTI tests --- .../datetimes/methods/test_normalize.py | 56 +++ .../datetimes/methods/test_to_pydatetime.py | 51 +++ .../datetimes/methods/test_tz_convert.py | 283 ++++++++++++++ pandas/tests/indexes/datetimes/test_misc.py | 1 + .../tests/indexes/datetimes/test_timezones.py | 358 +----------------- 5 files changed, 392 insertions(+), 357 deletions(-) create mode 100644 pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py create mode 100644 pandas/tests/indexes/datetimes/methods/test_tz_convert.py diff --git a/pandas/tests/indexes/datetimes/methods/test_normalize.py b/pandas/tests/indexes/datetimes/methods/test_normalize.py index e9d1b1a81fc6c..74711f67e6446 100644 --- a/pandas/tests/indexes/datetimes/methods/test_normalize.py +++ b/pandas/tests/indexes/datetimes/methods/test_normalize.py @@ -1,4 +1,8 @@ +from dateutil.tz import tzlocal import numpy as np +import pytest + +import pandas.util._test_decorators as td from pandas import ( DatetimeIndex, @@ -37,3 +41,55 @@ def test_normalize_nat(self): result = dti.normalize() expected = DatetimeIndex([NaT, Timestamp("2018-01-01")]) tm.assert_index_equal(result, expected) + + def test_normalize_tz(self): + rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="US/Eastern") + + result = rng.normalize() # does not preserve freq + expected = date_range("1/1/2000", periods=10, freq="D", tz="US/Eastern") + tm.assert_index_equal(result, expected._with_freq(None)) + + assert result.is_normalized + assert not rng.is_normalized + + rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="UTC") + + result = rng.normalize() + expected = date_range("1/1/2000", periods=10, freq="D", tz="UTC") + tm.assert_index_equal(result, expected) + + assert result.is_normalized + assert not rng.is_normalized + + rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal()) + result = rng.normalize() # does not preserve freq + expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal()) + tm.assert_index_equal(result, expected._with_freq(None)) + + assert result.is_normalized + assert not rng.is_normalized + + @td.skip_if_windows + @pytest.mark.parametrize( + "timezone", + [ + "US/Pacific", + "US/Eastern", + "UTC", + "Asia/Kolkata", + "Asia/Shanghai", + "Australia/Canberra", + ], + ) + def test_normalize_tz_local(self, timezone): + # GH#13459 + with tm.set_timezone(timezone): + rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal()) + + result = rng.normalize() + expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal()) + expected = expected._with_freq(None) + tm.assert_index_equal(result, expected) + + assert result.is_normalized + assert not rng.is_normalized diff --git a/pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py b/pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py new file mode 100644 index 0000000000000..fe97ff0cca8eb --- /dev/null +++ b/pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py @@ -0,0 +1,51 @@ +from datetime import ( + datetime, + timezone, +) + +import dateutil.parser +import dateutil.tz +from dateutil.tz import tzlocal +import numpy as np + +from pandas import ( + DatetimeIndex, + date_range, + to_datetime, +) +import pandas._testing as tm +from pandas.tests.indexes.datetimes.test_timezones import FixedOffset + +fixed_off = FixedOffset(-420, "-07:00") + + +class TestToPyDatetime: + def test_dti_to_pydatetime(self): + dt = dateutil.parser.parse("2012-06-13T01:39:00Z") + dt = dt.replace(tzinfo=tzlocal()) + + arr = np.array([dt], dtype=object) + + result = to_datetime(arr, utc=True) + assert result.tz is timezone.utc + + rng = date_range("2012-11-03 03:00", "2012-11-05 03:00", tz=tzlocal()) + arr = rng.to_pydatetime() + result = to_datetime(arr, utc=True) + assert result.tz is timezone.utc + + def test_dti_to_pydatetime_fizedtz(self): + dates = np.array( + [ + datetime(2000, 1, 1, tzinfo=fixed_off), + datetime(2000, 1, 2, tzinfo=fixed_off), + datetime(2000, 1, 3, tzinfo=fixed_off), + ] + ) + dti = DatetimeIndex(dates) + + result = dti.to_pydatetime() + tm.assert_numpy_array_equal(dates, result) + + result = dti._mpl_repr() + tm.assert_numpy_array_equal(dates, result) diff --git a/pandas/tests/indexes/datetimes/methods/test_tz_convert.py b/pandas/tests/indexes/datetimes/methods/test_tz_convert.py new file mode 100644 index 0000000000000..b2cf488ac8313 --- /dev/null +++ b/pandas/tests/indexes/datetimes/methods/test_tz_convert.py @@ -0,0 +1,283 @@ +from datetime import datetime + +import dateutil.tz +from dateutil.tz import gettz +import numpy as np +import pytest +import pytz + +from pandas._libs.tslibs import timezones + +from pandas import ( + DatetimeIndex, + Index, + NaT, + Timestamp, + date_range, + offsets, +) +import pandas._testing as tm + + +class TestTZConvert: + def test_tz_convert_nat(self): + # GH#5546 + dates = [NaT] + idx = DatetimeIndex(dates) + idx = idx.tz_localize("US/Pacific") + tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific")) + idx = idx.tz_convert("US/Eastern") + tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Eastern")) + idx = idx.tz_convert("UTC") + tm.assert_index_equal(idx, DatetimeIndex(dates, tz="UTC")) + + dates = ["2010-12-01 00:00", "2010-12-02 00:00", NaT] + idx = DatetimeIndex(dates) + idx = idx.tz_localize("US/Pacific") + tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific")) + idx = idx.tz_convert("US/Eastern") + expected = ["2010-12-01 03:00", "2010-12-02 03:00", NaT] + tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern")) + + idx = idx + offsets.Hour(5) + expected = ["2010-12-01 08:00", "2010-12-02 08:00", NaT] + tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern")) + idx = idx.tz_convert("US/Pacific") + expected = ["2010-12-01 05:00", "2010-12-02 05:00", NaT] + tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific")) + + idx = idx + np.timedelta64(3, "h") + expected = ["2010-12-01 08:00", "2010-12-02 08:00", NaT] + tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific")) + + idx = idx.tz_convert("US/Eastern") + expected = ["2010-12-01 11:00", "2010-12-02 11:00", NaT] + tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern")) + + @pytest.mark.parametrize("prefix", ["", "dateutil/"]) + def test_dti_tz_convert_compat_timestamp(self, prefix): + strdates = ["1/1/2012", "3/1/2012", "4/1/2012"] + idx = DatetimeIndex(strdates, tz=prefix + "US/Eastern") + + conv = idx[0].tz_convert(prefix + "US/Pacific") + expected = idx.tz_convert(prefix + "US/Pacific")[0] + + assert conv == expected + + def test_dti_tz_convert_hour_overflow_dst(self): + # Regression test for GH#13306 + + # sorted case US/Eastern -> UTC + ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2009-05-12 09:50:32"] + tt = DatetimeIndex(ts).tz_localize("US/Eastern") + ut = tt.tz_convert("UTC") + expected = Index([13, 14, 13], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + # sorted case UTC -> US/Eastern + ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2009-05-12 13:50:32"] + tt = DatetimeIndex(ts).tz_localize("UTC") + ut = tt.tz_convert("US/Eastern") + expected = Index([9, 9, 9], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + # unsorted case US/Eastern -> UTC + ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2008-05-12 09:50:32"] + tt = DatetimeIndex(ts).tz_localize("US/Eastern") + ut = tt.tz_convert("UTC") + expected = Index([13, 14, 13], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + # unsorted case UTC -> US/Eastern + ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2008-05-12 13:50:32"] + tt = DatetimeIndex(ts).tz_localize("UTC") + ut = tt.tz_convert("US/Eastern") + expected = Index([9, 9, 9], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + @pytest.mark.parametrize("tz", ["US/Eastern", "dateutil/US/Eastern"]) + def test_dti_tz_convert_hour_overflow_dst_timestamps(self, tz): + # Regression test for GH#13306 + + # sorted case US/Eastern -> UTC + ts = [ + Timestamp("2008-05-12 09:50:00", tz=tz), + Timestamp("2008-12-12 09:50:35", tz=tz), + Timestamp("2009-05-12 09:50:32", tz=tz), + ] + tt = DatetimeIndex(ts) + ut = tt.tz_convert("UTC") + expected = Index([13, 14, 13], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + # sorted case UTC -> US/Eastern + ts = [ + Timestamp("2008-05-12 13:50:00", tz="UTC"), + Timestamp("2008-12-12 14:50:35", tz="UTC"), + Timestamp("2009-05-12 13:50:32", tz="UTC"), + ] + tt = DatetimeIndex(ts) + ut = tt.tz_convert("US/Eastern") + expected = Index([9, 9, 9], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + # unsorted case US/Eastern -> UTC + ts = [ + Timestamp("2008-05-12 09:50:00", tz=tz), + Timestamp("2008-12-12 09:50:35", tz=tz), + Timestamp("2008-05-12 09:50:32", tz=tz), + ] + tt = DatetimeIndex(ts) + ut = tt.tz_convert("UTC") + expected = Index([13, 14, 13], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + # unsorted case UTC -> US/Eastern + ts = [ + Timestamp("2008-05-12 13:50:00", tz="UTC"), + Timestamp("2008-12-12 14:50:35", tz="UTC"), + Timestamp("2008-05-12 13:50:32", tz="UTC"), + ] + tt = DatetimeIndex(ts) + ut = tt.tz_convert("US/Eastern") + expected = Index([9, 9, 9], dtype=np.int32) + tm.assert_index_equal(ut.hour, expected) + + @pytest.mark.parametrize("freq, n", [("h", 1), ("min", 60), ("s", 3600)]) + def test_dti_tz_convert_trans_pos_plus_1__bug(self, freq, n): + # Regression test for tslib.tz_convert(vals, tz1, tz2). + # See GH#4496 for details. + idx = date_range(datetime(2011, 3, 26, 23), datetime(2011, 3, 27, 1), freq=freq) + idx = idx.tz_localize("UTC") + idx = idx.tz_convert("Europe/Moscow") + + expected = np.repeat(np.array([3, 4, 5]), np.array([n, n, 1])) + tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) + + def test_dti_tz_convert_dst(self): + for freq, n in [("h", 1), ("min", 60), ("s", 3600)]: + # Start DST + idx = date_range( + "2014-03-08 23:00", "2014-03-09 09:00", freq=freq, tz="UTC" + ) + idx = idx.tz_convert("US/Eastern") + expected = np.repeat( + np.array([18, 19, 20, 21, 22, 23, 0, 1, 3, 4, 5]), + np.array([n, n, n, n, n, n, n, n, n, n, 1]), + ) + tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) + + idx = date_range( + "2014-03-08 18:00", "2014-03-09 05:00", freq=freq, tz="US/Eastern" + ) + idx = idx.tz_convert("UTC") + expected = np.repeat( + np.array([23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + np.array([n, n, n, n, n, n, n, n, n, n, 1]), + ) + tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) + + # End DST + idx = date_range( + "2014-11-01 23:00", "2014-11-02 09:00", freq=freq, tz="UTC" + ) + idx = idx.tz_convert("US/Eastern") + expected = np.repeat( + np.array([19, 20, 21, 22, 23, 0, 1, 1, 2, 3, 4]), + np.array([n, n, n, n, n, n, n, n, n, n, 1]), + ) + tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) + + idx = date_range( + "2014-11-01 18:00", "2014-11-02 05:00", freq=freq, tz="US/Eastern" + ) + idx = idx.tz_convert("UTC") + expected = np.repeat( + np.array([22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + np.array([n, n, n, n, n, n, n, n, n, n, n, n, 1]), + ) + tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) + + # daily + # Start DST + idx = date_range("2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="UTC") + idx = idx.tz_convert("US/Eastern") + tm.assert_index_equal(idx.hour, Index([19, 19], dtype=np.int32)) + + idx = date_range( + "2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="US/Eastern" + ) + idx = idx.tz_convert("UTC") + tm.assert_index_equal(idx.hour, Index([5, 5], dtype=np.int32)) + + # End DST + idx = date_range("2014-11-01 00:00", "2014-11-02 00:00", freq="D", tz="UTC") + idx = idx.tz_convert("US/Eastern") + tm.assert_index_equal(idx.hour, Index([20, 20], dtype=np.int32)) + + idx = date_range( + "2014-11-01 00:00", "2014-11-02 000:00", freq="D", tz="US/Eastern" + ) + idx = idx.tz_convert("UTC") + tm.assert_index_equal(idx.hour, Index([4, 4], dtype=np.int32)) + + def test_tz_convert_roundtrip(self, tz_aware_fixture): + tz = tz_aware_fixture + idx1 = date_range(start="2014-01-01", end="2014-12-31", freq="ME", tz="UTC") + exp1 = date_range(start="2014-01-01", end="2014-12-31", freq="ME") + + idx2 = date_range(start="2014-01-01", end="2014-12-31", freq="D", tz="UTC") + exp2 = date_range(start="2014-01-01", end="2014-12-31", freq="D") + + idx3 = date_range(start="2014-01-01", end="2014-03-01", freq="h", tz="UTC") + exp3 = date_range(start="2014-01-01", end="2014-03-01", freq="h") + + idx4 = date_range(start="2014-08-01", end="2014-10-31", freq="min", tz="UTC") + exp4 = date_range(start="2014-08-01", end="2014-10-31", freq="min") + + for idx, expected in [(idx1, exp1), (idx2, exp2), (idx3, exp3), (idx4, exp4)]: + converted = idx.tz_convert(tz) + reset = converted.tz_convert(None) + tm.assert_index_equal(reset, expected) + assert reset.tzinfo is None + expected = converted.tz_convert("UTC").tz_localize(None) + expected = expected._with_freq("infer") + tm.assert_index_equal(reset, expected) + + def test_dti_tz_convert_tzlocal(self): + # GH#13583 + # tz_convert doesn't affect to internal + dti = date_range(start="2001-01-01", end="2001-03-01", tz="UTC") + dti2 = dti.tz_convert(dateutil.tz.tzlocal()) + tm.assert_numpy_array_equal(dti2.asi8, dti.asi8) + + dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal()) + dti2 = dti.tz_convert(None) + tm.assert_numpy_array_equal(dti2.asi8, dti.asi8) + + @pytest.mark.parametrize( + "tz", + [ + "US/Eastern", + "dateutil/US/Eastern", + pytz.timezone("US/Eastern"), + gettz("US/Eastern"), + ], + ) + def test_dti_tz_convert_utc_to_local_no_modify(self, tz): + rng = date_range("3/11/2012", "3/12/2012", freq="h", tz="utc") + rng_eastern = rng.tz_convert(tz) + + # Values are unmodified + tm.assert_numpy_array_equal(rng.asi8, rng_eastern.asi8) + + assert timezones.tz_compare(rng_eastern.tz, timezones.maybe_get_tz(tz)) + + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_tz_convert_unsorted(self, tzstr): + dr = date_range("2012-03-09", freq="h", periods=100, tz="utc") + dr = dr.tz_convert(tzstr) + + result = dr[::-1].hour + exp = dr.hour[::-1] + tm.assert_almost_equal(result, exp) diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index bdefdbbe5d5c9..5725220a6b409 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -135,6 +135,7 @@ def test_datetimeindex_accessors4(self): assert dti.is_month_start[0] == 1 + # TODO: belongs in offsets tests? def test_datetimeindex_accessors5(self): freq_m = to_offset("ME") bm = to_offset("BME") diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 4434f2b85d9c0..16b669e91018c 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -10,11 +10,7 @@ tzinfo, ) -import dateutil -from dateutil.tz import ( - gettz, - tzlocal, -) +from dateutil.tz import gettz import numpy as np import pytest import pytz @@ -23,7 +19,6 @@ conversion, timezones, ) -import pandas.util._test_decorators as td import pandas as pd from pandas import ( @@ -55,331 +50,10 @@ def dst(self, dt): return timedelta(0) -fixed_off = FixedOffset(-420, "-07:00") fixed_off_no_name = FixedOffset(-330, None) class TestDatetimeIndexTimezones: - # ------------------------------------------------------------- - # DatetimeIndex.tz_convert - def test_tz_convert_nat(self): - # GH#5546 - dates = [pd.NaT] - idx = DatetimeIndex(dates) - idx = idx.tz_localize("US/Pacific") - tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific")) - idx = idx.tz_convert("US/Eastern") - tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Eastern")) - idx = idx.tz_convert("UTC") - tm.assert_index_equal(idx, DatetimeIndex(dates, tz="UTC")) - - dates = ["2010-12-01 00:00", "2010-12-02 00:00", pd.NaT] - idx = DatetimeIndex(dates) - idx = idx.tz_localize("US/Pacific") - tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific")) - idx = idx.tz_convert("US/Eastern") - expected = ["2010-12-01 03:00", "2010-12-02 03:00", pd.NaT] - tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern")) - - idx = idx + pd.offsets.Hour(5) - expected = ["2010-12-01 08:00", "2010-12-02 08:00", pd.NaT] - tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern")) - idx = idx.tz_convert("US/Pacific") - expected = ["2010-12-01 05:00", "2010-12-02 05:00", pd.NaT] - tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific")) - - idx = idx + np.timedelta64(3, "h") - expected = ["2010-12-01 08:00", "2010-12-02 08:00", pd.NaT] - tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific")) - - idx = idx.tz_convert("US/Eastern") - expected = ["2010-12-01 11:00", "2010-12-02 11:00", pd.NaT] - tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern")) - - @pytest.mark.parametrize("prefix", ["", "dateutil/"]) - def test_dti_tz_convert_compat_timestamp(self, prefix): - strdates = ["1/1/2012", "3/1/2012", "4/1/2012"] - idx = DatetimeIndex(strdates, tz=prefix + "US/Eastern") - - conv = idx[0].tz_convert(prefix + "US/Pacific") - expected = idx.tz_convert(prefix + "US/Pacific")[0] - - assert conv == expected - - def test_dti_tz_convert_hour_overflow_dst(self): - # Regression test for: - # https://github.com/pandas-dev/pandas/issues/13306 - - # sorted case US/Eastern -> UTC - ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2009-05-12 09:50:32"] - tt = DatetimeIndex(ts).tz_localize("US/Eastern") - ut = tt.tz_convert("UTC") - expected = Index([13, 14, 13], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - # sorted case UTC -> US/Eastern - ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2009-05-12 13:50:32"] - tt = DatetimeIndex(ts).tz_localize("UTC") - ut = tt.tz_convert("US/Eastern") - expected = Index([9, 9, 9], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - # unsorted case US/Eastern -> UTC - ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2008-05-12 09:50:32"] - tt = DatetimeIndex(ts).tz_localize("US/Eastern") - ut = tt.tz_convert("UTC") - expected = Index([13, 14, 13], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - # unsorted case UTC -> US/Eastern - ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2008-05-12 13:50:32"] - tt = DatetimeIndex(ts).tz_localize("UTC") - ut = tt.tz_convert("US/Eastern") - expected = Index([9, 9, 9], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - @pytest.mark.parametrize("tz", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_tz_convert_hour_overflow_dst_timestamps(self, tz): - # Regression test for GH#13306 - - # sorted case US/Eastern -> UTC - ts = [ - Timestamp("2008-05-12 09:50:00", tz=tz), - Timestamp("2008-12-12 09:50:35", tz=tz), - Timestamp("2009-05-12 09:50:32", tz=tz), - ] - tt = DatetimeIndex(ts) - ut = tt.tz_convert("UTC") - expected = Index([13, 14, 13], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - # sorted case UTC -> US/Eastern - ts = [ - Timestamp("2008-05-12 13:50:00", tz="UTC"), - Timestamp("2008-12-12 14:50:35", tz="UTC"), - Timestamp("2009-05-12 13:50:32", tz="UTC"), - ] - tt = DatetimeIndex(ts) - ut = tt.tz_convert("US/Eastern") - expected = Index([9, 9, 9], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - # unsorted case US/Eastern -> UTC - ts = [ - Timestamp("2008-05-12 09:50:00", tz=tz), - Timestamp("2008-12-12 09:50:35", tz=tz), - Timestamp("2008-05-12 09:50:32", tz=tz), - ] - tt = DatetimeIndex(ts) - ut = tt.tz_convert("UTC") - expected = Index([13, 14, 13], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - # unsorted case UTC -> US/Eastern - ts = [ - Timestamp("2008-05-12 13:50:00", tz="UTC"), - Timestamp("2008-12-12 14:50:35", tz="UTC"), - Timestamp("2008-05-12 13:50:32", tz="UTC"), - ] - tt = DatetimeIndex(ts) - ut = tt.tz_convert("US/Eastern") - expected = Index([9, 9, 9], dtype=np.int32) - tm.assert_index_equal(ut.hour, expected) - - @pytest.mark.parametrize("freq, n", [("h", 1), ("min", 60), ("s", 3600)]) - def test_dti_tz_convert_trans_pos_plus_1__bug(self, freq, n): - # Regression test for tslib.tz_convert(vals, tz1, tz2). - # See https://github.com/pandas-dev/pandas/issues/4496 for details. - idx = date_range(datetime(2011, 3, 26, 23), datetime(2011, 3, 27, 1), freq=freq) - idx = idx.tz_localize("UTC") - idx = idx.tz_convert("Europe/Moscow") - - expected = np.repeat(np.array([3, 4, 5]), np.array([n, n, 1])) - tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) - - def test_dti_tz_convert_dst(self): - for freq, n in [("h", 1), ("min", 60), ("s", 3600)]: - # Start DST - idx = date_range( - "2014-03-08 23:00", "2014-03-09 09:00", freq=freq, tz="UTC" - ) - idx = idx.tz_convert("US/Eastern") - expected = np.repeat( - np.array([18, 19, 20, 21, 22, 23, 0, 1, 3, 4, 5]), - np.array([n, n, n, n, n, n, n, n, n, n, 1]), - ) - tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) - - idx = date_range( - "2014-03-08 18:00", "2014-03-09 05:00", freq=freq, tz="US/Eastern" - ) - idx = idx.tz_convert("UTC") - expected = np.repeat( - np.array([23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), - np.array([n, n, n, n, n, n, n, n, n, n, 1]), - ) - tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) - - # End DST - idx = date_range( - "2014-11-01 23:00", "2014-11-02 09:00", freq=freq, tz="UTC" - ) - idx = idx.tz_convert("US/Eastern") - expected = np.repeat( - np.array([19, 20, 21, 22, 23, 0, 1, 1, 2, 3, 4]), - np.array([n, n, n, n, n, n, n, n, n, n, 1]), - ) - tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) - - idx = date_range( - "2014-11-01 18:00", "2014-11-02 05:00", freq=freq, tz="US/Eastern" - ) - idx = idx.tz_convert("UTC") - expected = np.repeat( - np.array([22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - np.array([n, n, n, n, n, n, n, n, n, n, n, n, 1]), - ) - tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32)) - - # daily - # Start DST - idx = date_range("2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="UTC") - idx = idx.tz_convert("US/Eastern") - tm.assert_index_equal(idx.hour, Index([19, 19], dtype=np.int32)) - - idx = date_range( - "2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="US/Eastern" - ) - idx = idx.tz_convert("UTC") - tm.assert_index_equal(idx.hour, Index([5, 5], dtype=np.int32)) - - # End DST - idx = date_range("2014-11-01 00:00", "2014-11-02 00:00", freq="D", tz="UTC") - idx = idx.tz_convert("US/Eastern") - tm.assert_index_equal(idx.hour, Index([20, 20], dtype=np.int32)) - - idx = date_range( - "2014-11-01 00:00", "2014-11-02 000:00", freq="D", tz="US/Eastern" - ) - idx = idx.tz_convert("UTC") - tm.assert_index_equal(idx.hour, Index([4, 4], dtype=np.int32)) - - def test_tz_convert_roundtrip(self, tz_aware_fixture): - tz = tz_aware_fixture - idx1 = date_range(start="2014-01-01", end="2014-12-31", freq="ME", tz="UTC") - exp1 = date_range(start="2014-01-01", end="2014-12-31", freq="ME") - - idx2 = date_range(start="2014-01-01", end="2014-12-31", freq="D", tz="UTC") - exp2 = date_range(start="2014-01-01", end="2014-12-31", freq="D") - - idx3 = date_range(start="2014-01-01", end="2014-03-01", freq="h", tz="UTC") - exp3 = date_range(start="2014-01-01", end="2014-03-01", freq="h") - - idx4 = date_range(start="2014-08-01", end="2014-10-31", freq="min", tz="UTC") - exp4 = date_range(start="2014-08-01", end="2014-10-31", freq="min") - - for idx, expected in [(idx1, exp1), (idx2, exp2), (idx3, exp3), (idx4, exp4)]: - converted = idx.tz_convert(tz) - reset = converted.tz_convert(None) - tm.assert_index_equal(reset, expected) - assert reset.tzinfo is None - expected = converted.tz_convert("UTC").tz_localize(None) - expected = expected._with_freq("infer") - tm.assert_index_equal(reset, expected) - - def test_dti_tz_convert_tzlocal(self): - # GH#13583 - # tz_convert doesn't affect to internal - dti = date_range(start="2001-01-01", end="2001-03-01", tz="UTC") - dti2 = dti.tz_convert(dateutil.tz.tzlocal()) - tm.assert_numpy_array_equal(dti2.asi8, dti.asi8) - - dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal()) - dti2 = dti.tz_convert(None) - tm.assert_numpy_array_equal(dti2.asi8, dti.asi8) - - @pytest.mark.parametrize( - "tz", - [ - "US/Eastern", - "dateutil/US/Eastern", - pytz.timezone("US/Eastern"), - gettz("US/Eastern"), - ], - ) - def test_dti_tz_convert_utc_to_local_no_modify(self, tz): - rng = date_range("3/11/2012", "3/12/2012", freq="h", tz="utc") - rng_eastern = rng.tz_convert(tz) - - # Values are unmodified - tm.assert_numpy_array_equal(rng.asi8, rng_eastern.asi8) - - assert timezones.tz_compare(rng_eastern.tz, timezones.maybe_get_tz(tz)) - - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_tz_convert_unsorted(self, tzstr): - dr = date_range("2012-03-09", freq="h", periods=100, tz="utc") - dr = dr.tz_convert(tzstr) - - result = dr[::-1].hour - exp = dr.hour[::-1] - tm.assert_almost_equal(result, exp) - - # ------------------------------------------------------------- - # DatetimeIndex.normalize - - def test_normalize_tz(self): - rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="US/Eastern") - - result = rng.normalize() # does not preserve freq - expected = date_range("1/1/2000", periods=10, freq="D", tz="US/Eastern") - tm.assert_index_equal(result, expected._with_freq(None)) - - assert result.is_normalized - assert not rng.is_normalized - - rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="UTC") - - result = rng.normalize() - expected = date_range("1/1/2000", periods=10, freq="D", tz="UTC") - tm.assert_index_equal(result, expected) - - assert result.is_normalized - assert not rng.is_normalized - - rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal()) - result = rng.normalize() # does not preserve freq - expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal()) - tm.assert_index_equal(result, expected._with_freq(None)) - - assert result.is_normalized - assert not rng.is_normalized - - @td.skip_if_windows - @pytest.mark.parametrize( - "timezone", - [ - "US/Pacific", - "US/Eastern", - "UTC", - "Asia/Kolkata", - "Asia/Shanghai", - "Australia/Canberra", - ], - ) - def test_normalize_tz_local(self, timezone): - # GH#13459 - with tm.set_timezone(timezone): - rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal()) - - result = rng.normalize() - expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal()) - expected = expected._with_freq(None) - tm.assert_index_equal(result, expected) - - assert result.is_normalized - assert not rng.is_normalized - # ------------------------------------------------------------ # DatetimeIndex.__new__ @@ -634,36 +308,6 @@ def test_utc_box_timestamp_and_localize(self, tzstr): rng_eastern[0].tzinfo ) - def test_dti_to_pydatetime(self): - dt = dateutil.parser.parse("2012-06-13T01:39:00Z") - dt = dt.replace(tzinfo=tzlocal()) - - arr = np.array([dt], dtype=object) - - result = to_datetime(arr, utc=True) - assert result.tz is timezone.utc - - rng = date_range("2012-11-03 03:00", "2012-11-05 03:00", tz=tzlocal()) - arr = rng.to_pydatetime() - result = to_datetime(arr, utc=True) - assert result.tz is timezone.utc - - def test_dti_to_pydatetime_fizedtz(self): - dates = np.array( - [ - datetime(2000, 1, 1, tzinfo=fixed_off), - datetime(2000, 1, 2, tzinfo=fixed_off), - datetime(2000, 1, 3, tzinfo=fixed_off), - ] - ) - dti = DatetimeIndex(dates) - - result = dti.to_pydatetime() - tm.assert_numpy_array_equal(dates, result) - - result = dti._mpl_repr() - tm.assert_numpy_array_equal(dates, result) - @pytest.mark.parametrize("tz", [pytz.timezone("US/Central"), gettz("US/Central")]) def test_with_tz(self, tz): # just want it to work From 3220e8296f96de8bb55c423c39ea7102645de865 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 16:32:05 -0700 Subject: [PATCH 08/12] DTI tests --- .../datetimes/methods/test_isocalendar.py | 7 ++ .../indexes/datetimes/test_constructors.py | 58 +++++++++++++++++ .../tests/indexes/datetimes/test_datetime.py | 1 + .../tests/indexes/datetimes/test_indexing.py | 8 +++ .../indexes/datetimes/test_scalar_compat.py | 6 -- .../tests/indexes/datetimes/test_timezones.py | 65 ------------------- 6 files changed, 74 insertions(+), 71 deletions(-) diff --git a/pandas/tests/indexes/datetimes/methods/test_isocalendar.py b/pandas/tests/indexes/datetimes/methods/test_isocalendar.py index 128a8b3e10eb3..3f5a18675735a 100644 --- a/pandas/tests/indexes/datetimes/methods/test_isocalendar.py +++ b/pandas/tests/indexes/datetimes/methods/test_isocalendar.py @@ -18,3 +18,10 @@ def test_isocalendar_returns_correct_values_close_to_new_year_with_tz(): dtype="UInt32", ) tm.assert_frame_equal(result, expected_data_frame) + + +def test_dti_timestamp_isocalendar_fields(): + idx = tm.makeDateIndex(100) + expected = tuple(idx.isocalendar().iloc[-1].to_list()) + result = idx[-1].isocalendar() + assert result == expected diff --git a/pandas/tests/indexes/datetimes/test_constructors.py b/pandas/tests/indexes/datetimes/test_constructors.py index e16ca1b183705..fa58dbb7f8793 100644 --- a/pandas/tests/indexes/datetimes/test_constructors.py +++ b/pandas/tests/indexes/datetimes/test_constructors.py @@ -9,6 +9,8 @@ from operator import attrgetter import dateutil +import dateutil.tz +from dateutil.tz import gettz import numpy as np import pytest import pytz @@ -16,6 +18,7 @@ from pandas._libs.tslibs import ( OutOfBoundsDatetime, astype_overflowsafe, + timezones, ) import pandas as pd @@ -32,6 +35,7 @@ DatetimeArray, period_array, ) +from pandas.tests.indexes.datetimes.test_timezones import FixedOffset class TestDatetimeIndex: @@ -958,6 +962,60 @@ def test_index_constructor_with_numpy_object_array_and_timestamp_tz_with_nan(sel expected = DatetimeIndex([Timestamp("2019", tz="UTC"), pd.NaT]) tm.assert_index_equal(result, expected) + @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) + def test_dti_from_tzaware_datetime(self, tz): + d = [datetime(2012, 8, 19, tzinfo=tz)] + + index = DatetimeIndex(d) + assert timezones.tz_compare(index.tz, tz) + + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_dti_tz_constructors(self, tzstr): + """Test different DatetimeIndex constructions with timezone + Follow-up of GH#4229 + """ + arr = ["11/10/2005 08:00:00", "11/10/2005 09:00:00"] + + idx1 = to_datetime(arr).tz_localize(tzstr) + idx2 = date_range(start="2005-11-10 08:00:00", freq="h", periods=2, tz=tzstr) + idx2 = idx2._with_freq(None) # the others all have freq=None + idx3 = DatetimeIndex(arr, tz=tzstr) + idx4 = DatetimeIndex(np.array(arr), tz=tzstr) + + for other in [idx2, idx3, idx4]: + tm.assert_index_equal(idx1, other) + + def test_dti_construction_univalent(self): + rng = date_range("03/12/2012 00:00", periods=10, freq="W-FRI", tz="US/Eastern") + rng2 = DatetimeIndex(data=rng, tz="US/Eastern") + tm.assert_index_equal(rng, rng2) + + @pytest.mark.parametrize("prefix", ["", "dateutil/"]) + def test_dti_constructor_static_tzinfo(self, prefix): + # it works! + index = DatetimeIndex([datetime(2012, 1, 1)], tz=prefix + "EST") + index.hour + index[0] + + def test_dti_constructor_with_fixed_tz(self): + off = FixedOffset(420, "+07:00") + start = datetime(2012, 3, 11, 5, 0, 0, tzinfo=off) + end = datetime(2012, 6, 11, 5, 0, 0, tzinfo=off) + rng = date_range(start=start, end=end) + assert off == rng.tz + + rng2 = date_range(start, periods=len(rng), tz=off) + tm.assert_index_equal(rng, rng2) + + rng3 = date_range("3/11/2012 05:00:00+07:00", "6/11/2012 05:00:00+07:00") + assert (rng.values == rng3.values).all() + + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_dti_convert_datetime_list(self, tzstr): + dr = date_range("2012-06-02", periods=10, tz=tzstr, name="foo") + dr2 = DatetimeIndex(list(dr), name="foo", freq="D") + tm.assert_index_equal(dr, dr2) + class TestTimeSeries: def test_dti_constructor_preserve_dti_freq(self): diff --git a/pandas/tests/indexes/datetimes/test_datetime.py b/pandas/tests/indexes/datetimes/test_datetime.py index d5999436b915d..89c4433db1c7b 100644 --- a/pandas/tests/indexes/datetimes/test_datetime.py +++ b/pandas/tests/indexes/datetimes/test_datetime.py @@ -70,6 +70,7 @@ def test_misc_coverage(self): result = rng.groupby(rng.day) assert isinstance(next(iter(result.values()))[0], Timestamp) + # TODO: belongs in frame groupby tests? def test_groupby_function_tuple_1677(self): df = DataFrame( np.random.default_rng(2).random(100), diff --git a/pandas/tests/indexes/datetimes/test_indexing.py b/pandas/tests/indexes/datetimes/test_indexing.py index 37c580c98b139..da11920ac1cba 100644 --- a/pandas/tests/indexes/datetimes/test_indexing.py +++ b/pandas/tests/indexes/datetimes/test_indexing.py @@ -214,6 +214,14 @@ def test_where_tz(self): class TestTake: + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_dti_take_dont_lose_meta(self, tzstr): + rng = date_range("1/1/2000", periods=20, tz=tzstr) + + result = rng.take(range(5)) + assert result.tz == rng.tz + assert result.freq == rng.freq + def test_take_nan_first_datetime(self): index = DatetimeIndex([pd.NaT, Timestamp("20130101"), Timestamp("20130102")]) result = index.take([-1, 0, 1]) diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index 8545e0506210e..d882731a7b3fb 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -49,9 +49,3 @@ def test_dti_timestamp_fields(self, field): result = getattr(Timestamp(idx[-1]), field) assert result == expected - - def test_dti_timestamp_isocalendar_fields(self): - idx = tm.makeDateIndex(100) - expected = tuple(idx.isocalendar().iloc[-1].to_list()) - result = idx[-1].isocalendar() - assert result == expected diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 16b669e91018c..b0a3bd6aaf5e7 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -54,63 +54,6 @@ def dst(self, dt): class TestDatetimeIndexTimezones: - # ------------------------------------------------------------ - # DatetimeIndex.__new__ - - @pytest.mark.parametrize("prefix", ["", "dateutil/"]) - def test_dti_constructor_static_tzinfo(self, prefix): - # it works! - index = DatetimeIndex([datetime(2012, 1, 1)], tz=prefix + "EST") - index.hour - index[0] - - def test_dti_constructor_with_fixed_tz(self): - off = FixedOffset(420, "+07:00") - start = datetime(2012, 3, 11, 5, 0, 0, tzinfo=off) - end = datetime(2012, 6, 11, 5, 0, 0, tzinfo=off) - rng = date_range(start=start, end=end) - assert off == rng.tz - - rng2 = date_range(start, periods=len(rng), tz=off) - tm.assert_index_equal(rng, rng2) - - rng3 = date_range("3/11/2012 05:00:00+07:00", "6/11/2012 05:00:00+07:00") - assert (rng.values == rng3.values).all() - - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_convert_datetime_list(self, tzstr): - dr = date_range("2012-06-02", periods=10, tz=tzstr, name="foo") - dr2 = DatetimeIndex(list(dr), name="foo", freq="D") - tm.assert_index_equal(dr, dr2) - - def test_dti_construction_univalent(self): - rng = date_range("03/12/2012 00:00", periods=10, freq="W-FRI", tz="US/Eastern") - rng2 = DatetimeIndex(data=rng, tz="US/Eastern") - tm.assert_index_equal(rng, rng2) - - @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) - def test_dti_from_tzaware_datetime(self, tz): - d = [datetime(2012, 8, 19, tzinfo=tz)] - - index = DatetimeIndex(d) - assert timezones.tz_compare(index.tz, tz) - - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_tz_constructors(self, tzstr): - """Test different DatetimeIndex constructions with timezone - Follow-up of GH#4229 - """ - arr = ["11/10/2005 08:00:00", "11/10/2005 09:00:00"] - - idx1 = to_datetime(arr).tz_localize(tzstr) - idx2 = date_range(start="2005-11-10 08:00:00", freq="h", periods=2, tz=tzstr) - idx2 = idx2._with_freq(None) # the others all have freq=None - idx3 = DatetimeIndex(arr, tz=tzstr) - idx4 = DatetimeIndex(np.array(arr), tz=tzstr) - - for other in [idx2, idx3, idx4]: - tm.assert_index_equal(idx1, other) - # ------------------------------------------------------------- # Unsorted @@ -278,14 +221,6 @@ def test_dti_with_timezone_repr(self, tzstr): rng_repr = repr(rng_eastern) assert "2010-04-13 00:00:00" in rng_repr - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_take_dont_lose_meta(self, tzstr): - rng = date_range("1/1/2000", periods=20, tz=tzstr) - - result = rng.take(range(5)) - assert result.tz == rng.tz - assert result.freq == rng.freq - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) def test_utc_box_timestamp_and_localize(self, tzstr): tz = timezones.maybe_get_tz(tzstr) From 2be4dda740b6cb5d604fa29e12ff54031ce04c50 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 16:35:07 -0700 Subject: [PATCH 09/12] split tests --- .../indexes/datetimes/methods/test_delete.py | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/pandas/tests/indexes/datetimes/methods/test_delete.py b/pandas/tests/indexes/datetimes/methods/test_delete.py index 90dfdb46cdfa5..620e41bf2d43a 100644 --- a/pandas/tests/indexes/datetimes/methods/test_delete.py +++ b/pandas/tests/indexes/datetimes/methods/test_delete.py @@ -40,28 +40,29 @@ def test_delete(self): # either depending on numpy version idx.delete(5) - for tz in [None, "Asia/Tokyo", "US/Pacific"]: - idx = date_range( - start="2000-01-01 09:00", periods=10, freq="h", name="idx", tz=tz - ) - - expected = date_range( - start="2000-01-01 10:00", periods=9, freq="h", name="idx", tz=tz - ) - result = idx.delete(0) - tm.assert_index_equal(result, expected) - assert result.name == expected.name - assert result.freqstr == "h" - assert result.tz == expected.tz + @pytest.mark.parametrize("tz", [None, "Asia/Tokyo", "US/Pacific"]) + def test_delete2(self, tz): + idx = date_range( + start="2000-01-01 09:00", periods=10, freq="h", name="idx", tz=tz + ) - expected = date_range( - start="2000-01-01 09:00", periods=9, freq="h", name="idx", tz=tz - ) - result = idx.delete(-1) - tm.assert_index_equal(result, expected) - assert result.name == expected.name - assert result.freqstr == "h" - assert result.tz == expected.tz + expected = date_range( + start="2000-01-01 10:00", periods=9, freq="h", name="idx", tz=tz + ) + result = idx.delete(0) + tm.assert_index_equal(result, expected) + assert result.name == expected.name + assert result.freqstr == "h" + assert result.tz == expected.tz + + expected = date_range( + start="2000-01-01 09:00", periods=9, freq="h", name="idx", tz=tz + ) + result = idx.delete(-1) + tm.assert_index_equal(result, expected) + assert result.name == expected.name + assert result.freqstr == "h" + assert result.tz == expected.tz def test_delete_slice(self): idx = date_range(start="2000-01-01", periods=10, freq="D", name="idx") @@ -101,38 +102,40 @@ def test_delete_slice(self): assert result.name == expected.name assert result.freq == expected.freq - for tz in [None, "Asia/Tokyo", "US/Pacific"]: - ts = Series( - 1, - index=date_range( - "2000-01-01 09:00", periods=10, freq="h", name="idx", tz=tz - ), - ) - # preserve freq - result = ts.drop(ts.index[:5]).index - expected = date_range( - "2000-01-01 14:00", periods=5, freq="h", name="idx", tz=tz - ) - tm.assert_index_equal(result, expected) - assert result.name == expected.name - assert result.freq == expected.freq - assert result.tz == expected.tz - - # reset freq to None - result = ts.drop(ts.index[[1, 3, 5, 7, 9]]).index - expected = DatetimeIndex( - [ - "2000-01-01 09:00", - "2000-01-01 11:00", - "2000-01-01 13:00", - "2000-01-01 15:00", - "2000-01-01 17:00", - ], - freq=None, - name="idx", - tz=tz, - ) - tm.assert_index_equal(result, expected) - assert result.name == expected.name - assert result.freq == expected.freq - assert result.tz == expected.tz + # TODO: belongs in Series.drop tests? + @pytest.mark.parametrize("tz", [None, "Asia/Tokyo", "US/Pacific"]) + def test_delete_slice2(self, tz): + ts = Series( + 1, + index=date_range( + "2000-01-01 09:00", periods=10, freq="h", name="idx", tz=tz + ), + ) + # preserve freq + result = ts.drop(ts.index[:5]).index + expected = date_range( + "2000-01-01 14:00", periods=5, freq="h", name="idx", tz=tz + ) + tm.assert_index_equal(result, expected) + assert result.name == expected.name + assert result.freq == expected.freq + assert result.tz == expected.tz + + # reset freq to None + result = ts.drop(ts.index[[1, 3, 5, 7, 9]]).index + expected = DatetimeIndex( + [ + "2000-01-01 09:00", + "2000-01-01 11:00", + "2000-01-01 13:00", + "2000-01-01 15:00", + "2000-01-01 17:00", + ], + freq=None, + name="idx", + tz=tz, + ) + tm.assert_index_equal(result, expected) + assert result.name == expected.name + assert result.freq == expected.freq + assert result.tz == expected.tz From c6e0a57741493a875dcd086e38396bef90593ef6 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 20 Oct 2023 16:49:51 -0700 Subject: [PATCH 10/12] DTI tests --- .../tests/indexes/datetimes/test_formats.py | 9 + pandas/tests/indexes/datetimes/test_misc.py | 282 ----------------- .../indexes/datetimes/test_scalar_compat.py | 284 +++++++++++++++++- .../tests/indexes/datetimes/test_timezones.py | 63 ---- pandas/tests/tseries/offsets/test_offsets.py | 49 +++ 5 files changed, 339 insertions(+), 348 deletions(-) delete mode 100644 pandas/tests/indexes/datetimes/test_misc.py diff --git a/pandas/tests/indexes/datetimes/test_formats.py b/pandas/tests/indexes/datetimes/test_formats.py index deed5926bca91..a181c38e2102a 100644 --- a/pandas/tests/indexes/datetimes/test_formats.py +++ b/pandas/tests/indexes/datetimes/test_formats.py @@ -59,6 +59,15 @@ def test_get_values_for_csv(): class TestDatetimeIndexRendering: + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_dti_with_timezone_repr(self, tzstr): + rng = pd.date_range("4/13/2010", "5/6/2010") + + rng_eastern = rng.tz_localize(tzstr) + + rng_repr = repr(rng_eastern) + assert "2010-04-13 00:00:00" in rng_repr + def test_dti_repr_dates(self): text = str(pd.to_datetime([datetime(2013, 1, 1), datetime(2014, 1, 1)])) assert "['2013-01-01'," in text diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py deleted file mode 100644 index 5725220a6b409..0000000000000 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ /dev/null @@ -1,282 +0,0 @@ -import calendar -from datetime import datetime -import locale -import unicodedata - -import numpy as np -import pytest - -import pandas as pd -from pandas import ( - DatetimeIndex, - Index, - Timestamp, - date_range, - offsets, -) -import pandas._testing as tm -from pandas.core.arrays import DatetimeArray - -from pandas.tseries.frequencies import to_offset - - -class TestDatetime64: - def test_no_millisecond_field(self): - msg = "type object 'DatetimeIndex' has no attribute 'millisecond'" - with pytest.raises(AttributeError, match=msg): - DatetimeIndex.millisecond - - msg = "'DatetimeIndex' object has no attribute 'millisecond'" - with pytest.raises(AttributeError, match=msg): - DatetimeIndex([]).millisecond - - @pytest.mark.parametrize("tz", [None, "US/Eastern"]) - def test_datetimeindex_accessors(self, tz): - # GH#13303 - dti = date_range(freq="D", start=datetime(1998, 1, 1), periods=365, tz=tz) - assert dti.year[0] == 1998 - assert dti.month[0] == 1 - assert dti.day[0] == 1 - assert dti.hour[0] == 0 - assert dti.minute[0] == 0 - assert dti.second[0] == 0 - assert dti.microsecond[0] == 0 - assert dti.dayofweek[0] == 3 - - assert dti.dayofyear[0] == 1 - assert dti.dayofyear[120] == 121 - - assert dti.isocalendar().week.iloc[0] == 1 - assert dti.isocalendar().week.iloc[120] == 18 - - assert dti.quarter[0] == 1 - assert dti.quarter[120] == 2 - - assert dti.days_in_month[0] == 31 - assert dti.days_in_month[90] == 30 - - assert dti.is_month_start[0] - assert not dti.is_month_start[1] - assert dti.is_month_start[31] - assert dti.is_quarter_start[0] - assert dti.is_quarter_start[90] - assert dti.is_year_start[0] - assert not dti.is_year_start[364] - assert not dti.is_month_end[0] - assert dti.is_month_end[30] - assert not dti.is_month_end[31] - assert dti.is_month_end[364] - assert not dti.is_quarter_end[0] - assert not dti.is_quarter_end[30] - assert dti.is_quarter_end[89] - assert dti.is_quarter_end[364] - assert not dti.is_year_end[0] - assert dti.is_year_end[364] - - assert len(dti.year) == 365 - assert len(dti.month) == 365 - assert len(dti.day) == 365 - assert len(dti.hour) == 365 - assert len(dti.minute) == 365 - assert len(dti.second) == 365 - assert len(dti.microsecond) == 365 - assert len(dti.dayofweek) == 365 - assert len(dti.dayofyear) == 365 - assert len(dti.isocalendar()) == 365 - assert len(dti.quarter) == 365 - assert len(dti.is_month_start) == 365 - assert len(dti.is_month_end) == 365 - assert len(dti.is_quarter_start) == 365 - assert len(dti.is_quarter_end) == 365 - assert len(dti.is_year_start) == 365 - assert len(dti.is_year_end) == 365 - - dti.name = "name" - - # non boolean accessors -> return Index - for accessor in DatetimeArray._field_ops: - res = getattr(dti, accessor) - assert len(res) == 365 - assert isinstance(res, Index) - assert res.name == "name" - - # boolean accessors -> return array - for accessor in DatetimeArray._bool_ops: - res = getattr(dti, accessor) - assert len(res) == 365 - assert isinstance(res, np.ndarray) - - # test boolean indexing - res = dti[dti.is_quarter_start] - exp = dti[[0, 90, 181, 273]] - tm.assert_index_equal(res, exp) - res = dti[dti.is_leap_year] - exp = DatetimeIndex([], freq="D", tz=dti.tz, name="name") - tm.assert_index_equal(res, exp) - - def test_datetimeindex_accessors2(self): - dti = date_range(freq="BQ-FEB", start=datetime(1998, 1, 1), periods=4) - - assert sum(dti.is_quarter_start) == 0 - assert sum(dti.is_quarter_end) == 4 - assert sum(dti.is_year_start) == 0 - assert sum(dti.is_year_end) == 1 - - def test_datetimeindex_accessors3(self): - # Ensure is_start/end accessors throw ValueError for CustomBusinessDay, - bday_egypt = offsets.CustomBusinessDay(weekmask="Sun Mon Tue Wed Thu") - dti = date_range(datetime(2013, 4, 30), periods=5, freq=bday_egypt) - msg = "Custom business days is not supported by is_month_start" - with pytest.raises(ValueError, match=msg): - dti.is_month_start - - def test_datetimeindex_accessors4(self): - dti = DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-03"]) - - assert dti.is_month_start[0] == 1 - - # TODO: belongs in offsets tests? - def test_datetimeindex_accessors5(self): - freq_m = to_offset("ME") - bm = to_offset("BME") - qfeb = to_offset("Q-FEB") - qsfeb = to_offset("QS-FEB") - bq = to_offset("BQ") - bqs_apr = to_offset("BQS-APR") - as_nov = to_offset("YS-NOV") - - tests = [ - (freq_m.is_month_start(Timestamp("2013-06-01")), 1), - (bm.is_month_start(Timestamp("2013-06-01")), 0), - (freq_m.is_month_start(Timestamp("2013-06-03")), 0), - (bm.is_month_start(Timestamp("2013-06-03")), 1), - (qfeb.is_month_end(Timestamp("2013-02-28")), 1), - (qfeb.is_quarter_end(Timestamp("2013-02-28")), 1), - (qfeb.is_year_end(Timestamp("2013-02-28")), 1), - (qfeb.is_month_start(Timestamp("2013-03-01")), 1), - (qfeb.is_quarter_start(Timestamp("2013-03-01")), 1), - (qfeb.is_year_start(Timestamp("2013-03-01")), 1), - (qsfeb.is_month_end(Timestamp("2013-03-31")), 1), - (qsfeb.is_quarter_end(Timestamp("2013-03-31")), 0), - (qsfeb.is_year_end(Timestamp("2013-03-31")), 0), - (qsfeb.is_month_start(Timestamp("2013-02-01")), 1), - (qsfeb.is_quarter_start(Timestamp("2013-02-01")), 1), - (qsfeb.is_year_start(Timestamp("2013-02-01")), 1), - (bq.is_month_end(Timestamp("2013-06-30")), 0), - (bq.is_quarter_end(Timestamp("2013-06-30")), 0), - (bq.is_year_end(Timestamp("2013-06-30")), 0), - (bq.is_month_end(Timestamp("2013-06-28")), 1), - (bq.is_quarter_end(Timestamp("2013-06-28")), 1), - (bq.is_year_end(Timestamp("2013-06-28")), 0), - (bqs_apr.is_month_end(Timestamp("2013-06-30")), 0), - (bqs_apr.is_quarter_end(Timestamp("2013-06-30")), 0), - (bqs_apr.is_year_end(Timestamp("2013-06-30")), 0), - (bqs_apr.is_month_end(Timestamp("2013-06-28")), 1), - (bqs_apr.is_quarter_end(Timestamp("2013-06-28")), 1), - (bqs_apr.is_year_end(Timestamp("2013-03-29")), 1), - (as_nov.is_year_start(Timestamp("2013-11-01")), 1), - (as_nov.is_year_end(Timestamp("2013-10-31")), 1), - (Timestamp("2012-02-01").days_in_month, 29), - (Timestamp("2013-02-01").days_in_month, 28), - ] - - for ts, value in tests: - assert ts == value - - def test_datetimeindex_accessors6(self): - # GH 6538: Check that DatetimeIndex and its TimeStamp elements - # return the same weekofyear accessor close to new year w/ tz - dates = ["2013/12/29", "2013/12/30", "2013/12/31"] - dates = DatetimeIndex(dates, tz="Europe/Brussels") - expected = [52, 1, 1] - assert dates.isocalendar().week.tolist() == expected - assert [d.weekofyear for d in dates] == expected - - # GH 12806 - # error: Unsupported operand types for + ("List[None]" and "List[str]") - @pytest.mark.parametrize( - "time_locale", [None] + tm.get_locales() # type: ignore[operator] - ) - def test_datetime_name_accessors(self, time_locale): - # Test Monday -> Sunday and January -> December, in that sequence - if time_locale is None: - # If the time_locale is None, day-name and month_name should - # return the english attributes - expected_days = [ - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - "Sunday", - ] - expected_months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ] - else: - with tm.set_locale(time_locale, locale.LC_TIME): - expected_days = calendar.day_name[:] - expected_months = calendar.month_name[1:] - - # GH#11128 - dti = date_range(freq="D", start=datetime(1998, 1, 1), periods=365) - english_days = [ - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - "Sunday", - ] - for day, name, eng_name in zip(range(4, 11), expected_days, english_days): - name = name.capitalize() - assert dti.day_name(locale=time_locale)[day] == name - assert dti.day_name(locale=None)[day] == eng_name - ts = Timestamp(datetime(2016, 4, day)) - assert ts.day_name(locale=time_locale) == name - dti = dti.append(DatetimeIndex([pd.NaT])) - assert np.isnan(dti.day_name(locale=time_locale)[-1]) - ts = Timestamp(pd.NaT) - assert np.isnan(ts.day_name(locale=time_locale)) - - # GH#12805 - dti = date_range(freq="ME", start="2012", end="2013") - result = dti.month_name(locale=time_locale) - expected = Index([month.capitalize() for month in expected_months]) - - # work around different normalization schemes - # https://github.com/pandas-dev/pandas/issues/22342 - result = result.str.normalize("NFD") - expected = expected.str.normalize("NFD") - - tm.assert_index_equal(result, expected) - - for date, expected in zip(dti, expected_months): - result = date.month_name(locale=time_locale) - expected = expected.capitalize() - - result = unicodedata.normalize("NFD", result) - expected = unicodedata.normalize("NFD", result) - - assert result == expected - dti = dti.append(DatetimeIndex([pd.NaT])) - assert np.isnan(dti.month_name(locale=time_locale)[-1]) - - def test_nanosecond_field(self): - dti = DatetimeIndex(np.arange(10)) - expected = Index(np.arange(10, dtype=np.int32)) - - tm.assert_index_equal(dti.nanosecond, expected) diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index d882731a7b3fb..6fbfe27acc0e0 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -2,29 +2,91 @@ Tests for DatetimeIndex methods behaving like their Timestamp counterparts """ +import calendar +from datetime import ( + date, + datetime, + time, +) +import locale +import unicodedata + +import numpy as np import pytest -import pandas as pd +from pandas._libs.tslibs import timezones + from pandas import ( + DatetimeIndex, + Index, + NaT, Timestamp, date_range, + offsets, ) import pandas._testing as tm +from pandas.core.arrays import DatetimeArray class TestDatetimeIndexOps: + def test_dti_no_millisecond_field(self): + msg = "type object 'DatetimeIndex' has no attribute 'millisecond'" + with pytest.raises(AttributeError, match=msg): + DatetimeIndex.millisecond + + msg = "'DatetimeIndex' object has no attribute 'millisecond'" + with pytest.raises(AttributeError, match=msg): + DatetimeIndex([]).millisecond + def test_dti_time(self): rng = date_range("1/1/2000", freq="12min", periods=10) - result = pd.Index(rng).time + result = Index(rng).time expected = [t.time() for t in rng] assert (result == expected).all() def test_dti_date(self): rng = date_range("1/1/2000", freq="12h", periods=10) - result = pd.Index(rng).date + result = Index(rng).date expected = [t.date() for t in rng] assert (result == expected).all() + @pytest.mark.parametrize( + "dtype", + [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"], + ) + def test_dti_date2(self, dtype): + # Regression test for GH#21230 + expected = np.array([date(2018, 6, 4), NaT]) + + index = DatetimeIndex(["2018-06-04 10:00:00", NaT], dtype=dtype) + result = index.date + + tm.assert_numpy_array_equal(result, expected) + + @pytest.mark.parametrize( + "dtype", + [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"], + ) + def test_dti_time2(self, dtype): + # Regression test for GH#21267 + expected = np.array([time(10, 20, 30), NaT]) + + index = DatetimeIndex(["2018-06-04 10:20:30", NaT], dtype=dtype) + result = index.time + + tm.assert_numpy_array_equal(result, expected) + + def test_dti_timetz(self, tz_naive_fixture): + # GH#21358 + tz = timezones.maybe_get_tz(tz_naive_fixture) + + expected = np.array([time(10, 20, 30, tzinfo=tz), NaT]) + + index = DatetimeIndex(["2018-06-04 10:20:30", NaT], tz=tz) + result = index.timetz + + tm.assert_numpy_array_equal(result, expected) + @pytest.mark.parametrize( "field", [ @@ -49,3 +111,219 @@ def test_dti_timestamp_fields(self, field): result = getattr(Timestamp(idx[-1]), field) assert result == expected + + def test_dti_nanosecond(self): + dti = DatetimeIndex(np.arange(10)) + expected = Index(np.arange(10, dtype=np.int32)) + + tm.assert_index_equal(dti.nanosecond, expected) + + @pytest.mark.parametrize("prefix", ["", "dateutil/"]) + def test_dti_hour_tzaware(self, prefix): + strdates = ["1/1/2012", "3/1/2012", "4/1/2012"] + rng = DatetimeIndex(strdates, tz=prefix + "US/Eastern") + assert (rng.hour == 0).all() + + # a more unusual time zone, GH#1946 + dr = date_range( + "2011-10-02 00:00", freq="h", periods=10, tz=prefix + "America/Atikokan" + ) + + expected = Index(np.arange(10, dtype=np.int32)) + tm.assert_index_equal(dr.hour, expected) + + # GH#12806 + # error: Unsupported operand types for + ("List[None]" and "List[str]") + @pytest.mark.parametrize( + "time_locale", [None] + tm.get_locales() # type: ignore[operator] + ) + def test_day_name_month_name(self, time_locale): + # Test Monday -> Sunday and January -> December, in that sequence + if time_locale is None: + # If the time_locale is None, day-name and month_name should + # return the english attributes + expected_days = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ] + expected_months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + else: + with tm.set_locale(time_locale, locale.LC_TIME): + expected_days = calendar.day_name[:] + expected_months = calendar.month_name[1:] + + # GH#11128 + dti = date_range(freq="D", start=datetime(1998, 1, 1), periods=365) + english_days = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ] + for day, name, eng_name in zip(range(4, 11), expected_days, english_days): + name = name.capitalize() + assert dti.day_name(locale=time_locale)[day] == name + assert dti.day_name(locale=None)[day] == eng_name + ts = Timestamp(datetime(2016, 4, day)) + assert ts.day_name(locale=time_locale) == name + dti = dti.append(DatetimeIndex([NaT])) + assert np.isnan(dti.day_name(locale=time_locale)[-1]) + ts = Timestamp(NaT) + assert np.isnan(ts.day_name(locale=time_locale)) + + # GH#12805 + dti = date_range(freq="ME", start="2012", end="2013") + result = dti.month_name(locale=time_locale) + expected = Index([month.capitalize() for month in expected_months]) + + # work around different normalization schemes GH#22342 + result = result.str.normalize("NFD") + expected = expected.str.normalize("NFD") + + tm.assert_index_equal(result, expected) + + for item, expected in zip(dti, expected_months): + result = item.month_name(locale=time_locale) + expected = expected.capitalize() + + result = unicodedata.normalize("NFD", result) + expected = unicodedata.normalize("NFD", result) + + assert result == expected + dti = dti.append(DatetimeIndex([NaT])) + assert np.isnan(dti.month_name(locale=time_locale)[-1]) + + def test_dti_week(self): + # GH#6538: Check that DatetimeIndex and its TimeStamp elements + # return the same weekofyear accessor close to new year w/ tz + dates = ["2013/12/29", "2013/12/30", "2013/12/31"] + dates = DatetimeIndex(dates, tz="Europe/Brussels") + expected = [52, 1, 1] + assert dates.isocalendar().week.tolist() == expected + assert [d.weekofyear for d in dates] == expected + + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_dti_fields(self, tz): + # GH#13303 + dti = date_range(freq="D", start=datetime(1998, 1, 1), periods=365, tz=tz) + assert dti.year[0] == 1998 + assert dti.month[0] == 1 + assert dti.day[0] == 1 + assert dti.hour[0] == 0 + assert dti.minute[0] == 0 + assert dti.second[0] == 0 + assert dti.microsecond[0] == 0 + assert dti.dayofweek[0] == 3 + + assert dti.dayofyear[0] == 1 + assert dti.dayofyear[120] == 121 + + assert dti.isocalendar().week.iloc[0] == 1 + assert dti.isocalendar().week.iloc[120] == 18 + + assert dti.quarter[0] == 1 + assert dti.quarter[120] == 2 + + assert dti.days_in_month[0] == 31 + assert dti.days_in_month[90] == 30 + + assert dti.is_month_start[0] + assert not dti.is_month_start[1] + assert dti.is_month_start[31] + assert dti.is_quarter_start[0] + assert dti.is_quarter_start[90] + assert dti.is_year_start[0] + assert not dti.is_year_start[364] + assert not dti.is_month_end[0] + assert dti.is_month_end[30] + assert not dti.is_month_end[31] + assert dti.is_month_end[364] + assert not dti.is_quarter_end[0] + assert not dti.is_quarter_end[30] + assert dti.is_quarter_end[89] + assert dti.is_quarter_end[364] + assert not dti.is_year_end[0] + assert dti.is_year_end[364] + + assert len(dti.year) == 365 + assert len(dti.month) == 365 + assert len(dti.day) == 365 + assert len(dti.hour) == 365 + assert len(dti.minute) == 365 + assert len(dti.second) == 365 + assert len(dti.microsecond) == 365 + assert len(dti.dayofweek) == 365 + assert len(dti.dayofyear) == 365 + assert len(dti.isocalendar()) == 365 + assert len(dti.quarter) == 365 + assert len(dti.is_month_start) == 365 + assert len(dti.is_month_end) == 365 + assert len(dti.is_quarter_start) == 365 + assert len(dti.is_quarter_end) == 365 + assert len(dti.is_year_start) == 365 + assert len(dti.is_year_end) == 365 + + dti.name = "name" + + # non boolean accessors -> return Index + for accessor in DatetimeArray._field_ops: + res = getattr(dti, accessor) + assert len(res) == 365 + assert isinstance(res, Index) + assert res.name == "name" + + # boolean accessors -> return array + for accessor in DatetimeArray._bool_ops: + res = getattr(dti, accessor) + assert len(res) == 365 + assert isinstance(res, np.ndarray) + + # test boolean indexing + res = dti[dti.is_quarter_start] + exp = dti[[0, 90, 181, 273]] + tm.assert_index_equal(res, exp) + res = dti[dti.is_leap_year] + exp = DatetimeIndex([], freq="D", tz=dti.tz, name="name") + tm.assert_index_equal(res, exp) + + def test_dti_is_year_quarter_start(self): + dti = date_range(freq="BQ-FEB", start=datetime(1998, 1, 1), periods=4) + + assert sum(dti.is_quarter_start) == 0 + assert sum(dti.is_quarter_end) == 4 + assert sum(dti.is_year_start) == 0 + assert sum(dti.is_year_end) == 1 + + def test_dti_is_month_start(self): + dti = DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-03"]) + + assert dti.is_month_start[0] == 1 + + def test_dti_is_month_start_custom(self): + # Ensure is_start/end accessors throw ValueError for CustomBusinessDay, + bday_egypt = offsets.CustomBusinessDay(weekmask="Sun Mon Tue Wed Thu") + dti = date_range(datetime(2013, 4, 30), periods=5, freq=bday_egypt) + msg = "Custom business days is not supported by is_month_start" + with pytest.raises(ValueError, match=msg): + dti.is_month_start diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index b0a3bd6aaf5e7..379c727f4ed0f 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -2,9 +2,7 @@ Tests for DatetimeIndex timezone-related methods """ from datetime import ( - date, datetime, - time, timedelta, timezone, tzinfo, @@ -23,7 +21,6 @@ import pandas as pd from pandas import ( DatetimeIndex, - Index, Timestamp, bdate_range, date_range, @@ -57,43 +54,6 @@ class TestDatetimeIndexTimezones: # ------------------------------------------------------------- # Unsorted - @pytest.mark.parametrize( - "dtype", - [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"], - ) - def test_date_accessor(self, dtype): - # Regression test for GH#21230 - expected = np.array([date(2018, 6, 4), pd.NaT]) - - index = DatetimeIndex(["2018-06-04 10:00:00", pd.NaT], dtype=dtype) - result = index.date - - tm.assert_numpy_array_equal(result, expected) - - @pytest.mark.parametrize( - "dtype", - [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"], - ) - def test_time_accessor(self, dtype): - # Regression test for GH#21267 - expected = np.array([time(10, 20, 30), pd.NaT]) - - index = DatetimeIndex(["2018-06-04 10:20:30", pd.NaT], dtype=dtype) - result = index.time - - tm.assert_numpy_array_equal(result, expected) - - def test_timetz_accessor(self, tz_naive_fixture): - # GH21358 - tz = timezones.maybe_get_tz(tz_naive_fixture) - - expected = np.array([time(10, 20, 30, tzinfo=tz), pd.NaT]) - - index = DatetimeIndex(["2018-06-04 10:20:30", pd.NaT], tz=tz) - result = index.timetz - - tm.assert_numpy_array_equal(result, expected) - def test_dti_drop_dont_lose_tz(self): # GH#2621 ind = date_range("2012-12-01", periods=10, tz="utc") @@ -212,15 +172,6 @@ def test_dti_tz_nat(self, tzstr): assert isna(idx[1]) assert idx[0].tzinfo is not None - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_with_timezone_repr(self, tzstr): - rng = date_range("4/13/2010", "5/6/2010") - - rng_eastern = rng.tz_localize(tzstr) - - rng_repr = repr(rng_eastern) - assert "2010-04-13 00:00:00" in rng_repr - @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) def test_utc_box_timestamp_and_localize(self, tzstr): tz = timezones.maybe_get_tz(tzstr) @@ -274,20 +225,6 @@ def test_with_tz(self, tz): with pytest.raises(Exception, match=msg): bdate_range(datetime(2005, 1, 1, tzinfo=pytz.utc), "1/1/2009", tz=tz) - @pytest.mark.parametrize("prefix", ["", "dateutil/"]) - def test_field_access_localize(self, prefix): - strdates = ["1/1/2012", "3/1/2012", "4/1/2012"] - rng = DatetimeIndex(strdates, tz=prefix + "US/Eastern") - assert (rng.hour == 0).all() - - # a more unusual time zone, #1946 - dr = date_range( - "2011-10-02 00:00", freq="h", periods=10, tz=prefix + "America/Atikokan" - ) - - expected = Index(np.arange(10, dtype=np.int32)) - tm.assert_index_equal(dr.hour, expected) - @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")]) def test_dti_convert_tz_aware_datetime_datetime(self, tz): # GH#1581 diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 451033947fc99..31e118ecaf5a7 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -22,6 +22,7 @@ from pandas._libs.tslibs.offsets import ( _get_offset, _offset_map, + to_offset, ) from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG from pandas.errors import PerformanceWarning @@ -1114,3 +1115,51 @@ def test_dateoffset_operations_on_dataframes(): assert frameresult1[0] == expecteddate assert frameresult2[0] == expecteddate + + +def test_is_yqm_start_end(): + freq_m = to_offset("ME") + bm = to_offset("BME") + qfeb = to_offset("Q-FEB") + qsfeb = to_offset("QS-FEB") + bq = to_offset("BQ") + bqs_apr = to_offset("BQS-APR") + as_nov = to_offset("YS-NOV") + + tests = [ + (freq_m.is_month_start(Timestamp("2013-06-01")), 1), + (bm.is_month_start(Timestamp("2013-06-01")), 0), + (freq_m.is_month_start(Timestamp("2013-06-03")), 0), + (bm.is_month_start(Timestamp("2013-06-03")), 1), + (qfeb.is_month_end(Timestamp("2013-02-28")), 1), + (qfeb.is_quarter_end(Timestamp("2013-02-28")), 1), + (qfeb.is_year_end(Timestamp("2013-02-28")), 1), + (qfeb.is_month_start(Timestamp("2013-03-01")), 1), + (qfeb.is_quarter_start(Timestamp("2013-03-01")), 1), + (qfeb.is_year_start(Timestamp("2013-03-01")), 1), + (qsfeb.is_month_end(Timestamp("2013-03-31")), 1), + (qsfeb.is_quarter_end(Timestamp("2013-03-31")), 0), + (qsfeb.is_year_end(Timestamp("2013-03-31")), 0), + (qsfeb.is_month_start(Timestamp("2013-02-01")), 1), + (qsfeb.is_quarter_start(Timestamp("2013-02-01")), 1), + (qsfeb.is_year_start(Timestamp("2013-02-01")), 1), + (bq.is_month_end(Timestamp("2013-06-30")), 0), + (bq.is_quarter_end(Timestamp("2013-06-30")), 0), + (bq.is_year_end(Timestamp("2013-06-30")), 0), + (bq.is_month_end(Timestamp("2013-06-28")), 1), + (bq.is_quarter_end(Timestamp("2013-06-28")), 1), + (bq.is_year_end(Timestamp("2013-06-28")), 0), + (bqs_apr.is_month_end(Timestamp("2013-06-30")), 0), + (bqs_apr.is_quarter_end(Timestamp("2013-06-30")), 0), + (bqs_apr.is_year_end(Timestamp("2013-06-30")), 0), + (bqs_apr.is_month_end(Timestamp("2013-06-28")), 1), + (bqs_apr.is_quarter_end(Timestamp("2013-06-28")), 1), + (bqs_apr.is_year_end(Timestamp("2013-03-29")), 1), + (as_nov.is_year_start(Timestamp("2013-11-01")), 1), + (as_nov.is_year_end(Timestamp("2013-10-31")), 1), + (Timestamp("2012-02-01").days_in_month, 29), + (Timestamp("2013-02-01").days_in_month, 28), + ] + + for ts, value in tests: + assert ts == value From b29b381084ef53322756c3adcedbef07c747c10f Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 21 Oct 2023 07:24:29 -0700 Subject: [PATCH 11/12] woops --- pandas/tests/tools/test_to_datetime.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/tests/tools/test_to_datetime.py b/pandas/tests/tools/test_to_datetime.py index aefaba1aed058..12ac2cfebfb01 100644 --- a/pandas/tests/tools/test_to_datetime.py +++ b/pandas/tests/tools/test_to_datetime.py @@ -1787,7 +1787,9 @@ def test_to_datetime_utc(self): assert result.tz is timezone.utc def test_to_datetime_fixed_offset(self): - from pandas.tests.indexes.datetimes.test_timezones import fixed_off + from pandas.tests.indexes.datetimes.test_timezones import FixedOffset + + fixed_off = FixedOffset(-420, "-07:00") dates = [ datetime(2000, 1, 1, tzinfo=fixed_off), From 106247aa83b72adcd277949d0c10ae84c20fda7d Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 21 Oct 2023 08:15:42 -0700 Subject: [PATCH 12/12] better test name --- pandas/tests/indexes/datetimes/methods/test_astype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/datetimes/methods/test_astype.py b/pandas/tests/indexes/datetimes/methods/test_astype.py index a35d2624276be..e527f6f28cd9d 100644 --- a/pandas/tests/indexes/datetimes/methods/test_astype.py +++ b/pandas/tests/indexes/datetimes/methods/test_astype.py @@ -19,7 +19,7 @@ class TestDatetimeIndex: @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) - def test_dti_astype_asobject_tzinfos(self, tzstr): + def test_dti_astype_asobject_around_dst_transition(self, tzstr): # GH#1345 # dates around a dst transition