Skip to content

Commit 596a06e

Browse files
committed
ENH: add dayfirst/yearfirst to Timestamp for compat
TST: fix up python-dateutil compat for 2.5.3, xref pandas-dev#12944
1 parent 68f73bf commit 596a06e

File tree

5 files changed

+95
-75
lines changed

5 files changed

+95
-75
lines changed

doc/source/whatsnew/v0.18.1.txt

-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ Other Enhancements
170170

171171
- ``pd.crosstab()`` has gained a ``normalize`` argument for normalizing frequency tables (:issue:`12569`). Examples in the updated docs :ref:`here <reshaping.crosstabulations>`.
172172

173-
174173
.. _whatsnew_0181.sparse:
175174

176175
Sparse changes

pandas/src/inference.pyx

+2-2
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ def maybe_convert_objects(ndarray[object] objects, bint try_float=0,
683683
seen_float = 1
684684
elif util.is_datetime64_object(val):
685685
if convert_datetime:
686-
idatetimes[i] = convert_to_tsobject(val, None, None).value
686+
idatetimes[i] = convert_to_tsobject(val, None, None, 0, 0).value
687687
seen_datetime = 1
688688
else:
689689
seen_object = 1
@@ -712,7 +712,7 @@ def maybe_convert_objects(ndarray[object] objects, bint try_float=0,
712712
elif PyDateTime_Check(val) or util.is_datetime64_object(val):
713713
if convert_datetime:
714714
seen_datetime = 1
715-
idatetimes[i] = convert_to_tsobject(val, None, None).value
715+
idatetimes[i] = convert_to_tsobject(val, None, None, 0, 0).value
716716
else:
717717
seen_object = 1
718718
break

pandas/tseries/tests/test_tslib.py

+71-48
Original file line numberDiff line numberDiff line change
@@ -592,78 +592,101 @@ def test_parsers_quarter_invalid(self):
592592
self.assertRaises(ValueError, tools.parse_time_string, case)
593593

594594
def test_parsers_dayfirst_yearfirst(self):
595+
tm._skip_if_no_dateutil()
596+
597+
# OK
598+
# 2.5.1 10-11-12 [dayfirst=0, yearfirst=0] -> 2012-10-11 00:00:00
599+
# 2.5.2 10-11-12 [dayfirst=0, yearfirst=1] -> 2012-10-11 00:00:00
600+
# 2.5.3 10-11-12 [dayfirst=0, yearfirst=0] -> 2012-10-11 00:00:00
601+
602+
# OK
603+
# 2.5.1 10-11-12 [dayfirst=0, yearfirst=1] -> 2010-11-12 00:00:00
604+
# 2.5.2 10-11-12 [dayfirst=0, yearfirst=1] -> 2010-11-12 00:00:00
605+
# 2.5.3 10-11-12 [dayfirst=0, yearfirst=1] -> 2010-11-12 00:00:00
606+
607+
# bug fix in 2.5.2
608+
# 2.5.1 10-11-12 [dayfirst=1, yearfirst=1] -> 2010-11-12 00:00:00
609+
# 2.5.2 10-11-12 [dayfirst=1, yearfirst=1] -> 2010-12-11 00:00:00
610+
# 2.5.3 10-11-12 [dayfirst=1, yearfirst=1] -> 2010-12-11 00:00:00
611+
612+
# OK
613+
# 2.5.1 10-11-12 [dayfirst=1, yearfirst=0] -> 2012-11-10 00:00:00
614+
# 2.5.2 10-11-12 [dayfirst=1, yearfirst=0] -> 2012-11-10 00:00:00
615+
# 2.5.3 10-11-12 [dayfirst=1, yearfirst=0] -> 2012-11-10 00:00:00
616+
617+
# OK
618+
# 2.5.1 20/12/21 [dayfirst=0, yearfirst=0] -> 2021-12-20 00:00:00
619+
# 2.5.2 20/12/21 [dayfirst=0, yearfirst=0] -> 2021-12-20 00:00:00
620+
# 2.5.3 20/12/21 [dayfirst=0, yearfirst=0] -> 2021-12-20 00:00:00
621+
622+
# OK
623+
# 2.5.1 20/12/21 [dayfirst=0, yearfirst=1] -> 2020-12-21 00:00:00
624+
# 2.5.2 20/12/21 [dayfirst=0, yearfirst=1] -> 2020-12-21 00:00:00
625+
# 2.5.3 20/12/21 [dayfirst=0, yearfirst=1] -> 2020-12-21 00:00:00
626+
627+
# revert of bug in 2.5.2
628+
# 2.5.1 20/12/21 [dayfirst=1, yearfirst=1] -> 2020-12-21 00:00:00
629+
# 2.5.2 20/12/21 [dayfirst=1, yearfirst=1] -> month must be in 1..12
630+
# 2.5.3 20/12/21 [dayfirst=1, yearfirst=1] -> 2020-12-21 00:00:00
631+
632+
# OK
633+
# 2.5.1 20/12/21 [dayfirst=1, yearfirst=0] -> 2021-12-20 00:00:00
634+
# 2.5.2 20/12/21 [dayfirst=1, yearfirst=0] -> 2021-12-20 00:00:00
635+
# 2.5.3 20/12/21 [dayfirst=1, yearfirst=0] -> 2021-12-20 00:00:00
595636

596-
# https://github.com/dateutil/dateutil/issues/217
597-
# this issue was closed
598637
import dateutil
599-
is_compat_version = dateutil.__version__ >= LooseVersion('2.5.2')
600-
if is_compat_version:
601-
dayfirst_yearfirst1 = datetime.datetime(2010, 12, 11)
602-
dayfirst_yearfirst2 = datetime.datetime(2020, 12, 21)
603-
else:
604-
dayfirst_yearfirst1 = datetime.datetime(2010, 11, 12)
605-
dayfirst_yearfirst2 = datetime.datetime(2020, 12, 21)
638+
is_lt_253 = dateutil.__version__ < LooseVersion('2.5.3')
606639

607640
# str : dayfirst, yearfirst, expected
608-
cases = {'10-11-12': [(False, False, False,
641+
cases = {'10-11-12': [(False, False,
609642
datetime.datetime(2012, 10, 11)),
610-
(True, False, False,
643+
(True, False,
611644
datetime.datetime(2012, 11, 10)),
612-
(False, True, False,
645+
(False, True,
613646
datetime.datetime(2010, 11, 12)),
614-
(True, True, False, dayfirst_yearfirst1)],
615-
'20/12/21': [(False, False, False,
647+
(True, True,
648+
datetime.datetime(2010, 12, 11))],
649+
'20/12/21': [(False, False,
616650
datetime.datetime(2021, 12, 20)),
617-
(True, False, False,
651+
(True, False,
618652
datetime.datetime(2021, 12, 20)),
619-
(False, True, False,
653+
(False, True,
620654
datetime.datetime(2020, 12, 21)),
621-
(True, True, True, dayfirst_yearfirst2)]}
655+
(True, True,
656+
datetime.datetime(2020, 12, 21))]}
622657

623-
tm._skip_if_no_dateutil()
624658
from dateutil.parser import parse
625659
for date_str, values in compat.iteritems(cases):
626-
for dayfirst, yearfirst, is_compat, expected in values:
660+
for dayfirst, yearfirst, expected in values:
627661

628-
f = lambda x: tools.parse_time_string(x,
629-
dayfirst=dayfirst,
630-
yearfirst=yearfirst)
631-
632-
# we now have an invalid parse
633-
if is_compat and is_compat_version:
634-
self.assertRaises(tslib.DateParseError, f, date_str)
635-
636-
def f(date_str):
637-
return to_datetime(date_str, dayfirst=dayfirst,
638-
yearfirst=yearfirst)
639-
640-
self.assertRaises(ValueError, f, date_str)
641-
642-
def f(date_str):
643-
return DatetimeIndex([date_str], dayfirst=dayfirst,
644-
yearfirst=yearfirst)[0]
662+
# odd comparisons across version
663+
# let's just skip
664+
if dayfirst and yearfirst and is_lt_253:
665+
continue
645666

646-
self.assertRaises(ValueError, f, date_str)
667+
# compare with dateutil result
668+
dateutil_result = parse(date_str, dayfirst=dayfirst,
669+
yearfirst=yearfirst)
670+
self.assertEqual(dateutil_result, expected)
647671

648-
continue
672+
result1, _, _ = tools.parse_time_string(date_str,
673+
dayfirst=dayfirst,
674+
yearfirst=yearfirst)
649675

650-
result1, _, _ = f(date_str)
676+
# we don't support dayfirst/yearfirst here:
677+
if not dayfirst and not yearfirst:
678+
result2 = Timestamp(date_str)
679+
self.assertEqual(result2, expected)
651680

652-
result2 = to_datetime(date_str, dayfirst=dayfirst,
681+
result3 = to_datetime(date_str, dayfirst=dayfirst,
653682
yearfirst=yearfirst)
654683

655-
result3 = DatetimeIndex([date_str], dayfirst=dayfirst,
684+
result4 = DatetimeIndex([date_str], dayfirst=dayfirst,
656685
yearfirst=yearfirst)[0]
657686

658-
# Timestamp doesn't support dayfirst and yearfirst
659687
self.assertEqual(result1, expected)
660-
self.assertEqual(result2, expected)
661688
self.assertEqual(result3, expected)
662-
663-
# compare with dateutil result
664-
dateutil_result = parse(date_str, dayfirst=dayfirst,
665-
yearfirst=yearfirst)
666-
self.assertEqual(dateutil_result, expected)
689+
self.assertEqual(result4, expected)
667690

668691
def test_parsers_timestring(self):
669692
tm._skip_if_no_dateutil()

pandas/tslib.pxd

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from numpy cimport ndarray, int64_t
22

3-
cdef convert_to_tsobject(object, object, object)
3+
cdef convert_to_tsobject(object, object, object, bint, bint)
44
cdef convert_to_timedelta64(object, object, object)
55
cpdef object maybe_get_tz(object)
66
cdef bint _is_utc(object)

pandas/tslib.pyx

+21-23
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,6 @@ class Timestamp(_Timestamp):
237237
numpy unit used for conversion, if ts_input is int or float
238238
"""
239239

240-
# Do not add ``dayfirst`` and ``yearfist`` to Timestamp based on the discussion
241-
# https://github.com/pydata/pandas/pull/7599
242-
243240
@classmethod
244241
def fromordinal(cls, ordinal, offset=None, tz=None):
245242
""" passed an ordinal, translate and convert to a ts
@@ -295,7 +292,7 @@ class Timestamp(_Timestamp):
295292
cdef _TSObject ts
296293
cdef _Timestamp ts_base
297294

298-
ts = convert_to_tsobject(ts_input, tz, unit)
295+
ts = convert_to_tsobject(ts_input, tz, unit, 0, 0)
299296

300297
if ts.value == NPY_NAT:
301298
return NaT
@@ -544,7 +541,7 @@ class Timestamp(_Timestamp):
544541

545542
if self.nanosecond != 0 and warn:
546543
print 'Warning: discarding nonzero nanoseconds'
547-
ts = convert_to_tsobject(self, self.tzinfo, None)
544+
ts = convert_to_tsobject(self, self.tzinfo, None, 0, 0)
548545

549546
return datetime(ts.dts.year, ts.dts.month, ts.dts.day,
550547
ts.dts.hour, ts.dts.min, ts.dts.sec,
@@ -997,7 +994,7 @@ cdef class _Timestamp(datetime):
997994
cdef:
998995
pandas_datetimestruct dts
999996
_TSObject ts
1000-
ts = convert_to_tsobject(self, self.tzinfo, None)
997+
ts = convert_to_tsobject(self, self.tzinfo, None, 0, 0)
1001998
dts = ts.dts
1002999
return datetime(dts.year, dts.month, dts.day,
10031000
dts.hour, dts.min, dts.sec,
@@ -1237,7 +1234,8 @@ cpdef _get_utcoffset(tzinfo, obj):
12371234
return tzinfo.utcoffset(obj)
12381235

12391236
# helper to extract datetime and int64 from several different possibilities
1240-
cdef convert_to_tsobject(object ts, object tz, object unit):
1237+
cdef convert_to_tsobject(object ts, object tz, object unit,
1238+
bint dayfirst, bint yearfirst):
12411239
"""
12421240
Extract datetime and int64 from any of:
12431241
- np.int64 (with unit providing a possible modifier)
@@ -1259,7 +1257,7 @@ cdef convert_to_tsobject(object ts, object tz, object unit):
12591257
obj = _TSObject()
12601258

12611259
if util.is_string_object(ts):
1262-
return convert_str_to_tsobject(ts, tz, unit)
1260+
return convert_str_to_tsobject(ts, tz, unit, dayfirst, yearfirst)
12631261

12641262
if ts is None or ts is NaT:
12651263
obj.value = NPY_NAT
@@ -1329,7 +1327,7 @@ cdef convert_to_tsobject(object ts, object tz, object unit):
13291327
elif PyDate_Check(ts):
13301328
# Keep the converter same as PyDateTime's
13311329
ts = datetime.combine(ts, datetime_time())
1332-
return convert_to_tsobject(ts, tz, None)
1330+
return convert_to_tsobject(ts, tz, None, 0, 0)
13331331
elif getattr(ts, '_typ', None) == 'period':
13341332
raise ValueError("Cannot convert Period to Timestamp unambiguously. Use to_timestamp")
13351333
else:
@@ -1390,7 +1388,7 @@ cpdef convert_str_to_tsobject(object ts, object tz, object unit,
13901388
except Exception:
13911389
raise ValueError
13921390

1393-
return convert_to_tsobject(ts, tz, unit)
1391+
return convert_to_tsobject(ts, tz, unit, dayfirst, yearfirst)
13941392

13951393
def _test_parse_iso8601(object ts):
13961394
"""
@@ -1581,7 +1579,7 @@ def datetime_to_datetime64(ndarray[object] values):
15811579
else:
15821580
inferred_tz = _get_zone(val.tzinfo)
15831581

1584-
_ts = convert_to_tsobject(val, None, None)
1582+
_ts = convert_to_tsobject(val, None, None, 0, 0)
15851583
iresult[i] = _ts.value
15861584
_check_dts_bounds(&_ts.dts)
15871585
else:
@@ -1993,7 +1991,7 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
19931991
seen_datetime=1
19941992
if val.tzinfo is not None:
19951993
if utc_convert:
1996-
_ts = convert_to_tsobject(val, None, unit)
1994+
_ts = convert_to_tsobject(val, None, unit, 0, 0)
19971995
iresult[i] = _ts.value
19981996
try:
19991997
_check_dts_bounds(&_ts.dts)
@@ -2091,7 +2089,7 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
20912089
raise TypeError("invalid string coercion to datetime")
20922090

20932091
try:
2094-
_ts = convert_to_tsobject(py_dt, None, None)
2092+
_ts = convert_to_tsobject(py_dt, None, None, 0, 0)
20952093
iresult[i] = _ts.value
20962094
except ValueError:
20972095
if is_coerce:
@@ -2180,7 +2178,7 @@ def parse_str_array_to_datetime(ndarray values, dayfirst=False,
21802178
yearfirst=yearfirst, freq=freq)
21812179
except Exception:
21822180
raise ValueError
2183-
_ts = convert_to_tsobject(py_dt, None, None)
2181+
_ts = convert_to_tsobject(py_dt, None, None, 0, 0)
21842182
iresult[i] = _ts.value
21852183

21862184
return iresult
@@ -3466,7 +3464,7 @@ def pydt_to_i8(object pydt):
34663464
cdef:
34673465
_TSObject ts
34683466

3469-
ts = convert_to_tsobject(pydt, None, None)
3467+
ts = convert_to_tsobject(pydt, None, None, 0, 0)
34703468

34713469
return ts.value
34723470

@@ -4230,7 +4228,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N
42304228
if dtindex[i] == NPY_NAT: out[i] = -1; continue
42314229

42324230
pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts)
4233-
ts = convert_to_tsobject(dtindex[i], None, None)
4231+
ts = convert_to_tsobject(dtindex[i], None, None, 0, 0)
42344232
dom = dts.day
42354233
dow = ts_dayofweek(ts)
42364234

@@ -4254,7 +4252,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N
42544252
if dtindex[i] == NPY_NAT: out[i] = -1; continue
42554253

42564254
pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts)
4257-
ts = convert_to_tsobject(dtindex[i], None, None)
4255+
ts = convert_to_tsobject(dtindex[i], None, None, 0, 0)
42584256
isleap = is_leapyear(dts.year)
42594257
mo_off = _month_offset[isleap, dts.month - 1]
42604258
dom = dts.day
@@ -4286,7 +4284,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N
42864284
if dtindex[i] == NPY_NAT: out[i] = -1; continue
42874285

42884286
pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts)
4289-
ts = convert_to_tsobject(dtindex[i], None, None)
4287+
ts = convert_to_tsobject(dtindex[i], None, None, 0, 0)
42904288
dom = dts.day
42914289
dow = ts_dayofweek(ts)
42924290

@@ -4310,7 +4308,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N
43104308
if dtindex[i] == NPY_NAT: out[i] = -1; continue
43114309

43124310
pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts)
4313-
ts = convert_to_tsobject(dtindex[i], None, None)
4311+
ts = convert_to_tsobject(dtindex[i], None, None, 0, 0)
43144312
isleap = is_leapyear(dts.year)
43154313
mo_off = _month_offset[isleap, dts.month - 1]
43164314
dom = dts.day
@@ -4342,7 +4340,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N
43424340
if dtindex[i] == NPY_NAT: out[i] = -1; continue
43434341

43444342
pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts)
4345-
ts = convert_to_tsobject(dtindex[i], None, None)
4343+
ts = convert_to_tsobject(dtindex[i], None, None, 0, 0)
43464344
dom = dts.day
43474345
dow = ts_dayofweek(ts)
43484346

@@ -4366,7 +4364,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N
43664364
if dtindex[i] == NPY_NAT: out[i] = -1; continue
43674365

43684366
pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts)
4369-
ts = convert_to_tsobject(dtindex[i], None, None)
4367+
ts = convert_to_tsobject(dtindex[i], None, None, 0, 0)
43704368
isleap = is_leapyear(dts.year)
43714369
dom = dts.day
43724370
mo_off = _month_offset[isleap, dts.month - 1]
@@ -4382,7 +4380,7 @@ def get_start_end_field(ndarray[int64_t] dtindex, object field, object freqstr=N
43824380
if dtindex[i] == NPY_NAT: out[i] = -1; continue
43834381

43844382
pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, &dts)
4385-
ts = convert_to_tsobject(dtindex[i], None, None)
4383+
ts = convert_to_tsobject(dtindex[i], None, None, 0, 0)
43864384
isleap = is_leapyear(dts.year)
43874385
mo_off = _month_offset[isleap, dts.month - 1]
43884386
dom = dts.day
@@ -4429,7 +4427,7 @@ def get_date_name_field(ndarray[int64_t] dtindex, object field):
44294427

44304428

44314429
cdef inline int m8_weekday(int64_t val):
4432-
ts = convert_to_tsobject(val, None, None)
4430+
ts = convert_to_tsobject(val, None, None, 0, 0)
44334431
return ts_dayofweek(ts)
44344432

44354433
cdef int64_t DAY_NS = 86400000000000LL

0 commit comments

Comments
 (0)