5.10. Datetime Timezone

  • Always keep dates and times only in UTC (important!)

  • Datetimes should be converted to local time only when displaying to user

  • Computerphile Time & Time Zones [3]

  • Abolition of Time Zones [1]

  • Since Python 3.9: PEP 615 -- Support for the IANA Time Zone Database in the Standard Library

  • pip install tzdata

5.10.1. SetUp

>>> from datetime import date, time, datetime, timedelta, timezone
>>> from zoneinfo import ZoneInfo

5.10.2. Daylight Saving Time

  • Daylight Saving Time date is different for each country and even US state

  • Australia is 9h 30m shifted

  • India is 3h 30m shifted

  • Nepal is 3h 45m shifted

  • In southern hemisphere the Daylight Saving Time is opposite direction

  • They subtract hour in March and add in October

  • Samoa is on the international date line

  • Samoa changed from UTC-1200 to UTC+1200 for easier trades with Australia

  • During World War II England was GMT+0200

  • Libya in 2013 discontinued DST with couple of days notice

  • Israel is on a different timezone than Palestine (multiple timezones in one location, based on nationality)

  • Change from Julian to Gregorian calendar caused to skip few weeks

  • In 18th century World change from Julian to Gregorian calendar

  • In 20th century Russia change from Julian to Gregorian calendar (different days which was skipped than for worldwide change)

  • In britain until 16th century the year started on 25th of March

  • Mind leap seconds (add, subtract)

  • UTC includes leap seconds

  • Astronomical time does not include leap seconds

  • Google invented smear second (on the day of leap second) they add a small fraction of a second to each second that day until midnight

  • Not all cities has DST https://www.timeanddate.com/time/us/arizona-no-dst.html

Comparing datetime works only when all has the same timezone (UTC):

../../_images/datetime-compare.png

5.10.3. Timezone Naive Datetimes

>>> datetime(1957, 10, 4, 19, 28, 34)
datetime.datetime(1957, 10, 4, 19, 28, 34)
>>> datetime.now()  
datetime.datetime(1957, 10, 4, 19, 28, 34)

5.10.4. Timezone Aware Datetimes

>>> datetime.now(timezone.utc)  
datetime.datetime(1957, 10, 4, 19, 28, 34, tzinfo=datetime.timezone.utc)
>>> datetime(1957, 10, 4, 19, 28, 34, tzinfo=timezone.utc)
datetime.datetime(1957, 10, 4, 19, 28, 34, tzinfo=datetime.timezone.utc)
>>> dt = datetime(1957, 10, 4, 19, 28, 34)
>>> dt.replace(tzinfo=timezone.utc)
datetime.datetime(1957, 10, 4, 19, 28, 34, tzinfo=datetime.timezone.utc)

5.10.5. UTCNow

  • datetime.utcnow() produces timezone naive datetimes!

>>> datetime.utcnow()  
datetime.datetime(1957, 10, 4, 17, 28, 34)
>>> datetime.utcnow(tz=timezone.utc)
Traceback (most recent call last):
TypeError: datetime.utcnow() takes no keyword arguments
>>> datetime.utcnow(timezone.utc)
Traceback (most recent call last):
TypeError: datetime.utcnow() takes no arguments (1 given)

5.10.6. IANA Time Zone Database

IANA 2017a timezone database [2]:

../../_images/datetime-timezone-iana2017a.png

5.10.7. ZoneInfo

>>> utc = ZoneInfo('UTC')
>>> est = ZoneInfo('US/Eastern')
>>> cet = ZoneInfo('Europe/Warsaw')
>>> alm = ZoneInfo('Asia/Almaty')

Working with ZoneInfo objects:

>>> dt = datetime(1969, 7, 21, 2, 56, 15, tzinfo=ZoneInfo('UTC'))
>>> print(dt)
1969-07-21 02:56:15+00:00
>>> dt += timedelta(days=7)
>>> print(dt)
1969-07-28 02:56:15+00:00

ZoneInfo objects knows Daylight Saving Time:

