Skip to content

Commit f7f33a7

Browse files
jbrockmendeljreback
authored andcommitted
Checks for tz/tzinfo validity in Timestamp.__new__ (#17943)
1 parent fc1e507 commit f7f33a7

File tree

3 files changed

+37
-3
lines changed

3 files changed

+37
-3
lines changed

doc/source/whatsnew/v0.22.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Backwards incompatible API changes
4040
Other API Changes
4141
^^^^^^^^^^^^^^^^^
4242

43-
-
43+
- :class:`Timestamp` will no longer silently ignore unused or invalid `tz` or `tzinfo` arguments (:issue:`17690`)
4444
-
4545
-
4646

pandas/_libs/tslib.pyx

+18-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ from util cimport (is_integer_object, is_float_object, is_datetime64_object,
3030
is_timedelta64_object, INT64_MAX)
3131
cimport util
3232

33+
from cpython.datetime cimport PyTZInfo_Check
3334
# this is our datetime.pxd
3435
from datetime cimport (
3536
pandas_datetimestruct,
@@ -68,7 +69,7 @@ from .tslibs.parsing import parse_datetime_string
6869

6970
cimport cython
7071

71-
from pandas.compat import iteritems, callable
72+
from pandas.compat import iteritems
7273

7374
import collections
7475
import warnings
@@ -373,12 +374,23 @@ class Timestamp(_Timestamp):
373374
FutureWarning)
374375
freq = offset
375376

377+
if tzinfo is not None:
378+
if not PyTZInfo_Check(tzinfo):
379+
# tzinfo must be a datetime.tzinfo object, GH#17690
380+
raise TypeError('tzinfo must be a datetime.tzinfo object, '
381+
'not %s' % type(tzinfo))
382+
elif tz is not None:
383+
raise ValueError('Can provide at most one of tz, tzinfo')
384+
376385
if ts_input is _no_input:
377386
# User passed keyword arguments.
387+
if tz is None:
388+
# Handle the case where the user passes `tz` and not `tzinfo`
389+
tz = tzinfo
378390
return Timestamp(datetime(year, month, day, hour or 0,
379391
minute or 0, second or 0,
380392
microsecond or 0, tzinfo),
381-
tz=tzinfo)
393+
tz=tz)
382394
elif is_integer_object(freq):
383395
# User passed positional arguments:
384396
# Timestamp(year, month, day[, hour[, minute[, second[,
@@ -387,6 +399,10 @@ class Timestamp(_Timestamp):
387399
year or 0, month or 0, day or 0,
388400
hour), tz=hour)
389401

402+
if tzinfo is not None:
403+
# User passed tzinfo instead of tz; avoid silently ignoring
404+
tz, tzinfo = tzinfo, None
405+
390406
ts = convert_to_tsobject(ts_input, tz, unit, 0, 0)
391407

392408
if ts.value == NPY_NAT:

pandas/tests/scalar/test_timestamp.py

+18
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,24 @@ def test_constructor_invalid(self):
175175
with tm.assert_raises_regex(ValueError, 'Cannot convert Period'):
176176
Timestamp(Period('1000-01-01'))
177177

178+
def test_constructor_invalid_tz(self):
179+
# GH#17690, GH#5168
180+
with tm.assert_raises_regex(TypeError, 'must be a datetime.tzinfo'):
181+
Timestamp('2017-10-22', tzinfo='US/Eastern')
182+
183+
with tm.assert_raises_regex(ValueError, 'at most one of'):
184+
Timestamp('2017-10-22', tzinfo=utc, tz='UTC')
185+
186+
def test_constructor_tz_or_tzinfo(self):
187+
# GH#17943, GH#17690, GH#5168
188+
stamps = [Timestamp(year=2017, month=10, day=22, tz='UTC'),
189+
Timestamp(year=2017, month=10, day=22, tzinfo=utc),
190+
Timestamp(year=2017, month=10, day=22, tz=utc),
191+
Timestamp(datetime(2017, 10, 22), tzinfo=utc),
192+
Timestamp(datetime(2017, 10, 22), tz='UTC'),
193+
Timestamp(datetime(2017, 10, 22), tz=utc)]
194+
assert all(ts == stamps[0] for ts in stamps)
195+
178196
def test_constructor_positional(self):
179197
# see gh-10758
180198
with pytest.raises(TypeError):

0 commit comments

Comments
 (0)