Skip to content
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

ENH: add fold support to Timestamp constructor #31563

Merged
merged 137 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
84bfff2
TST: add basic test for construction with fold
AlexKirko Jan 30, 2020
ba7fcd5
ENH: add basic fold support
AlexKirko Jan 30, 2020
0b6f894
ENH: add fold to ts properties
AlexKirko Jan 30, 2020
5c58b3a
baseline fold in conversion
AlexKirko Jan 30, 2020
546789a
add fold placeholder to conversion
AlexKirko Jan 30, 2020
fc69bbb
add fold to convert_to_tsobject
AlexKirko Jan 30, 2020
57d42b3
TST: add test to infer fold from value
AlexKirko Jan 30, 2020
f2ad196
ENH: infer fold from value for dateutil
AlexKirko Jan 30, 2020
935a3ec
ENH: infer fold for pytz (loss in performance)
AlexKirko Jan 30, 2020
d35af8f
PERF: remove unnecessary ifs
AlexKirko Jan 30, 2020
e6d4aaa
check that we are not at the left edge of deltas
AlexKirko Jan 30, 2020
fd98b27
ENH: adjust Timestamp.value for fold (if not from datetime or string)
AlexKirko Jan 31, 2020
af86f79
CLN: remove extra spaces
AlexKirko Jan 31, 2020
a6965e9
TST: add test for adjusting value for fold
AlexKirko Jan 31, 2020
4caf9bb
DOC: add to comment for local timezone
AlexKirko Jan 31, 2020
6843ed2
TST: add string case to fold inferring test
AlexKirko Jan 31, 2020
ebbf21f
ENH: infer fold for timestamp from string
AlexKirko Jan 31, 2020
4f43638
TST: add fold value adjustment from string
AlexKirko Jan 31, 2020
6238f9b
ENH: adjust value for fold from string
AlexKirko Jan 31, 2020
3584791
basic from datetime fold support with bugs
AlexKirko Jan 31, 2020
237341b
complete adjust value for fold from datetime
AlexKirko Jan 31, 2020
12b8b4e
use input fold as default
AlexKirko Jan 31, 2020
92e990e
TST: adjust datetime for included fold
AlexKirko Jan 31, 2020
c2189d3
TST: infer fold from fold included in datetime
AlexKirko Jan 31, 2020
9c9c2dd
FIX: fix bool condition for inferring fold for str
AlexKirko Jan 31, 2020
f0bbbcb
remove unnecessary value shift for inferring fold from str
AlexKirko Jan 31, 2020
ca46078
remove unnecessary GH24329 fix
AlexKirko Jan 31, 2020
411b036
pass fold through Timstamp.replace
AlexKirko Feb 1, 2020
464dadf
TST:fix ambiguous compatibility test in scalar test_timezones
AlexKirko Feb 1, 2020
2b9f2f6
TST: remove test for ambiguous error near dst boundary
AlexKirko Feb 1, 2020
ef87010
DOC: add fold arg description to functions and methods
AlexKirko Feb 1, 2020
ecfde58
REFACTOR: remove code duplication in localize_tso
AlexKirko Feb 1, 2020
6970ed1
DOC: add fold description to ints_to_pydatetime
AlexKirko Feb 1, 2020
353e554
REFACTOR: make obj.fold assignment process more transparent
AlexKirko Feb 1, 2020
bce8f0d
DOC: clarify comments for fold adjustment and inferring
AlexKirko Feb 1, 2020
558c237
REFACTOR: compact datetime_to_tsobject and refactor checks
AlexKirko Feb 1, 2020
7b88ffd
DOC: clarify comments for datetime_to_tsobject
AlexKirko Feb 1, 2020
9621c0a
add pos shift for adjusting value for fold in localize_tso
AlexKirko Feb 1, 2020
6bf58b5
CLN: remove unnecessary comment
AlexKirko Feb 1, 2020
73364af
try to speed up datetime processing
AlexKirko Feb 2, 2020
a8ad96c
Revert "try to speed up datetime processing" - does not work
AlexKirko Feb 2, 2020
104c97d
CLN: remove unnecessary fold assignment in timestamps
AlexKirko Feb 2, 2020
1f9e810
DOC: add whatsnew entry
AlexKirko Feb 2, 2020
55f0b8a
DOC: fix whatsnew
AlexKirko Feb 2, 2020
5e1be83
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 2, 2020
bda4934
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 3, 2020
1065085
CLN: finish merging
AlexKirko Feb 3, 2020
f9c6956
cut the logic to benchmark function signatures
AlexKirko Feb 3, 2020
62d5d6b
Revert "cut the logic to benchmark function signatures" - return to o…
AlexKirko Feb 3, 2020
6294ee9
PERF: change fold to fold or 0
AlexKirko Feb 3, 2020
f13e3d7
CLN: clean up TODOs
AlexKirko Feb 3, 2020
eade807
CLN: trim comments, add issues to whatsnew
AlexKirko Feb 4, 2020
7269f9a
fold: set Tiemstamp default to 0, reformat description
AlexKirko Feb 4, 2020
d650086
rollback Timestamp fold default to None
AlexKirko Feb 4, 2020
693cb6c
REFACT: move value adjustment for fold to function
AlexKirko Feb 4, 2020
2fe9ce7
REFACT: move inferring fold to a function
AlexKirko Feb 4, 2020
3e2c76c
CLN: remove unnecessary whitespace
AlexKirko Feb 4, 2020
2f4fdda
DOC: trim fold arg description in tslib.pyx
AlexKirko Feb 5, 2020
9f7a16e
REFACT: tighten code in datetime_to_tsobject
AlexKirko Feb 5, 2020
a6d37ea
REFACT: set TSObject default fold to 0
AlexKirko Feb 5, 2020
a017953
CLN: lint multiline if in datetime_to_tsobject
AlexKirko Feb 5, 2020
1d716e7
TST: reparametrize tests
AlexKirko Feb 6, 2020
b47efe0
raise if ts_input.fold and fold do not match
AlexKirko Feb 6, 2020
cf7c091
restart tests
AlexKirko Feb 6, 2020
a49a7e9
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 6, 2020
c39c490
Merge branch 'master' into add-fold-to-timestamp to resolve conflicts
AlexKirko Feb 7, 2020
75e1633
add ambiguous timezone to whatsnew example
AlexKirko Feb 7, 2020
21883be
combine is_utc and is_tzlocal
AlexKirko Feb 7, 2020
b21cb47
remove inner parenthesis
AlexKirko Feb 7, 2020
3ca1fc3
combine if statements in Timestamp constructor
AlexKirko Feb 7, 2020
edde445
CLN: replace "typ == or typ ==" with "typ in [...]"
AlexKirko Feb 10, 2020
d5925af
REFACT: move fold assignment to cdef
AlexKirko Feb 10, 2020
b128cde
DOC: add more note and examples for Timestamp.fold
AlexKirko Feb 10, 2020
a673b65
add valid fold value check to Timestamp constructor
AlexKirko Feb 10, 2020
276fad7
REFACT: move fold default from _TSObject cinit to explicit
AlexKirko Feb 10, 2020
5540de1
DOC: fix typo in error message
AlexKirko Feb 11, 2020
30eef01
TST: fix the rest of typo
AlexKirko Feb 11, 2020
68b05fc
change raise bahvior to allow overriding naive datetime
AlexKirko Feb 11, 2020
d67ec4f
DOC: update localize_tso docstring
AlexKirko Feb 12, 2020
3262085
change override default to raise
AlexKirko Feb 14, 2020
4790b76
finish transition to new behavior
AlexKirko Feb 14, 2020
353bd87
DOC: revert localize_tso docstring
AlexKirko Feb 14, 2020
94e9e65
CLN: remove unnecessary change
AlexKirko Feb 14, 2020
a69833a
restore pytz ambiguous test
AlexKirko Feb 14, 2020
e58ecb9
CLN: lint the tests
AlexKirko Feb 14, 2020
e1ffa8d
set fold in Timestamp.replace after pytz resets it
AlexKirko Feb 14, 2020
c57ec65
fix datetime call in docs
AlexKirko Feb 14, 2020
f3f8690
fix doc error
AlexKirko Feb 14, 2020
476c4a4
for naive datetime, set default to 0
AlexKirko Feb 14, 2020
afaeb88
TST: expand tests
AlexKirko Feb 14, 2020
4a33d36
REFACT: bundle fold in ts_input
AlexKirko Feb 14, 2020
82ed93c
pass fold to func_create candidates in tslib.pyx
AlexKirko Feb 14, 2020
d9aea09
remove dateutil adjustment from convert_datetime_to_tsobject
AlexKirko Feb 14, 2020
d68efb6
move relevant parts of adjust function where called and drop
AlexKirko Feb 14, 2020
9328071
CLN: revert localize_tso docstring notes
AlexKirko Feb 14, 2020
4ecbaf1
REFACT: combine raise conditions in timestamps.pyx
AlexKirko Feb 15, 2020
ee90ac7
CLN: tighten error messages
AlexKirko Feb 15, 2020
25291e4
CLN: black the tests
AlexKirko Feb 15, 2020
08cc256
DOC: expand doc in timeseries.rst and make it a note
AlexKirko Feb 15, 2020
2910720
TST: fix pytz fold conflict test
AlexKirko Feb 15, 2020
f6c11da
DOC: improve formatting in timeseries.rst addition
AlexKirko Feb 15, 2020
6f16ea5
DOC: update whatsnew
AlexKirko Feb 15, 2020
5024452
CLN: fix linting mistakes
AlexKirko Feb 15, 2020
3e49b7a
add versionadded where appropriate
AlexKirko Feb 15, 2020
bcf0905
DOC: add references to PEP 495
AlexKirko Feb 15, 2020
9b614ae
add local timezone support
AlexKirko Feb 15, 2020
467a11e
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 15, 2020
2145b05
Revert "add local timezone support"
AlexKirko Feb 15, 2020
8f82aa1
make fold keyword-only
AlexKirko Feb 17, 2020
d39e811
CLN: prune pytz-specific logic in Timestamp.replace
AlexKirko Feb 17, 2020
3091689
switch from getattr to hasattr in the constructor
AlexKirko Feb 17, 2020
4cfac36
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 17, 2020
e58fe0c
ENH: add fold support
AlexKirko Feb 17, 2020
d166a67
REFACT: initalize _TSObject.fold to False explicitly
AlexKirko Feb 17, 2020
5729eb8
CLN: fix linting
AlexKirko Feb 17, 2020
c9863e1
CLN: remove unnecessary TODOs
AlexKirko Feb 17, 2020
1d72e2d
add comment to __cinit__
AlexKirko Feb 17, 2020
97883ce
try reverting the changes to fix test error
AlexKirko Feb 17, 2020
aa5232b
Revert "try reverting the changes to fix test error"
AlexKirko Feb 17, 2020
0ebbe02
REFACT: no longer set delta by pointer
AlexKirko Feb 17, 2020
c840fd6
fix linting
AlexKirko Feb 17, 2020
a793bb5
DOC: tweak docstrings in tzconversion
AlexKirko Feb 17, 2020
920d52a
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 18, 2020
d732139
statically type trans and deltas
AlexKirko Feb 19, 2020
752acbc
REFACT: use PyDateTime_Check when deciding if to raise
AlexKirko Feb 19, 2020
a1f69cf
DOC: add refs to docs, add blank lines to docstrings
AlexKirko Feb 19, 2020
97dc342
REFACT: merge replace with error raising in timestamp.pyx
AlexKirko Feb 19, 2020
7ac14df
REFACT: move +/- delta logic out of _tzlocal_get_offset_components
AlexKirko Feb 19, 2020
397b2c8
CLN: fix indentation
AlexKirko Feb 19, 2020
757bd41
DOC: anonimize references
AlexKirko Feb 19, 2020
46a279b
DOC: give descriptive names to refs
AlexKirko Feb 19, 2020
81560bb
DOC: make fold a subsection in timeseries.rst
AlexKirko Feb 25, 2020
4256642
TST: add test for invalid fold raise
AlexKirko Feb 25, 2020
a24594a
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 25, 2020
0168aa6
DOC: rephrase text in timeseries.rst to improve readability
AlexKirko Feb 25, 2020
3a605c3
Merge branch 'master' into add-fold-to-timestamp
AlexKirko Feb 26, 2020
cd02318
add fold support to merged code
AlexKirko Feb 26, 2020
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
29 changes: 29 additions & 0 deletions doc/source/user_guide/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2297,6 +2297,35 @@ To remove time zone information, use ``tz_localize(None)`` or ``tz_convert(None)
# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
didx.tz_convert('UTC').tz_localize(None)

