from __future__ import annotations

from datetime import (
    datetime,
    timedelta,
    timezone,
)
from functools import partial
from operator import attrgetter
import zoneinfo

import dateutil
import dateutil.tz
from dateutil.tz import gettz
import numpy as np
import pytest

from pandas._libs.tslibs import (
    astype_overflowsafe,
    timezones,
)
from pandas.errors import Pandas4Warning

import pandas as pd
import pandas._testing as tm
from pandas.core.arrays import DatetimeArray


class TestDatetimeIndex:
    def test_0d_raises(self):
        # GH#69411 used to segfault
        arr = np.array(1)

        msg = "Cannot construct DatetimeArray from 0-dim input"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(arr)
        with pytest.raises(ValueError, match=msg):
            DatetimeArray._from_sequence(arr)

    def test_from_dt64_unsupported_unit(self):
        # GH#49292
        val = np.datetime64(1, "D")
        result = pd.DatetimeIndex([val], tz="US/Pacific")

        expected = pd.DatetimeIndex([val.astype("M8[s]")], tz="US/Pacific")
        tm.assert_index_equal(result, expected)

    def test_explicit_tz_none(self):
        # GH#48659
        dti = pd.date_range("2016-01-01", periods=10, tz="UTC", unit="ns")

        msg = "Passed data is timezone-aware, incompatible with 'tz=None'"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(dti, tz=None)

        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(np.array(dti), tz=None)

        msg = "Cannot pass both a timezone-aware dtype and tz=None"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex([], dtype="M8[ns, UTC]", tz=None)

    def test_freq_validation_with_nat(self):
        # GH#11587 make sure we get a useful error message when generate_range
        #  raises
        msg = (
            "Inferred frequency None from passed values does not conform "
            "to passed frequency D"
        )
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex([pd.NaT, pd.Timestamp("2011-01-01")], freq="D")
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex([pd.NaT, pd.Timestamp("2011-01-01")._value], freq="D")

    # TODO: better place for tests shared by DTI/TDI?
    @pytest.mark.parametrize(
        "index",
        [
            pd.date_range("2016-01-01", periods=5, tz="US/Pacific", unit="ns"),
            pd.timedelta_range("1 Day", periods=5),
        ],
    )
    def test_shallow_copy_inherits_array_freq(self, index):
        # If we pass a DTA/TDA to shallow_copy and dont specify a freq,
        #  we should inherit the array's freq, not our own.
        array = index._data

        arr = array[[0, 3, 2, 4, 1]]

        result = index._shallow_copy(arr)
        assert result.freq is None

    def test_categorical_preserves_tz(self):
        # GH#18664 retain tz when going DTI-->Categorical-->DTI
        dti = pd.DatetimeIndex(
            [pd.NaT, "2015-01-01", "1999-04-06 15:14:13", "2015-01-01"], tz="US/Eastern"
        )

        for dtobj in [dti, dti._data]:
            # works for DatetimeIndex or DatetimeArray

            ci = pd.CategoricalIndex(dtobj)
            carr = pd.Categorical(dtobj)
            cser = pd.Series(ci)

            for obj in [ci, carr, cser]:
                result = pd.DatetimeIndex(obj)
                tm.assert_index_equal(result, dti)

    def test_dti_with_period_data_raises(self):
        # GH#23675
        data = pd.PeriodIndex(["2016Q1", "2016Q2"], freq="Q")

        with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
            pd.DatetimeIndex(data)

        with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
            pd.to_datetime(data)

        with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
            pd.DatetimeIndex(pd.PeriodIndex(data))

        with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
            pd.to_datetime(pd.PeriodIndex(data))

    def test_dti_with_timedelta64_data_raises(self):
        # GH#23675 deprecated, enforced in GH#29794
        data = np.array([0], dtype="m8[ns]")
        msg = r"timedelta64\[ns\] cannot be converted to datetime64"
        with pytest.raises(TypeError, match=msg):
            pd.DatetimeIndex(data)

        with pytest.raises(TypeError, match=msg):
            pd.to_datetime(data)

        with pytest.raises(TypeError, match=msg):
            pd.DatetimeIndex(pd.TimedeltaIndex(data))

        with pytest.raises(TypeError, match=msg):
            pd.to_datetime(pd.TimedeltaIndex(data))

    def test_constructor_from_sparse_array(self):
        # https://github.com/pandas-dev/pandas/issues/35843
        values = [
            pd.Timestamp("2012-05-01T01:00:00.000000"),
            pd.Timestamp("2016-05-01T01:00:00.000000"),
        ]
        arr = pd.arrays.SparseArray(values)
        result = pd.Index(arr)
        assert type(result) is pd.Index
        assert result.dtype == arr.dtype

    def test_construction_caching(self):
        df = pd.DataFrame(
            {
                "dt": pd.date_range("20130101", periods=3, unit="ns"),
                "dttz": pd.date_range(
                    "20130101", periods=3, tz=zoneinfo.ZoneInfo("US/Eastern"), unit="ns"
                ),
                "dt_with_null": [
                    pd.Timestamp("20130101"),
                    pd.NaT,
                    pd.Timestamp("20130103"),
                ],
                "dtns": pd.date_range("20130101", periods=3, freq="ns", unit="ns"),
            }
        )
        assert df.dttz.dtype.tz.key == "US/Eastern"

    @pytest.mark.parametrize(
        "kwargs",
        [{"tz": "dtype.tz"}, {"dtype": "dtype"}, {"dtype": "dtype", "tz": "dtype.tz"}],
    )
    def test_construction_with_alt(self, kwargs, tz_aware_fixture):
        tz = tz_aware_fixture
        i = pd.date_range("20130101", periods=5, freq="h", tz=tz, unit="ns")
        kwargs = {key: attrgetter(val)(i) for key, val in kwargs.items()}
        result = pd.DatetimeIndex(i, **kwargs)
        tm.assert_index_equal(i, result)

    @pytest.mark.parametrize(
        "kwargs",
        [{"tz": "dtype.tz"}, {"dtype": "dtype"}, {"dtype": "dtype", "tz": "dtype.tz"}],
    )
    def test_construction_with_alt_tz_localize(self, kwargs, tz_aware_fixture):
        tz = tz_aware_fixture
        i = pd.date_range("20130101", periods=5, freq="h", tz=tz, unit="ns")
        i = i._with_freq(None)
        kwargs = {key: attrgetter(val)(i) for key, val in kwargs.items()}

        if "tz" in kwargs:
            result = pd.DatetimeIndex(i.asi8, tz="UTC").tz_convert(kwargs["tz"])

            expected = pd.DatetimeIndex(i, **kwargs)
            tm.assert_index_equal(result, expected)

        # localize into the provided tz
        i2 = pd.DatetimeIndex(i.tz_localize(None).asi8, tz="UTC")
        expected = i.tz_localize(None).tz_localize("UTC")
        tm.assert_index_equal(i2, expected)

        # incompat tz/dtype
        msg = "cannot supply both a tz and a dtype with a tz"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(
                i.tz_localize(None).asi8,
                dtype=i.dtype,
                tz=zoneinfo.ZoneInfo("US/Hawaii"),
            )

    def test_construction_index_with_mixed_timezones(self):
        # gh-11488: no tz results in DatetimeIndex
        result = pd.Index(
            [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")], name="idx"
        )
        exp = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")], name="idx"
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is None

        # same tz results in DatetimeIndex
        result = pd.Index(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.Timestamp("2011-01-02 10:00", tz="Asia/Tokyo"),
            ],
            name="idx",
        )
        exp = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01 10:00"), pd.Timestamp("2011-01-02 10:00")],
            tz="Asia/Tokyo",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is not None
        assert result.tz == exp.tz

        # same tz results in DatetimeIndex (DST)
        result = pd.Index(
            [
                pd.Timestamp("2011-01-01 10:00", tz="US/Eastern"),
                pd.Timestamp("2011-08-01 10:00", tz="US/Eastern"),
            ],
            name="idx",
        )
        exp = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01 10:00"), pd.Timestamp("2011-08-01 10:00")],
            tz="US/Eastern",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is not None
        assert result.tz == exp.tz

        # Different tz results in Index(dtype=object)
        result = pd.Index(
            [
                pd.Timestamp("2011-01-01 10:00"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            name="idx",
        )
        exp = pd.Index(
            [
                pd.Timestamp("2011-01-01 10:00"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            dtype="object",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert not isinstance(result, pd.DatetimeIndex)

        result = pd.Index(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            name="idx",
        )
        exp = pd.Index(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            dtype="object",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert not isinstance(result, pd.DatetimeIndex)

        msg = "Mixed timezones detected. Pass utc=True in to_datetime"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(["2013-11-02 22:00-05:00", "2013-11-03 22:00-06:00"])

        # length = 1
        result = pd.Index([pd.Timestamp("2011-01-01")], name="idx")
        exp = pd.DatetimeIndex([pd.Timestamp("2011-01-01")], name="idx")
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is None

        # length = 1 with tz
        result = pd.Index(
            [pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo")], name="idx"
        )
        exp = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01 10:00")], tz="Asia/Tokyo", name="idx"
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is not None
        assert result.tz == exp.tz

    def test_construction_index_with_mixed_timezones_with_NaT(self):
        # see gh-11488
        result = pd.Index(
            [pd.NaT, pd.Timestamp("2011-01-01"), pd.NaT, pd.Timestamp("2011-01-02")],
            name="idx",
        )
        exp = pd.DatetimeIndex(
            [pd.NaT, pd.Timestamp("2011-01-01"), pd.NaT, pd.Timestamp("2011-01-02")],
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is None

        # Same tz results in DatetimeIndex
        result = pd.Index(
            [
                pd.NaT,
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.NaT,
                pd.Timestamp("2011-01-02 10:00", tz="Asia/Tokyo"),
            ],
            name="idx",
        )
        exp = pd.DatetimeIndex(
            [
                pd.NaT,
                pd.Timestamp("2011-01-01 10:00"),
                pd.NaT,
                pd.Timestamp("2011-01-02 10:00"),
            ],
            tz="Asia/Tokyo",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is not None
        assert result.tz == exp.tz

        # same tz results in DatetimeIndex (DST)
        result = pd.Index(
            [
                pd.Timestamp("2011-01-01 10:00", tz="US/Eastern"),
                pd.NaT,
                pd.Timestamp("2011-08-01 10:00", tz="US/Eastern"),
            ],
            name="idx",
        )
        exp = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00"),
                pd.NaT,
                pd.Timestamp("2011-08-01 10:00"),
            ],
            tz="US/Eastern",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is not None
        assert result.tz == exp.tz

        # different tz results in Index(dtype=object)
        result = pd.Index(
            [
                pd.NaT,
                pd.Timestamp("2011-01-01 10:00"),
                pd.NaT,
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            name="idx",
        )
        exp = pd.Index(
            [
                pd.NaT,
                pd.Timestamp("2011-01-01 10:00"),
                pd.NaT,
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            dtype="object",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert not isinstance(result, pd.DatetimeIndex)

        result = pd.Index(
            [
                pd.NaT,
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.NaT,
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            name="idx",
        )
        exp = pd.Index(
            [
                pd.NaT,
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.NaT,
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            dtype="object",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert not isinstance(result, pd.DatetimeIndex)

        # all NaT
        result = pd.Index([pd.NaT, pd.NaT], name="idx")
        exp = pd.DatetimeIndex([pd.NaT, pd.NaT], name="idx")
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)
        assert result.tz is None

    def test_construction_dti_with_mixed_timezones(self):
        # GH 11488 (not changed, added explicit tests)

        # no tz results in DatetimeIndex
        result = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")], name="idx"
        )
        exp = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")], name="idx"
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)

        # same tz results in DatetimeIndex
        result = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.Timestamp("2011-01-02 10:00", tz="Asia/Tokyo"),
            ],
            name="idx",
        )
        exp = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01 10:00"), pd.Timestamp("2011-01-02 10:00")],
            tz="Asia/Tokyo",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)

        # same tz results in DatetimeIndex (DST)
        result = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00", tz="US/Eastern"),
                pd.Timestamp("2011-08-01 10:00", tz="US/Eastern"),
            ],
            name="idx",
        )
        exp = pd.DatetimeIndex(
            [pd.Timestamp("2011-01-01 10:00"), pd.Timestamp("2011-08-01 10:00")],
            tz="US/Eastern",
            name="idx",
        )
        tm.assert_index_equal(result, exp, exact=True)
        assert isinstance(result, pd.DatetimeIndex)

        # tz mismatch affecting to tz-aware raises TypeError/ValueError

        msg = "cannot be converted to datetime64"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(
                [
                    pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                    pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
                ],
                name="idx",
            )

        # pre-2.0 this raised bc of awareness mismatch. in 2.0 with a tz#
        #  specified we behave as if this was called pointwise, so
        #  the naive Timestamp is treated as a wall time.
        dti = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            tz="Asia/Tokyo",
            name="idx",
        )
        expected = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern").tz_convert(
                    "Asia/Tokyo"
                ),
            ],
            tz="Asia/Tokyo",
            name="idx",
        )
        tm.assert_index_equal(dti, expected)

        # pre-2.0 mixed-tz scalars raised even if a tz/dtype was specified.
        #  as of 2.0 we successfully return the requested tz/dtype
        dti = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            tz="US/Eastern",
            name="idx",
        )
        expected = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo").tz_convert(
                    "US/Eastern"
                ),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            tz="US/Eastern",
            name="idx",
        )
        tm.assert_index_equal(dti, expected)

        # same thing but pass dtype instead of tz
        dti = pd.DatetimeIndex(
            [
                pd.Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
                pd.Timestamp("2011-01-02 10:00", tz="US/Eastern"),
            ],
            dtype="M8[us, US/Eastern]",
            name="idx",
        )
        tm.assert_index_equal(dti, expected)

    def test_construction_base_constructor(self):
        arr = [pd.Timestamp("2011-01-01"), pd.NaT, pd.Timestamp("2011-01-03")]
        tm.assert_index_equal(pd.Index(arr), pd.DatetimeIndex(arr))
        tm.assert_index_equal(pd.Index(np.array(arr)), pd.DatetimeIndex(np.array(arr)))

        arr = [np.nan, pd.NaT, pd.Timestamp("2011-01-03")]
        tm.assert_index_equal(pd.Index(arr), pd.DatetimeIndex(arr))
        tm.assert_index_equal(pd.Index(np.array(arr)), pd.DatetimeIndex(np.array(arr)))

    def test_construction_outofbounds(self):
        # GH 13663
        dates = [
            datetime(3000, 1, 1),
            datetime(4000, 1, 1),
            datetime(5000, 1, 1),
            datetime(6000, 1, 1),
        ]
        exp = pd.Index(dates, dtype="M8[us]")
        res = pd.Index(dates)
        tm.assert_index_equal(res, exp)

        pd.DatetimeIndex(dates)

    @pytest.mark.parametrize("data", [["1400-01-01"], [datetime(1400, 1, 1)]])
    def test_dti_date_out_of_range(self, data):
        # GH#1475
        pd.DatetimeIndex(data)

    def test_construction_with_ndarray(self):
        # GH 5152
        dates = [datetime(2013, 10, 7), datetime(2013, 10, 8), datetime(2013, 10, 9)]
        data = pd.DatetimeIndex(dates, freq=pd.offsets.BDay()).values
        result = pd.DatetimeIndex(data, freq=pd.offsets.BDay())
        expected = pd.DatetimeIndex(
            ["2013-10-07", "2013-10-08", "2013-10-09"], dtype="M8[us]", freq="B"
        )
        tm.assert_index_equal(result, expected)

    def test_integer_values_and_tz_interpreted_as_utc(self):
        # GH-24559
        val = np.datetime64("2000-01-01 00:00:00", "ns")
        values = np.array([val.view("i8")])

        result = pd.DatetimeIndex(values).tz_localize("US/Central")

        expected = pd.DatetimeIndex(["2000-01-01T00:00:00"], dtype="M8[ns, US/Central]")
        tm.assert_index_equal(result, expected)

        # but UTC is *not* deprecated.
        with tm.assert_produces_warning(None):
            result = pd.DatetimeIndex(values, tz="UTC")
        expected = pd.DatetimeIndex(["2000-01-01T00:00:00"], dtype="M8[ns, UTC]")
        tm.assert_index_equal(result, expected)

    def test_constructor_coverage(self):
        msg = r"DatetimeIndex\(\.\.\.\) must be called with a collection"
        with pytest.raises(TypeError, match=msg):
            pd.DatetimeIndex("1/1/2000")

        # generator expression
        gen = (datetime(2000, 1, 1) + timedelta(i) for i in range(10))
        result = pd.DatetimeIndex(gen)
        expected = pd.DatetimeIndex(
            [datetime(2000, 1, 1) + timedelta(i) for i in range(10)]
        )
        tm.assert_index_equal(result, expected)

        # NumPy string array
        strings = np.array(["2000-01-01", "2000-01-02", "2000-01-03"])
        result = pd.DatetimeIndex(strings)
        expected = pd.DatetimeIndex(strings.astype("O"))
        tm.assert_index_equal(result, expected)

        from_ints = pd.DatetimeIndex(expected.as_unit("ns").asi8).as_unit("us")
        tm.assert_index_equal(from_ints, expected)

        # string with NaT
        strings = np.array(["2000-01-01", "2000-01-02", "NaT"])
        result = pd.DatetimeIndex(strings)
        expected = pd.DatetimeIndex(strings.astype("O"))
        tm.assert_index_equal(result, expected)

        from_ints = pd.DatetimeIndex(expected.as_unit("ns").asi8).as_unit("us")
        tm.assert_index_equal(from_ints, expected)

        # non-conforming
        msg = (
            "Inferred frequency None from passed values does not conform "
            "to passed frequency D"
        )
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-04"], freq="D")

    @pytest.mark.parametrize("freq", ["YS", "W-SUN"])
    def test_constructor_datetime64_tzformat(self, freq):
        # see GH#6572: ISO 8601 format results in stdlib timezone object
        idx = pd.date_range(
            "2013-01-01T00:00:00-05:00", "2016-01-01T23:59:59-05:00", freq=freq
        )
        expected = pd.date_range(
            "2013-01-01T00:00:00",
            "2016-01-01T23:59:59",
            freq=freq,
            tz=timezone(timedelta(minutes=-300)),
        )
        tm.assert_index_equal(idx, expected)
        # Unable to use `US/Eastern` because of DST
        expected_i8 = pd.date_range(
            "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="America/Lima"
        )
        tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)

        idx = pd.date_range(
            "2013-01-01T00:00:00+09:00", "2016-01-01T23:59:59+09:00", freq=freq
        )
        expected = pd.date_range(
            "2013-01-01T00:00:00",
            "2016-01-01T23:59:59",
            freq=freq,
            tz=timezone(timedelta(minutes=540)),
        )
        tm.assert_index_equal(idx, expected)
        expected_i8 = pd.date_range(
            "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="Asia/Tokyo"
        )
        tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)

        # Non ISO 8601 format results in dateutil.tz.tzoffset
        idx = pd.date_range(
            "2013/1/1 0:00:00-5:00", "2016/1/1 23:59:59-5:00", freq=freq
        )
        expected = pd.date_range(
            "2013-01-01T00:00:00",
            "2016-01-01T23:59:59",
            freq=freq,
            tz=timezone(timedelta(minutes=-300)),
        )
        tm.assert_index_equal(idx, expected)
        # Unable to use `US/Eastern` because of DST
        expected_i8 = pd.date_range(
            "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="America/Lima"
        )
        tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)

        idx = pd.date_range(
            "2013/1/1 0:00:00+9:00", "2016/1/1 23:59:59+09:00", freq=freq
        )
        expected = pd.date_range(
            "2013-01-01T00:00:00",
            "2016-01-01T23:59:59",
            freq=freq,
            tz=timezone(timedelta(minutes=540)),
        )
        tm.assert_index_equal(idx, expected)
        expected_i8 = pd.date_range(
            "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="Asia/Tokyo"
        )
        tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)

    def test_constructor_dtype(self):
        # passing a dtype with a tz should localize
        idx = pd.DatetimeIndex(
            ["2013-01-01", "2013-01-02"], dtype="datetime64[ns, US/Eastern]"
        )
        expected = (
            pd.DatetimeIndex(["2013-01-01", "2013-01-02"])
            .as_unit("ns")
            .tz_localize("US/Eastern")
        )
        tm.assert_index_equal(idx, expected)

        idx = pd.DatetimeIndex(["2013-01-01", "2013-01-02"], tz="US/Eastern").as_unit(
            "ns"
        )
        tm.assert_index_equal(idx, expected)

    def test_constructor_dtype_tz_mismatch_raises(self):
        # if we already have a tz and its not the same, then raise
        idx = pd.DatetimeIndex(
            ["2013-01-01", "2013-01-02"], dtype="datetime64[ns, US/Eastern]"
        )

        msg = (
            "cannot supply both a tz and a timezone-naive dtype "
            r"\(i\.e\. datetime64\[ns\]\)"
        )
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(idx, dtype="datetime64[ns]")

        # this is effectively trying to convert tz's
        msg = "data is already tz-aware US/Eastern, unable to set specified tz: CET"
        with pytest.raises(TypeError, match=msg):
            pd.DatetimeIndex(idx, dtype="datetime64[ns, CET]")
        msg = "cannot supply both a tz and a dtype with a tz"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(idx, tz="CET", dtype="datetime64[ns, US/Eastern]")

        result = pd.DatetimeIndex(idx, dtype="datetime64[ns, US/Eastern]")
        tm.assert_index_equal(idx, result)

    @pytest.mark.parametrize("dtype", [object, np.int32, np.int64])
    def test_constructor_invalid_dtype_raises(self, dtype):
        # GH 23986
        msg = "Unexpected value for 'dtype'"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex([1, 2], dtype=dtype)

    def test_000constructor_resolution(self):
        # 2252
        t1 = pd.Timestamp((1352934390 * 1000000000) + 1000000 + 1000 + 1)
        idx = pd.DatetimeIndex([t1])

        assert idx.nanosecond[0] == t1.nanosecond

    def test_disallow_setting_tz(self):
        # GH 3746
        dti = pd.DatetimeIndex(["2010"], tz="UTC")
        msg = "Cannot directly set timezone"
        with pytest.raises(AttributeError, match=msg):
            dti.tz = zoneinfo.ZoneInfo("US/Pacific")

    @pytest.mark.parametrize(
        "tz",
        [
            None,
            "America/Los_Angeles",
            zoneinfo.ZoneInfo("America/Los_Angeles"),
            pd.Timestamp("2000", tz="America/Los_Angeles").tz,
        ],
    )
    def test_constructor_start_end_with_tz(self, tz):
        # GH 18595
        start = pd.Timestamp("2013-01-01 06:00:00", tz="America/Los_Angeles")
        end = pd.Timestamp("2013-01-02 06:00:00", tz="America/Los_Angeles")
        result = pd.date_range(freq="D", start=start, end=end, tz=tz, unit="ns")
        expected = pd.DatetimeIndex(
            ["2013-01-01 06:00:00", "2013-01-02 06:00:00"],
            dtype="M8[ns, America/Los_Angeles]",
            freq="D",
        )
        tm.assert_index_equal(result, expected)
        # Especially assert that the timezone is consistent for zoneinfo
        assert zoneinfo.ZoneInfo("America/Los_Angeles") is result.tz

    @pytest.mark.parametrize("tz", ["US/Pacific", "US/Eastern", "Asia/Tokyo"])
    def test_constructor_with_non_normalized_pytz(self, tz):
        # GH 18595
        pytz = pytest.importorskip("pytz")
        tz_in = pytz.timezone(tz)
        non_norm_tz = pd.Timestamp("2010", tz=tz_in).tz
        result = pd.DatetimeIndex(["2010"], tz=non_norm_tz)
        assert pytz.timezone(tz) is result.tz

    def test_constructor_timestamp_near_dst(self):
        # GH 20854
        ts = [
            pd.Timestamp("2016-10-30 03:00:00+0300", tz="Europe/Helsinki"),
            pd.Timestamp("2016-10-30 03:00:00+0200", tz="Europe/Helsinki"),
        ]
        result = pd.DatetimeIndex(ts).as_unit("ns")
        expected = pd.DatetimeIndex(
            [ts[0].to_pydatetime(), ts[1].to_pydatetime()]
        ).as_unit("ns")
        tm.assert_index_equal(result, expected)

    @pytest.mark.parametrize("klass", [pd.Index, pd.DatetimeIndex])
    @pytest.mark.parametrize("box", [np.array, partial(np.array, dtype=object), list])
    @pytest.mark.parametrize(
        "tz, dtype",
        [("US/Pacific", "datetime64[ns, US/Pacific]"), (None, "datetime64[ns]")],
    )
    def test_constructor_with_int_tz(self, klass, box, tz, dtype):
        # GH 20997, 20964
        ts = pd.Timestamp("2018-01-01", tz=tz).as_unit("ns")
        result = klass(box([ts._value]), dtype=dtype)
        expected = klass([ts])
        assert result == expected

    def test_construction_int_rountrip(self, tz_naive_fixture):
        # GH 12619, GH#24559
        tz = tz_naive_fixture

        result = 1293858000000000000
        expected = pd.DatetimeIndex([result], tz=tz).asi8[0]
        assert result == expected

    def test_construction_from_replaced_timestamps_with_dst(self):
        # GH 18785
        index = pd.date_range(
            pd.Timestamp(2000, 12, 31),
            pd.Timestamp(2005, 12, 31),
            freq="YE-DEC",
            tz="Australia/Melbourne",
            unit="ns",
        )
        result = pd.DatetimeIndex([x.replace(month=6, day=1) for x in index])
        expected = pd.DatetimeIndex(
            [
                "2000-06-01 00:00:00",
                "2001-06-01 00:00:00",
                "2002-06-01 00:00:00",
                "2003-06-01 00:00:00",
                "2004-06-01 00:00:00",
                "2005-06-01 00:00:00",
            ],
            tz="Australia/Melbourne",
        ).as_unit("ns")
        tm.assert_index_equal(result, expected)

    def test_construction_with_tz_and_tz_aware_dti(self):
        # GH 23579
        dti = pd.date_range("2016-01-01", periods=3, tz="US/Central")
        msg = "data is already tz-aware US/Central, unable to set specified tz"
        with pytest.raises(TypeError, match=msg):
            pd.DatetimeIndex(dti, tz="Asia/Tokyo")

    def test_construction_with_nat_and_tzlocal(self):
        tz = dateutil.tz.tzlocal()
        result = pd.DatetimeIndex(["2018", "NaT"], tz=tz).as_unit("ns")
        expected = pd.DatetimeIndex([pd.Timestamp("2018", tz=tz), pd.NaT]).as_unit("ns")
        tm.assert_index_equal(result, expected)

    def test_constructor_with_ambiguous_keyword_arg(self):
        # GH 35297

        msg = "The 'ambiguous' keyword in DatetimeIndex is deprecated"
        with tm.assert_produces_warning(Pandas4Warning, match=msg):
            expected = pd.DatetimeIndex(
                ["2020-11-01 01:00:00", "2020-11-02 01:00:00"],
                dtype="datetime64[ns, America/New_York]",
                freq="D",
                ambiguous=False,
            )

        # ambiguous keyword in start
        timezone = "America/New_York"
        start = pd.Timestamp(year=2020, month=11, day=1, hour=1).tz_localize(
            timezone, ambiguous=False
        )
        result = pd.date_range(start=start, periods=2, ambiguous=False, unit="ns")
        tm.assert_index_equal(result, expected)

        # ambiguous keyword in end
        timezone = "America/New_York"
        end = pd.Timestamp(year=2020, month=11, day=2, hour=1).tz_localize(
            timezone, ambiguous=False
        )
        result = pd.date_range(end=end, periods=2, ambiguous=False, unit="ns")
        tm.assert_index_equal(result, expected)

    def test_constructor_with_nonexistent_keyword_arg(self, warsaw):
        # GH 35297
        timezone = warsaw

        # nonexistent keyword in start
        start = pd.Timestamp("2015-03-29 02:30:00").tz_localize(
            timezone, nonexistent="shift_forward"
        )
        result = pd.date_range(start=start, periods=2, freq="h", unit="ns")
        expected = pd.DatetimeIndex(
            [
                pd.Timestamp("2015-03-29 03:00:00+02:00", tz=timezone),
                pd.Timestamp("2015-03-29 04:00:00+02:00", tz=timezone),
            ]
        ).as_unit("ns")

        tm.assert_index_equal(result, expected, check_freq=False)

        # nonexistent keyword in end
        end = start
        result = pd.date_range(end=end, periods=2, freq="h", unit="ns")
        expected = pd.DatetimeIndex(
            [
                pd.Timestamp("2015-03-29 01:00:00+01:00", tz=timezone),
                pd.Timestamp("2015-03-29 03:00:00+02:00", tz=timezone),
            ]
        ).as_unit("ns")

        tm.assert_index_equal(result, expected, check_freq=False)

    def test_constructor_no_precision_raises(self):
        # GH-24753, GH-24739

        msg = "with no precision is not allowed"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(["2000"], dtype="datetime64")

        msg = "The 'datetime64' dtype has no unit. Please pass in"
        with pytest.raises(ValueError, match=msg):
            pd.Index(["2000"], dtype="datetime64")

    def test_constructor_wrong_precision_raises(self):
        dti = pd.DatetimeIndex(["2000"], dtype="datetime64[us]")
        assert dti.dtype == "M8[us]"
        assert dti[0] == pd.Timestamp(2000, 1, 1)

    def test_index_constructor_with_numpy_object_array_and_timestamp_tz_with_nan(self):
        # GH 27011
        result = pd.Index(
            np.array([pd.Timestamp("2019", tz="UTC"), np.nan], dtype=object)
        )
        expected = pd.DatetimeIndex([pd.Timestamp("2019", tz="UTC"), pd.NaT])
        tm.assert_index_equal(result, expected)

    @pytest.mark.parametrize(
        "tz", [zoneinfo.ZoneInfo("US/Eastern"), gettz("US/Eastern")]
    )
    def test_dti_from_tzaware_datetime(self, tz):
        d = [datetime(2012, 8, 19, tzinfo=tz)]

        index = pd.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 = pd.to_datetime(arr).tz_localize(tzstr)
        idx2 = pd.date_range(
            start="2005-11-10 08:00:00", freq="h", periods=2, tz=tzstr, unit="us"
        )
        idx2 = idx2._with_freq(None)  # the others all have freq=None
        idx3 = pd.DatetimeIndex(arr, tz=tzstr)
        idx4 = pd.DatetimeIndex(np.array(arr), tz=tzstr)

        tm.assert_index_equal(idx1, idx2)
        tm.assert_index_equal(idx1, idx3)
        tm.assert_index_equal(idx1, idx4)

    def test_dti_construction_idempotent(self, unit):
        rng = pd.date_range(
            "03/12/2012 00:00", periods=10, freq="W-FRI", tz="US/Eastern", unit=unit
        )
        rng2 = pd.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 = pd.DatetimeIndex([datetime(2012, 1, 1)], tz=prefix + "EST")
        index.hour
        index[0]

    @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
    def test_dti_convert_datetime_list(self, tzstr):
        dr = pd.date_range("2012-06-02", periods=10, tz=tzstr, name="foo")
        dr2 = pd.DatetimeIndex(list(dr), name="foo", freq="D")
        tm.assert_index_equal(dr, dr2)

    @pytest.mark.parametrize(
        "tz",
        [
            "pytz/US/Eastern",
            gettz("US/Eastern"),
        ],
    )
    @pytest.mark.parametrize("use_str", [True, False])
    @pytest.mark.parametrize("box_cls", [pd.Timestamp, pd.DatetimeIndex])
    def test_dti_ambiguous_matches_timestamp(self, tz, use_str, box_cls, request):
        # GH#47471 check that we get the same raising behavior in the DTI
        # constructor and Timestamp constructor
        if isinstance(tz, str) and tz.startswith("pytz/"):
            pytz = pytest.importorskip("pytz")
            tz = pytz.timezone(tz.removeprefix("pytz/"))
        dtstr = "2013-11-03 01:59:59.999999"
        item = dtstr
        if not use_str:
            item = pd.Timestamp(dtstr).to_pydatetime()
        if box_cls is not pd.Timestamp:
            item = [item]

        if not use_str and isinstance(tz, dateutil.tz.tzfile):
            # FIXME: The Timestamp constructor here behaves differently than all
            #  the other cases bc with dateutil/zoneinfo tzinfos we implicitly
            #  get fold=0. Having this raise is not important, but having the
            #  behavior be consistent across cases is.
            mark = pytest.mark.xfail(reason="We implicitly get fold=0.")
            request.applymarker(mark)

        with pytest.raises(ValueError, match=dtstr):
            box_cls(item, tz=tz)

    @pytest.mark.parametrize("tz", [None, "UTC", "US/Pacific"])
    def test_dti_constructor_with_non_nano_dtype(self, tz):
        # GH#55756, GH#54620
        ts = pd.Timestamp("2999-01-01")
        dtype = "M8[us]"
        if tz is not None:
            dtype = f"M8[us, {tz}]"
        vals = [ts, "2999-01-02 03:04:05.678910", 2500]
        result = pd.DatetimeIndex(vals, dtype=dtype)
        # The 2500 is interpreted as microseconds, consistent with what
        #  we would get if we created DatetimeIndexes from vals[:2] and vals[2:]
        #  and concated the results.
        pointwise = [
            vals[0].tz_localize(tz),
            pd.Timestamp(vals[1], tz=tz),
            pd.to_datetime(vals[2], unit="us", utc=True).tz_convert(tz),
        ]
        exp_vals = [x.as_unit("us").asm8 for x in pointwise]
        exp_arr = np.array(exp_vals, dtype="M8[us]")
        expected = pd.DatetimeIndex(exp_arr, dtype="M8[us]")
        if tz is not None:
            expected = expected.tz_localize("UTC").tz_convert(tz)
        tm.assert_index_equal(result, expected)

        result2 = pd.DatetimeIndex(np.array(vals, dtype=object), dtype=dtype)
        tm.assert_index_equal(result2, expected)

    def test_dti_constructor_with_non_nano_now_today(self):
        # GH#55756, GH#57535
        now = pd.Timestamp.now()  # noqa: TID251
        today = pd.Timestamp.today()  # noqa: TID251
        result = pd.DatetimeIndex(["now", "today"], dtype="M8[s]")
        now_after = pd.Timestamp.now()  # noqa: TID251
        assert result.dtype == "M8[s]"

        # result should be between the before/after timestamps
        assert result[0] >= now.as_unit("s")
        assert result[0] <= now_after.as_unit("s")
        assert result[1] >= today.as_unit("s")
        assert result[1] <= now_after.as_unit("s")

    def test_dti_constructor_object_float_matches_float_dtype(self):
        # GH#55780
        arr = np.array([0, np.nan], dtype=np.float64)
        arr2 = arr.astype(object)

        dti1 = pd.DatetimeIndex(arr, tz="CET")
        dti2 = pd.DatetimeIndex(arr2, tz="CET")
        tm.assert_index_equal(dti1, dti2)

    @pytest.mark.parametrize("dtype", ["M8[us]", "M8[us, US/Pacific]"])
    def test_dti_constructor_with_dtype_object_int_matches_int_dtype(self, dtype):
        # Going through the object path should match the non-object path

        vals1 = np.arange(5, dtype="i8") * 1000
        vals1[0] = pd.NaT.value

        vals2 = vals1.astype(np.float64)
        vals2[0] = np.nan

        vals3 = vals1.astype(object)
        # change lib.infer_dtype(vals3) from "integer" so we go through
        #  array_to_datetime in _sequence_to_dt64
        vals3[0] = pd.NaT

        vals4 = vals2.astype(object)

        res1 = pd.DatetimeIndex(vals1, dtype=dtype)
        res2 = pd.DatetimeIndex(vals2, dtype=dtype)
        res3 = pd.DatetimeIndex(vals3, dtype=dtype)
        res4 = pd.DatetimeIndex(vals4, dtype=dtype)

        expected = pd.DatetimeIndex(vals1.view("M8[us]"))
        if res1.tz is not None:
            expected = expected.tz_localize("UTC").tz_convert(res1.tz)
        tm.assert_index_equal(res1, expected)
        tm.assert_index_equal(res2, expected)
        tm.assert_index_equal(res3, expected)
        tm.assert_index_equal(res4, expected)


