| #include "Python.h" | |
| #include "pycore_time.h" | |
| /* Implement common parts of strptime in C to improve performance. Callers | |
| * fall back to Lib/_strptime.py for a full parse when this doesn't match. | |
| * | |
| * Supported inputs are exact str objects containing only ASCII, with: | |
| * - numeric %Y, %y, %m, %d, %H, %M, %S, and %f fields; | |
| * - a terminal %z: empty, Z, or +/-HHMM or +/-HH:MM with hours below 24; | |
| * - literal characters, %%, and ASCII whitespace. | |
| * Duplicate directives, mixed %Y/%y, and day-of-month without a year use the | |
| * fallback. So do other directives (including locale-dependent names and | |
| * week/day-of-year calculations), and offsets with seconds or fractions. | |
| */ | |
| static int | |
| strptime_digits(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos, | |
| int minimum, int maximum, int *value) | |
| { | |
| int count = 0; | |
| *value = 0; | |
| while (count < maximum && pos + count < length) { | |
| unsigned char c = data[pos + count]; | |
| if (c < '0' || c > '9') { | |
| break; | |
| } | |
| *value = *value * 10 + c - '0'; | |
| count++; | |
| } | |
| return count >= minimum ? count : 0; | |
| } | |
| static int | |
| strptime_offset(const unsigned char *data, Py_ssize_t length, Py_ssize_t pos, | |
| _PyTime_StrptimeFields *fields) | |
| { | |
| Py_ssize_t remaining = length - pos; | |
| if (remaining == 0) { | |
| return 1; | |
| } | |
| if (remaining == 1 && data[pos] == 'Z') { | |
| fields->gmtoff = 0; | |
| return 1; | |
| } | |
| /* Offsets containing seconds or fractions fall back to _strptime.py. */ | |
| if ((remaining != 5 && remaining != 6) || | |
| (data[pos] != '+' && data[pos] != '-')) { | |
| return 0; | |
| } | |
| int hours, minutes; | |
| int colon = remaining == 6; | |
| if ((colon && data[pos + 3] != ':') || | |
| strptime_digits(data, length, pos + 1, 2, 2, &hours) != 2 || | |
| strptime_digits(data, length, pos + 3 + colon, 2, 2, &minutes) != 2 || | |
| hours > 23 || minutes > 59) { | |
| return 0; | |
| } | |
| fields->gmtoff = (hours * 3600 + minutes * 60) * | |
| (data[pos] == '-' ? -1 : 1); | |
| return 1; | |
| } | |
| /* Return 1 for a complete numeric match, or 0 if fallback is required. | |
| * | |
| * Parsing doesn't allocate or set exceptions. Some mismatches require regex | |
| * backtracking, but even inputs known to be invalid use the Python parser: | |
| * it owns the exception messages and error precedence. For example, month 13 | |
| * and empty input can't be repaired by backtracking, but diagnosing them here | |
| * would duplicate the Python parser's error handling. A return value of 0 | |
| * therefore doesn't distinguish invalid input from an unsupported format. | |
| */ | |
| int | |
| _PyTime_Strptime(PyObject *string, PyObject *format, _PyTime_StrptimeFields *fields) | |
| { | |
| enum { | |
| YEAR = 1U << 0, | |
| SHORT_YEAR = 1U << 1, | |
| MONTH = 1U << 2, | |
| DAY = 1U << 3, | |
| HOUR = 1U << 4, | |
| MINUTE = 1U << 5, | |
| SECOND = 1U << 6, | |
| FRACTION = 1U << 7, | |
| OFFSET = 1U << 8, | |
| ANY_YEAR = YEAR | SHORT_YEAR, | |
| }; | |
| static const unsigned short directive_bits[128] = { | |
| ['Y'] = YEAR, | |
| ['y'] = SHORT_YEAR, | |
| ['m'] = MONTH, | |
| ['d'] = DAY, | |
| ['H'] = HOUR, | |
| ['M'] = MINUTE, | |
| ['S'] = SECOND, | |
| ['f'] = FRACTION, | |
| ['z'] = OFFSET, | |
| }; | |
| /* String subclasses and non-ASCII input retain Python's behavior. */ | |
| if (!PyUnicode_CheckExact(string) || !PyUnicode_CheckExact(format) || | |
| !PyUnicode_IS_ASCII(string) || !PyUnicode_IS_ASCII(format)) { | |
| return 0; | |
| } | |
| const unsigned char *data = PyUnicode_1BYTE_DATA(string); | |
| const unsigned char *fmt = PyUnicode_1BYTE_DATA(format); | |
| Py_ssize_t length = PyUnicode_GET_LENGTH(string); | |
| Py_ssize_t fmt_length = PyUnicode_GET_LENGTH(format); | |
| *fields = (_PyTime_StrptimeFields){.year = 1900, .month = 1, .day = 1, .gmtoff = INT_MIN}; | |
| unsigned int seen = 0; | |
| Py_ssize_t pos = 0; | |
| for (Py_ssize_t i = 0; i < fmt_length; i++) { | |
| unsigned char c = fmt[i]; | |
| /* Match regex \s, including ASCII separators U+001C through U+001F. */ | |
| if (Py_UNICODE_ISSPACE(c)) { | |
| if (pos == length || !Py_UNICODE_ISSPACE(data[pos])) { | |
| return 0; | |
| } | |
| while (i + 1 < fmt_length && Py_UNICODE_ISSPACE(fmt[i + 1])) { | |
| i++; | |
| } | |
| do { | |
| pos++; | |
| } while (pos < length && Py_UNICODE_ISSPACE(data[pos])); | |
| continue; | |
| } | |
| if (c != '%') { | |
| if (pos == length || data[pos++] != c) { | |
| return 0; | |
| } | |
| continue; | |
| } | |
| if (++i == fmt_length) { | |
| return 0; | |
| } | |
| c = fmt[i]; | |
| if (c == '%') { | |
| if (pos == length || data[pos++] != '%') { | |
| return 0; | |
| } | |
| continue; | |
| } | |
| /* Reject duplicate groups, aliases, and locale-dependent directives. */ | |
| unsigned int bit = directive_bits[c]; | |
| if (bit == 0) { | |
| return 0; | |
| } | |
| if (seen & bit) { | |
| return 0; | |
| } | |
| seen |= bit; | |
| if (c == 'z') { | |
| if (i != fmt_length - 1 || | |
| !strptime_offset(data, length, pos, fields)) { | |
| return 0; | |
| } | |
| pos = length; | |
| continue; | |
| } | |
| int minimum = 1; | |
| int maximum = 2; | |
| if (c == 'Y') { | |
| minimum = maximum = 4; | |
| } | |
| else if (c == 'y') { | |
| minimum = maximum = 2; | |
| } | |
| else if (c == 'f') { | |
| maximum = 6; | |
| } | |
| else if ((c == 'd' || c == 'H') && pos < length && data[pos] == ' ') { | |
| pos++; | |
| maximum = 1; | |
| } | |
| int value; | |
| int count = strptime_digits(data, length, pos, minimum, maximum, &value); | |
| if (count == 0) { | |
| return 0; | |
| } | |
| pos += count; | |
| switch (c) { | |
| case 'Y': | |
| fields->year = value; | |
| break; | |
| case 'y': | |
| fields->year = value + (value <= 68 ? 2000 : 1900); | |
| break; | |
| case 'm': | |
| if (value < 1 || value > 12) { | |
| return 0; | |
| } | |
| fields->month = value; | |
| break; | |
| case 'd': | |
| if (value < 1 || value > 31) { | |
| return 0; | |
| } | |
| fields->day = value; | |
| break; | |
| case 'H': | |
| if (value > 23) { | |
| return 0; | |
| } | |
| fields->hour = value; | |
| break; | |
| case 'M': | |
| if (value > 59) { | |
| return 0; | |
| } | |
| fields->minute = value; | |
| break; | |
| case 'S': | |
| if (value > 61) { | |
| return 0; | |
| } | |
| fields->second = value; | |
| break; | |
| case 'f': | |
| for (; count < 6; count++) { | |
| value *= 10; | |
| } | |
| fields->fraction = value; | |
| break; | |
| } | |
| } | |
| static const int days_before_month[] = { | |
| 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 | |
| }; | |
| int leap = fields->year % 4 == 0 && | |
| (fields->year % 100 != 0 || fields->year % 400 == 0); | |
| int month_days = days_before_month[fields->month + 1] - | |
| days_before_month[fields->month]; | |
| if (fields->month == 2) { | |
| month_days += leap; | |
| } | |
| /* Mixed %Y/%y and day-without-year diagnostics belong to Python. */ | |
| if (pos != length || (seen & ANY_YEAR) == ANY_YEAR || | |
| ((seen & DAY) && !(seen & ANY_YEAR)) || | |
| fields->year < 1 || | |
| fields->day > month_days) { | |
| return 0; | |
| } | |
| fields->yday = days_before_month[fields->month] + fields->day + | |
| (fields->month > 2 && leap); | |
| return 1; | |
| } |