Skip to content

Commit aedf135

Browse files
authored
ENH: infer Timestamp unit in non-iso paths (#51039)
* ENH: infer Timestamp unit in non-iso paths * test for each of three paths * fix test to use desired path
1 parent 0124148 commit aedf135

File tree

6 files changed

+64
-24
lines changed

6 files changed

+64
-24
lines changed

pandas/_libs/tslibs/conversion.pyx

+13-13
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,26 @@ from pandas._libs.tslibs.np_datetime cimport (
5050

5151
from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime
5252

53-
from pandas._libs.tslibs.timezones cimport (
54-
get_utcoffset,
55-
is_utc,
56-
)
57-
from pandas._libs.tslibs.util cimport (
58-
is_datetime64_object,
59-
is_float_object,
60-
is_integer_object,
61-
)
62-
63-
from pandas._libs.tslibs.parsing import parse_datetime_string
64-
6553
from pandas._libs.tslibs.nattype cimport (
6654
NPY_NAT,
6755
c_NaT as NaT,
6856
c_nat_strings as nat_strings,
6957
)
58+
from pandas._libs.tslibs.parsing cimport parse_datetime_string
7059
from pandas._libs.tslibs.timestamps cimport _Timestamp
60+
from pandas._libs.tslibs.timezones cimport (
61+
get_utcoffset,
62+
is_utc,
63+
)
7164
from pandas._libs.tslibs.tzconversion cimport (
7265
Localizer,
7366
tz_localize_to_utc_single,
7467
)
68+
from pandas._libs.tslibs.util cimport (
69+
is_datetime64_object,
70+
is_float_object,
71+
is_integer_object,
72+
)
7573

7674
# ----------------------------------------------------------------------
7775
# Constants
@@ -550,8 +548,10 @@ cdef _TSObject convert_str_to_tsobject(str ts, tzinfo tz, str unit,
550548
return obj
551549

552550
dt = parse_datetime_string(
553-
ts, dayfirst=dayfirst, yearfirst=yearfirst
551+
ts, dayfirst=dayfirst, yearfirst=yearfirst, out_bestunit=&out_bestunit
554552
)
553+
reso = get_supported_reso(out_bestunit)
554+
return convert_datetime_to_tsobject(dt, tz, nanos=0, reso=reso)
555555

556556
return convert_datetime_to_tsobject(dt, tz)
557557

pandas/_libs/tslibs/parsing.pxd

+11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
from cpython.datetime cimport datetime
2+
3+
from pandas._libs.tslibs.np_datetime cimport NPY_DATETIMEUNIT
4+
15

26
cpdef str get_rule_month(str source)
37
cpdef quarter_to_myear(int year, int quarter, str freq)
8+
9+
cdef datetime parse_datetime_string(
10+
str date_string,
11+
bint dayfirst,
12+
bint yearfirst,
13+
NPY_DATETIMEUNIT* out_bestunit
14+
)

pandas/_libs/tslibs/parsing.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ from pandas._typing import npt
66

77
class DateParseError(ValueError): ...
88

9-
def parse_datetime_string(
9+
def py_parse_datetime_string(
1010
date_string: str,
1111
dayfirst: bool = ...,
1212
yearfirst: bool = ...,

pandas/_libs/tslibs/parsing.pyx

+19-8
Original file line numberDiff line numberDiff line change
@@ -264,14 +264,26 @@ cdef bint _does_string_look_like_time(str parse_string):
264264
return 0 <= hour <= 23 and 0 <= minute <= 59
265265

266266

267-
def parse_datetime_string(
267+
def py_parse_datetime_string(
268+
str date_string, bint dayfirst=False, bint yearfirst=False
269+
):
270+
# Python-accessible version for testing (we can't just make
271+
# parse_datetime_string cpdef bc it has a pointer argument)
272+
cdef:
273+
NPY_DATETIMEUNIT out_bestunit
274+
275+
return parse_datetime_string(date_string, dayfirst, yearfirst, &out_bestunit)
276+
277+
278+
cdef datetime parse_datetime_string(
268279
# NB: This will break with np.str_ (GH#32264) even though
269280
# isinstance(npstrobj, str) evaluates to True, so caller must ensure
270281
# the argument is *exactly* 'str'
271282
str date_string,
272-
bint dayfirst=False,
273-
bint yearfirst=False,
274-
) -> datetime:
283+
bint dayfirst,
284+
bint yearfirst,
285+
NPY_DATETIMEUNIT* out_bestunit
286+
):
275287
"""
276288
Parse datetime string, only returns datetime.
277289
Also cares special handling matching time patterns.
@@ -287,7 +299,6 @@ def parse_datetime_string(
287299

288300
cdef:
289301
datetime dt
290-
NPY_DATETIMEUNIT out_bestunit
291302
bint is_quarter = 0
292303

293304
if not _does_string_look_like_datetime(date_string):
@@ -299,13 +310,13 @@ def parse_datetime_string(
299310
yearfirst=yearfirst)
300311
return dt
301312

302-
dt = _parse_delimited_date(date_string, dayfirst, &out_bestunit)
313+
dt = _parse_delimited_date(date_string, dayfirst, out_bestunit)
303314
if dt is not None:
304315
return dt
305316

306317
try:
307318
dt = _parse_dateabbr_string(
308-
date_string, _DEFAULT_DATETIME, None, &out_bestunit, &is_quarter
319+
date_string, _DEFAULT_DATETIME, None, out_bestunit, &is_quarter
309320
)
310321
return dt
311322
except DateParseError:
@@ -315,7 +326,7 @@ def parse_datetime_string(
315326

316327
dt = dateutil_parse(date_string, default=_DEFAULT_DATETIME,
317328
dayfirst=dayfirst, yearfirst=yearfirst,
318-
ignoretz=False, out_bestunit=&out_bestunit)
329+
ignoretz=False, out_bestunit=out_bestunit)
319330
return dt
320331

321332

pandas/tests/io/parser/test_parse_dates.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import pytz
1919

2020
from pandas._libs.tslibs import parsing
21-
from pandas._libs.tslibs.parsing import parse_datetime_string
21+
from pandas._libs.tslibs.parsing import py_parse_datetime_string
2222
from pandas.compat.pyarrow import (
2323
pa_version_under6p0,
2424
pa_version_under7p0,
@@ -1760,7 +1760,7 @@ def test_hypothesis_delimited_date(
17601760
date_string = test_datetime.strftime(date_format.replace(" ", delimiter))
17611761

17621762
except_out_dateutil, result = _helper_hypothesis_delimited_date(
1763-
parse_datetime_string, date_string, dayfirst=dayfirst
1763+
py_parse_datetime_string, date_string, dayfirst=dayfirst
17641764
)
17651765
except_in_dateutil, expected = _helper_hypothesis_delimited_date(
17661766
du_parse,

pandas/tests/scalar/timestamp/test_constructors.py

+18
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ def test_construct_from_string_invalid_raises(self):
3434
with pytest.raises(ValueError, match="gives an invalid tzoffset"):
3535
Timestamp("200622-12-31")
3636

37+
def test_constructor_str_infer_reso(self):
38+
# non-iso8601 path
39+
40+
# _parse_delimited_date path
41+
ts = Timestamp("01/30/2023")
42+
assert ts.unit == "s"
43+
44+
# _parse_dateabbr_string path
45+
ts = Timestamp("2015Q1")
46+
assert ts.unit == "s"
47+
48+
# dateutil_parse path
49+
ts = Timestamp("2016-01-01 1:30:01 PM")
50+
assert ts.unit == "s"
51+
52+
ts = Timestamp("2016 June 3 15:25:01.345")
53+
assert ts.unit == "ms"
54+
3755
def test_constructor_from_iso8601_str_with_offset_reso(self):
3856
# GH#49737
3957
ts = Timestamp("2016-01-01 04:05:06-01:00")

0 commit comments

Comments
 (0)