4.9. Datetime Timezone

4.9.1. Rationale

  • 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 2

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

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

4.9.2. Timezone Naive Datetimes

>>> from datetime import datetime
>>>
>>>
>>> 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)

4.9.3. Timezone Aware Datetimes

>>> from datetime import datetime, timezone
>>>
>>>
>>> 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)

4.9.4. UTCNow

  • datetime.utcnow() produces timezone naive datetimes!

>>> from datetime import datetime, timezone
>>>
>>>
>>> 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)

4.9.5. IANA Time Zone Database

IANA 2017a timezone database 1:

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

4.9.6. ZoneInfo

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

Working with ZoneInfo objects:

>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime, timedelta
>>>
>>>
>>> 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:

>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime, timedelta
>>>
>>>
>>> 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'

4.9.7. Pytz

pytz brings the Olson tz database into Python:

>>> from pytz import timezone
>>>
>>>
>>> utc = timezone('UTC')
>>> est = timezone('US/Eastern')
>>> waw = timezone('Europe/Warsaw')
>>> alm = timezone('Asia/Almaty')

From naive to local time:

>>> from datetime import datetime
>>> from pytz import timezone
>>>
>>>
>>> dt = datetime(1969, 7, 21, 2, 56, 15)
>>> dt = timezone('UTC').localize(dt)
>>> dt
datetime.datetime(1969, 7, 21, 2, 56, 15, tzinfo=<UTC>)

From naive to local time:

>>> from datetime import datetime
>>> from pytz import timezone
>>>
>>>
>>> dt = datetime(1961, 4, 12, 6, 7)
>>> dt = timezone('Asia/Almaty').localize(dt)
>>> dt
datetime.datetime(1961, 4, 12, 6, 7, tzinfo=<DstTzInfo 'Asia/Almaty' +06+6:00:00 STD>)

From UTC to local time:

>>> from datetime import datetime
>>> from pytz import timezone
>>>
>>>
>>> dt = datetime(1969, 7, 21, 2, 56, 15, tzinfo=timezone('UTC'))
>>> dt = dt.astimezone(timezone('Europe/Warsaw'))
>>> dt
datetime.datetime(1969, 7, 21, 3, 56, 15, tzinfo=<DstTzInfo 'Europe/Warsaw' CET+1:00:00 STD>)

Between timezones:

>>> from datetime import datetime
>>> from pytz import timezone
>>>
>>>
>>> dt = datetime(1961, 4, 12, 6, 7, tzinfo=timezone('Asia/Almaty'))
>>> dt = dt.astimezone(timezone('Europe/Warsaw'))
>>> dt
datetime.datetime(1961, 4, 12, 1, 59, tzinfo=<DstTzInfo 'Europe/Warsaw' CET+1:00:00 STD>)

4.9.8. Use Case - 0x01

>>> from datetime import datetime
>>> from zoneinfo import ZoneInfo
>>>
>>>
>>> UTC = ZoneInfo('UTC')
>>> BAJKONUR = ZoneInfo('Asia/Almaty')
>>> MOSCOW = ZoneInfo('Europe/Moscow')
>>> 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(MOSCOW)
datetime.datetime(1961, 4, 12, 9, 7, tzinfo=zoneinfo.ZoneInfo(key='Europe/Moscow'))
>>>
>>> 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'))

4.9.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):
...         return parent.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')
...     moscow = Timezone('Europe/Moscow')
...     eastern = Timezone('America/New_York')
...     pacific = Timezone('America/Los_Angeles')
>>>
>>>
>>> t = Time()
>>>
>>> t.utc = datetime(1961, 4, 12, 6, 7)  # Gagarin's launch to space
>>> print(t.utc)
1961-04-12 06:07:00
>>> print(t.moscow)
1961-04-12 09:07:00+03: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
>>>
>>>
>>> t.warsaw = datetime(1969, 7, 21, 3, 56, 15)  # Armstrong's first Lunar step
>>> print(t.utc)
1969-07-21 02:56:15+00:00
>>> print(t.warsaw)
1969-07-21 03:56:15+01:00
>>> print(t.moscow)
1969-07-21 05:56:15+03:00
>>> print(t.eastern)
1969-07-20 22:56:15-04:00
>>> print(t.pacific)
1969-07-20 19:56:15-07:00

4.9.10. References

1

