1111 * - numeric %Y, %y, %m, %d, %H, %M, %S, and %f fields;
1212 * - ASCII locale month/weekday names and numeric %w/%u weekdays;
1313 * - %I with ASCII %p indicators, and the %k, %l, %P aliases;
14- * - a terminal %z: empty, Z, or +/-HHMM or +/-HH:MM with hours below 24 ;
14+ * - %z and %:z offsets with hours below 24, and terminal UTC/GMT %Z names ;
1515 * - literal characters, %%, and ASCII whitespace.
1616 * Duplicate directives, mixed %Y/%y, and day-of-month without a year use the
1717 * fallback. So do other directives (including non-ASCII locale names and
18- * week/day-of-year calculations), and offsets with seconds or fractions .
18+ * week/day-of-year calculations).
1919 */
2020
2121static int
@@ -104,32 +104,58 @@ strptime_name(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos,
104104
105105static int
106106strptime_offset (const unsigned char * data , Py_ssize_t length , Py_ssize_t pos ,
107- _PyTime_StrptimeFields * fields )
107+ int require_colon , _PyTime_StrptimeFields * fields )
108108{
109- Py_ssize_t remaining = length - pos ;
110- if (remaining == 0 ) {
111- return 1 ;
109+ Py_ssize_t start = pos ;
110+ if (pos == length ) {
111+ return 0 ;
112112 }
113- if (remaining == 1 && data [pos ] == 'Z' ) {
113+ if (data [pos ] == 'Z' ) {
114114 fields -> gmtoff = 0 ;
115115 return 1 ;
116116 }
117- /* Offsets containing seconds or fractions fall back to _strptime.py. */
118- if ((remaining != 5 && remaining != 6 ) ||
119- (data [pos ] != '+' && data [pos ] != '-' )) {
117+ if (data [pos ] != '+' && data [pos ] != '-' ) {
120118 return 0 ;
121119 }
122- int hours , minutes ;
123- int colon = remaining == 6 ;
124- if ((colon && data [pos + 3 ] != ':' ) ||
125- strptime_digits (data , length , pos + 1 , 2 , 2 , & hours ) != 2 ||
126- strptime_digits (data , length , pos + 3 + colon , 2 , 2 , & minutes ) != 2 ||
127- hours > 23 || minutes > 59 ) {
128- return 0 ;
120+ int sign = data [pos ++ ] == '-' ? -1 : 1 ;
121+ int hours , minutes , seconds = 0 , fraction = 0 ;
122+ if (strptime_digits (data , length , pos , 2 , 2 , & hours ) != 2 || hours > 23 ) {
123+ return -1 ;
129124 }
130- fields -> gmtoff = (hours * 3600 + minutes * 60 ) *
131- (data [pos ] == '-' ? -1 : 1 );
132- return 1 ;
125+ pos += 2 ;
126+ int colon = pos < length && data [pos ] == ':' ;
127+ if (require_colon && !colon ) {
128+ return -1 ;
129+ }
130+ pos += colon ;
131+ if (strptime_digits (data , length , pos , 2 , 2 , & minutes ) != 2 || minutes > 59 ) {
132+ return -1 ;
133+ }
134+ pos += 2 ;
135+ int seconds_colon = pos < length && data [pos ] == ':' ;
136+ Py_ssize_t seconds_pos = pos + seconds_colon ;
137+ if (strptime_digits (data , length , seconds_pos , 2 , 2 , & seconds ) == 2 ) {
138+ if (seconds > 59 || seconds_colon != colon ) {
139+ return -1 ;
140+ }
141+ pos = seconds_pos + 2 ;
142+ if (pos < length && data [pos ] == '.' ) {
143+ int count = strptime_digits (data , length , pos + 1 , 1 , 6 , & fraction );
144+ if (count == 0 ) {
145+ return -1 ;
146+ }
147+ pos += count + 1 ;
148+ for (; count < 6 ; count ++ ) {
149+ fraction *= 10 ;
150+ }
151+ }
152+ }
153+ else {
154+ seconds = 0 ;
155+ }
156+ fields -> gmtoff = sign * (hours * 3600 + minutes * 60 + seconds );
157+ fields -> gmtoff_fraction = sign * fraction ;
158+ return (int )(pos - start );
133159}
134160
135161/* Return 1 for a complete numeric match, or 0 if fallback is required.
@@ -156,6 +182,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
156182 OFFSET = 1U << 8 ,
157183 WEEKDAY = 1U << 9 ,
158184 AMPM = 1U << 10 ,
185+ ZONE = 1U << 11 ,
159186 ANY_YEAR = YEAR | SHORT_YEAR ,
160187 };
161188 static const unsigned short directive_bits [128 ] = {
@@ -170,6 +197,7 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
170197 ['z' ] = OFFSET ,
171198 ['w' ] = WEEKDAY ,
172199 ['p' ] = AMPM ,
200+ ['Z' ] = ZONE ,
173201 };
174202 /* String subclasses and non-ASCII input retain Python's behavior. */
175203 if (!PyUnicode_CheckExact (string ) || !PyUnicode_CheckExact (format ) ||
@@ -211,6 +239,11 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
211239 return 0 ;
212240 }
213241 c = fmt [i ];
242+ int require_colon = 0 ;
243+ if (c == ':' && i + 1 < fmt_length && fmt [i + 1 ] == 'z' ) {
244+ c = fmt [++ i ];
245+ require_colon = 1 ;
246+ }
214247 if (c == 'k' ) {
215248 c = 'H' ;
216249 }
@@ -266,14 +299,32 @@ _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fie
266299 continue ;
267300 }
268301
269- if (c == 'z' ) {
270- if (i != fmt_length - 1 ||
271- !strptime_offset (data , length , pos , fields )) {
302+ if (c == 'Z' ) {
303+ /* Local timezone names and nonterminal names retain Python's
304+ * tzset state, DST rules and regex backtracking.
305+ */
306+ if (i != fmt_length - 1 || length - pos != 3 ||
307+ !((Py_TOLOWER (data [pos ]) == 'u' &&
308+ Py_TOLOWER (data [pos + 1 ]) == 't' &&
309+ Py_TOLOWER (data [pos + 2 ]) == 'c' ) ||
310+ (Py_TOLOWER (data [pos ]) == 'g' &&
311+ Py_TOLOWER (data [pos + 1 ]) == 'm' &&
312+ Py_TOLOWER (data [pos + 2 ]) == 't' ))) {
272313 return 0 ;
273314 }
315+ fields -> zone_start = pos ;
316+ fields -> zone_length = 3 ;
274317 pos = length ;
275318 continue ;
276319 }
320+ if (c == 'z' ) {
321+ int count = strptime_offset (data , length , pos , require_colon , fields );
322+ if (count < 0 ) {
323+ return 0 ;
324+ }
325+ pos += count ;
326+ continue ;
327+ }
277328
278329 int minimum = 1 ;
279330 int maximum = 2 ;
0 commit comments