Skip to content

API: default to stdlib timezone objects for fixed-offsets #49677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ Other API changes
^^^^^^^^^^^^^^^^^
- The ``freq``, ``tz``, ``nanosecond``, and ``unit`` keywords in the :class:`Timestamp` constructor are now keyword-only (:issue:`45307`)
- Passing ``nanoseconds`` greater than 999 or less than 0 in :class:`Timestamp` now raises a ``ValueError`` (:issue:`48538`, :issue:`48255`)
- When inferring a fixed-offset ``tzinfo`` from a string or integer ``tz``, a standard-library ``datetime.timezone`` object is returned instead of a ``pytz`` object (:issue:`34916`)
- :func:`read_csv`: specifying an incorrect number of columns with ``index_col`` of now raises ``ParserError`` instead of ``IndexError`` when using the c parser.
- Default value of ``dtype`` in :func:`get_dummies` is changed to ``bool`` from ``uint8`` (:issue:`45848`)
- :meth:`DataFrame.astype`, :meth:`Series.astype`, and :meth:`DatetimeIndex.astype` casting datetime64 data to any of "datetime64[s]", "datetime64[ms]", "datetime64[us]" will return an object with the given resolution instead of coercing back to "datetime64[ns]" (:issue:`48928`)
Expand Down
14 changes: 8 additions & 6 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
cimport cython

from datetime import timezone

from cpython.datetime cimport (
PyDate_Check,
PyDateTime_Check,
datetime,
import_datetime,
timedelta,
tzinfo,
)
from cpython.object cimport PyObject
Expand All @@ -23,8 +27,6 @@ import numpy as np

cnp.import_array()

import pytz

