Skip to content

Commit 02bcc3a

Browse files
committed
gh-152079: Fix C datetime.fromisoformat() dropping sub-second UTC offset
The C accelerator's tzinfo_from_isoformat_results() collapsed any offset with a zero whole-second part to timezone.utc, discarding the parsed sub-second microseconds. So '+00:00:00.000001' round-tripped through isoformat()/fromisoformat() lost its 1-microsecond offset, while pure Python preserved it. Only short-circuit to UTC when both the whole-second and sub-second parts are zero.
1 parent ee78d43 commit 02bcc3a

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

Lib/test/datetimetester.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3802,6 +3802,32 @@ def test_fromisoformat_utc(self):
38023802

38033803
self.assertIs(dt.tzinfo, timezone.utc)
38043804

3805+
def test_fromisoformat_utc_subsecond_offset(self):
3806+
# A UTC offset whose whole-second part is zero but with a non-zero
3807+
# microsecond part must be preserved, not collapsed to UTC.
3808+
for us in (1, -1, 999999, -999999):
3809+
with self.subTest(microseconds=us):
3810+
tz = timezone(timedelta(microseconds=us))
3811+
dt = self.theclass(2020, 6, 15, 12, 34, 56, tzinfo=tz)
3812+
rt = self.theclass.fromisoformat(dt.isoformat())
3813+
self.assertEqual(rt.utcoffset(), timedelta(microseconds=us))
3814+
self.assertEqual(rt, dt)
3815+
self.assertIsNot(rt.tzinfo, timezone.utc)
3816+
3817+
tz = timezone(timedelta(hours=5, minutes=30, seconds=15,
3818+
microseconds=123456))
3819+
dt = self.theclass(2020, 6, 15, 12, 34, 56, tzinfo=tz)
3820+
rt = self.theclass.fromisoformat(dt.isoformat())
3821+
self.assertEqual(rt.utcoffset(), tz.utcoffset(None))
3822+
self.assertEqual(rt, dt)
3823+
3824+
for tstr in ('2020-06-15T12:34:56+00:00',
3825+
'2020-06-15T12:34:56+00:00:00.000000',
3826+
'2020-06-15T12:34:56Z'):
3827+
with self.subTest(tstr=tstr):
3828+
self.assertIs(self.theclass.fromisoformat(tstr).tzinfo,
3829+
timezone.utc)
3830+
38053831
def test_fromisoformat_subclass(self):
38063832
class DateTimeSubclass(self.theclass):
38073833
pass
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :meth:`datetime.datetime.fromisoformat` in the C implementation dropping
2+
the sub-second part of a UTC offset whose whole-second part is zero, matching
3+
the pure-Python implementation.

Modules/_datetimemodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,7 @@ tzinfo_from_isoformat_results(int rv, int tzoffset, int tz_useconds)
16691669
PyObject *tzinfo;
16701670
if (rv == 1) {
16711671
// Create a timezone from offset in seconds (0 returns UTC)
1672-
if (tzoffset == 0) {
1672+
if (tzoffset == 0 && tz_useconds == 0) {
16731673
return Py_NewRef(CONST_UTC(NO_STATE));
16741674
}
16751675

0 commit comments

Comments
 (0)