1010 * Supported inputs are exact str objects containing only ASCII, with:
1111 * - numeric %Y, %y, %m, %d, %H, %M, %S, and %f fields;
1212 * - ASCII locale month/weekday names and numeric %w/%u weekdays;
13+ * - %I with ASCII %p indicators, and the %k, %l, %P aliases;
1314 * - a terminal %z: empty, Z, or +/-HHMM or +/-HH:MM with hours below 24;
1415 * - literal characters, %%, and ASCII whitespace.
1516 * Duplicate directives, mixed %Y/%y, and day-of-month without a year use the
@@ -44,13 +45,14 @@ strptime_name(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos,
4445 * As in TimeRE, prefer the longest match, then the first equal name.
4546 */
4647 int month = directive == 'b' || directive == 'B' ;
47- int count = month ? 12 : 7 ;
48+ int count = month ? 12 : directive == 'p' ? 2 : 7 ;
4849 char format [] = {'%' , directive , '\0' };
4950 int matched = 0 ;
5051 for (int i = 0 ; i < count ; i ++ ) {
5152 struct tm tm = {.tm_year = 101 , .tm_mday = 1 ,
5253 .tm_mon = month ? i : 0 ,
53- .tm_wday = month ? 0 : (i + 1 ) % 7 };
54+ .tm_wday = month ? 0 : (i + 1 ) % 7 ,
55+ .tm_hour = i == 0 ? 1 : 22 };
5456 char name [256 ];
5557 size_t size = strftime (name , sizeof (name ), format , & tm );
5658 if (size == 0 ) {
@@ -61,12 +63,31 @@ strptime_name(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos,
6163 return 0 ;
6264 }
6365 }
64- if (size <= (size_t )matched || size > (size_t )(length - pos )) {
66+ char * start = name ;
67+ if (directive == 'p' ) {
68+ while (size && Py_ISSPACE (start [size - 1 ])) {
69+ size -- ;
70+ }
71+ while (size && Py_ISSPACE (* start )) {
72+ start ++ ;
73+ size -- ;
74+ }
75+ /* Empty indicators have special defaulting rules in Python. */
76+ if (size == 0 ) {
77+ return 0 ;
78+ }
79+ }
80+ if (data == NULL ) {
81+ continue ;
82+ }
83+ if (size < (size_t )matched ||
84+ (size == (size_t )matched && directive != 'p' ) ||
85+ size > (size_t )(length - pos )) {
6586 continue ;
6687 }
6788 size_t j ;
6889 for (j = 0 ; j < size ; j ++ ) {
69- if (Py_TOLOWER (data [pos + j ]) != Py_TOLOWER (name [j ])) {
90+ if (Py_TOLOWER (data [pos + j ]) != Py_TOLOWER (start [j ])) {
7091 break ;
7192 }
7293 }
@@ -75,7 +96,7 @@ strptime_name(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos,
7596 * value = month ? i + 1 : i ;
7697 }
7798 }
78- return matched ;
99+ return data == NULL ? 1 : matched ;
79100#else
80101 return 0 ;
81102#endif
@@ -134,6 +155,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
134155 FRACTION = 1U << 7 ,
135156 OFFSET = 1U << 8 ,
136157 WEEKDAY = 1U << 9 ,
158+ AMPM = 1U << 10 ,
137159 ANY_YEAR = YEAR | SHORT_YEAR ,
138160 };
139161 static const unsigned short directive_bits [128 ] = {
@@ -147,6 +169,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
147169 ['f' ] = FRACTION ,
148170 ['z' ] = OFFSET ,
149171 ['w' ] = WEEKDAY ,
172+ ['p' ] = AMPM ,
150173 };
151174 /* String subclasses and non-ASCII input retain Python's behavior. */
152175 if (!PyUnicode_CheckExact (string ) || !PyUnicode_CheckExact (format ) ||
@@ -160,6 +183,8 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
160183 * fields = (_PyTime_StrptimeFields ){.year = 1900 , .month = 1 , .day = 1 ,
161184 .gmtoff = INT_MIN , .weekday = -1 };
162185 unsigned int seen = 0 ;
186+ int hour12 = 0 ;
187+ int ampm = -1 ;
163188 Py_ssize_t pos = 0 ;
164189
165190 for (Py_ssize_t i = 0 ; i < fmt_length ; i ++ ) {
@@ -187,6 +212,15 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
187212 return 0 ;
188213 }
189214 c = fmt [i ];
215+ if (c == 'k' ) {
216+ c = 'H' ;
217+ }
218+ else if (c == 'l' ) {
219+ c = 'I' ;
220+ }
221+ else if (c == 'P' ) {
222+ c = 'p' ;
223+ }
190224 if (c == '%' ) {
191225 if (pos == length || data [pos ++ ] != '%' ) {
192226 return 0 ;
@@ -202,6 +236,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
202236 else if (c == 'a' || c == 'A' || c == 'u' ) {
203237 field = 'w' ;
204238 }
239+ else if (c == 'I' ) {
240+ field = 'H' ;
241+ }
205242 unsigned int bit = directive_bits [field ];
206243 if (bit == 0 ) {
207244 return 0 ;
@@ -211,7 +248,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
211248 }
212249 seen |= bit ;
213250
214- if (c == 'b' || c == 'B' || c == 'a' || c == 'A' ) {
251+ if (c == 'b' || c == 'B' || c == 'a' || c == 'A' || c == 'p' ) {
215252 int value ;
216253 int count = strptime_name (data , length , pos , c , & value );
217254 if (count == 0 ) {
@@ -221,6 +258,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
221258 if (field == 'm' ) {
222259 fields -> month = value ;
223260 }
261+ else if (c == 'p' ) {
262+ ampm = value ;
263+ }
224264 else {
225265 fields -> weekday = value ;
226266 }
@@ -250,7 +290,8 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
250290 else if (field == 'w' ) {
251291 maximum = 1 ;
252292 }
253- else if ((c == 'd' || c == 'H' ) && pos < length && data [pos ] == ' ' ) {
293+ else if ((c == 'd' || c == 'H' || c == 'I' ) &&
294+ pos < length && data [pos ] == ' ' ) {
254295 pos ++ ;
255296 maximum = 1 ;
256297 }
@@ -261,6 +302,13 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
261302 }
262303 pos += count ;
263304 switch (c ) {
305+ case 'I' :
306+ if (value < 1 || value > 12 ) {
307+ return 0 ;
308+ }
309+ hour12 = 1 ;
310+ fields -> hour = value ;
311+ break ;
264312 case 'w' :
265313 if (value > 6 ) {
266314 return 0 ;
@@ -317,6 +365,12 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
317365 break ;
318366 }
319367 }
368+ if (hour12 ) {
369+ if (ampm < 0 && !strptime_name (NULL , 0 , 0 , 'p' , & ampm )) {
370+ return 0 ;
371+ }
372+ fields -> hour = fields -> hour % 12 + (ampm == 1 ? 12 : 0 );
373+ }
320374 static const int days_before_month [] = {
321375 0 , 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 , 365
322376 };
0 commit comments