>>> dt = datetime(2000, 1, 1, tzinfo=ZoneInfo('America/Los_Angeles'))  # Daylight saving time
>>>
>>> dt.tzname()
'PST'
>>>
>>> dt += timedelta(days=100)  # Standard time
>>> dt.tzname()
'PDT'

5.10.8. Use Case - 0x01

>>> from datetime import datetime
>>> from zoneinfo import ZoneInfo
>>>
>>>
>>> UTC = ZoneInfo('UTC')
>>> BAJKONUR = ZoneInfo('Asia/Almaty')
>>> WAW = ZoneInfo('Europe/Warsaw')
>>> LOS_ANGELES = ZoneInfo('America/Los_Angeles')
>>>
>>>
>>> dt = datetime(1961, 4, 12, 6, 7, tzinfo=UTC)
>>> dt
datetime.datetime(1961, 4, 12, 6, 7, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
>>>
>>> dt.astimezone(BAJKONUR)
datetime.datetime(1961, 4, 12, 12, 7, tzinfo=zoneinfo.ZoneInfo(key='Asia/Almaty'))
>>>
>>> dt.astimezone(WAW)
datetime.datetime(1961, 4, 12, 7, 7, tzinfo=zoneinfo.ZoneInfo(key='Europe/Warsaw'))
>>>
>>> dt.astimezone(LOS_ANGELES)
datetime.datetime(1961, 4, 11, 22, 7, tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles'))

5.10.9. Use Case - 0x02

Descriptor Timezone Converter:

>>> from dataclasses import dataclass
>>> from datetime import datetime
>>> from zoneinfo import ZoneInfo
>>>
>>>
>>> class Timezone:
...     def __init__(self, name):
...         self.timezone = ZoneInfo(name)
...
...     def __get__(self, parent, *args):
...         utc = parent.utc.replace(tzinfo=ZoneInfo('UTC'))
...         return utc.astimezone(self.timezone)
...
...     def __set__(self, parent, new_datetime):
...         local_time = new_datetime.replace(tzinfo=self.timezone)
...         parent.utc = local_time.astimezone(ZoneInfo('UTC'))
>>>
>>>
>>> @dataclass
... class Time:
...     utc = datetime.now(tz=ZoneInfo('UTC'))
...     warsaw = Timezone('Europe/Warsaw')
...     eastern = Timezone('America/New_York')
...     pacific = Timezone('America/Los_Angeles')
>>>
>>>
>>> t = Time()
>>>
>>> # Gagarin's launch to space
>>> t.utc = datetime(1961, 4, 12, 6, 7)
>>>
>>> print(t.utc)
1961-04-12 06:07:00
>>> print(t.warsaw)
1961-04-12 07:07:00+01:00
>>> print(t.eastern)
1961-04-12 01:07:00-05:00
>>> print(t.pacific)
1961-04-11 22:07:00-08:00
>>>
>>>
>>> # Armstrong's first Lunar step
>>> t.warsaw = datetime(1969, 7, 21, 3, 56, 15)
>>>
>>> print(t.utc)
1969-07-21 02:56:15+00:00
>>> print(t.warsaw)
1969-07-21 03:56:15+01:00
>>> print(t.eastern)
1969-07-20 22:56:15-04:00
>>> print(t.pacific)
1969-07-20 19:56:15-07:00

5.10.10. References

5.10.11. Assignments

Code 5.28. Solution
"""
* Assignment: Datetime Timezone ZoneInfo
* Complexity: easy
* Lines of code: 8 lines
* Time: 8 min

English:
    0. If you're on Windows then install module `tzdata` (`pip install tzdata`)
    1. Create `zoneinfo.ZoneInfo` object of:
        a. UTC
        b. London, United Kingdom
        c. Buenos Aires, Argentina
        d. Warsaw, Poland
        e. Tokyo, Japan
        f. Sydney, Australia
        g. Auckland, New Zealand
        h. New York, USA
    2. Use `List of tz database time zones` [1]
    3. Run doctests - all must succeed

Polish:
    0. Jeżeli masz Windows to zainstaluj moduł `tzdata` (`pip install tzdata`)
    1. Stwórz obiekt `zoneinfo.ZoneInfo` z:
        a. UTC
        b. London, Wielka Brytania
        c. Buenos Aires, Argentyna
        d. Warsaw, Polska
        e. Tokyo, Japan
        f. Sydney, Australia
        g. Auckland, Nowa Zelandia
        h. New York, USA
    2. Użyj `List of tz database time zones` [1]
    3. Uruchom doctesty - wszystkie muszą się powieść

References:
    [1] Wikipedia. List of tz database time zones.
        Retrieved: 2022-12-01
        Year: 2022
        URL: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
    [2] IANA. Time Zone Database.
        Retrieved: 2022-12-01
        Year: 2022
        URL: https://data.iana.org/time-zones/releases/

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from datetime import datetime

    >>> assert utc is not Ellipsis, \
    'Assign value to variable `utc` has instead of Ellipsis (...)'
    >>> assert london is not Ellipsis, \
    'Assign value to variable `london` instead of Ellipsis (...)'
    >>> assert buenos_aires is not Ellipsis, \
    'Assign value to variable `buenos_aires` instead of Ellipsis (...)'
    >>> assert warsaw is not Ellipsis, \
    'Assign value to variable `warsaw` instead of Ellipsis (...)'
    >>> assert tokyo is not Ellipsis, \
    'Assign value to variable `tokyo` instead of Ellipsis (...)'
    >>> assert sydney is not Ellipsis, \
    'Assign value to variable `sydney` instead of Ellipsis (...)'
    >>> assert auckland is not Ellipsis, \
    'Assign value to variable `auckland` instead of Ellipsis (...)'
    >>> assert new_york is not Ellipsis, \
    'Assign value to variable `new_york` instead of Ellipsis (...)'

    >>> assert isinstance(utc, ZoneInfo), \
    'Variable `utc` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(london, ZoneInfo), \
    'Variable `london` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(buenos_aires, ZoneInfo), \
    'Variable `buenos_aires` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(warsaw, ZoneInfo), \
    'Variable `warsaw` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(tokyo, ZoneInfo), \
    'Variable `tokyo` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(sydney, ZoneInfo), \
    'Variable `sydney` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(auckland, ZoneInfo), \
    'Variable `auckland` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(new_york, ZoneInfo), \
    'Variable `new_york` has invalid type, must be a ZoneInfo'

    >>> def utcoffset(tz: ZoneInfo):
    ...     dt = datetime(2023, 1, 1)
    ...     return tz.utcoffset(dt).total_seconds()
    >>>
    >>> assert utcoffset(utc) == 0, \
    'Invalid time zone, check IANA Time Zone Database'
    >>> assert utcoffset(london) == 0, \
    'Invalid time zone, check IANA Time Zone Database'
    >>> assert utcoffset(buenos_aires) == -10800.0, \
    'Invalid time zone, check IANA Time Zone Database'
    >>> assert utcoffset(warsaw) == 3600, \
    'Invalid time zone, check IANA Time Zone Database'
    >>> assert utcoffset(tokyo) == 32400, \
    'Invalid time zone, check IANA Time Zone Database'
    >>> assert utcoffset(sydney) == 39600, \
    'Invalid time zone, check IANA Time Zone Database'
    >>> assert utcoffset(auckland) == 46800, \
    'Invalid time zone, check IANA Time Zone Database'
    >>> assert utcoffset(new_york) == -18000.0, \
    'Invalid time zone, check IANA Time Zone Database'
"""

from zoneinfo import ZoneInfo


# Timezone in UTC
# type: ZoneInfo
utc = ...

# Timezone in London, United Kingdom
# type: ZoneInfo
london = ...

# Timezone in Buenos Aires, Argentina
# type: ZoneInfo
buenos_aires = ...

# Timezone in Warsaw, Poland
# type: ZoneInfo
warsaw = ...

# Timezone in Tokyo, Japan
# type: ZoneInfo
tokyo = ...

# Timezone in Sydney, Australia
# type: ZoneInfo
sydney = ...

# Timezone in Auckland, New Zealand
# type: ZoneInfo
auckland = ...

# Timezone in New York, USA
# type: ZoneInfo
new_york = ...