11#include "Python.h"
22#include "pycore_time.h"
33
4+ #include <time.h>
5+
46
57/* Implement common parts of strptime in C to improve performance. Callers
68 * fall back to Lib/_strptime.py for a full parse when this doesn't match.
79 *
810 * Supported inputs are exact str objects containing only ASCII, with:
911 * - numeric %Y, %y, %m, %d, %H, %M, %S, and %f fields;
12+ * - ASCII locale month/weekday names and numeric %w/%u weekdays;
1013 * - a terminal %z: empty, Z, or +/-HHMM or +/-HH:MM with hours below 24;
1114 * - literal characters, %%, and ASCII whitespace.
1215 * Duplicate directives, mixed %Y/%y, and day-of-month without a year use the
13- * fallback. So do other directives (including locale-dependent names and
16+ * fallback. So do other directives (including non-ASCII locale names and
1417 * week/day-of-year calculations), and offsets with seconds or fractions.
1518 */
1619
@@ -31,6 +34,53 @@ strptime_digits(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos,
3134 return count >= minimum ? count : 0 ;
3235}
3336
37+ static int
38+ strptime_name (const unsigned char * data , Py_ssize_t length , Py_ssize_t pos ,
39+ unsigned char directive , int * value )
40+ {
41+ #ifdef HAVE_STRFTIME
42+ /* Read the current locale without keeping a cache across setlocale().
43+ * Non-ASCII names retain Python's decoding and Unicode case handling.
44+ * As in TimeRE, prefer the longest match, then the first equal name.
45+ */
46+ int month = directive == 'b' || directive == 'B' ;
47+ int count = month ? 12 : 7 ;
48+ char format [] = {'%' , directive , '\0' };
49+ int matched = 0 ;
50+ for (int i = 0 ; i < count ; i ++ ) {
51+ struct tm tm = {.tm_year = 101 , .tm_mday = 1 ,
52+ .tm_mon = month ? i : 0 ,
53+ .tm_wday = month ? 0 : (i + 1 ) % 7 };
54+ char name [256 ];
55+ size_t size = strftime (name , sizeof (name ), format , & tm );
56+ if (size == 0 ) {
57+ return 0 ;
58+ }
59+ for (size_t j = 0 ; j < size ; j ++ ) {
60+ if ((unsigned char )name [j ] >= 128 ) {
61+ return 0 ;
62+ }
63+ }
64+ if (size <= (size_t )matched || size > (size_t )(length - pos )) {
65+ continue ;
66+ }
67+ size_t j ;
68+ for (j = 0 ; j < size ; j ++ ) {
69+ if (Py_TOLOWER (data [pos + j ]) != Py_TOLOWER (name [j ])) {
70+ break ;
71+ }
72+ }
73+ if (j == size ) {
74+ matched = (int )size ;
75+ * value = month ? i + 1 : i ;
76+ }
77+ }
78+ return matched ;
79+ #else
80+ return 0 ;
81+ #endif
82+ }
83+
3484static int
3585strptime_offset (const unsigned char * data , Py_ssize_t length , Py_ssize_t pos ,
3686 _PyTime_StrptimeFields * fields )
@@ -83,6 +133,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
83133 SECOND = 1U << 6 ,
84134 FRACTION = 1U << 7 ,
85135 OFFSET = 1U << 8 ,
136+ WEEKDAY = 1U << 9 ,
86137 ANY_YEAR = YEAR | SHORT_YEAR ,
87138 };
88139 static const unsigned short directive_bits [128 ] = {
@@ -95,6 +146,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
95146 ['S' ] = SECOND ,
96147 ['f' ] = FRACTION ,
97148 ['z' ] = OFFSET ,
149+ ['w' ] = WEEKDAY ,
98150 };
99151 /* String subclasses and non-ASCII input retain Python's behavior. */
100152 if (!PyUnicode_CheckExact (string ) || !PyUnicode_CheckExact (format ) ||
@@ -105,7 +157,8 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
105157 const unsigned char * fmt = PyUnicode_1BYTE_DATA (format );
106158 Py_ssize_t length = PyUnicode_GET_LENGTH (string );
107159 Py_ssize_t fmt_length = PyUnicode_GET_LENGTH (format );
108- * fields = (_PyTime_StrptimeFields ){.year = 1900 , .month = 1 , .day = 1 , .gmtoff = INT_MIN };
160+ * fields = (_PyTime_StrptimeFields ){.year = 1900 , .month = 1 , .day = 1 ,
161+ .gmtoff = INT_MIN , .weekday = -1 };
109162 unsigned int seen = 0 ;
110163 Py_ssize_t pos = 0 ;
111164
@@ -140,8 +193,15 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
140193 continue ;
141194 }
142195
143- /* Reject duplicate groups, aliases, and locale-dependent directives. */
144- unsigned int bit = directive_bits [c ];
196+ /* Mixed spellings of the same field retain Python's precedence. */
197+ unsigned char field = c ;
198+ if (c == 'b' || c == 'B' ) {
199+ field = 'm' ;
200+ }
201+ else if (c == 'a' || c == 'A' || c == 'u' ) {
202+ field = 'w' ;
203+ }
204+ unsigned int bit = directive_bits [field ];
145205 if (bit == 0 ) {
146206 return 0 ;
147207 }
@@ -150,6 +210,22 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
150210 }
151211 seen |= bit ;
152212
213+ if (c == 'b' || c == 'B' || c == 'a' || c == 'A' ) {
214+ int value ;
215+ int count = strptime_name (data , length , pos , c , & value );
216+ if (count == 0 ) {
217+ return 0 ;
218+ }
219+ pos += count ;
220+ if (field == 'm' ) {
221+ fields -> month = value ;
222+ }
223+ else {
224+ fields -> weekday = value ;
225+ }
226+ continue ;
227+ }
228+
153229 if (c == 'z' ) {
154230 if (i != fmt_length - 1 ||
155231 !strptime_offset (data , length , pos , fields )) {
@@ -170,6 +246,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
170246 else if (c == 'f' ) {
171247 maximum = 6 ;
172248 }
249+ else if (field == 'w' ) {
250+ maximum = 1 ;
251+ }
173252 else if ((c == 'd' || c == 'H' ) && pos < length && data [pos ] == ' ' ) {
174253 pos ++ ;
175254 maximum = 1 ;
@@ -181,6 +260,18 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
181260 }
182261 pos += count ;
183262 switch (c ) {
263+ case 'w' :
264+ if (value > 6 ) {
265+ return 0 ;
266+ }
267+ fields -> weekday = (value + 6 ) % 7 ;
268+ break ;
269+ case 'u' :
270+ if (value < 1 || value > 7 ) {
271+ return 0 ;
272+ }
273+ fields -> weekday = value - 1 ;
274+ break ;
184275 case 'Y' :
185276 fields -> year = value ;
186277 break ;
@@ -244,5 +335,8 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
244335 }
245336 fields -> yday = days_before_month [fields -> month ] + fields -> day +
246337 (fields -> month > 2 && leap );
338+ if (fields -> weekday < 0 ) {
339+ fields -> weekday = _PyTime_Weekday (fields -> year , fields -> yday );
340+ }
247341 return 1 ;
248342}
0 commit comments