1"""
2Tests for DatetimeIndex timezone-related methods
3"""
4from datetime import date, datetime, time, timedelta, tzinfo
5
6import dateutil
7from dateutil.tz import gettz, tzlocal
8import numpy as np
9import pytest
10import pytz
11
12from pandas._libs.tslibs import conversion, timezones
13import pandas.util._test_decorators as td
14
15import pandas as pd
16from pandas import (
17    DatetimeIndex,
18    Index,
19    Timestamp,
20    bdate_range,
21    date_range,
22    isna,
23    to_datetime,
24)
25import pandas._testing as tm
26
27
28class FixedOffset(tzinfo):
29    """Fixed offset in minutes east from UTC."""
30
31    def __init__(self, offset, name):
32        self.__offset = timedelta(minutes=offset)
33        self.__name = name
34
35    def utcoffset(self, dt):
36        return self.__offset
37
38    def tzname(self, dt):
39        return self.__name
40
41    def dst(self, dt):
42        return timedelta(0)
43
44
45fixed_off = FixedOffset(-420, "-07:00")
46fixed_off_no_name = FixedOffset(-330, None)
47
48
49class TestDatetimeIndexTimezones:
50    # -------------------------------------------------------------
51    # DatetimeIndex.tz_convert
52    def test_tz_convert_nat(self):
53        # GH#5546
54        dates = [pd.NaT]
55        idx = DatetimeIndex(dates)
56        idx = idx.tz_localize("US/Pacific")
57        tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific"))
58        idx = idx.tz_convert("US/Eastern")
59        tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Eastern"))
60        idx = idx.tz_convert("UTC")
61        tm.assert_index_equal(idx, DatetimeIndex(dates, tz="UTC"))
62
63        dates = ["2010-12-01 00:00", "2010-12-02 00:00", pd.NaT]
64        idx = DatetimeIndex(dates)
65        idx = idx.tz_localize("US/Pacific")
66        tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific"))
67        idx = idx.tz_convert("US/Eastern")
68        expected = ["2010-12-01 03:00", "2010-12-02 03:00", pd.NaT]
69        tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern"))
70
71        idx = idx + pd.offsets.Hour(5)
72        expected = ["2010-12-01 08:00", "2010-12-02 08:00", pd.NaT]
73        tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern"))
74        idx = idx.tz_convert("US/Pacific")
75        expected = ["2010-12-01 05:00", "2010-12-02 05:00", pd.NaT]
76        tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific"))
77
78        idx = idx + np.timedelta64(3, "h")
79        expected = ["2010-12-01 08:00", "2010-12-02 08:00", pd.NaT]
80        tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific"))
81
82        idx = idx.tz_convert("US/Eastern")
83        expected = ["2010-12-01 11:00", "2010-12-02 11:00", pd.NaT]
84        tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern"))
85
86    @pytest.mark.parametrize("prefix", ["", "dateutil/"])
87    def test_dti_tz_convert_compat_timestamp(self, prefix):
88        strdates = ["1/1/2012", "3/1/2012", "4/1/2012"]
89        idx = DatetimeIndex(strdates, tz=prefix + "US/Eastern")
90
91        conv = idx[0].tz_convert(prefix + "US/Pacific")
92        expected = idx.tz_convert(prefix + "US/Pacific")[0]
93
94        assert conv == expected
95
96    def test_dti_tz_convert_hour_overflow_dst(self):
97        # Regression test for:
98        # https://github.com/pandas-dev/pandas/issues/13306
99
100        # sorted case US/Eastern -> UTC
101        ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2009-05-12 09:50:32"]
102        tt = DatetimeIndex(ts).tz_localize("US/Eastern")
103        ut = tt.tz_convert("UTC")
104        expected = Index([13, 14, 13])
105        tm.assert_index_equal(ut.hour, expected)
106
107        # sorted case UTC -> US/Eastern
108        ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2009-05-12 13:50:32"]
109        tt = DatetimeIndex(ts).tz_localize("UTC")
110        ut = tt.tz_convert("US/Eastern")
111        expected = Index([9, 9, 9])
112        tm.assert_index_equal(ut.hour, expected)
113
114        # unsorted case US/Eastern -> UTC
115        ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2008-05-12 09:50:32"]
116        tt = DatetimeIndex(ts).tz_localize("US/Eastern")
117        ut = tt.tz_convert("UTC")
118        expected = Index([13, 14, 13])
119        tm.assert_index_equal(ut.hour, expected)
120
121        # unsorted case UTC -> US/Eastern
122        ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2008-05-12 13:50:32"]
123        tt = DatetimeIndex(ts).tz_localize("UTC")
124        ut = tt.tz_convert("US/Eastern")
125        expected = Index([9, 9, 9])
126        tm.assert_index_equal(ut.hour, expected)
127
128    @pytest.mark.parametrize("tz", ["US/Eastern", "dateutil/US/Eastern"])
129    def test_dti_tz_convert_hour_overflow_dst_timestamps(self, tz):
130        # Regression test for GH#13306
131
132        # sorted case US/Eastern -> UTC
133        ts = [
134            Timestamp("2008-05-12 09:50:00", tz=tz),
135            Timestamp("2008-12-12 09:50:35", tz=tz),
136            Timestamp("2009-05-12 09:50:32", tz=tz),
137        ]
138        tt = DatetimeIndex(ts)
139        ut = tt.tz_convert("UTC")
140        expected = Index([13, 14, 13])
141        tm.assert_index_equal(ut.hour, expected)
142
143        # sorted case UTC -> US/Eastern
144        ts = [
145            Timestamp("2008-05-12 13:50:00", tz="UTC"),
146            Timestamp("2008-12-12 14:50:35", tz="UTC"),
147            Timestamp("2009-05-12 13:50:32", tz="UTC"),
148        ]
149        tt = DatetimeIndex(ts)
150        ut = tt.tz_convert("US/Eastern")
151        expected = Index([9, 9, 9])
152        tm.assert_index_equal(ut.hour, expected)
153
154        # unsorted case US/Eastern -> UTC
155        ts = [
156            Timestamp("2008-05-12 09:50:00", tz=tz),
157            Timestamp("2008-12-12 09:50:35", tz=tz),
158            Timestamp("2008-05-12 09:50:32", tz=tz),
159        ]
160        tt = DatetimeIndex(ts)
161        ut = tt.tz_convert("UTC")
162        expected = Index([13, 14, 13])
163        tm.assert_index_equal(ut.hour, expected)
164
165        # unsorted case UTC -> US/Eastern
166        ts = [
167            Timestamp("2008-05-12 13:50:00", tz="UTC"),
168            Timestamp("2008-12-12 14:50:35", tz="UTC"),
169            Timestamp("2008-05-12 13:50:32", tz="UTC"),
170        ]
171        tt = DatetimeIndex(ts)
172        ut = tt.tz_convert("US/Eastern")
173        expected = Index([9, 9, 9])
174        tm.assert_index_equal(ut.hour, expected)
175
176    @pytest.mark.parametrize("freq, n", [("H", 1), ("T", 60), ("S", 3600)])
177    def test_dti_tz_convert_trans_pos_plus_1__bug(self, freq, n):
178        # Regression test for tslib.tz_convert(vals, tz1, tz2).
179        # See https://github.com/pandas-dev/pandas/issues/4496 for details.
180        idx = date_range(datetime(2011, 3, 26, 23), datetime(2011, 3, 27, 1), freq=freq)
181        idx = idx.tz_localize("UTC")
182        idx = idx.tz_convert("Europe/Moscow")
183
184        expected = np.repeat(np.array([3, 4, 5]), np.array([n, n, 1]))
185        tm.assert_index_equal(idx.hour, Index(expected))
186
187    def test_dti_tz_convert_dst(self):
188        for freq, n in [("H", 1), ("T", 60), ("S", 3600)]:
189            # Start DST
190            idx = date_range(
191                "2014-03-08 23:00", "2014-03-09 09:00", freq=freq, tz="UTC"
192            )
193            idx = idx.tz_convert("US/Eastern")
194            expected = np.repeat(
195                np.array([18, 19, 20, 21, 22, 23, 0, 1, 3, 4, 5]),
196                np.array([n, n, n, n, n, n, n, n, n, n, 1]),
197            )
198            tm.assert_index_equal(idx.hour, Index(expected))
199
200            idx = date_range(
201                "2014-03-08 18:00", "2014-03-09 05:00", freq=freq, tz="US/Eastern"
202            )
203            idx = idx.tz_convert("UTC")
204            expected = np.repeat(
205                np.array([23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
206                np.array([n, n, n, n, n, n, n, n, n, n, 1]),
207            )
208            tm.assert_index_equal(idx.hour, Index(expected))
209
210            # End DST
211            idx = date_range(
212                "2014-11-01 23:00", "2014-11-02 09:00", freq=freq, tz="UTC"
213            )
214            idx = idx.tz_convert("US/Eastern")
215            expected = np.repeat(
216                np.array([19, 20, 21, 22, 23, 0, 1, 1, 2, 3, 4]),
217                np.array([n, n, n, n, n, n, n, n, n, n, 1]),
218            )
219            tm.assert_index_equal(idx.hour, Index(expected))
220
221            idx = date_range(
222                "2014-11-01 18:00", "2014-11-02 05:00", freq=freq, tz="US/Eastern"
223            )
224            idx = idx.tz_convert("UTC")
225            expected = np.repeat(
226                np.array([22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
227                np.array([n, n, n, n, n, n, n, n, n, n, n, n, 1]),
228            )
229            tm.assert_index_equal(idx.hour, Index(expected))
230
231        # daily
232        # Start DST
233        idx = date_range("2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="UTC")
234        idx = idx.tz_convert("US/Eastern")
235        tm.assert_index_equal(idx.hour, Index([19, 19]))
236
237        idx = date_range(
238            "2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="US/Eastern"
239        )
240        idx = idx.tz_convert("UTC")
241        tm.assert_index_equal(idx.hour, Index([5, 5]))
242
243        # End DST
244        idx = date_range("2014-11-01 00:00", "2014-11-02 00:00", freq="D", tz="UTC")
245        idx = idx.tz_convert("US/Eastern")
246        tm.assert_index_equal(idx.hour, Index([20, 20]))
247
248        idx = date_range(
249            "2014-11-01 00:00", "2014-11-02 000:00", freq="D", tz="US/Eastern"
250        )
251        idx = idx.tz_convert("UTC")
252        tm.assert_index_equal(idx.hour, Index([4, 4]))
253
254    def test_tz_convert_roundtrip(self, tz_aware_fixture):
255        tz = tz_aware_fixture
256        idx1 = date_range(start="2014-01-01", end="2014-12-31", freq="M", tz="UTC")
257        exp1 = date_range(start="2014-01-01", end="2014-12-31", freq="M")
258
259        idx2 = date_range(start="2014-01-01", end="2014-12-31", freq="D", tz="UTC")
260        exp2 = date_range(start="2014-01-01", end="2014-12-31", freq="D")
261
262        idx3 = date_range(start="2014-01-01", end="2014-03-01", freq="H", tz="UTC")
263        exp3 = date_range(start="2014-01-01", end="2014-03-01", freq="H")
264
265        idx4 = date_range(start="2014-08-01", end="2014-10-31", freq="T", tz="UTC")
266        exp4 = date_range(start="2014-08-01", end="2014-10-31", freq="T")
267
268        for idx, expected in [(idx1, exp1), (idx2, exp2), (idx3, exp3), (idx4, exp4)]:
269            converted = idx.tz_convert(tz)
270            reset = converted.tz_convert(None)
271            tm.assert_index_equal(reset, expected)
272            assert reset.tzinfo is None
273            expected = converted.tz_convert("UTC").tz_localize(None)
274            expected = expected._with_freq("infer")
275            tm.assert_index_equal(reset, expected)
276
277    def test_dti_tz_convert_tzlocal(self):
278        # GH#13583
279        # tz_convert doesn't affect to internal
280        dti = date_range(start="2001-01-01", end="2001-03-01", tz="UTC")
281        dti2 = dti.tz_convert(dateutil.tz.tzlocal())
282        tm.assert_numpy_array_equal(dti2.asi8, dti.asi8)
283
284        dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal())
285        dti2 = dti.tz_convert(None)
286        tm.assert_numpy_array_equal(dti2.asi8, dti.asi8)
287
288    @pytest.mark.parametrize(
289        "tz",
290        [
291            "US/Eastern",
292            "dateutil/US/Eastern",
293            pytz.timezone("US/Eastern"),
294            gettz("US/Eastern"),
295        ],
296    )
297    def test_dti_tz_convert_utc_to_local_no_modify(self, tz):
298        rng = date_range("3/11/2012", "3/12/2012", freq="H", tz="utc")
299        rng_eastern = rng.tz_convert(tz)
300
301        # Values are unmodified
302        tm.assert_numpy_array_equal(rng.asi8, rng_eastern.asi8)
303
304        assert timezones.tz_compare(rng_eastern.tz, timezones.maybe_get_tz(tz))
305
306    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
307    def test_tz_convert_unsorted(self, tzstr):
308        dr = date_range("2012-03-09", freq="H", periods=100, tz="utc")
309        dr = dr.tz_convert(tzstr)
310
311        result = dr[::-1].hour
312        exp = dr.hour[::-1]
313        tm.assert_almost_equal(result, exp)
314
315    # -------------------------------------------------------------
316    # DatetimeIndex.tz_localize
317
318    def test_dti_tz_localize_nonexistent_raise_coerce(self):
319        # GH#13057
320        times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"]
321        index = DatetimeIndex(times)
322        tz = "US/Eastern"
323        with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)):
324            index.tz_localize(tz=tz)
325
326        with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)):
327            index.tz_localize(tz=tz, nonexistent="raise")
328
329        result = index.tz_localize(tz=tz, nonexistent="NaT")
330        test_times = ["2015-03-08 01:00-05:00", "NaT", "2015-03-08 03:00-04:00"]
331        dti = to_datetime(test_times, utc=True)
332        expected = dti.tz_convert("US/Eastern")
333        tm.assert_index_equal(result, expected)
334
335    @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")])
336    def test_dti_tz_localize_ambiguous_infer(self, tz):
337        # November 6, 2011, fall back, repeat 2 AM hour
338        # With no repeated hours, we cannot infer the transition
339        dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=pd.offsets.Hour())
340        with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
341            dr.tz_localize(tz)
342
343        # With repeated hours, we can infer the transition
344        dr = date_range(
345            datetime(2011, 11, 6, 0), periods=5, freq=pd.offsets.Hour(), tz=tz
346        )
347        times = [
348            "11/06/2011 00:00",
349            "11/06/2011 01:00",
350            "11/06/2011 01:00",
351            "11/06/2011 02:00",
352            "11/06/2011 03:00",
353        ]
354        di = DatetimeIndex(times)
355        localized = di.tz_localize(tz, ambiguous="infer")
356        expected = dr._with_freq(None)
357        tm.assert_index_equal(expected, localized)
358        tm.assert_index_equal(expected, DatetimeIndex(times, tz=tz, ambiguous="infer"))
359
360        # When there is no dst transition, nothing special happens
361        dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=pd.offsets.Hour())
362        localized = dr.tz_localize(tz)
363        localized_infer = dr.tz_localize(tz, ambiguous="infer")
364        tm.assert_index_equal(localized, localized_infer)
365
366    @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")])
367    def test_dti_tz_localize_ambiguous_times(self, tz):
368        # March 13, 2011, spring forward, skip from 2 AM to 3 AM
369        dr = date_range(datetime(2011, 3, 13, 1, 30), periods=3, freq=pd.offsets.Hour())
370        with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:30:00"):
371            dr.tz_localize(tz)
372
373        # after dst transition, it works
374        dr = date_range(
375            datetime(2011, 3, 13, 3, 30), periods=3, freq=pd.offsets.Hour(), tz=tz
376        )
377
378        # November 6, 2011, fall back, repeat 2 AM hour
379        dr = date_range(datetime(2011, 11, 6, 1, 30), periods=3, freq=pd.offsets.Hour())
380        with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
381            dr.tz_localize(tz)
382
383        # UTC is OK
384        dr = date_range(
385            datetime(2011, 3, 13), periods=48, freq=pd.offsets.Minute(30), tz=pytz.utc
386        )
387
388    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
389    def test_dti_tz_localize_pass_dates_to_utc(self, tzstr):
390        strdates = ["1/1/2012", "3/1/2012", "4/1/2012"]
391
392        idx = DatetimeIndex(strdates)
393        conv = idx.tz_localize(tzstr)
394
395        fromdates = DatetimeIndex(strdates, tz=tzstr)
396
397        assert conv.tz == fromdates.tz
398        tm.assert_numpy_array_equal(conv.values, fromdates.values)
399
400    @pytest.mark.parametrize("prefix", ["", "dateutil/"])
401    def test_dti_tz_localize(self, prefix):
402        tzstr = prefix + "US/Eastern"
403        dti = date_range(start="1/1/2005", end="1/1/2005 0:00:30.256", freq="L")
404        dti2 = dti.tz_localize(tzstr)
405
406        dti_utc = date_range(
407            start="1/1/2005 05:00", end="1/1/2005 5:00:30.256", freq="L", tz="utc"
408        )
409
410        tm.assert_numpy_array_equal(dti2.values, dti_utc.values)
411
412        dti3 = dti2.tz_convert(prefix + "US/Pacific")
413        tm.assert_numpy_array_equal(dti3.values, dti_utc.values)
414
415        dti = date_range(start="11/6/2011 1:59", end="11/6/2011 2:00", freq="L")
416        with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
417            dti.tz_localize(tzstr)
418
419        dti = date_range(start="3/13/2011 1:59", end="3/13/2011 2:00", freq="L")
420        with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:00:00"):
421            dti.tz_localize(tzstr)
422
423    @pytest.mark.parametrize(
424        "tz",
425        [
426            "US/Eastern",
427            "dateutil/US/Eastern",
428            pytz.timezone("US/Eastern"),
429            gettz("US/Eastern"),
430        ],
431    )
432    def test_dti_tz_localize_utc_conversion(self, tz):
433        # Localizing to time zone should:
434        #  1) check for DST ambiguities
435        #  2) convert to UTC
436
437        rng = date_range("3/10/2012", "3/11/2012", freq="30T")
438
439        converted = rng.tz_localize(tz)
440        expected_naive = rng + pd.offsets.Hour(5)
441        tm.assert_numpy_array_equal(converted.asi8, expected_naive.asi8)
442
443        # DST ambiguity, this should fail
444        rng = date_range("3/11/2012", "3/12/2012", freq="30T")
445        # Is this really how it should fail??
446        with pytest.raises(pytz.NonExistentTimeError, match="2012-03-11 02:00:00"):
447            rng.tz_localize(tz)
448
449    def test_dti_tz_localize_roundtrip(self, tz_aware_fixture):
450        # note: this tz tests that a tz-naive index can be localized
451        # and de-localized successfully, when there are no DST transitions
452        # in the range.
453        idx = date_range(start="2014-06-01", end="2014-08-30", freq="15T")
454        tz = tz_aware_fixture
455        localized = idx.tz_localize(tz)
456        # cant localize a tz-aware object
457        with pytest.raises(
458            TypeError, match="Already tz-aware, use tz_convert to convert"
459        ):
460            localized.tz_localize(tz)
461        reset = localized.tz_localize(None)
462        assert reset.tzinfo is None
463        expected = idx._with_freq(None)
464        tm.assert_index_equal(reset, expected)
465
466    def test_dti_tz_localize_naive(self):
467        rng = date_range("1/1/2011", periods=100, freq="H")
468
469        conv = rng.tz_localize("US/Pacific")
470        exp = date_range("1/1/2011", periods=100, freq="H", tz="US/Pacific")
471
472        tm.assert_index_equal(conv, exp._with_freq(None))
473
474    def test_dti_tz_localize_tzlocal(self):
475        # GH#13583
476        offset = dateutil.tz.tzlocal().utcoffset(datetime(2011, 1, 1))
477        offset = int(offset.total_seconds() * 1000000000)
478
479        dti = date_range(start="2001-01-01", end="2001-03-01")
480        dti2 = dti.tz_localize(dateutil.tz.tzlocal())
481        tm.assert_numpy_array_equal(dti2.asi8 + offset, dti.asi8)
482
483        dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal())
484        dti2 = dti.tz_localize(None)
485        tm.assert_numpy_array_equal(dti2.asi8 - offset, dti.asi8)
486
487    @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")])
488    def test_dti_tz_localize_ambiguous_nat(self, tz):
489        times = [
490            "11/06/2011 00:00",
491            "11/06/2011 01:00",
492            "11/06/2011 01:00",
493            "11/06/2011 02:00",
494            "11/06/2011 03:00",
495        ]
496        di = DatetimeIndex(times)
497        localized = di.tz_localize(tz, ambiguous="NaT")
498
499        times = [
500            "11/06/2011 00:00",
501            np.NaN,
502            np.NaN,
503            "11/06/2011 02:00",
504            "11/06/2011 03:00",
505        ]
506        di_test = DatetimeIndex(times, tz="US/Eastern")
507
508        # left dtype is datetime64[ns, US/Eastern]
509        # right is datetime64[ns, tzfile('/usr/share/zoneinfo/US/Eastern')]
510        tm.assert_numpy_array_equal(di_test.values, localized.values)
511
512    @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")])
513    def test_dti_tz_localize_ambiguous_flags(self, tz):
514        # November 6, 2011, fall back, repeat 2 AM hour
515
516        # Pass in flags to determine right dst transition
517        dr = date_range(
518            datetime(2011, 11, 6, 0), periods=5, freq=pd.offsets.Hour(), tz=tz
519        )
520        times = [
521            "11/06/2011 00:00",
522            "11/06/2011 01:00",
523            "11/06/2011 01:00",
524            "11/06/2011 02:00",
525            "11/06/2011 03:00",
526        ]
527
528        # Test tz_localize
529        di = DatetimeIndex(times)
530        is_dst = [1, 1, 0, 0, 0]
531        localized = di.tz_localize(tz, ambiguous=is_dst)
532        expected = dr._with_freq(None)
533        tm.assert_index_equal(expected, localized)
534        tm.assert_index_equal(expected, DatetimeIndex(times, tz=tz, ambiguous=is_dst))
535
536        localized = di.tz_localize(tz, ambiguous=np.array(is_dst))
537        tm.assert_index_equal(dr, localized)
538
539        localized = di.tz_localize(tz, ambiguous=np.array(is_dst).astype("bool"))
540        tm.assert_index_equal(dr, localized)
541
542        # Test constructor
543        localized = DatetimeIndex(times, tz=tz, ambiguous=is_dst)
544        tm.assert_index_equal(dr, localized)
545
546        # Test duplicate times where inferring the dst fails
547        times += times
548        di = DatetimeIndex(times)
549
550        # When the sizes are incompatible, make sure error is raised
551        msg = "Length of ambiguous bool-array must be the same size as vals"
552        with pytest.raises(Exception, match=msg):
553            di.tz_localize(tz, ambiguous=is_dst)
554
555        # When sizes are compatible and there are repeats ('infer' won't work)
556        is_dst = np.hstack((is_dst, is_dst))
557        localized = di.tz_localize(tz, ambiguous=is_dst)
558        dr = dr.append(dr)
559        tm.assert_index_equal(dr, localized)
560
561        # When there is no dst transition, nothing special happens
562        dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=pd.offsets.Hour())
563        is_dst = np.array([1] * 10)
564        localized = dr.tz_localize(tz)
565        localized_is_dst = dr.tz_localize(tz, ambiguous=is_dst)
566        tm.assert_index_equal(localized, localized_is_dst)
567
568    # TODO: belongs outside tz_localize tests?
569    @pytest.mark.parametrize("tz", ["Europe/London", "dateutil/Europe/London"])
570    def test_dti_construction_ambiguous_endpoint(self, tz):
571        # construction with an ambiguous end-point
572        # GH#11626
573
574        with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
575            date_range(
576                "2013-10-26 23:00", "2013-10-27 01:00", tz="Europe/London", freq="H"
577            )
578
579        times = date_range(
580            "2013-10-26 23:00", "2013-10-27 01:00", freq="H", tz=tz, ambiguous="infer"
581        )
582        assert times[0] == Timestamp("2013-10-26 23:00", tz=tz, freq="H")
583        assert times[-1] == Timestamp("2013-10-27 01:00:00+0000", tz=tz, freq="H")
584
585    @pytest.mark.parametrize(
586        "tz, option, expected",
587        [
588            ["US/Pacific", "shift_forward", "2019-03-10 03:00"],
589            ["dateutil/US/Pacific", "shift_forward", "2019-03-10 03:00"],
590            ["US/Pacific", "shift_backward", "2019-03-10 01:00"],
591            ["dateutil/US/Pacific", "shift_backward", "2019-03-10 01:00"],
592            ["US/Pacific", timedelta(hours=1), "2019-03-10 03:00"],
593        ],
594    )
595    def test_dti_construction_nonexistent_endpoint(self, tz, option, expected):
596        # construction with an nonexistent end-point
597
598        with pytest.raises(pytz.NonExistentTimeError, match="2019-03-10 02:00:00"):
599            date_range(
600                "2019-03-10 00:00", "2019-03-10 02:00", tz="US/Pacific", freq="H"
601            )
602
603        times = date_range(
604            "2019-03-10 00:00", "2019-03-10 02:00", freq="H", tz=tz, nonexistent=option
605        )
606        assert times[-1] == Timestamp(expected, tz=tz, freq="H")
607
608    def test_dti_tz_localize_bdate_range(self):
609        dr = bdate_range("1/1/2009", "1/1/2010")
610        dr_utc = bdate_range("1/1/2009", "1/1/2010", tz=pytz.utc)
611        localized = dr.tz_localize(pytz.utc)
612        tm.assert_index_equal(dr_utc, localized)
613
614    @pytest.mark.parametrize("tz", ["Europe/Warsaw", "dateutil/Europe/Warsaw"])
615    @pytest.mark.parametrize(
616        "method, exp", [["NaT", pd.NaT], ["raise", None], ["foo", "invalid"]]
617    )
618    def test_dti_tz_localize_nonexistent(self, tz, method, exp):
619        # GH 8917
620        n = 60
621        dti = date_range(start="2015-03-29 02:00:00", periods=n, freq="min")
622        if method == "raise":
623            with pytest.raises(pytz.NonExistentTimeError, match="2015-03-29 02:00:00"):
624                dti.tz_localize(tz, nonexistent=method)
625        elif exp == "invalid":
626            msg = (
627                "The nonexistent argument must be one of "
628                "'raise', 'NaT', 'shift_forward', 'shift_backward' "
629                "or a timedelta object"
630            )
631            with pytest.raises(ValueError, match=msg):
632                dti.tz_localize(tz, nonexistent=method)
633        else:
634            result = dti.tz_localize(tz, nonexistent=method)
635            expected = DatetimeIndex([exp] * n, tz=tz)
636            tm.assert_index_equal(result, expected)
637
638    @pytest.mark.parametrize(
639        "start_ts, tz, end_ts, shift",
640        [
641            ["2015-03-29 02:20:00", "Europe/Warsaw", "2015-03-29 03:00:00", "forward"],
642            [
643                "2015-03-29 02:20:00",
644                "Europe/Warsaw",
645                "2015-03-29 01:59:59.999999999",
646                "backward",
647            ],
648            [
649                "2015-03-29 02:20:00",
650                "Europe/Warsaw",
651                "2015-03-29 03:20:00",
652                timedelta(hours=1),
653            ],
654            [
655                "2015-03-29 02:20:00",
656                "Europe/Warsaw",
657                "2015-03-29 01:20:00",
658                timedelta(hours=-1),
659            ],
660            ["2018-03-11 02:33:00", "US/Pacific", "2018-03-11 03:00:00", "forward"],
661            [
662                "2018-03-11 02:33:00",
663                "US/Pacific",
664                "2018-03-11 01:59:59.999999999",
665                "backward",
666            ],
667            [
668                "2018-03-11 02:33:00",
669                "US/Pacific",
670                "2018-03-11 03:33:00",
671                timedelta(hours=1),
672            ],
673            [
674                "2018-03-11 02:33:00",
675                "US/Pacific",
676                "2018-03-11 01:33:00",
677                timedelta(hours=-1),
678            ],
679        ],
680    )
681    @pytest.mark.parametrize("tz_type", ["", "dateutil/"])
682    def test_dti_tz_localize_nonexistent_shift(
683        self, start_ts, tz, end_ts, shift, tz_type
684    ):
685        # GH 8917
686        tz = tz_type + tz
687        if isinstance(shift, str):
688            shift = "shift_" + shift
689        dti = DatetimeIndex([Timestamp(start_ts)])
690        result = dti.tz_localize(tz, nonexistent=shift)
691        expected = DatetimeIndex([Timestamp(end_ts)]).tz_localize(tz)
692        tm.assert_index_equal(result, expected)
693
694    @pytest.mark.parametrize("offset", [-1, 1])
695    @pytest.mark.parametrize("tz_type", ["", "dateutil/"])
696    def test_dti_tz_localize_nonexistent_shift_invalid(self, offset, tz_type):
697        # GH 8917
698        tz = tz_type + "Europe/Warsaw"
699        dti = DatetimeIndex([Timestamp("2015-03-29 02:20:00")])
700        msg = "The provided timedelta will relocalize on a nonexistent time"
701        with pytest.raises(ValueError, match=msg):
702            dti.tz_localize(tz, nonexistent=timedelta(seconds=offset))
703
704    # -------------------------------------------------------------
705    # DatetimeIndex.normalize
706
707    def test_normalize_tz(self):
708        rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="US/Eastern")
709
710        result = rng.normalize()  # does not preserve freq
711        expected = date_range("1/1/2000", periods=10, freq="D", tz="US/Eastern")
712        tm.assert_index_equal(result, expected._with_freq(None))
713
714        assert result.is_normalized
715        assert not rng.is_normalized
716
717        rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="UTC")
718
719        result = rng.normalize()
720        expected = date_range("1/1/2000", periods=10, freq="D", tz="UTC")
721        tm.assert_index_equal(result, expected)
722
723        assert result.is_normalized
724        assert not rng.is_normalized
725
726        rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal())
727        result = rng.normalize()  # does not preserve freq
728        expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal())
729        tm.assert_index_equal(result, expected._with_freq(None))
730
731        assert result.is_normalized
732        assert not rng.is_normalized
733
734    @td.skip_if_windows
735    @pytest.mark.parametrize(
736        "timezone",
737        [
738            "US/Pacific",
739            "US/Eastern",
740            "UTC",
741            "Asia/Kolkata",
742            "Asia/Shanghai",
743            "Australia/Canberra",
744        ],
745    )
746    def test_normalize_tz_local(self, timezone):
747        # GH#13459
748        with tm.set_timezone(timezone):
749            rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal())
750
751            result = rng.normalize()
752            expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal())
753            expected = expected._with_freq(None)
754            tm.assert_index_equal(result, expected)
755
756            assert result.is_normalized
757            assert not rng.is_normalized
758
759    # ------------------------------------------------------------
760    # DatetimeIndex.__new__
761
762    @pytest.mark.parametrize("prefix", ["", "dateutil/"])
763    def test_dti_constructor_static_tzinfo(self, prefix):
764        # it works!
765        index = DatetimeIndex([datetime(2012, 1, 1)], tz=prefix + "EST")
766        index.hour
767        index[0]
768
769    def test_dti_constructor_with_fixed_tz(self):
770        off = FixedOffset(420, "+07:00")
771        start = datetime(2012, 3, 11, 5, 0, 0, tzinfo=off)
772        end = datetime(2012, 6, 11, 5, 0, 0, tzinfo=off)
773        rng = date_range(start=start, end=end)
774        assert off == rng.tz
775
776        rng2 = date_range(start, periods=len(rng), tz=off)
777        tm.assert_index_equal(rng, rng2)
778
779        rng3 = date_range("3/11/2012 05:00:00+07:00", "6/11/2012 05:00:00+07:00")
780        assert (rng.values == rng3.values).all()
781
782    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
783    def test_dti_convert_datetime_list(self, tzstr):
784        dr = date_range("2012-06-02", periods=10, tz=tzstr, name="foo")
785        dr2 = DatetimeIndex(list(dr), name="foo", freq="D")
786        tm.assert_index_equal(dr, dr2)
787
788    def test_dti_construction_univalent(self):
789        rng = date_range("03/12/2012 00:00", periods=10, freq="W-FRI", tz="US/Eastern")
790        rng2 = DatetimeIndex(data=rng, tz="US/Eastern")
791        tm.assert_index_equal(rng, rng2)
792
793    @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")])
794    def test_dti_from_tzaware_datetime(self, tz):
795        d = [datetime(2012, 8, 19, tzinfo=tz)]
796
797        index = DatetimeIndex(d)
798        assert timezones.tz_compare(index.tz, tz)
799
800    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
801    def test_dti_tz_constructors(self, tzstr):
802        """Test different DatetimeIndex constructions with timezone
803        Follow-up of GH#4229
804        """
805        arr = ["11/10/2005 08:00:00", "11/10/2005 09:00:00"]
806
807        idx1 = to_datetime(arr).tz_localize(tzstr)
808        idx2 = date_range(start="2005-11-10 08:00:00", freq="H", periods=2, tz=tzstr)
809        idx2 = idx2._with_freq(None)  # the others all have freq=None
810        idx3 = DatetimeIndex(arr, tz=tzstr)
811        idx4 = DatetimeIndex(np.array(arr), tz=tzstr)
812
813        for other in [idx2, idx3, idx4]:
814            tm.assert_index_equal(idx1, other)
815
816    # -------------------------------------------------------------
817    # Unsorted
818
819    @pytest.mark.parametrize(
820        "dtype",
821        [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"],
822    )
823    def test_date_accessor(self, dtype):
824        # Regression test for GH#21230
825        expected = np.array([date(2018, 6, 4), pd.NaT])
826
827        index = DatetimeIndex(["2018-06-04 10:00:00", pd.NaT], dtype=dtype)
828        result = index.date
829
830        tm.assert_numpy_array_equal(result, expected)
831
832    @pytest.mark.parametrize(
833        "dtype",
834        [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"],
835    )
836    def test_time_accessor(self, dtype):
837        # Regression test for GH#21267
838        expected = np.array([time(10, 20, 30), pd.NaT])
839
840        index = DatetimeIndex(["2018-06-04 10:20:30", pd.NaT], dtype=dtype)
841        result = index.time
842
843        tm.assert_numpy_array_equal(result, expected)
844
845    def test_timetz_accessor(self, tz_naive_fixture):
846        # GH21358
847        tz = timezones.maybe_get_tz(tz_naive_fixture)
848
849        expected = np.array([time(10, 20, 30, tzinfo=tz), pd.NaT])
850
851        index = DatetimeIndex(["2018-06-04 10:20:30", pd.NaT], tz=tz)
852        result = index.timetz
853
854        tm.assert_numpy_array_equal(result, expected)
855
856    def test_dti_drop_dont_lose_tz(self):
857        # GH#2621
858        ind = date_range("2012-12-01", periods=10, tz="utc")
859        ind = ind.drop(ind[-1])
860
861        assert ind.tz is not None
862
863    def test_dti_tz_conversion_freq(self, tz_naive_fixture):
864        # GH25241
865        t3 = DatetimeIndex(["2019-01-01 10:00"], freq="H")
866        assert t3.tz_localize(tz=tz_naive_fixture).freq == t3.freq
867        t4 = DatetimeIndex(["2019-01-02 12:00"], tz="UTC", freq="T")
868        assert t4.tz_convert(tz="UTC").freq == t4.freq
869
870    def test_drop_dst_boundary(self):
871        # see gh-18031
872        tz = "Europe/Brussels"
873        freq = "15min"
874
875        start = Timestamp("201710290100", tz=tz)
876        end = Timestamp("201710290300", tz=tz)
877        index = date_range(start=start, end=end, freq=freq)
878
879        expected = DatetimeIndex(
880            [
881                "201710290115",
882                "201710290130",
883                "201710290145",
884                "201710290200",
885                "201710290215",
886                "201710290230",
887                "201710290245",
888                "201710290200",
889                "201710290215",
890                "201710290230",
891                "201710290245",
892                "201710290300",
893            ],
894            tz=tz,
895            freq=freq,
896            ambiguous=[
897                True,
898                True,
899                True,
900                True,
901                True,
902                True,
903                True,
904                False,
905                False,
906                False,
907                False,
908                False,
909            ],
910        )
911        result = index.drop(index[0])
912        tm.assert_index_equal(result, expected)
913
914    def test_date_range_localize(self):
915        rng = date_range("3/11/2012 03:00", periods=15, freq="H", tz="US/Eastern")
916        rng2 = DatetimeIndex(["3/11/2012 03:00", "3/11/2012 04:00"], tz="US/Eastern")
917        rng3 = date_range("3/11/2012 03:00", periods=15, freq="H")
918        rng3 = rng3.tz_localize("US/Eastern")
919
920        tm.assert_index_equal(rng._with_freq(None), rng3)
921
922        # DST transition time
923        val = rng[0]
924        exp = Timestamp("3/11/2012 03:00", tz="US/Eastern")
925
926        assert val.hour == 3
927        assert exp.hour == 3
928        assert val == exp  # same UTC value
929        tm.assert_index_equal(rng[:2], rng2)
930
931        # Right before the DST transition
932        rng = date_range("3/11/2012 00:00", periods=2, freq="H", tz="US/Eastern")
933        rng2 = DatetimeIndex(
934            ["3/11/2012 00:00", "3/11/2012 01:00"], tz="US/Eastern", freq="H"
935        )
936        tm.assert_index_equal(rng, rng2)
937        exp = Timestamp("3/11/2012 00:00", tz="US/Eastern")
938        assert exp.hour == 0
939        assert rng[0] == exp
940        exp = Timestamp("3/11/2012 01:00", tz="US/Eastern")
941        assert exp.hour == 1
942        assert rng[1] == exp
943
944        rng = date_range("3/11/2012 00:00", periods=10, freq="H", tz="US/Eastern")
945        assert rng[2].hour == 3
946
947    def test_timestamp_equality_different_timezones(self):
948        utc_range = date_range("1/1/2000", periods=20, tz="UTC")
949        eastern_range = utc_range.tz_convert("US/Eastern")
950        berlin_range = utc_range.tz_convert("Europe/Berlin")
951
952        for a, b, c in zip(utc_range, eastern_range, berlin_range):
953            assert a == b
954            assert b == c
955            assert a == c
956
957        assert (utc_range == eastern_range).all()
958        assert (utc_range == berlin_range).all()
959        assert (berlin_range == eastern_range).all()
960
961    def test_dti_intersection(self):
962        rng = date_range("1/1/2011", periods=100, freq="H", tz="utc")
963
964        left = rng[10:90][::-1]
965        right = rng[20:80][::-1]
966
967        assert left.tz == rng.tz
968        result = left.intersection(right)
969        assert result.tz == left.tz
970
971    def test_dti_equals_with_tz(self):
972        left = date_range("1/1/2011", periods=100, freq="H", tz="utc")
973        right = date_range("1/1/2011", periods=100, freq="H", tz="US/Eastern")
974
975        assert not left.equals(right)
976
977    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
978    def test_dti_tz_nat(self, tzstr):
979        idx = DatetimeIndex([Timestamp("2013-1-1", tz=tzstr), pd.NaT])
980
981        assert isna(idx[1])
982        assert idx[0].tzinfo is not None
983
984    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
985    def test_dti_astype_asobject_tzinfos(self, tzstr):
986        # GH#1345
987
988        # dates around a dst transition
989        rng = date_range("2/13/2010", "5/6/2010", tz=tzstr)
990
991        objs = rng.astype(object)
992        for i, x in enumerate(objs):
993            exval = rng[i]
994            assert x == exval
995            assert x.tzinfo == exval.tzinfo
996
997        objs = rng.astype(object)
998        for i, x in enumerate(objs):
999            exval = rng[i]
1000            assert x == exval
1001            assert x.tzinfo == exval.tzinfo
1002
1003    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
1004    def test_dti_with_timezone_repr(self, tzstr):
1005        rng = date_range("4/13/2010", "5/6/2010")
1006
1007        rng_eastern = rng.tz_localize(tzstr)
1008
1009        rng_repr = repr(rng_eastern)
1010        assert "2010-04-13 00:00:00" in rng_repr
1011
1012    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
1013    def test_dti_take_dont_lose_meta(self, tzstr):
1014        rng = date_range("1/1/2000", periods=20, tz=tzstr)
1015
1016        result = rng.take(range(5))
1017        assert result.tz == rng.tz
1018        assert result.freq == rng.freq
1019
1020    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
1021    def test_utc_box_timestamp_and_localize(self, tzstr):
1022        tz = timezones.maybe_get_tz(tzstr)
1023
1024        rng = date_range("3/11/2012", "3/12/2012", freq="H", tz="utc")
1025        rng_eastern = rng.tz_convert(tzstr)
1026
1027        expected = rng[-1].astimezone(tz)
1028
1029        stamp = rng_eastern[-1]
1030        assert stamp == expected
1031        assert stamp.tzinfo == expected.tzinfo
1032
1033        # right tzinfo
1034        rng = date_range("3/13/2012", "3/14/2012", freq="H", tz="utc")
1035        rng_eastern = rng.tz_convert(tzstr)
1036        # test not valid for dateutil timezones.
1037        # assert 'EDT' in repr(rng_eastern[0].tzinfo)
1038        assert "EDT" in repr(rng_eastern[0].tzinfo) or "tzfile" in repr(
1039            rng_eastern[0].tzinfo
1040        )
1041
1042    def test_dti_to_pydatetime(self):
1043        dt = dateutil.parser.parse("2012-06-13T01:39:00Z")
1044        dt = dt.replace(tzinfo=tzlocal())
1045
1046        arr = np.array([dt], dtype=object)
1047
1048        result = to_datetime(arr, utc=True)
1049        assert result.tz is pytz.utc
1050
1051        rng = date_range("2012-11-03 03:00", "2012-11-05 03:00", tz=tzlocal())
1052        arr = rng.to_pydatetime()
1053        result = to_datetime(arr, utc=True)
1054        assert result.tz is pytz.utc
1055
1056    def test_dti_to_pydatetime_fizedtz(self):
1057        dates = np.array(
1058            [
1059                datetime(2000, 1, 1, tzinfo=fixed_off),
1060                datetime(2000, 1, 2, tzinfo=fixed_off),
1061                datetime(2000, 1, 3, tzinfo=fixed_off),
1062            ]
1063        )
1064        dti = DatetimeIndex(dates)
1065
1066        result = dti.to_pydatetime()
1067        tm.assert_numpy_array_equal(dates, result)
1068
1069        result = dti._mpl_repr()
1070        tm.assert_numpy_array_equal(dates, result)
1071
1072    @pytest.mark.parametrize("tz", [pytz.timezone("US/Central"), gettz("US/Central")])
1073    def test_with_tz(self, tz):
1074        # just want it to work
1075        start = datetime(2011, 3, 12, tzinfo=pytz.utc)
1076        dr = bdate_range(start, periods=50, freq=pd.offsets.Hour())
1077        assert dr.tz is pytz.utc
1078
1079        # DateRange with naive datetimes
1080        dr = bdate_range("1/1/2005", "1/1/2009", tz=pytz.utc)
1081        dr = bdate_range("1/1/2005", "1/1/2009", tz=tz)
1082
1083        # normalized
1084        central = dr.tz_convert(tz)
1085        assert central.tz is tz
1086        naive = central[0].to_pydatetime().replace(tzinfo=None)
1087        comp = conversion.localize_pydatetime(naive, tz).tzinfo
1088        assert central[0].tz is comp
1089
1090        # compare vs a localized tz
1091        naive = dr[0].to_pydatetime().replace(tzinfo=None)
1092        comp = conversion.localize_pydatetime(naive, tz).tzinfo
1093        assert central[0].tz is comp
1094
1095        # datetimes with tzinfo set
1096        dr = bdate_range(
1097            datetime(2005, 1, 1, tzinfo=pytz.utc), datetime(2009, 1, 1, tzinfo=pytz.utc)
1098        )
1099        msg = "Start and end cannot both be tz-aware with different timezones"
1100        with pytest.raises(Exception, match=msg):
1101            bdate_range(datetime(2005, 1, 1, tzinfo=pytz.utc), "1/1/2009", tz=tz)
1102
1103    @pytest.mark.parametrize("prefix", ["", "dateutil/"])
1104    def test_field_access_localize(self, prefix):
1105        strdates = ["1/1/2012", "3/1/2012", "4/1/2012"]
1106        rng = DatetimeIndex(strdates, tz=prefix + "US/Eastern")
1107        assert (rng.hour == 0).all()
1108
1109        # a more unusual time zone, #1946
1110        dr = date_range(
1111            "2011-10-02 00:00", freq="h", periods=10, tz=prefix + "America/Atikokan"
1112        )
1113
1114        expected = Index(np.arange(10, dtype=np.int64))
1115        tm.assert_index_equal(dr.hour, expected)
1116
1117    @pytest.mark.parametrize("tz", [pytz.timezone("US/Eastern"), gettz("US/Eastern")])
1118    def test_dti_convert_tz_aware_datetime_datetime(self, tz):
1119        # GH#1581
1120        dates = [datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)]
1121
1122        dates_aware = [conversion.localize_pydatetime(x, tz) for x in dates]
1123        result = DatetimeIndex(dates_aware)
1124        assert timezones.tz_compare(result.tz, tz)
1125
1126        converted = to_datetime(dates_aware, utc=True)
1127        ex_vals = np.array([Timestamp(x).value for x in dates_aware])
1128        tm.assert_numpy_array_equal(converted.asi8, ex_vals)
1129        assert converted.tz is pytz.utc
1130
1131    def test_dti_union_aware(self):
1132        # non-overlapping
1133        rng = date_range("2012-11-15 00:00:00", periods=6, freq="H", tz="US/Central")
1134
1135        rng2 = date_range("2012-11-15 12:00:00", periods=6, freq="H", tz="US/Eastern")
1136
1137        result = rng.union(rng2)
1138        expected = rng.astype("O").union(rng2.astype("O"))
1139        tm.assert_index_equal(result, expected)
1140        assert result[0].tz.zone == "US/Central"
1141        assert result[-1].tz.zone == "US/Eastern"
1142
1143    def test_dti_union_mixed(self):
1144        # GH 21671
1145        rng = DatetimeIndex([Timestamp("2011-01-01"), pd.NaT])
1146        rng2 = DatetimeIndex(["2012-01-01", "2012-01-02"], tz="Asia/Tokyo")
1147        result = rng.union(rng2)
1148        expected = Index(
1149            [
1150                Timestamp("2011-01-01"),
1151                pd.NaT,
1152                Timestamp("2012-01-01", tz="Asia/Tokyo"),
1153                Timestamp("2012-01-02", tz="Asia/Tokyo"),
1154            ],
1155            dtype=object,
1156        )
1157        tm.assert_index_equal(result, expected)
1158
1159    @pytest.mark.parametrize(
1160        "tz", [None, "UTC", "US/Central", dateutil.tz.tzoffset(None, -28800)]
1161    )
1162    @pytest.mark.usefixtures("datetime_tz_utc")
1163    def test_iteration_preserves_nanoseconds(self, tz):
1164        # GH 19603
1165        index = DatetimeIndex(
1166            ["2018-02-08 15:00:00.168456358", "2018-02-08 15:00:00.168456359"], tz=tz
1167        )
1168        for i, ts in enumerate(index):
1169            assert ts == index[i]
1170
1171
1172def test_tz_localize_invalidates_freq():
1173    # we only preserve freq in unambiguous cases
1174
1175    # if localized to US/Eastern, this crosses a DST transition
1176    dti = date_range("2014-03-08 23:00", "2014-03-09 09:00", freq="H")
1177    assert dti.freq == "H"
1178
1179    result = dti.tz_localize(None)  # no-op
1180    assert result.freq == "H"
1181
1182    result = dti.tz_localize("UTC")  # unambiguous freq preservation
1183    assert result.freq == "H"
1184
1185    result = dti.tz_localize("US/Eastern", nonexistent="shift_forward")
1186    assert result.freq is None
1187    assert result.inferred_freq is None  # i.e. we are not _too_ strict here
1188
1189    # Case where we _can_ keep freq because we're length==1
1190    dti2 = dti[:1]
1191    result = dti2.tz_localize("US/Eastern")
1192    assert result.freq == "H"
1193