From 465fd393b13ed3462aa041cb74dc9c5154a0cfec Mon Sep 17 00:00:00 2001 From: ba-13 Date: Mon, 12 Feb 2024 01:58:10 +0530 Subject: [PATCH 1/6] fix fraction handling in fromisoformat in _datetimemodule and _pydatetime --- Lib/_pydatetime.py | 21 +++++++++++++++++---- Modules/_datetimemodule.c | 35 ++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 54c12d3b2f3f16a..d54254a2046c86c 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -372,11 +372,15 @@ def _parse_isoformat_date(dtstr): def _parse_hh_mm_ss_ff(tstr): - # Parses things of the form HH[:?MM[:?SS[{.,}fff[fff]]]] + # Parses things of the forms + # HH[{.,}fff[fff]] + # HH[:?MM[{.,}fff[fff]]] + # HH[:?MM[:?SS[{.,}fff[fff]]]] len_str = len(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 +396,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 +417,15 @@ 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: + time_comps[comp + 1] = int(extra_time * 60) + extra_time = extra_time * 60 - time_comps[comp + 1] + comp += 1 + 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/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 9b8e0a719d9048c..05fdd1292446831 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,30 @@ 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 + static int dividing_factor[] = { + 10, 100, 1000, 10000, 100000, 1000000 + }; + int *pointers[] = { + minute, second, microsecond }; - if (to_parse < 6) { - *microsecond *= correction[to_parse-1]; + float extra_time = extra_time_digits / (dividing_factor[to_parse - 1] * 1.); + while (i < 2) { + *pointers[i] = (extra_time * 60); + extra_time = (extra_time * 60) - *pointers[i]; + i++; + } + if (i == 2) { + *pointers[i] = (extra_time * dividing_factor[5]); } - while (is_digit(*p)){ + while (is_digit(*p) && p < p_end) { ++p; // skip truncated digits } From d7b2fc4043776291d3a34fc86c04cd3df0bb7689 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 11 Feb 2024 20:56:53 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2024-02-11-20-56-52.gh-issue-115225.8uXOZy.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-02-11-20-56-52.gh-issue-115225.8uXOZy.rst 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..37c088b4aa26fdb --- /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". This has been corrected, with the addition of handling cases of "T03.50" as "03:30", "T0320.25" as "03:20:15" and "T032010.15" as "03:20:10.15". From 72ee0fe5b90a786a44aac066742d864c7792b83c Mon Sep 17 00:00:00 2001 From: ba-13 Date: Mon, 12 Feb 2024 02:30:17 +0530 Subject: [PATCH 3/6] Fix lint extra space --- Modules/_datetimemodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 574b6ef5205d7ef..1993669ffc59fc9 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -855,7 +855,7 @@ parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, int *hour, else if (c == '.' || c == ',') { has_decimal = 1; break; - } + } else if (!has_separator) { --p; } else { From 3c3fcbf1ead445e6f8bcdc0d5228b4275d8af53c Mon Sep 17 00:00:00 2001 From: ba-13 Date: Mon, 12 Feb 2024 02:52:47 +0530 Subject: [PATCH 4/6] fractional element now raises valueerror --- Lib/_pydatetime.py | 10 +++------- .../2024-02-11-20-56-52.gh-issue-115225.8uXOZy.rst | 4 ++-- Modules/_datetimemodule.c | 9 ++------- datetime_test.py | 4 ++++ 4 files changed, 11 insertions(+), 16 deletions(-) create mode 100644 datetime_test.py diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index dcba2b72b149598..c8991ed0bc93524 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -372,10 +372,7 @@ def _parse_isoformat_date(dtstr): def _parse_hh_mm_ss_ff(tstr): - # Parses things of the forms - # HH[{.,}fff[fff]] - # HH[:?MM[{.,}fff[fff]]] - # HH[:?MM[:?SS[{.,}fff[fff]]]] + # Parses things of the form HH[:?MM[:?SS[{.,}fff[fff]]]] len_str = len(tstr) time_comps = [0, 0, 0, 0] @@ -419,9 +416,8 @@ def _parse_hh_mm_ss_ff(tstr): extra_time = float("." + tstr[pos:(pos+to_parse)]) while comp < 2: - time_comps[comp + 1] = int(extra_time * 60) - extra_time = extra_time * 60 - time_comps[comp + 1] - comp += 1 + 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) 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 index 37c088b4aa26fdb..191be0f3f83155c 100644 --- 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 @@ -1,2 +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". This has been corrected, with the addition of handling cases of "T03.50" as "03:30", "T0320.25" as "03:20:15" and "T032010.15" as "03:20:10.15". +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 1993669ffc59fc9..16b8bbe68830d2e 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -882,18 +882,13 @@ parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, int *hour, static int dividing_factor[] = { 10, 100, 1000, 10000, 100000, 1000000 }; - int *pointers[] = { - minute, second, microsecond - }; float extra_time = extra_time_digits / (dividing_factor[to_parse - 1] * 1.); while (i < 2) { - *pointers[i] = (extra_time * 60); - extra_time = (extra_time * 60) - *pointers[i]; - i++; + return -3; } if (i == 2) { - *pointers[i] = (extra_time * dividing_factor[5]); + *microsecond = (extra_time * dividing_factor[5]); } while (is_digit(*p) && p < p_end) { diff --git a/datetime_test.py b/datetime_test.py new file mode 100644 index 000000000000000..0a8e598ae6ac5dd --- /dev/null +++ b/datetime_test.py @@ -0,0 +1,4 @@ +import datetime as dt + +time = dt.time.fromisoformat("T1205.50") +print(time) From fc1f8ce118979495f03f48de8ea909a046b8d17c Mon Sep 17 00:00:00 2001 From: ba-13 Date: Mon, 12 Feb 2024 02:59:09 +0530 Subject: [PATCH 5/6] Remove extra file --- datetime_test.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 datetime_test.py diff --git a/datetime_test.py b/datetime_test.py deleted file mode 100644 index 0a8e598ae6ac5dd..000000000000000 --- a/datetime_test.py +++ /dev/null @@ -1,4 +0,0 @@ -import datetime as dt - -time = dt.time.fromisoformat("T1205.50") -print(time) From 4e3934bd8b57428566939ad95243a3db93c31dfa Mon Sep 17 00:00:00 2001 From: ba-13 Date: Mon, 12 Feb 2024 12:22:24 +0530 Subject: [PATCH 6/6] used pre-commit --- .../next/Library/2024-02-11-20-56-52.gh-issue-115225.8uXOZy.rst | 2 +- Modules/_datetimemodule.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 191be0f3f83155c..f65c6bf9e9ee08e 100644 --- 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 @@ -1,2 +1,2 @@ -ISO8601 Format recommends "a decimal fraction may be added to the lowest order time element present." +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 16b8bbe68830d2e..1ed286fa6ce1a98 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -879,7 +879,7 @@ parse_hh_mm_ss_ff(const char *tstr, const char *tstr_end, int *hour, return -3; } - static int dividing_factor[] = { + int dividing_factor[] = { 10, 100, 1000, 10000, 100000, 1000000 };