/src/cpython/Python/_strptime.c
Line | Count | Source |
1 | | #include "Python.h" |
2 | | #include "pycore_time.h" |
3 | | |
4 | | |
5 | | /* Implement common parts of strptime in C to improve performance. Callers |
6 | | * fall back to Lib/_strptime.py for a full parse when this doesn't match. |
7 | | * |
8 | | * Supported inputs are exact str objects containing only ASCII, with: |
9 | | * - numeric %Y, %y, %m, %d, %H, %M, %S, and %f fields; |
10 | | * - a terminal %z: empty, Z, or +/-HHMM or +/-HH:MM with hours below 24; |
11 | | * - literal characters, %%, and ASCII whitespace. |
12 | | * |
13 | | * Duplicate directives, mixed %Y/%y, and day-of-month without a year use the |
14 | | * fallback. So do other directives (including locale-dependent names and |
15 | | * week/day-of-year calculations), and offsets with seconds or fractions. |
16 | | */ |
17 | | |
18 | | static int |
19 | | strptime_digits(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos, |
20 | | int minimum, int maximum, int *value) |
21 | 0 | { |
22 | 0 | int count = 0; |
23 | 0 | *value = 0; |
24 | 0 | while (count < maximum && pos + count < length) { |
25 | 0 | unsigned char c = data[pos + count]; |
26 | 0 | if (c < '0' || c > '9') { |
27 | 0 | break; |
28 | 0 | } |
29 | 0 | *value = *value * 10 + c - '0'; |
30 | 0 | count++; |
31 | 0 | } |
32 | 0 | return count >= minimum ? count : 0; |
33 | 0 | } |
34 | | |
35 | | static int |
36 | | strptime_offset(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos, |
37 | | _PyTime_StrptimeFields *fields) |
38 | 0 | { |
39 | 0 | Py_ssize_t remaining = length - pos; |
40 | 0 | if (remaining == 0) { |
41 | 0 | return 1; |
42 | 0 | } |
43 | 0 | if (remaining == 1 && data[pos] == 'Z') { |
44 | 0 | fields->gmtoff = 0; |
45 | 0 | return 1; |
46 | 0 | } |
47 | | /* Offsets containing seconds or fractions fall back to _strptime.py. */ |
48 | 0 | if ((remaining != 5 && remaining != 6) || |
49 | 0 | (data[pos] != '+' && data[pos] != '-')) { |
50 | 0 | return 0; |
51 | 0 | } |
52 | 0 | int hours, minutes; |
53 | 0 | int colon = remaining == 6; |
54 | 0 | if ((colon && data[pos + 3] != ':') || |
55 | 0 | strptime_digits(data, length, pos + 1, 2, 2, &hours) != 2 || |
56 | 0 | strptime_digits(data, length, pos + 3 + colon, 2, 2, &minutes) != 2 || |
57 | 0 | hours > 23 || minutes > 59) { |
58 | 0 | return 0; |
59 | 0 | } |
60 | 0 | fields->gmtoff = (hours * 3600 + minutes * 60) * |
61 | 0 | (data[pos] == '-' ? -1 : 1); |
62 | 0 | return 1; |
63 | 0 | } |
64 | | |
65 | | /* Return 1 for a complete numeric match, or 0 if fallback is required. |
66 | | * |
67 | | * Parsing doesn't allocate or set exceptions. Some mismatches require regex |
68 | | * backtracking, but even inputs known to be invalid use the Python parser: |
69 | | * it owns the exception messages and error precedence. For example, month 13 |
70 | | * and empty input can't be repaired by backtracking, but diagnosing them here |
71 | | * would duplicate the Python parser's error handling. A return value of 0 |
72 | | * therefore doesn't distinguish invalid input from an unsupported format. |
73 | | */ |
74 | | int |
75 | | _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fields) |
76 | 0 | { |
77 | 0 | enum { |
78 | 0 | YEAR = 1U << 0, |
79 | 0 | SHORT_YEAR = 1U << 1, |
80 | 0 | MONTH = 1U << 2, |
81 | 0 | DAY = 1U << 3, |
82 | 0 | HOUR = 1U << 4, |
83 | 0 | MINUTE = 1U << 5, |
84 | 0 | SECOND = 1U << 6, |
85 | 0 | FRACTION = 1U << 7, |
86 | 0 | OFFSET = 1U << 8, |
87 | 0 | ANY_YEAR = YEAR | SHORT_YEAR, |
88 | 0 | }; |
89 | 0 | static const unsigned short directive_bits[128] = { |
90 | 0 | ['Y'] = YEAR, |
91 | 0 | ['y'] = SHORT_YEAR, |
92 | 0 | ['m'] = MONTH, |
93 | 0 | ['d'] = DAY, |
94 | 0 | ['H'] = HOUR, |
95 | 0 | ['M'] = MINUTE, |
96 | 0 | ['S'] = SECOND, |
97 | 0 | ['f'] = FRACTION, |
98 | 0 | ['z'] = OFFSET, |
99 | 0 | }; |
100 | | /* String subclasses and non-ASCII input retain Python's behavior. */ |
101 | 0 | if (!PyUnicode_CheckExact(string) || !PyUnicode_CheckExact(format) || |
102 | 0 | !PyUnicode_IS_ASCII(string) || !PyUnicode_IS_ASCII(format)) { |
103 | 0 | return 0; |
104 | 0 | } |
105 | 0 | const unsigned char *data = PyUnicode_1BYTE_DATA(string); |
106 | 0 | const unsigned char *fmt = PyUnicode_1BYTE_DATA(format); |
107 | 0 | Py_ssize_t length = PyUnicode_GET_LENGTH(string); |
108 | 0 | Py_ssize_t fmt_length = PyUnicode_GET_LENGTH(format); |
109 | 0 | *fields = (_PyTime_StrptimeFields){.year = 1900, .month = 1, .day = 1, .gmtoff = INT_MIN}; |
110 | 0 | unsigned int seen = 0; |
111 | 0 | Py_ssize_t pos = 0; |
112 | |
|
113 | 0 | for (Py_ssize_t i = 0; i < fmt_length; i++) { |
114 | 0 | unsigned char c = fmt[i]; |
115 | | /* Match regex \s, including ASCII separators U+001C through U+001F. */ |
116 | 0 | if (Py_UNICODE_ISSPACE(c)) { |
117 | 0 | if (pos == length || !Py_UNICODE_ISSPACE(data[pos])) { |
118 | 0 | return 0; |
119 | 0 | } |
120 | 0 | while (i + 1 < fmt_length && Py_UNICODE_ISSPACE(fmt[i + 1])) { |
121 | 0 | i++; |
122 | 0 | } |
123 | 0 | do { |
124 | 0 | pos++; |
125 | 0 | } while (pos < length && Py_UNICODE_ISSPACE(data[pos])); |
126 | 0 | continue; |
127 | 0 | } |
128 | 0 | if (c != '%') { |
129 | 0 | if (pos == length || data[pos++] != c) { |
130 | 0 | return 0; |
131 | 0 | } |
132 | 0 | continue; |
133 | 0 | } |
134 | 0 | if (++i == fmt_length) { |
135 | 0 | return 0; |
136 | 0 | } |
137 | 0 | c = fmt[i]; |
138 | 0 | if (c == '%') { |
139 | 0 | if (pos == length || data[pos++] != '%') { |
140 | 0 | return 0; |
141 | 0 | } |
142 | 0 | continue; |
143 | 0 | } |
144 | | |
145 | | /* Reject duplicate groups, aliases, and locale-dependent directives. */ |
146 | 0 | unsigned int bit = directive_bits[c]; |
147 | 0 | if (bit == 0) { |
148 | 0 | return 0; |
149 | 0 | } |
150 | 0 | if (seen & bit) { |
151 | 0 | return 0; |
152 | 0 | } |
153 | 0 | seen |= bit; |
154 | |
|
155 | 0 | if (c == 'z') { |
156 | 0 | if (i != fmt_length - 1 || |
157 | 0 | !strptime_offset(data, length, pos, fields)) { |
158 | 0 | return 0; |
159 | 0 | } |
160 | 0 | pos = length; |
161 | 0 | continue; |
162 | 0 | } |
163 | | |
164 | 0 | int minimum = 1; |
165 | 0 | int maximum = 2; |
166 | 0 | if (c == 'Y') { |
167 | 0 | minimum = maximum = 4; |
168 | 0 | } |
169 | 0 | else if (c == 'y') { |
170 | 0 | minimum = maximum = 2; |
171 | 0 | } |
172 | 0 | else if (c == 'f') { |
173 | 0 | maximum = 6; |
174 | 0 | } |
175 | 0 | else if ((c == 'd' || c == 'H') && pos < length && data[pos] == ' ') { |
176 | 0 | pos++; |
177 | 0 | maximum = 1; |
178 | 0 | } |
179 | 0 | int value; |
180 | 0 | int count = strptime_digits(data, length, pos, minimum, maximum, &value); |
181 | 0 | if (count == 0) { |
182 | 0 | return 0; |
183 | 0 | } |
184 | 0 | pos += count; |
185 | 0 | switch (c) { |
186 | 0 | case 'Y': |
187 | 0 | fields->year = value; |
188 | 0 | break; |
189 | 0 | case 'y': |
190 | 0 | fields->year = value + (value <= 68 ? 2000 : 1900); |
191 | 0 | break; |
192 | 0 | case 'm': |
193 | 0 | if (value < 1 || value > 12) { |
194 | 0 | return 0; |
195 | 0 | } |
196 | 0 | fields->month = value; |
197 | 0 | break; |
198 | 0 | case 'd': |
199 | 0 | if (value < 1 || value > 31) { |
200 | 0 | return 0; |
201 | 0 | } |
202 | 0 | fields->day = value; |
203 | 0 | break; |
204 | 0 | case 'H': |
205 | 0 | if (value > 23) { |
206 | 0 | return 0; |
207 | 0 | } |
208 | 0 | fields->hour = value; |
209 | 0 | break; |
210 | 0 | case 'M': |
211 | 0 | if (value > 59) { |
212 | 0 | return 0; |
213 | 0 | } |
214 | 0 | fields->minute = value; |
215 | 0 | break; |
216 | 0 | case 'S': |
217 | 0 | if (value > 61) { |
218 | 0 | return 0; |
219 | 0 | } |
220 | 0 | fields->second = value; |
221 | 0 | break; |
222 | 0 | case 'f': |
223 | 0 | for (; count < 6; count++) { |
224 | 0 | value *= 10; |
225 | 0 | } |
226 | 0 | fields->fraction = value; |
227 | 0 | break; |
228 | 0 | } |
229 | 0 | } |
230 | 0 | static const int days_before_month[] = { |
231 | 0 | 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 |
232 | 0 | }; |
233 | 0 | int leap = fields->year % 4 == 0 && |
234 | 0 | (fields->year % 100 != 0 || fields->year % 400 == 0); |
235 | 0 | int month_days = days_before_month[fields->month + 1] - |
236 | 0 | days_before_month[fields->month]; |
237 | 0 | if (fields->month == 2) { |
238 | 0 | month_days += leap; |
239 | 0 | } |
240 | | /* Mixed %Y/%y and day-without-year diagnostics belong to Python. */ |
241 | 0 | if (pos != length || (seen & ANY_YEAR) == ANY_YEAR || |
242 | 0 | ((seen & DAY) && !(seen & ANY_YEAR)) || |
243 | 0 | fields->year < 1 || |
244 | 0 | fields->day > month_days) { |
245 | 0 | return 0; |
246 | 0 | } |
247 | 0 | fields->yday = days_before_month[fields->month] + fields->day + |
248 | 0 | (fields->month > 2 && leap); |
249 | 0 | return 1; |
250 | 0 | } |