class TestTimeSeries:
    def test_dti_constructor_preserve_dti_freq(self):
        rng = pd.date_range("1/1/2000", "1/2/2000", freq="5min")

        rng2 = pd.DatetimeIndex(rng)
        assert rng.freq == rng2.freq

    def test_explicit_none_freq(self):
        # Explicitly passing freq=None is respected
        rng = pd.date_range("1/1/2000", "1/2/2000", freq="5min")

        result = pd.DatetimeIndex(rng, freq=None)
        assert result.freq is None

        result = pd.DatetimeIndex(rng._data, freq=None)
        assert result.freq is None

    def test_constructor_equivalent_freq(self):
        # GH#61086 - equivalent but not equal frequencies should be accepted
        dti = pd.date_range("2020-02-01", freq="QS-MAY", periods=3)

        result = pd.DatetimeIndex(dti, freq="QS-FEB")
        expected = pd.DatetimeIndex(
            ["2020-02-01", "2020-05-01", "2020-08-01"], freq="QS-FEB"
        )
        tm.assert_index_equal(result, expected)

    def test_constructor_equivalent_freq_raises(self):
        # GH#61086 - non-equivalent frequencies should still raise
        dti = pd.date_range("2020-02-01", freq="QS-MAY", periods=3)

        msg = "does not conform to passed frequency"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(dti, freq="QS-JAN")

    @pytest.mark.parametrize(
        "data_freq, passed_freq",
        [
            # Jan 2000 has 31 days, so the first month-start gap equals 31D/744h
            ("MS", "31D"),
            ("MS", "744h"),
            # Apr 30 2000 is a Sunday, so ME data does not conform to BME even
            # though the earlier month-end gaps happen to agree
            ("ME", "BME"),
        ],
    )
    def test_constructor_freq_first_step_coincidence(self, data_freq, passed_freq):
        # GH#65012 a passed freq that only agrees with the data on the first
        # step must not be pinned; later steps diverge, so it does not conform
        dti = pd.date_range("2000-01-01", freq=data_freq, periods=4)

        msg = "does not conform to passed frequency"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(dti, freq=passed_freq)

    def test_constructor_tick_freq_conforming_accepted(self):
        # GH#65012 a Tick freq that differs from the inferred (non-Tick) freq
        # but genuinely conforms is accepted via the uniform-spacing fast path
        dti = pd.date_range("2000-01-01", freq="D", periods=5)  # inferred Day

        result = pd.DatetimeIndex(dti, freq="24h")
        assert result.freqstr == "24h"

    def test_constructor_tick_freq_tz_aware_across_dst_raises(self):
        # GH#65012 a Tick is a fixed step in i8 space even across a DST
        # transition, so the uniform-spacing check runs on the (UTC) i8 view
        dti = pd.date_range("2021-03-13", freq="2h", periods=4, tz="US/Eastern")

        msg = "does not conform to passed frequency"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(dti, freq="3h")

    def test_constructor_tick_freq_subunit_resolution_raises(self):
        # GH#65012 a Tick finer than the index resolution cannot conform even
        # if it rounds to the data's step (1500ms -> 1s at second resolution)
        dti = pd.date_range("2000-01-01", freq="s", periods=4).as_unit("s")

        msg = "does not conform to passed frequency"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(dti, freq="1500ms")

    def test_dti_constructor_small_int(self, any_int_numpy_dtype):
        # see gh-13721
        exp = pd.DatetimeIndex(
            [
                "1970-01-01 00:00:00.00000000",
                "1970-01-01 00:00:00.00000001",
                "1970-01-01 00:00:00.00000002",
            ]
        )

        arr = np.array([0, 10, 20], dtype=any_int_numpy_dtype)
        tm.assert_index_equal(pd.DatetimeIndex(arr), exp)

    def test_ctor_str_intraday(self):
        rng = pd.DatetimeIndex(["1-1-2000 00:00:01"])
        assert rng[0].second == 1

    def test_index_cast_datetime64_other_units(self):
        arr = np.arange(0, 100, 10, dtype=np.int64).view("M8[D]")
        idx = pd.Index(arr)

        assert (idx.values == astype_overflowsafe(arr, dtype=np.dtype("M8[ns]"))).all()

    def test_constructor_int64_nocopy(self):
        # GH#1624
        arr = np.arange(1000, dtype=np.int64)
        index = pd.DatetimeIndex(arr, copy=False)

        arr[50:100] = -1
        assert (index.asi8[50:100] == -1).all()

        arr = np.arange(1000, dtype=np.int64)
        index = pd.DatetimeIndex(arr, copy=True)

        arr[50:100] = -1
        assert (index.asi8[50:100] != -1).all()

    @pytest.mark.parametrize(
        "freq",
        ["ME", "QE", "YE", "D", "B", "bh", "min", "s", "ms", "us", "h", "ns", "C"],
    )
    def test_from_freq_recreate_from_data(self, freq):
        org = pd.date_range(start="2001/02/01 09:00", freq=freq, periods=1)
        idx = pd.DatetimeIndex(org, freq=freq)
        tm.assert_index_equal(idx, org)

        org = pd.date_range(
            start="2001/02/01 09:00", freq=freq, tz="US/Pacific", periods=1
        )
        idx = pd.DatetimeIndex(org, freq=freq, tz="US/Pacific")
        tm.assert_index_equal(idx, org)

    def test_datetimeindex_constructor_misc(self):
        arr = ["1/1/2005", "1/2/2005", "Jn 3, 2005", "2005-01-04"]
        msg = r"(\(')?Unknown datetime string format(:', 'Jn 3, 2005'\))?"
        with pytest.raises(ValueError, match=msg):
            pd.DatetimeIndex(arr)

        arr = ["1/1/2005", "1/2/2005", "1/3/2005", "2005-01-04"]
        idx1 = pd.DatetimeIndex(arr)

        arr = [datetime(2005, 1, 1), "1/2/2005", "1/3/2005", "2005-01-04"]
        idx2 = pd.DatetimeIndex(arr)

        arr = [pd.Timestamp(datetime(2005, 1, 1)), "1/2/2005", "1/3/2005", "2005-01-04"]
        idx3 = pd.DatetimeIndex(arr)

        arr = np.array(["1/1/2005", "1/2/2005", "1/3/2005", "2005-01-04"], dtype="O")
        idx4 = pd.DatetimeIndex(arr)

        depr_msg = "keyword in DatetimeIndex is deprecated"
        with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
            idx5 = pd.DatetimeIndex(["12/05/2007", "25/01/2008"], dayfirst=True)
        with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
            idx6 = pd.DatetimeIndex(
                ["2007/05/12", "2008/01/25"], dayfirst=False, yearfirst=True
            )
        tm.assert_index_equal(idx5, idx6)

        for other in [idx2, idx3, idx4]:
            assert (idx1.values == other.values).all()

    def test_dti_constructor_object_dtype_dayfirst_yearfirst_with_tz(self):
        # GH#55813
        val = "5/10/16"

        dfirst = pd.Timestamp(2016, 10, 5, tz="US/Pacific")
        yfirst = pd.Timestamp(2005, 10, 16, tz="US/Pacific")

        depr_msg = "keyword in DatetimeIndex is deprecated"
        with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
            result1 = pd.DatetimeIndex([val], tz="US/Pacific", dayfirst=True)
        expected1 = pd.DatetimeIndex([dfirst])
        tm.assert_index_equal(result1, expected1)

        with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
            result2 = pd.DatetimeIndex([val], tz="US/Pacific", yearfirst=True)
        expected2 = pd.DatetimeIndex([yfirst])
        tm.assert_index_equal(result2, expected2)


