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 /* Match regex \s, including ASCII separators U+001C through U+001F. */
221236 if (Py_UNICODE_ISSPACE (c )) {
@@ -240,6 +255,34 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
240255 return 0 ;
241256 }
242257 c = fmt [i ];
258+ const char * alias = NULL ;
259+ switch (c ) {
260+ case 'F' : alias = "%Y-%m-%d" ; break ;
261+ case 'D' : alias = "%m/%d/%y" ; break ;
262+ case 'T' : alias = "%H:%M:%S" ; break ;
263+ case 'R' : alias = "%H:%M" ; break ;
264+ case 'e' : c = 'd' ; break ;
265+ }
266+ if (alias != NULL ) {
267+ /* These fixed expansions contain no further aliases. Parsing
268+ * them in place keeps duplicate-field checks and avoids copying
269+ * an arbitrarily long user format into a temporary buffer.
270+ */
271+ assert (outer_fmt == NULL );
272+ outer_fmt = fmt ;
273+ outer_length = fmt_length ;
274+ outer_pos = i + 1 ;
275+ fmt = (const unsigned char * )alias ;
276+ fmt_length = strlen (alias );
277+ i = -1 ;
278+ continue ;
279+ }
280+ if (c == 'n' || c == 't' ) {
281+ while (pos < length && Py_ISSPACE (data [pos ])) {
282+ pos ++ ;
283+ }
284+ continue ;
285+ }
243286 int require_colon = 0 ;
244287 if (c == ':' && i + 1 < fmt_length && fmt [i + 1 ] == 'z' ) {
245288 c = fmt [++ i ];
0 commit comments