IANA. Time Zone Database. Year: 2017. Retrieved: 2019-08-05.

2

Computerphile. The Problem with Time & Timezones. Year: 2019. Retrieved: 2019-04-05. URL: https://www.youtube.com/watch?v=-5wpm-gesOY

4.9.11. Assignments

Code 4.43. Solution
"""
* Assignment: Datetime Timezone Pytz
* Complexity: easy
* Lines of code: 13 lines
* Time: 13 min

English:
    1. Create `pytz.timezone` object of:
        a. UTC
        b. London, United Kingdom
        c. Moscow, Russian Federation
        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:
    1. Stwórz obiekt `pytz.timezone` z:
        a. UCT
        b. London, Wielka Brytania
        c. Moscow, Rosja
        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.
        URL: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
        Retrieved: 2021-03-24
    [2] IANA. Time Zone Database.
        URL: https://data.iana.org/time-zones/releases/
        Retrieved: 2021-03-24

Extra Task:
    1. Cape Canaveral, FL, USA
    2. Houston, TX, USA
    3. Bajkonur Cosmodrome, Kazachstan
    5. North Pole
    6. South Pole (Henryk Arctowski Polish Antarctic Station)

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pytz.tzinfo import DstTzInfo, BaseTzInfo

    >>> 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 moscow is not Ellipsis, \
    'Assign value to variable `moscow` 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 cape_canaveral is not Ellipsis, \
    'Assign value to variable `cape_canaveral` instead of Ellipsis (...)'
    >>> assert houston is not Ellipsis, \
    'Assign value to variable `houston` instead of Ellipsis (...)'
    >>> assert bajkonur is not Ellipsis, \
    'Assign value to variable `bajkonur` instead of Ellipsis (...)'
    >>> assert north_pole is not Ellipsis, \
    'Assign value to variable `north_pole` instead of Ellipsis (...)'
    >>> assert south_pole is not Ellipsis, \
    'Assign value to variable `south_pole` instead of Ellipsis (...)'

    >>> assert isinstance(utc, BaseTzInfo), \
    'Variable `utc` has invalid type, must be a BaseTzInfo'
    >>> assert isinstance(london, DstTzInfo), \
    'Variable `london` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(moscow, DstTzInfo), \
    'Variable `moscow` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(warsaw, DstTzInfo), \
    'Variable `warsaw` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(tokyo, DstTzInfo), \
    'Variable `tokyo` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(sydney, DstTzInfo), \
    'Variable `sydney` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(auckland, DstTzInfo), \
    'Variable `auckland` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(new_york, DstTzInfo), \
    'Variable `new_york` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(cape_canaveral, DstTzInfo), \
    'Variable `cape_canaveral` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(houston, DstTzInfo), \
    'Variable `houston` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(bajkonur, DstTzInfo), \
    'Variable `bajkonur` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(north_pole, DstTzInfo), \
    'Variable `north_pole` has invalid type, must be a DstTzInfo'
    >>> assert isinstance(south_pole, DstTzInfo), \
    'Variable `south_pole` has invalid type, must be a DstTzInfo'

    >>> utc._utcoffset
    datetime.timedelta(0)
    >>> london._utcoffset
    datetime.timedelta(days=-1, seconds=86340)
    >>> moscow._utcoffset
    datetime.timedelta(seconds=9000)
    >>> warsaw._utcoffset
    datetime.timedelta(seconds=5040)
    >>> tokyo._utcoffset
    datetime.timedelta(seconds=33540)
    >>> sydney._utcoffset
    datetime.timedelta(seconds=36300)
    >>> auckland._utcoffset
    datetime.timedelta(seconds=41940)
    >>> new_york._utcoffset
    datetime.timedelta(days=-1, seconds=68640)
    >>> cape_canaveral._utcoffset
    datetime.timedelta(days=-1, seconds=68640)
    >>> houston._utcoffset
    datetime.timedelta(days=-1, seconds=65340)
    >>> bajkonur._utcoffset
    datetime.timedelta(seconds=18480)
    >>> north_pole._utcoffset
    datetime.timedelta(seconds=2580)
    >>> south_pole._utcoffset
    datetime.timedelta(seconds=5040)
"""

from pytz import timezone


# timezone in UTC
utc = ...

# timezone in London, United Kingdom
london = ...

# timezone in Moscow, Russian Federation
moscow = ...

# timezone in Warsaw, Poland
warsaw = ...

