How to serialize time objects while preserving their timezone?

Viewed 167

I'm trying to serialize time-objects to ISO8601 format while preserving their timezone information with Python 3.8.

The documentation for time.isoformat states:

time.isoformat(timespec='auto')

Return a string representing the time in ISO 8601 format, one of:

  • HH:MM:SS.ffffff, if microsecond is not 0
  • HH:MM:SS, if microsecond is 0
  • HH:MM:SS.ffffff+HH:MM[:SS[.ffffff]], if utcoffset() does not return None
  • HH:MM:SS+HH:MM[:SS[.ffffff]], if microsecond is 0 and utcoffset() does not return None

As far as I understand from that documentation I just have to generate a time-object whose utcoffset() isn't None to get the timezone information as part of the ISO8601-formatted string. That works fine when using datetime.timezone.utc as timezone:

>>> from datetime import time, timezone
>>> t = time(1, 2, 3, tzinfo=timezone.utc)
>>> t
datetime.time(1, 2, 3, tzinfo=datetime.timezone.utc)
>>> t.utcoffset()
datetime.timedelta(0)
>>> t.isoformat()
'01:02:03+00:00'

However as soon as I want to use a timezone which isn't provided by the Python standard library, that doesn't seem to work anymore. I tried dateutil and pytz and in both cases got the same result:

>>> from datetime import time
>>> from dateutil.tz import gettz
>>> t = time(1, 2, 3, tzinfo=gettz("US/Eastern"))
>>> t
datetime.time(1, 2, 3, tzinfo=tzfile('/usr/share/zoneinfo/US/Eastern'))
>>> t.utcoffset()
>>> t.isoformat()
'01:02:03'

>>> from datetime import time
>>> from pytz import timezone
>>> t = time(1, 2, 3, tzinfo=timezone("US/Eastern"))
>>> t
datetime.time(1, 2, 3, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)
>>> t.utcoffset()
>>> t.isoformat()
'01:02:03'

I also tried using time.strftime() instead, however that doesn't solve my problem either:

>>> from datetime import time
>>> from dateutil.tz import gettz
>>> t = time(1, 2, 3, tzinfo=gettz("US/Eastern"))
>>> t.strftime("%H:%M:%S%z")
'01:02:03'

Why is that the case and how can I solve this issue?

1 Answers

Without a date, time doesn't give an unambiguous UTC offset. For example in your case, time zone "US/Eastern" has daylight saving time partly over the year, with UTC offsets EST (UTC-5) and EDT (UTC-4). Python is kind of quiet there, not telling you about it e.g. in a warning.

If you add a date, strftime works:

import datetime as dt
from dateutil.tz import gettz

t_est = dt.datetime.combine(dt.date(2020,1,1), dt.time(1, 2, 3, tzinfo=gettz("US/Eastern")))
t_est.strftime("%H:%M:%S%z")
# '01:02:03-0500'
t_edt = dt.datetime.combine(dt.date(2020,6,6), dt.time(1, 2, 3, tzinfo=gettz("US/Eastern")))
t_edt.strftime("%H:%M:%S%z")
# '01:02:03-0400'

On the other hand, if you provide tzinfo as a pure UTC offset, your code would work. Quoting from the docs:

t = dt.time.fromisoformat('04:23:01+04:00')
# datetime.time(4, 23, 1, tzinfo=datetime.timezone(datetime.timedelta(seconds=14400)))
t.isoformat()
# '04:23:01+04:00'

If you know for example that all your time objects refer to EST, you could use the respective UTC offset:

t = dt.time(1, 2, 3, tzinfo=dt.timezone(dt.timedelta(seconds=3600*-5)))
t.isoformat()
# '01:02:03-05:00'
Related