@pytest.mark.parametrize(
    "kwarg, val",
    [
        ("dayfirst", True),
        ("dayfirst", False),
        ("yearfirst", True),
        ("yearfirst", False),
        ("ambiguous", False),
        ("ambiguous", True),
        ("ambiguous", "infer"),
        ("ambiguous", "NaT"),
        ("ambiguous", "raise"),
        ("ambiguous", np.array([True])),
    ],
)
def test_dti_constructor_deprecated_keywords(kwarg, val):
    # GH#55499 explicitly passing a keyword warns even with the default value
    msg = f"The '{kwarg}' keyword in DatetimeIndex is deprecated"
    with tm.assert_produces_warning(Pandas4Warning, match=msg):
        pd.DatetimeIndex(["2020-01-01"], **{kwarg: val})


def test_dti_freq_validation_wall_time_dst():
    # GH#55499 freq validation compares wall times, so wall-preserving freqs
    #  spanning a DST transition validate without the ambiguous keyword
    vals = pd.DatetimeIndex(["2020-10-31 01:30", "2020-11-01 01:30"]).tz_localize(
        "America/New_York", ambiguous=[False, False]
    )

    result = pd.DatetimeIndex(vals, freq="D")
    assert result.freq == "D"
    tm.assert_numpy_array_equal(result.asi8, vals.asi8)

    setter_result = vals.copy()
    setter_result.freq = "D"
    assert setter_result.freq == "D"

    msg = "does not conform to passed frequency"
    with pytest.raises(ValueError, match=msg):
        # Tick freq, so still validated against the UTC values
        pd.DatetimeIndex(vals, freq="h")

    # non-Tick freq that does not conform in wall time still raises
    naive = pd.DatetimeIndex(["2020-10-31 01:30", "2020-11-01 02:30"])
    with pytest.raises(ValueError, match=msg):
        pd.DatetimeIndex(naive.tz_localize("America/New_York"), freq="D")