from pandas._libs.tslibs.np_datetime cimport (
NPY_DATETIMEUNIT,
NPY_FR_ns,
Expand Down Expand Up @@ -94,7 +96,7 @@ def _test_parse_iso8601(ts: str):
obj.value = npy_datetimestruct_to_datetime(NPY_FR_ns, &obj.dts)
check_dts_bounds(&obj.dts)
if out_local == 1:
obj.tzinfo = pytz.FixedOffset(out_tzoffset)
obj.tzinfo = timezone(timedelta(minutes=out_tzoffset))
obj.value = tz_localize_to_utc_single(obj.value, obj.tzinfo)
return Timestamp(obj.value, tz=obj.tzinfo)
else:
Expand Down Expand Up @@ -454,7 +456,7 @@ cpdef array_to_datetime(
2) datetime.datetime objects, if OutOfBoundsDatetime or TypeError
is encountered

Also returns a pytz.FixedOffset if an array of strings with the same
Also returns a fixed-offset tzinfo object if an array of strings with the same
timezone offset is passed and utc=True is not passed. Otherwise, None
is returned

Expand Down Expand Up @@ -655,7 +657,7 @@ cpdef array_to_datetime(
# since we store the total_seconds of
# dateutil.tz.tzoffset objects
out_tzoffset_vals.add(out_tzoffset * 60.)
tz = pytz.FixedOffset(out_tzoffset)
tz = timezone(timedelta(minutes=out_tzoffset))
value = tz_localize_to_utc_single(value, tz)
out_local = 0
out_tzoffset = 0
Expand Down Expand Up @@ -729,7 +731,7 @@ cpdef array_to_datetime(
return _array_to_datetime_object(values, errors, dayfirst, yearfirst)
else:
tz_offset = out_tzoffset_vals.pop()
tz_out = pytz.FixedOffset(tz_offset / 60.)
tz_out = timezone(timedelta(seconds=tz_offset))
return result, tz_out


Expand Down
7 changes: 4 additions & 3 deletions pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ from numpy cimport (

cnp.import_array()

import pytz

# stdlib datetime imports

from datetime import timezone

from cpython.datetime cimport (
PyDate_Check,
PyDateTime_Check,
datetime,
import_datetime,
time,
timedelta,
tzinfo,
)

Expand Down Expand Up @@ -407,7 +408,7 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts,

value = npy_datetimestruct_to_datetime(NPY_FR_ns, &dts)
obj.dts = dts
obj.tzinfo = pytz.FixedOffset(tzoffset)
obj.tzinfo = timezone(timedelta(minutes=tzoffset))
obj.value = tz_localize_to_utc_single(value, obj.tzinfo)
if tz is None:
check_overflows(obj, NPY_FR_ns)
Expand Down
11 changes: 7 additions & 4 deletions pandas/_libs/tslibs/strptime.pyx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Strptime-related classes and functions.
"""
from datetime import timezone

from cpython.datetime cimport (
date,
timedelta,
tzinfo,
)

Expand Down Expand Up @@ -488,15 +491,15 @@ cdef (int, int) _calc_julian_from_V(int iso_year, int iso_week, int iso_weekday)

cdef tzinfo parse_timezone_directive(str z):
"""
Parse the '%z' directive and return a pytz.FixedOffset
Parse the '%z' directive and return a datetime.timezone object.

Parameters
----------
z : string of the UTC offset

Returns
-------
pytz.FixedOffset
datetime.timezone

Notes
-----
Expand All @@ -510,7 +513,7 @@ cdef tzinfo parse_timezone_directive(str z):
object gmtoff_remainder, gmtoff_remainder_padding

if z == 'Z':
return pytz.FixedOffset(0)
return timezone(timedelta(0))
if z[3] == ':':
z = z[:3] + z[4:]
if len(z) > 5:
Expand All @@ -530,4 +533,4 @@ cdef tzinfo parse_timezone_directive(str z):
total_minutes = ((hours * 60) + minutes + (seconds // 60) +
(microseconds // 60_000_000))
total_minutes = -total_minutes if z.startswith("-") else total_minutes
return pytz.FixedOffset(total_minutes)
return timezone(timedelta(minutes=total_minutes))
2 changes: 1 addition & 1 deletion pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ from pandas._libs.tslibs.timezones cimport (
is_utc,
maybe_get_tz,
treat_tz_as_pytz,
utc_pytz as UTC,
utc_stdlib as UTC,
)
from pandas._libs.tslibs.tzconversion cimport (
tz_convert_from_utc_single,
Expand Down
2 changes: 1 addition & 1 deletion pandas/_libs/tslibs/timezones.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ from cpython.datetime cimport (
)


cdef tzinfo utc_pytz
cdef tzinfo utc_stdlib

cpdef bint is_utc(tzinfo tz)
cdef bint is_tzlocal(tzinfo tz)
Expand Down
10 changes: 3 additions & 7 deletions pandas/_libs/tslibs/timezones.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,10 @@ from dateutil.tz import (
tzlocal as _dateutil_tzlocal,
tzutc as _dateutil_tzutc,
)
import numpy as np
import pytz
from pytz.tzinfo import BaseTzInfo as _pytz_BaseTzInfo

UTC = pytz.utc


import numpy as np

cimport numpy as cnp
from numpy cimport int64_t

Expand All @@ -49,7 +45,7 @@ from pandas._libs.tslibs.util cimport (

cdef int64_t NPY_NAT = get_nat()
cdef tzinfo utc_stdlib = timezone.utc
cdef tzinfo utc_pytz = UTC
cdef tzinfo utc_pytz = pytz.utc
cdef tzinfo utc_dateutil_str = dateutil_gettz("UTC") # NB: *not* the same as tzutc()

cdef tzinfo utc_zoneinfo = None
Expand Down Expand Up @@ -171,7 +167,7 @@ cpdef inline tzinfo maybe_get_tz(object tz):
else:
tz = pytz.timezone(tz)
elif is_integer_object(tz):
tz = pytz.FixedOffset(tz / 60)
tz = timezone(timedelta(seconds=tz))
elif isinstance(tz, tzinfo):
pass
elif tz is None:
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/tools/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ def to_datetime(

>>> pd.to_datetime(['2018-10-26 12:00 -0500', '2018-10-26 13:00 -0500'])
DatetimeIndex(['2018-10-26 12:00:00-05:00', '2018-10-26 13:00:00-05:00'],
dtype='datetime64[ns, pytz.FixedOffset(-300)]', freq=None)
dtype='datetime64[ns, UTC-05:00]', freq=None)

- However, timezone-aware inputs *with mixed time offsets* (for example
issued from a timezone with daylight savings, such as Europe/Paris)
Expand All @@ -1010,7 +1010,7 @@ def to_datetime(
>>> from datetime import datetime
>>> pd.to_datetime(["2020-01-01 01:00 -01:00", datetime(2020, 1, 1, 3, 0)])
DatetimeIndex(['2020-01-01 01:00:00-01:00', '2020-01-01 02:00:00-01:00'],
dtype='datetime64[ns, pytz.FixedOffset(-60)]', freq=None)
dtype='datetime64[ns, UTC-01:00]', freq=None)

|

Expand Down
10 changes: 5 additions & 5 deletions pandas/tests/indexes/datetimes/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,15 +627,15 @@ def test_constructor_coverage(self):

@pytest.mark.parametrize("freq", ["AS", "W-SUN"])
def test_constructor_datetime64_tzformat(self, freq):
# see GH#6572: ISO 8601 format results in pytz.FixedOffset
# see GH#6572: ISO 8601 format results in stdlib timezone object
idx = date_range(
"2013-01-01T00:00:00-05:00", "2016-01-01T23:59:59-05:00", freq=freq
)
expected = date_range(
"2013-01-01T00:00:00",
"2016-01-01T23:59:59",
freq=freq,
tz=pytz.FixedOffset(-300),
tz=timezone(timedelta(minutes=-300)),
)
tm.assert_index_equal(idx, expected)
# Unable to use `US/Eastern` because of DST
Expand All @@ -651,7 +651,7 @@ def test_constructor_datetime64_tzformat(self, freq):
"2013-01-01T00:00:00",
"2016-01-01T23:59:59",
freq=freq,
tz=pytz.FixedOffset(540),
tz=timezone(timedelta(minutes=540)),
)
tm.assert_index_equal(idx, expected)
expected_i8 = date_range(
Expand All @@ -665,7 +665,7 @@ def test_constructor_datetime64_tzformat(self, freq):
"2013-01-01T00:00:00",
"2016-01-01T23:59:59",
freq=freq,
tz=pytz.FixedOffset(-300),
tz=timezone(timedelta(minutes=-300)),
)
tm.assert_index_equal(idx, expected)
# Unable to use `US/Eastern` because of DST
Expand All @@ -679,7 +679,7 @@ def test_constructor_datetime64_tzformat(self, freq):
"2013-01-01T00:00:00",
"2016-01-01T23:59:59",
freq=freq,
tz=pytz.FixedOffset(540),
tz=timezone(timedelta(minutes=540)),
)
tm.assert_index_equal(idx, expected)
expected_i8 = date_range(
Expand Down
10 changes: 8 additions & 2 deletions pandas/tests/io/parser/test_parse_dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from datetime import (
date,
datetime,
timedelta,
timezone,
)
from io import StringIO
import warnings
Expand Down Expand Up @@ -935,7 +937,11 @@ def test_parse_tz_aware(all_parsers, request):
{"x": [0.5]}, index=Index([Timestamp("2012-06-13 01:39:00+00:00")], name="Date")
)
tm.assert_frame_equal(result, expected)
assert result.index.tz is pytz.utc
if parser.engine == "pyarrow":
expected_tz = pytz.utc
else:
expected_tz = timezone.utc
assert result.index.tz is expected_tz


@xfail_pyarrow
Expand Down Expand Up @@ -1563,7 +1569,7 @@ def test_parse_timezone(all_parsers):
start="2018-01-04 09:01:00",
end="2018-01-04 09:05:00",
freq="1min",
tz=pytz.FixedOffset(540),
tz=timezone(timedelta(minutes=540)),
)
),
freq=None,
Expand Down
6 changes: 2 additions & 4 deletions pandas/tests/scalar/timestamp/test_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@ def test_repr_utcoffset(self):
date_with_utc_offset = Timestamp("2014-03-13 00:00:00-0400", tz=None)
assert "2014-03-13 00:00:00-0400" in repr(date_with_utc_offset)
assert "tzoffset" not in repr(date_with_utc_offset)
assert "pytz.FixedOffset(-240)" in repr(date_with_utc_offset)
expr = repr(date_with_utc_offset).replace(
"'pytz.FixedOffset(-240)'", "pytz.FixedOffset(-240)"
)
assert "UTC-04:00" in repr(date_with_utc_offset)
expr = repr(date_with_utc_offset)
assert date_with_utc_offset == eval(expr)

def test_timestamp_repr_pre1900(self):
Expand Down
10 changes: 4 additions & 6 deletions pandas/tests/scalar/timestamp/test_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import (
datetime,
timedelta,
timezone,
)
import locale
import unicodedata
Expand All @@ -12,10 +13,7 @@
import numpy as np
import pytest
import pytz
from pytz import (
timezone,
utc,
)
from pytz import utc

from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
from pandas._libs.tslibs.timezones import (
Expand Down Expand Up @@ -233,7 +231,7 @@ def test_tz(self):
assert conv.hour == 19

def test_utc_z_designator(self):
assert get_timezone(Timestamp("2014-11-02 01:00Z").tzinfo) is utc
assert get_timezone(Timestamp("2014-11-02 01:00Z").tzinfo) is timezone.utc

def test_asm8(self):
np.random.seed(7_960_929)
Expand All @@ -251,7 +249,7 @@ def compare(x, y):
assert int((Timestamp(x).value - Timestamp(y).value) / 1e9) == 0

compare(Timestamp.now(), datetime.now())
compare(Timestamp.now("UTC"), datetime.now(timezone("UTC")))
compare(Timestamp.now("UTC"), datetime.now(pytz.timezone("UTC")))
compare(Timestamp.utcnow(), datetime.utcnow())
compare(Timestamp.today(), datetime.today())
current_time = calendar.timegm(datetime.now().utctimetuple())
Expand Down
Loading