diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index b7d569cc41740e0..c8991ed0bc93524 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -377,6 +377,7 @@ def _parse_hh_mm_ss_ff(tstr): time_comps = [0, 0, 0, 0] pos = 0 + comp = None for comp in range(0, 3): if (len_str - pos) < 2: raise ValueError("Incomplete time component") @@ -392,6 +393,9 @@ def _parse_hh_mm_ss_ff(tstr): if not next_char or comp >= 2: break + if next_char == '.' or next_char == ',': + break + if has_sep and next_char != ':': raise ValueError("Invalid time separator: %c" % next_char) @@ -410,9 +414,14 @@ def _parse_hh_mm_ss_ff(tstr): else: to_parse = len_remainder - time_comps[3] = int(tstr[pos:(pos+to_parse)]) - if to_parse < 6: - time_comps[3] *= _FRACTION_CORRECTION[to_parse-1] + extra_time = float("." + tstr[pos:(pos+to_parse)]) + while comp < 2: + element = "hour" if comp == 0 else "minute" + raise ValueError(f"Fractional {element} not supported") + if comp == 2: + time_comps[comp + 1] = int(extra_time * 1e6) + + # parse the rest of characters if (len_remainder > to_parse and not all(map(_is_ascii_digit, tstr[(pos+to_parse):]))): raise ValueError("Non-digit values in unparsed fraction") diff --git a/Misc/NEWS.d/next/Library/2024-02-11-20-56-52.gh-issue-115225.8uXOZy.rst b/Misc/NEWS.d/next/Library/2024-02-11-20-56-52.gh-issue-115225.8uXOZy.rst new file mode 100644 index 000000000000000..f65c6bf9e9ee08e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-11-20-56-52.gh-issue-115225.8uXOZy.rst @@ -0,0 +1,2 @@ +ISO8601 Format recommends "a decimal fraction may be added to the lowest order time element present." +Thus "T0314.15" should be valid and yield "03:14:09", but currently yields "03:14:00.150000". Due to deliberate choice, such an expression would now throw a ValueError because such a string is more likely a typo than an actual fractional value. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index b984ea61b82f0fb..1ed286fa6ce1a98 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -831,9 +831,11 @@ parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, int *hour, int *vals[3] = {hour, minute, second}; // This is initialized to satisfy an erroneous compiler warning. unsigned char has_separator = 1; + unsigned char has_decimal = 0; // Parse [HH[:?MM[:?SS]]] - for (size_t i = 0; i < 3; ++i) { + size_t i = 0; + for (; i < 3; ++i) { p = parse_digits(p, vals[i], 2); if (NULL == p) { return -3; @@ -844,21 +846,26 @@ parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, int *hour, has_separator = (c == ':'); } - if (p >= p_end) { + if (p >= p_end) { // reached timezone info return c != '\0'; } else if (has_separator && (c == ':')) { continue; } else if (c == '.' || c == ',') { + has_decimal = 1; break; - } else if (!has_separator) { + } + else if (!has_separator) { --p; } else { return -4; // Malformed time separator } } + // expecting to get exhausted unless + if (!has_decimal) + return -3; // Parse fractional components size_t len_remains = p_end - p; size_t to_parse = len_remains; @@ -866,20 +873,25 @@ parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, int *hour, to_parse = 6; } - p = parse_digits(p, microsecond, to_parse); + int extra_time_digits = 0; + p = parse_digits(p, &extra_time_digits, to_parse); if (NULL == p) { return -3; } - static int correction[] = { - 100000, 10000, 1000, 100, 10 + int dividing_factor[] = { + 10, 100, 1000, 10000, 100000, 1000000 }; - if (to_parse < 6) { - *microsecond *= correction[to_parse-1]; + float extra_time = extra_time_digits / (dividing_factor[to_parse - 1] * 1.); + while (i < 2) { + return -3; + } + if (i == 2) { + *microsecond = (extra_time * dividing_factor[5]); } - while (is_digit(*p)){ + while (is_digit(*p) && p < p_end) { ++p; // skip truncated digits }