From dbba34eeb13381f16aadb7f1191cc201067534cb Mon Sep 17 00:00:00 2001 From: Pavlo Kostianov Date: Mon, 16 Feb 2026 15:23:52 +0000 Subject: [PATCH] Document parse_isotime() UTC behavior for naive timestamps The parse_isotime() function treats datetime strings without timezone designators as UTC rather than returning naive datetime objects as specified by ISO 8601. This behavior exists for historical reasons and backward compatibility. Update the docstring to clearly document this behavior and point users to alternatives (datetime.fromisoformat() or iso8601 library directly) for ISO 8601 compliant parsing of naive datetime strings. Add test to verify the documented behavior matches actual behavior. Closes-Bug: #1638124 Assisted-By: Claude Sonnet 4.5 Change-Id: Ib80e86d08cbe62217120d2aa191b0c65e4ef434f Signed-off-by: Pavlo Kostianov --- oslo_utils/tests/test_timeutils.py | 20 ++++++++++++ oslo_utils/timeutils.py | 31 ++++++++++++++++++- ...utc-behavior-1638124-8f3a1b5e9c4d2f1a.yaml | 11 +++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/document-parse-isotime-utc-behavior-1638124-8f3a1b5e9c4d2f1a.yaml diff --git a/oslo_utils/tests/test_timeutils.py b/oslo_utils/tests/test_timeutils.py index e886c20c..09cd031f 100644 --- a/oslo_utils/tests/test_timeutils.py +++ b/oslo_utils/tests/test_timeutils.py @@ -62,6 +62,26 @@ class TimeUtilsTest(test_base.BaseTestCase): ) self.assertEqual(skynet_self_aware_time_ms_utc, expect) + def test_parse_isotime_naive_treated_as_utc(self): + """Verify naive timestamps are treated as UTC (documented behavior).""" + # Naive string without timezone should be treated as UTC + result = timeutils.parse_isotime('2012-02-14T20:53:07') + self.assertIsNotNone(result.tzinfo) + assert result.tzinfo is not None # for type checker + self.assertEqual(result.tzinfo.tzname(None), 'UTC') + + # Explicit Z should also be UTC + result_z = timeutils.parse_isotime('2012-02-14T20:53:07Z') + assert result_z.tzinfo is not None # for type checker + self.assertEqual(result_z.tzinfo.tzname(None), 'UTC') + + # Explicit offset should be respected + result_offset = timeutils.parse_isotime('2012-02-14T20:53:07+05:30') + self.assertIsNotNone(result_offset.tzinfo) + assert result_offset.tzinfo is not None # for type checker + offset = result_offset.tzinfo.utcoffset(None) + self.assertEqual(offset, datetime.timedelta(hours=5, minutes=30)) + def test_parse_strtime(self): perfect_time_format = self.skynet_self_aware_time_perfect_str expect = timeutils.parse_strtime(perfect_time_format) diff --git a/oslo_utils/timeutils.py b/oslo_utils/timeutils.py index c677fde9..6d5f7af5 100644 --- a/oslo_utils/timeutils.py +++ b/oslo_utils/timeutils.py @@ -41,7 +41,36 @@ now = time.monotonic def parse_isotime(timestr: str) -> datetime.datetime: - """Parse time from ISO 8601 format.""" + """Parse time from ISO 8601 format. + + :param timestr: ISO 8601 formatted datetime string + :returns: A timezone-aware datetime.datetime instance + :raises ValueError: When the string cannot be parsed as ISO 8601 + + .. note:: + For historical reasons, datetime strings without explicit timezone + designators (Z, +HH:MM, -HH:MM) are treated as UTC timestamps rather + than naive/local time as specified by ISO 8601. This behavior is + preserved for backward compatibility. + + For ISO 8601 compliant parsing that returns naive datetime objects + when no timezone is specified, consider using + ``datetime.datetime.fromisoformat()`` (Python 3.7+) or the ``iso8601`` + library directly with ``default_timezone=None``. + + Examples: + + >>> # Strings without timezone designators are treated as UTC + >>> parse_isotime('2012-02-14T20:53:07') + datetime.datetime(2012, 2, 14, 20, 53, 7, tzinfo=) + + >>> # Explicit timezone designators are respected + >>> parse_isotime('2012-02-14T20:53:07Z') + datetime.datetime(2012, 2, 14, 20, 53, 7, tzinfo=) + + >>> parse_isotime('2012-02-14T20:53:07+05:30') + datetime.datetime(2012, 2, 14, 20, 53, 7, tzinfo=<+05:30>) + """ try: return iso8601.parse_date(timestr) except iso8601.ParseError as e: diff --git a/releasenotes/notes/document-parse-isotime-utc-behavior-1638124-8f3a1b5e9c4d2f1a.yaml b/releasenotes/notes/document-parse-isotime-utc-behavior-1638124-8f3a1b5e9c4d2f1a.yaml new file mode 100644 index 00000000..53900fd0 --- /dev/null +++ b/releasenotes/notes/document-parse-isotime-utc-behavior-1638124-8f3a1b5e9c4d2f1a.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + `Bug #1638124 `_: + The ``parse_isotime()`` function's docstring now clearly documents that + datetime strings without explicit timezone designators (Z, +HH:MM, -HH:MM) + are treated as UTC timestamps rather than naive/local time as specified + by ISO 8601. This behavior exists for historical reasons and backward + compatibility. The documentation now points users to alternatives + (``datetime.fromisoformat()`` or the ``iso8601`` library directly with + ``default_timezone=None``) for ISO 8601 compliant parsing.