.. _timeseries.fold:

Fold
~~~~

.. versionadded:: 1.1.0

For ambiguous times, pandas supports explicitly specifying the keyword-only fold argument.
Due to daylight saving time, one wall clock time can occur twice when shifting
from summer to winter time; fold describes whether the datetime-like corresponds
to the first (0) or the second time (1) the wall clock hits the ambiguous time.
Fold is supported only for constructing from naive ``datetime.datetime``
(see `datetime documentation <https://docs.python.org/3/library/datetime.html>`__ for details) or from :class:`Timestamp`
or for constructing from components (see below). Only ``dateutil`` timezones are supported
(see `dateutil documentation <https://dateutil.readthedocs.io/en/stable/tz.html#dateutil.tz.enfold>`__
for ``dateutil`` methods that deal with ambiguous datetimes) as ``pytz``
timezones do not support fold (see `pytz documentation <http://pytz.sourceforge.net/index.html>`__
for details on how ``pytz`` deals with ambiguous datetimes). To localize an ambiguous datetime
with ``pytz``, please use :meth:`Timestamp.tz_localize`. In general, we recommend to rely
on :meth:`Timestamp.tz_localize` when localizing ambiguous datetimes if you need direct
control over how they are handled.

.. ipython:: python

pd.Timestamp(datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
tz='dateutil/Europe/London', fold=0)
pd.Timestamp(year=2019, month=10, day=27, hour=1, minute=30,
tz='dateutil/Europe/London', fold=1)

