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;
1515 * - numeric aliases %F, %D, %T, %R, %e and whitespace %n/%t;
16+ * - ordinal dates (%j), calendar weeks (%U/%W), and ISO weeks (%G/%V);
1617 * - literal characters, %%, and ASCII whitespace.
1718 * Duplicate directives, mixed %Y/%y, and day-of-month without a year use the
18- * fallback. So do other directives (including non-ASCII locale names and
19- * week/day-of-year calculations).
19+ * fallback. So do non-ASCII locale names and directives not listed above.
2020 */
2121
2222static int
@@ -103,6 +103,48 @@ strptime_name(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos,
103103#endif
104104}
105105
106+ static int
107+ strptime_date_from_ordinal (int ordinal , _PyTime_StrptimeFields * fields )
108+ {
109+ if (ordinal < 1 || ordinal > _PyTime_DaysBeforeYear (10000 )) {
110+ return 0 ;
111+ }
112+ int lower = 1 , upper = 10000 ;
113+ while (lower + 1 < upper ) {
114+ int middle = (lower + upper ) / 2 ;
115+ if (_PyTime_DaysBeforeYear (middle ) < ordinal ) {
116+ lower = middle ;
117+ }
118+ else {
119+ upper = middle ;
120+ }
121+ }
122+ fields -> year = lower ;
123+ fields -> yday = ordinal - _PyTime_DaysBeforeYear (lower );
124+ fields -> day = fields -> yday ;
125+ int leap = lower % 4 == 0 && (lower % 100 != 0 || lower % 400 == 0 );
126+ static const int month_days [] = {31 , 28 , 31 , 30 , 31 , 30 ,
127+ 31 , 31 , 30 , 31 , 30 , 31 };
128+ for (fields -> month = 1 ; fields -> month < 12 ; fields -> month ++ ) {
129+ int days = month_days [fields -> month - 1 ] + (fields -> month == 2 && leap );
130+ if (fields -> day <= days ) {
131+ break ;
132+ }
133+ fields -> day -= days ;
134+ }
135+ if (fields -> weekday < 0 ) {
136+ fields -> weekday = (ordinal - 1 ) % 7 ;
137+ }
138+ return 1 ;
139+ }
140+
141+ static int
142+ strptime_iso_start (int year )
143+ {
144+ /* ISO week 1 contains January 4. */
145+ return _PyTime_DaysBeforeYear (year ) + 4 - _PyTime_Weekday (year , 4 );
146+ }
147+
106148static int
107149strptime_offset (const unsigned char * data , Py_ssize_t length , Py_ssize_t pos ,
108150 int require_colon , _PyTime_StrptimeFields * fields )
@@ -159,7 +201,7 @@ strptime_offset(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos,
159201 return (int )(pos - start );
160202}
161203
162- /* Return 1 for a complete numeric match, or 0 if fallback is required.
204+ /* Return 1 for a complete match, or 0 if fallback is required.
163205 *
164206 * Parsing doesn't allocate or set exceptions. Some mismatches require regex
165207 * backtracking, but even inputs known to be invalid use the Python parser:
@@ -184,6 +226,10 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
184226 WEEKDAY = 1U << 9 ,
185227 AMPM = 1U << 10 ,
186228 ZONE = 1U << 11 ,
229+ JULIAN = 1U << 12 ,
230+ WEEK = 1U << 13 ,
231+ ISO_YEAR = 1U << 14 ,
232+ ISO_WEEK = 1U << 15 ,
187233 ANY_YEAR = YEAR | SHORT_YEAR ,
188234 };
189235 static const unsigned short directive_bits [128 ] = {
@@ -199,6 +245,10 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
199245 ['w' ] = WEEKDAY ,
200246 ['p' ] = AMPM ,
201247 ['Z' ] = ZONE ,
248+ ['j' ] = JULIAN ,
249+ ['U' ] = WEEK ,
250+ ['G' ] = ISO_YEAR ,
251+ ['V' ] = ISO_WEEK ,
202252 };
203253 /* String subclasses and non-ASCII input retain Python's behavior. */
204254 if (!PyUnicode_CheckExact (string ) || !PyUnicode_CheckExact (format ) ||
@@ -216,6 +266,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
216266 unsigned int seen = 0 ;
217267 int hour12 = 0 ;
218268 int ampm = -1 ;
269+ int julian = -1 , week = -1 , monday_first = 0 ;
270+ int have_julian = 0 ;
271+ int iso_year = -1 , iso_week = -1 ;
219272 Py_ssize_t pos = 0 ;
220273
221274 for (Py_ssize_t i = 0 ; ; i ++ ) {
@@ -314,6 +367,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
314367 else if (c == 'I' ) {
315368 field = 'H' ;
316369 }
370+ else if (c == 'W' ) {
371+ field = 'U' ;
372+ }
317373 unsigned int bit = directive_bits [field ];
318374 if (bit == 0 ) {
319375 return 0 ;
@@ -371,7 +427,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
371427
372428 int minimum = 1 ;
373429 int maximum = 2 ;
374- if (c == 'Y' ) {
430+ if (c == 'Y' || c == 'G' ) {
375431 minimum = maximum = 4 ;
376432 }
377433 else if (c == 'y' ) {
@@ -380,6 +436,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
380436 else if (c == 'f' ) {
381437 maximum = 6 ;
382438 }
439+ else if (c == 'j' ) {
440+ maximum = 3 ;
441+ }
383442 else if (field == 'w' ) {
384443 maximum = 1 ;
385444 }
@@ -395,6 +454,30 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
395454 }
396455 pos += count ;
397456 switch (c ) {
457+ case 'j' :
458+ if (value < 1 || value > 366 ) {
459+ return 0 ;
460+ }
461+ julian = value ;
462+ have_julian = 1 ;
463+ break ;
464+ case 'U' :
465+ case 'W' :
466+ if (value > 53 ) {
467+ return 0 ;
468+ }
469+ week = value ;
470+ monday_first = c == 'W' ;
471+ break ;
472+ case 'G' :
473+ iso_year = value ;
474+ break ;
475+ case 'V' :
476+ if (value < 1 || value > 53 ) {
477+ return 0 ;
478+ }
479+ iso_week = value ;
480+ break ;
398481 case 'I' :
399482 if (value < 1 || value > 12 ) {
400483 return 0 ;
@@ -464,6 +547,53 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
464547 }
465548 fields -> hour = fields -> hour % 12 + (ampm == 1 ? 12 : 0 );
466549 }
550+ /* Mixed fields and incomplete ISO dates retain Python's diagnostics. */
551+ if (pos != length || (seen & ANY_YEAR ) == ANY_YEAR ||
552+ ((seen & DAY ) && !(seen & ANY_YEAR ) && iso_year < 0 )) {
553+ return 0 ;
554+ }
555+ if (iso_year >= 0 || iso_week >= 0 ) {
556+ if (iso_year < 1 || iso_week < 1 || fields -> weekday < 0 ||
557+ julian >= 0 || week >= 0 ) {
558+ return 0 ;
559+ }
560+ int monday = strptime_iso_start (iso_year ) + 7 * (iso_week - 1 );
561+ if (monday >= strptime_iso_start (iso_year + 1 ) ||
562+ !strptime_date_from_ordinal (monday + fields -> weekday , fields )) {
563+ return 0 ;
564+ }
565+ }
566+ if (fields -> year < 1 ) {
567+ return 0 ;
568+ }
569+ if (julian < 0 && week >= 0 && fields -> weekday >= 0 ) {
570+ int first = _PyTime_Weekday (fields -> year , 1 );
571+ int weekday = fields -> weekday ;
572+ if (!monday_first ) {
573+ first = (first + 1 ) % 7 ;
574+ weekday = (weekday + 1 ) % 7 ;
575+ }
576+ julian = week == 0 ? 1 + weekday - first
577+ : 1 + (7 - first ) % 7 + 7 * (week - 1 ) + weekday ;
578+ have_julian = 1 ;
579+ }
580+ if (have_julian ) {
581+ /* Week zero can fall in the previous year. A day spilling into the
582+ * next year retains its original yday in time.strptime's result.
583+ */
584+ int year = fields -> year ;
585+ if (julian <= 0 ) {
586+ if (year == 1 ) {
587+ return 0 ;
588+ }
589+ year -- ;
590+ julian += _PyTime_DaysBeforeYear (year + 1 ) - _PyTime_DaysBeforeYear (year );
591+ }
592+ if (!strptime_date_from_ordinal (_PyTime_DaysBeforeYear (year ) + julian , fields )) {
593+ return 0 ;
594+ }
595+ fields -> yday = julian ;
596+ }
467597 static const int days_before_month [] = {
468598 0 , 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 , 365
469599 };
@@ -474,15 +604,13 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
474604 if (fields -> month == 2 ) {
475605 month_days += leap ;
476606 }
477- /* Mixed %Y/%y and day-without-year diagnostics belong to Python. */
478- if (pos != length || (seen & ANY_YEAR ) == ANY_YEAR ||
479- ((seen & DAY ) && !(seen & ANY_YEAR )) ||
480- fields -> year < 1 ||
481- fields -> day > month_days ) {
607+ if (fields -> day > month_days ) {
482608 return 0 ;
483609 }
484- fields -> yday = days_before_month [fields -> month ] + fields -> day +
485- (fields -> month > 2 && leap );
610+ if (!have_julian ) {
611+ fields -> yday = days_before_month [fields -> month ] + fields -> day +
612+ (fields -> month > 2 && leap );
613+ }
486614 if (fields -> weekday < 0 ) {
487615 fields -> weekday = _PyTime_Weekday (fields -> year , fields -> yday );
488616 }
0 commit comments