# timezone in Tokyo, Japan
tokyo = ...

# timezone in Sydney, Australia
sydney = ...

# timezone in Auckland, New Zealand
auckland = ...

# timezone in New York, USA
new_york = ...

# timezone in Cape Canaveral, FL, USA
cape_canaveral = ...

# timezone in Houston, TX, USA= ...
houston = ...

# timezone in Bajkonur Cosmodrome, Kazachstan
bajkonur = ...

# timezone in North Pole
north_pole = ...

# timezone in South Pole
south_pole = ...

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

English:
    1. Create `zoneinfo.ZoneInfo` object of:
        a. UTC
        b. London, United Kingdom
        c. Moscow, Russian Federation
        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:
    1. Stwórz obiekt `zoneinfo.ZoneInfo` z:
        a. UCT
        b. London, Wielka Brytania
        c. Moscow, Rosja
        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.
        URL: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
        Retrieved: 2021-03-24
    [2] IANA. Time Zone Database.
        URL: https://data.iana.org/time-zones/releases/
        Retrieved: 2021-03-24

Extra Task:
    1. Cape Canaveral, FL, USA
    2. Houston, TX, USA
    3. Bajkonur Cosmodrome, Kazachstan
    5. North Pole
    6. South Pole (Henryk Arctowski Polish Antarctic Station)

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

    >>> 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 moscow is not Ellipsis, \
    'Assign value to variable `moscow` 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 cape_canaveral is not Ellipsis, \
    'Assign value to variable `cape_canaveral` instead of Ellipsis (...)'
    >>> assert houston is not Ellipsis, \
    'Assign value to variable `houston` instead of Ellipsis (...)'
    >>> assert bajkonur is not Ellipsis, \
    'Assign value to variable `bajkonur` instead of Ellipsis (...)'
    >>> assert north_pole is not Ellipsis, \
    'Assign value to variable `north_pole` instead of Ellipsis (...)'
    >>> assert south_pole is not Ellipsis, \
    'Assign value to variable `south_pole` 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(moscow, ZoneInfo), \
    'Variable `moscow` 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'
    >>> assert isinstance(cape_canaveral, ZoneInfo), \
    'Variable `cape_canaveral` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(houston, ZoneInfo), \
    'Variable `houston` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(bajkonur, ZoneInfo), \
    'Variable `bajkonur` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(north_pole, ZoneInfo), \
    'Variable `north_pole` has invalid type, must be a ZoneInfo'
    >>> assert isinstance(south_pole, ZoneInfo), \
    'Variable `south_pole` has invalid type, must be a ZoneInfo'

    >>> utc.key
    'UTC'
    >>> london.key
    'Europe/London'
    >>> moscow.key
    'Europe/Moscow'
    >>> warsaw.key
    'Europe/Warsaw'
    >>> tokyo.key
    'Asia/Tokyo'
    >>> sydney.key
    'Australia/Sydney'
    >>> auckland.key
    'Pacific/Auckland'
    >>> new_york.key
    'America/New_York'
    >>> cape_canaveral.key
    'America/New_York'
    >>> houston.key
    'America/Chicago'
    >>> bajkonur.key
    'Asia/Almaty'
    >>> north_pole.key
    'Arctic/Longyearbyen'
    >>> south_pole.key
    'Europe/Warsaw'
"""

from zoneinfo import ZoneInfo


# ZoneInfo: timezone in UTC
utc = ...

# ZoneInfo: timezone in London, United Kingdom
london = ...

# ZoneInfo: timezone in Moscow, Russian Federation
moscow = ...

# ZoneInfo: timezone in Warsaw, Poland
warsaw = ...

# ZoneInfo: timezone in Tokyo, Japan
tokyo = ...

# ZoneInfo: timezone in Sydney, Australia
sydney = ...

# ZoneInfo: timezone in Auckland, New Zealand
auckland = ...

# ZoneInfo: timezone in New York, USA
new_york = ...

# ZoneInfo: timezone in Cape Canaveral, FL, USA
cape_canaveral = ...

# ZoneInfo: timezone in Houston, TX, USA= ...
houston = ...

# ZoneInfo: timezone in Bajkonur Cosmodrome, Kazachstan
bajkonur = ...

# ZoneInfo: timezone in North Pole
north_pole = ...

# ZoneInfo: timezone in South Pole
south_pole = ...