.. _timeseries.timezone_ambiguous:

Ambiguous times when localizing
Expand Down
22 changes: 22 additions & 0 deletions doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ For example:
ser["2014"]
ser.loc["May 2015"]

.. _whatsnew_110.timestamp_fold_support:

Fold argument support in Timestamp constructor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:class:`Timestamp:` now supports the keyword-only fold argument according to `PEP 495 <https://www.python.org/dev/peps/pep-0495/#the-fold-attribute>`_ similar to parent ``datetime.datetime`` class. It supports both accepting fold as an initialization argument and inferring fold from other constructor arguments (:issue:`25057`, :issue:`31338`). Support is limited to ``dateutil`` timezones as ``pytz`` doesn't support fold.

For example:

.. ipython:: python

ts = pd.Timestamp("2019-10-27 01:30:00+00:00")
ts.fold

.. ipython:: python

ts = pd.Timestamp(year=2019, month=10, day=27, hour=1, minute=30,
tz="dateutil/Europe/London", fold=1)
ts

For more on working with fold, see :ref:`Fold subsection <timeseries.fold>` in the user guide.

.. _whatsnew_110.enhancements.other:

Other enhancements
Expand Down
32 changes: 20 additions & 12 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -49,30 +49,31 @@ from pandas._libs.tslibs.tzconversion cimport (

cdef inline object create_datetime_from_ts(
int64_t value, npy_datetimestruct dts,
object tz, object freq):
object tz, object freq, bint fold):
""" convenience routine to construct a datetime.datetime from its parts """
return datetime(dts.year, dts.month, dts.day, dts.hour,
jreback marked this conversation as resolved.
Show resolved Hide resolved
jreback marked this conversation as resolved.
Show resolved Hide resolved
dts.min, dts.sec, dts.us, tz)
dts.min, dts.sec, dts.us, tz, fold=fold)