@pytest.mark.parametrize("tz", [zoneinfo.ZoneInfo("US/Eastern"), gettz("US/Eastern")])
@pytest.mark.parametrize(
    "dtstr",
    [
        "11/5/2023 01:30",  # DST-ambiguous wall time
        "3/12/2023 02:30",  # nonexistent wall time
    ],
)
def test_dti_noniso_dst_transition_matches_timestamp(dtstr, tz):
    # GH#66123 dateutil-parsed (non-ISO) strings at ambiguous/nonexistent
    #  wall times resolve via fold=0 like Timestamp and datetime objects,
    #  rather than raising like ISO strings
    expected = pd.Timestamp(dtstr, tz=tz)
    result = pd.DatetimeIndex([dtstr], tz=tz)
    assert result[0] == expected


def test_dti_noniso_ambiguous_pytz_raises():
    # GH#66123 with pytz, dateutil-parsed strings at ambiguous wall times
    #  raise, matching the Timestamp constructor
    pytz = pytest.importorskip("pytz")
    tz = pytz.timezone("US/Eastern")
    dtstr = "11/5/2023 01:30"
    with pytest.raises(ValueError, match="2023-11-05 01:30:00"):
        pd.Timestamp(dtstr, tz=tz)
    with pytest.raises(ValueError, match="2023-11-05 01:30:00"):
        pd.DatetimeIndex([dtstr], tz=tz)
