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 ++ ) {
@@ -315,6 +368,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
315368 else if (c == 'I' ) {
316369 field = 'H' ;
317370 }
371+ else if (c == 'W' ) {
372+ field = 'U' ;
373+ }
318374 unsigned int bit = directive_bits [field ];
319375 if (bit == 0 ) {
320376 return 0 ;
@@ -372,7 +428,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
372428
373429 int minimum = 1 ;
374430 int maximum = 2 ;
375- if (c == 'Y' ) {
431+ if (c == 'Y' || c == 'G' ) {
376432 minimum = maximum = 4 ;
377433 }
378434 else if (c == 'y' ) {
@@ -381,6 +437,9 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
381437 else if (c == 'f' ) {
382438 maximum = 6 ;
383439 }
440+ else if (c == 'j' ) {
441+ maximum = 3 ;
442+ }
384443 else if (field == 'w' ) {
385444 maximum = 1 ;
386445 }
@@ -396,6 +455,30 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
396455 }
397456 pos += count ;
398457 switch (c ) {
458+ case 'j' :
459+ if (value < 1 || value > 366 ) {
460+ return 0 ;
461+ }
462+ julian = value ;
463+ have_julian = 1 ;
464+ break ;
465+ case 'U' :
466+ case 'W' :
467+ if (value > 53 ) {
468+ return 0 ;
469+ }
470+ week = value ;
471+ monday_first = c == 'W' ;
472+ break ;
473+ case 'G' :
474+ iso_year = value ;
475+ break ;
476+ case 'V' :
477+ if (value < 1 || value > 53 ) {
478+ return 0 ;
479+ }
480+ iso_week = value ;
481+ break ;
399482 case 'I' :
400483 if (value < 1 || value > 12 ) {
401484 return 0 ;
@@ -465,6 +548,53 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
465548 }
466549 fields -> hour = fields -> hour % 12 + (ampm == 1 ? 12 : 0 );
467550 }
551+ /* Mixed fields and incomplete ISO dates retain Python's diagnostics. */
552+ if (pos != length || (seen & ANY_YEAR ) == ANY_YEAR ||
553+ ((seen & DAY ) && !(seen & ANY_YEAR ) && iso_year < 0 )) {
554+ return 0 ;
555+ }
556+ if (iso_year >= 0 || iso_week >= 0 ) {
557+ if (iso_year < 1 || iso_week < 1 || fields -> weekday < 0 ||
558+ julian >= 0 || week >= 0 ) {
559+ return 0 ;
560+ }
561+ int monday = strptime_iso_start (iso_year ) + 7 * (iso_week - 1 );
562+ if (monday >= strptime_iso_start (iso_year + 1 ) ||
563+ !strptime_date_from_ordinal (monday + fields -> weekday , fields )) {
564+ return 0 ;
565+ }
566+ }
567+ if (fields -> year < 1 ) {
568+ return 0 ;
569+ }
570+ if (julian < 0 && week >= 0 && fields -> weekday >= 0 ) {
571+ int first = _PyTime_Weekday (fields -> year , 1 );
572+ int weekday = fields -> weekday ;
573+ if (!monday_first ) {
574+ first = (first + 1 ) % 7 ;
575+ weekday = (weekday + 1 ) % 7 ;
576+ }
577+ julian = week == 0 ? 1 + weekday - first
578+ : 1 + (7 - first ) % 7 + 7 * (week - 1 ) + weekday ;
579+ have_julian = 1 ;
580+ }
581+ if (have_julian ) {
582+ /* Week zero can fall in the previous year. A day spilling into the
583+ * next year retains its original yday in time.strptime's result.
584+ */
585+ int year = fields -> year ;
586+ if (julian <= 0 ) {
587+ if (year == 1 ) {
588+ return 0 ;
589+ }
590+ year -- ;
591+ julian += _PyTime_DaysBeforeYear (year + 1 ) - _PyTime_DaysBeforeYear (year );
592+ }
593+ if (!strptime_date_from_ordinal (_PyTime_DaysBeforeYear (year ) + julian , fields )) {
594+ return 0 ;
595+ }
596+ fields -> yday = julian ;
597+ }
468598 static const int days_before_month [] = {
469599 0 , 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 , 365
470600 };
@@ -475,15 +605,13 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
475605 if (fields -> month == 2 ) {
476606 month_days += leap ;
477607 }
478- /* Mixed %Y/%y and day-without-year diagnostics belong to Python. */
479- if (pos != length || (seen & ANY_YEAR ) == ANY_YEAR ||
480- ((seen & DAY ) && !(seen & ANY_YEAR )) ||
481- fields -> year < 1 ||
482- fields -> day > month_days ) {
608+ if (fields -> day > month_days ) {
483609 return 0 ;
484610 }
485- fields -> yday = days_before_month [fields -> month ] + fields -> day +
486- (fields -> month > 2 && leap );
611+ if (!have_julian ) {
612+ fields -> yday = days_before_month [fields -> month ] + fields -> day +
613+ (fields -> month > 2 && leap );
614+ }
487615 if (fields -> weekday < 0 ) {
488616 fields -> weekday = _PyTime_Weekday (fields -> year , fields -> yday );
489617 }
0 commit comments