cdef inline object create_date_from_ts(
int64_t value, npy_datetimestruct dts,
object tz, object freq):
object tz, object freq, bint fold):
jreback marked this conversation as resolved.
Show resolved Hide resolved
""" convenience routine to construct a datetime.date from its parts """
# GH 25057 add fold argument to match other func_create signatures
return date(dts.year, dts.month, dts.day)


cdef inline object create_time_from_ts(
int64_t value, npy_datetimestruct dts,
object tz, object freq):
object tz, object freq, bint fold):
jreback marked this conversation as resolved.
Show resolved Hide resolved
""" convenience routine to construct a datetime.time from its parts """
return time(dts.hour, dts.min, dts.sec, dts.us, tz)
return time(dts.hour, dts.min, dts.sec, dts.us, tz, fold=fold)


@cython.wraparound(False)
@cython.boundscheck(False)
def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
str box="datetime"):
bint fold=0, str box="datetime"):
"""
Convert an i8 repr to an ndarray of datetimes, date, time or Timestamp

Expand All @@ -83,6 +84,13 @@ def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
convert to this timezone
freq : str/Offset, default None
freq to convert
fold : bint, default is 0
jreback marked this conversation as resolved.
Show resolved Hide resolved
Due to daylight saving time, one wall clock time can occur twice
when shifting from summer to winter time; fold describes whether the
datetime-like corresponds to the first (0) or the second time (1)
the wall clock hits the ambiguous time

.. versionadded:: 1.1.0
jreback marked this conversation as resolved.
Show resolved Hide resolved
box : {'datetime', 'timestamp', 'date', 'time'}, default 'datetime'
If datetime, convert to datetime.datetime
If date, convert to datetime.date
Expand All @@ -104,7 +112,7 @@ def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
str typ
int64_t value, delta, local_value
ndarray[object] result = np.empty(n, dtype=object)
object (*func_create)(int64_t, npy_datetimestruct, object, object)
object (*func_create)(int64_t, npy_datetimestruct, object, object, bint)
jreback marked this conversation as resolved.
Show resolved Hide resolved

if box == "date":
assert (tz is None), "tz should be None when converting to date"
Expand All @@ -129,7 +137,7 @@ def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
result[i] = <object>NaT
else:
dt64_to_dtstruct(value, &dts)
result[i] = func_create(value, dts, tz, freq)
result[i] = func_create(value, dts, tz, freq, fold)
elif is_tzlocal(tz):
for i in range(n):
value = arr[i]
Expand All @@ -141,7 +149,7 @@ def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
# using the i8 representation.
local_value = tz_convert_utc_to_tzlocal(value, tz)
dt64_to_dtstruct(local_value, &dts)
result[i] = func_create(value, dts, tz, freq)
result[i] = func_create(value, dts, tz, freq, fold)
else:
trans, deltas, typ = get_dst_info(tz)

Expand All @@ -155,7 +163,7 @@ def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
else:
# Adjust datetime64 timestamp, recompute datetimestruct
dt64_to_dtstruct(value + delta, &dts)
result[i] = func_create(value, dts, tz, freq)
result[i] = func_create(value, dts, tz, freq, fold)

elif typ == 'dateutil':
# no zone-name change for dateutil tzs - dst etc
Expand All @@ -168,7 +176,7 @@ def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
# Adjust datetime64 timestamp, recompute datetimestruct
pos = trans.searchsorted(value, side='right') - 1
dt64_to_dtstruct(value + deltas[pos], &dts)
result[i] = func_create(value, dts, tz, freq)
result[i] = func_create(value, dts, tz, freq, fold)
else:
# pytz
for i in range(n):
Expand All @@ -182,7 +190,7 @@ def ints_to_pydatetime(const int64_t[:] arr, object tz=None, object freq=None,
new_tz = tz._tzinfos[tz._transition_info[pos]]

dt64_to_dtstruct(value + deltas[pos], &dts)
result[i] = func_create(value, dts, new_tz, freq)
result[i] = func_create(value, dts, new_tz, freq, fold)
jreback marked this conversation as resolved.
Show resolved Hide resolved

return result

Expand Down
1 change: 1 addition & 0 deletions pandas/_libs/tslibs/conversion.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ cdef class _TSObject:
npy_datetimestruct dts # npy_datetimestruct
int64_t value # numpy dt64
object tzinfo
bint fold


cdef convert_to_tsobject(object ts, object tz, object unit,
Expand Down
69 changes: 66 additions & 3 deletions pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ from pandas._libs.tslibs.nattype cimport (

from pandas._libs.tslibs.tzconversion import (
tz_localize_to_utc, tz_convert_single)
from pandas._libs.tslibs.tzconversion cimport _tz_convert_tzlocal_utc
from pandas._libs.tslibs.tzconversion cimport (
_tz_convert_tzlocal_utc, _tz_convert_tzlocal_fromutc)

# ----------------------------------------------------------------------
# Constants
Expand Down Expand Up @@ -215,6 +216,11 @@ cdef class _TSObject:
# npy_datetimestruct dts # npy_datetimestruct
# int64_t value # numpy dt64
# object tzinfo
# bint fold

def __cinit__(self):
# GH 25057. As per PEP 495, set fold to 0 by default
self.fold = 0

@property
def value(self):
Expand Down Expand Up @@ -322,6 +328,7 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz,
cdef:
_TSObject obj = _TSObject()

obj.fold = ts.fold
if tz is not None:
tz = maybe_get_tz(tz)

Expand Down Expand Up @@ -380,6 +387,8 @@ cdef _TSObject create_tsobject_tz_using_offset(npy_datetimestruct dts,
_TSObject obj = _TSObject()
int64_t value # numpy dt64
datetime dt
ndarray[int64_t] trans
int64_t[:] deltas

value = dtstruct_to_dt64(&dts)
obj.dts = dts
Expand All @@ -389,10 +398,23 @@ cdef _TSObject create_tsobject_tz_using_offset(npy_datetimestruct dts,
check_overflows(obj)
return obj

# Infer fold from offset-adjusted obj.value
# see PEP 495 https://www.python.org/dev/peps/pep-0495/#the-fold-attribute
if is_utc(tz):
jreback marked this conversation as resolved.
Show resolved Hide resolved
pass
elif is_tzlocal(tz):
_tz_convert_tzlocal_fromutc(obj.value, tz, &obj.fold)
else:
trans, deltas, typ = get_dst_info(tz)

if typ == 'dateutil':
pos = trans.searchsorted(obj.value, side='right') - 1
obj.fold = _infer_tsobject_fold(obj, trans, deltas, pos)

# Keep the converter same as PyDateTime's
dt = datetime(obj.dts.year, obj.dts.month, obj.dts.day,
obj.dts.hour, obj.dts.min, obj.dts.sec,
obj.dts.us, obj.tzinfo)
obj.dts.us, obj.tzinfo, fold=obj.fold)
obj = convert_datetime_to_tsobject(
dt, tz, nanos=obj.dts.ps // 1000)
return obj
Expand Down Expand Up @@ -543,7 +565,7 @@ cdef inline void localize_tso(_TSObject obj, tzinfo tz):
elif obj.value == NPY_NAT:
pass
elif is_tzlocal(tz):
local_val = _tz_convert_tzlocal_utc(obj.value, tz, to_utc=False)
local_val = _tz_convert_tzlocal_fromutc(obj.value, tz, &obj.fold)
dt64_to_dtstruct(local_val, &obj.dts)
else:
# Adjust datetime64 timestamp, recompute datetimestruct
Expand All @@ -562,6 +584,8 @@ cdef inline void localize_tso(_TSObject obj, tzinfo tz):
# i.e. treat_tz_as_dateutil(tz)
pos = trans.searchsorted(obj.value, side='right') - 1
dt64_to_dtstruct(obj.value + deltas[pos], &obj.dts)
# dateutil supports fold, so we infer fold from value
obj.fold = _infer_tsobject_fold(obj, trans, deltas, pos)
mroeschke marked this conversation as resolved.
Show resolved Hide resolved
else:
# Note: as of 2018-07-17 all tzinfo objects that are _not_
# either pytz or dateutil have is_fixed_offset(tz) == True,
Expand All @@ -571,6 +595,45 @@ cdef inline void localize_tso(_TSObject obj, tzinfo tz):
obj.tzinfo = tz


cdef inline bint _infer_tsobject_fold(_TSObject obj, ndarray[int64_t] trans,
int64_t[:] deltas, int32_t pos):
"""
Infer _TSObject fold property from value by assuming 0 and then setting
jreback marked this conversation as resolved.
Show resolved Hide resolved
to 1 if necessary.

