Skip to content

Commit efae1db

Browse files
committed
gh-158252: Parse numeric strptime aliases in C
Expand %F, %D, %T and %R through the same field parser without allocating a temporary format string. Include %e and the optional whitespace directives %n and %t.
1 parent 4031133 commit efae1db

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

‎Lib/test/test_strptime.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@
1616

1717

1818
class PublicStrptimeTests(unittest.TestCase):
19+
def test_numeric_aliases(self):
20+
for text, fmt in (
21+
('2024-02-29', '%F'), ('02/29/24', '%D'),
22+
('2024-02-29 12:34:56', '%F %T'),
23+
('2024-02-29T12:34:56Z', '%FT%TZ'),
24+
('12:34', '%R'), ('12:34:56', '%R:%S'),
25+
('2024 9', '%Y %e'), ('2024\t02\n29', '%Y%t%m%n%d'),
26+
('20240229', '%Y%t%m%n%d'),
27+
('2024-02-29 2024', '%F %Y'),
28+
('12:34:56 56', '%T %S'),
29+
('2023-02-29', '%F'), ('12:60', '%R'),
30+
('2024-02-29 2024-02-29', '%F %F'),
31+
):
32+
self.assert_matches_python(text, fmt)
33+
1934
def test_offsets_and_zone_names(self):
2035
offsets = ('', 'Z', '+0530', '-03:30', '+01:02:03', '-000001.000001',
2136
'+01:02:03.456789', '+000000.5', '-00:00:00.5',

‎Python/_strptime.c‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* - ASCII locale month/weekday names and numeric %w/%u weekdays;
1313
* - %I with ASCII %p indicators, and the %k, %l, %P aliases;
1414
* - %z and %:z offsets with hours below 24, and terminal UTC/GMT %Z names;
15+
* - numeric aliases %F, %D, %T, %R, %e and whitespace %n/%t;
1516
* - literal characters, %%, and ASCII whitespace.
1617
* Duplicate directives, mixed %Y/%y, and day-of-month without a year use the
1718
* fallback. So do other directives (including non-ASCII locale names and
@@ -206,6 +207,8 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
206207
}
207208
const unsigned char *data = PyUnicode_1BYTE_DATA(string);
208209
const unsigned char *fmt = PyUnicode_1BYTE_DATA(format);
210+
const unsigned char *outer_fmt = NULL;
211+
Py_ssize_t outer_length = 0, outer_pos = 0;
209212
Py_ssize_t length = PyUnicode_GET_LENGTH(string);
210213
Py_ssize_t fmt_length = PyUnicode_GET_LENGTH(format);
211214
*fields = (_PyTime_StrptimeFields){.year = 1900, .month = 1, .day = 1,
@@ -215,7 +218,19 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
215218
int ampm = -1;
216219
Py_ssize_t pos = 0;
217220

218-
for (Py_ssize_t i = 0; i < fmt_length; i++) {
221+
for (Py_ssize_t i = 0; ; i++) {
222+
if (i == fmt_length) {
223+
if (outer_fmt == NULL) {
224+
break;
225+
}
226+
fmt = outer_fmt;
227+
fmt_length = outer_length;
228+
i = outer_pos;
229+
outer_fmt = NULL;
230+
if (i == fmt_length) {
231+
break;
232+
}
233+
}
219234
unsigned char c = fmt[i];
220235
if (Py_ISSPACE(c)) {
221236
if (pos == length || !Py_ISSPACE(data[pos])) {
@@ -239,6 +254,34 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
239254
return 0;
240255
}
241256
c = fmt[i];
257+
const char *alias = NULL;
258+
switch (c) {
259+
case 'F': alias = "%Y-%m-%d"; break;
260+
case 'D': alias = "%m/%d/%y"; break;
261+
case 'T': alias = "%H:%M:%S"; break;
262+
case 'R': alias = "%H:%M"; break;
263+
case 'e': c = 'd'; break;
264+
}
265+
if (alias != NULL) {
266+
/* These fixed expansions contain no further aliases. Parsing
267+
* them in place keeps duplicate-field checks and avoids copying
268+
* an arbitrarily long user format into a temporary buffer.
269+
*/
270+
assert(outer_fmt == NULL);
271+
outer_fmt = fmt;
272+
outer_length = fmt_length;
273+
outer_pos = i + 1;
274+
fmt = (const unsigned char *)alias;
275+
fmt_length = strlen(alias);
276+
i = -1;
277+
continue;
278+
}
279+
if (c == 'n' || c == 't') {
280+
while (pos < length && Py_ISSPACE(data[pos])) {
281+
pos++;
282+
}
283+
continue;
284+
}
242285
int require_colon = 0;
243286
if (c == ':' && i + 1 < fmt_length && fmt[i + 1] == 'z') {
244287
c = fmt[++i];

0 commit comments

Comments
 (0)