From 1e0a3f2b439f7bd0b636b421befe52f1f851a629 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Wed, 29 Nov 2023 10:29:03 +1300 Subject: [PATCH 1/3] If the ms rounded is > 1s, carry that to the second field. --- ops/_private/timeconv.py | 7 +++++-- test/test_private.py | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ops/_private/timeconv.py b/ops/_private/timeconv.py index c254ffaed..c97c8051e 100644 --- a/ops/_private/timeconv.py +++ b/ops/_private/timeconv.py @@ -32,7 +32,8 @@ def parse_rfc3339(s: str) -> datetime.datetime: that Go's encoding/json package produces for time.Time values. Unfortunately we can't use datetime.fromisoformat(), as that does not - support more than 6 digits for the fractional second, nor the 'Z' for UTC. + support more than 6 digits for the fractional second, nor the 'Z' for UTC, + in Python 3.8 (Python 3.11+ has the required functionality). """ match = _TIMESTAMP_RE.match(s) if not match: @@ -50,6 +51,8 @@ def parse_rfc3339(s: str) -> datetime.datetime: tz = datetime.timezone(tz_delta if sign == '+' else -tz_delta) microsecond = round(float(sfrac or '0') * 1000000) + carry, microsecond = divmod(microsecond, 1000000) + ss = int(ss) + carry - return datetime.datetime(int(y), int(m), int(d), int(hh), int(mm), int(ss), + return datetime.datetime(int(y), int(m), int(d), int(hh), int(mm), ss, microsecond=microsecond, tzinfo=tz) diff --git a/test/test_private.py b/test/test_private.py index bf85db6a9..b2099fe07 100644 --- a/test/test_private.py +++ b/test/test_private.py @@ -72,6 +72,9 @@ def test_parse_rfc3339(self): self.assertEqual(timeconv.parse_rfc3339('2020-12-25T13:45:50.123456789+00:00'), datetime.datetime(2020, 12, 25, 13, 45, 50, 123457, tzinfo=utc)) + self.assertEqual(timeconv.parse_rfc3339('2006-08-28T13:20:00.99999999Z'), + datetime.datetime(2006, 8, 28, 13, 20, 1, 0, tzinfo=utc)) + tzinfo = datetime.timezone(datetime.timedelta(hours=-11, minutes=-30)) self.assertEqual(timeconv.parse_rfc3339('2020-12-25T13:45:50.123456789-11:30'), datetime.datetime(2020, 12, 25, 13, 45, 50, 123457, tzinfo=tzinfo)) From 240eb7c507ec9858c1b5f665d40ad035481eea0a Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Wed, 29 Nov 2023 10:43:03 +1300 Subject: [PATCH 2/3] Handle cascading rounding. --- ops/_private/timeconv.py | 8 +++++--- test/test_private.py | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ops/_private/timeconv.py b/ops/_private/timeconv.py index c97c8051e..0774e3942 100644 --- a/ops/_private/timeconv.py +++ b/ops/_private/timeconv.py @@ -52,7 +52,9 @@ def parse_rfc3339(s: str) -> datetime.datetime: microsecond = round(float(sfrac or '0') * 1000000) carry, microsecond = divmod(microsecond, 1000000) - ss = int(ss) + carry - return datetime.datetime(int(y), int(m), int(d), int(hh), int(mm), ss, - microsecond=microsecond, tzinfo=tz) + parsed = datetime.datetime(int(y), int(m), int(d), int(hh), int(mm), int(ss), + microsecond=microsecond, tzinfo=tz) + if carry: + return parsed + datetime.timedelta(seconds=carry) + return parsed diff --git a/test/test_private.py b/test/test_private.py index b2099fe07..f7c816a1a 100644 --- a/test/test_private.py +++ b/test/test_private.py @@ -75,6 +75,9 @@ def test_parse_rfc3339(self): self.assertEqual(timeconv.parse_rfc3339('2006-08-28T13:20:00.99999999Z'), datetime.datetime(2006, 8, 28, 13, 20, 1, 0, tzinfo=utc)) + self.assertEqual(timeconv.parse_rfc3339('2006-12-31T23:59:59.99999999Z'), + datetime.datetime(2007, 1, 1, 0, 0, 0, 0, tzinfo=utc)) + tzinfo = datetime.timezone(datetime.timedelta(hours=-11, minutes=-30)) self.assertEqual(timeconv.parse_rfc3339('2020-12-25T13:45:50.123456789-11:30'), datetime.datetime(2020, 12, 25, 13, 45, 50, 123457, tzinfo=tzinfo)) From 3bd9562a8a8c2491270d9a9ba2010e1f9e7843bc Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Wed, 29 Nov 2023 13:58:31 +1300 Subject: [PATCH 3/3] Truncate rather than round. --- ops/_private/timeconv.py | 11 +++++------ test/test_private.py | 8 ++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ops/_private/timeconv.py b/ops/_private/timeconv.py index 0774e3942..d7f0b24b9 100644 --- a/ops/_private/timeconv.py +++ b/ops/_private/timeconv.py @@ -51,10 +51,9 @@ def parse_rfc3339(s: str) -> datetime.datetime: tz = datetime.timezone(tz_delta if sign == '+' else -tz_delta) microsecond = round(float(sfrac or '0') * 1000000) - carry, microsecond = divmod(microsecond, 1000000) + # Ignore any overflow into the seconds - this aligns with the Python + # standard library behaviour. + microsecond = min(microsecond, 999999) - parsed = datetime.datetime(int(y), int(m), int(d), int(hh), int(mm), int(ss), - microsecond=microsecond, tzinfo=tz) - if carry: - return parsed + datetime.timedelta(seconds=carry) - return parsed + return datetime.datetime(int(y), int(m), int(d), int(hh), int(mm), int(ss), + microsecond=microsecond, tzinfo=tz) diff --git a/test/test_private.py b/test/test_private.py index f7c816a1a..40f6ac437 100644 --- a/test/test_private.py +++ b/test/test_private.py @@ -72,11 +72,11 @@ def test_parse_rfc3339(self): self.assertEqual(timeconv.parse_rfc3339('2020-12-25T13:45:50.123456789+00:00'), datetime.datetime(2020, 12, 25, 13, 45, 50, 123457, tzinfo=utc)) - self.assertEqual(timeconv.parse_rfc3339('2006-08-28T13:20:00.99999999Z'), - datetime.datetime(2006, 8, 28, 13, 20, 1, 0, tzinfo=utc)) + self.assertEqual(timeconv.parse_rfc3339('2006-08-28T13:20:00.9999999Z'), + datetime.datetime(2006, 8, 28, 13, 20, 0, 999999, tzinfo=utc)) - self.assertEqual(timeconv.parse_rfc3339('2006-12-31T23:59:59.99999999Z'), - datetime.datetime(2007, 1, 1, 0, 0, 0, 0, tzinfo=utc)) + self.assertEqual(timeconv.parse_rfc3339('2006-12-31T23:59:59.9999999Z'), + datetime.datetime(2006, 12, 31, 23, 59, 59, 999999, tzinfo=utc)) tzinfo = datetime.timezone(datetime.timedelta(hours=-11, minutes=-30)) self.assertEqual(timeconv.parse_rfc3339('2020-12-25T13:45:50.123456789-11:30'),