Parameters
----------
obj : _TSObject
trans : ndarray[int64_t]
ndarray of offset transition points in nanoseconds since epoch.
deltas : int64_t[:]
array of offsets corresponding to transition points in trans.
pos : int32_t
Position of the last transition point before taking fold into account.

Returns
-------
bint
Due to daylight saving time, one wall clock time can occur twice
when shifting from summer to winter time; fold describes whether the
datetime-like corresponds to the first (0) or the second time (1)
the wall clock hits the ambiguous time

References
----------
.. [1] "PEP 495 - Local Time Disambiguation"
https://www.python.org/dev/peps/pep-0495/#the-fold-attribute
"""
cdef:
bint fold = 0

if pos > 0:
fold_delta = deltas[pos - 1] - deltas[pos]
if obj.value - fold_delta < trans[pos]:
fold = 1

return fold

cdef inline datetime _localize_pydatetime(datetime dt, tzinfo tz):
"""
Take a datetime/Timestamp in UTC and localizes to timezone tz.
Expand Down
2 changes: 1 addition & 1 deletion pandas/_libs/tslibs/timestamps.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ from pandas._libs.tslibs.np_datetime cimport npy_datetimestruct

cdef object create_timestamp_from_ts(int64_t value,
npy_datetimestruct dts,
object tz, object freq)
object tz, object freq, bint fold)
Loading