3.6. Datetime Timezone

3.6.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 []

  • datetime.utcnow() - produces timezone naive date!

../_images/datetime-compare.png

Figure 3.1. Comparing datetime works only when all has the same timezone (UTC)

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)

datetime.utcnow()
# datetime.datetime(1957, 10, 4, 17, 28, 34)

Timezone aware datetime:

from datetime import datetime, timezone


datetime.now(tz=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)

datetime.utcnow(tz=timezone.utc)
# Traceback (most recent call last):
# TypeError: utcnow() takes no keyword arguments

datetime.utcnow(timezone.utc)
# Traceback (most recent call last):
# TypeError: utcnow() takes no arguments (1 given)

3.6.3. Standard Library

from zoneinfo import ZoneInfo
from datetime import datetime, timedelta


dt = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo("America/Los_Angeles"))  # Daylight saving time
print(dt)
# 2020-10-31 12:00:00-07:00
dt.tzname()
# 'PDT'


dt += timedelta(days=7)  # Standard time
print(dt)
# 2020-11-07 12:00:00-08:00
print(dt.tzname())
# PST

3.6.4. pytz

pytz brings the Olson tz database into Python:

from pytz import timezone


timezone('UTC')
timezone('US/Eastern')
timezone('Europe/Warsaw')
timezone('Asia/Almaty')

From naive to local time:

from datetime import datetime
from pytz import timezone


my_date = datetime(1969, 7, 21, 2, 56, 15)

timezone('UTC').localize(my_date)
# datetime.datetime(1969, 7, 21, 2, 56, 15, tzinfo=<UTC>)

From naive to local time:

from datetime import datetime
from pytz import timezone


my_date = datetime(1961, 4, 12, 6, 7)

timezone('Asia/Almaty').localize(my_date)
# 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


my_date = datetime(1969, 7, 21, 2, 56, 15, tzinfo=timezone('UTC'))

my_date.astimezone(timezone('Europe/Warsaw'))
# 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


my_date = datetime(1961, 4, 12, 6, 7, tzinfo=timezone('Asia/Almaty'))

my_date.astimezone(timezone('Europe/Warsaw'))
# datetime.datetime(1961, 4, 12, 1, 59, tzinfo=<DstTzInfo 'Europe/Warsaw' CET+1:00:00 STD>)

Descriptor Timezone Converter:

from dataclasses import dataclass
from datetime import datetime
from pytz import timezone


class Timezone:
    def __init__(self, name):
        self.timezone = timezone(name)

    def __get__(self, parent, *args):
        return parent.utc.astimezone(self.timezone)

    def __set__(self, parent, new_datetime):
        local_time = self.timezone.localize(new_datetime)
        parent.utc = local_time.astimezone(timezone('UTC'))


@dataclass
class Time:
    utc = datetime.now(tz=timezone('UTC'))
    warsaw = Timezone('Europe/Warsaw')
    moscow = Timezone('Europe/Moscow')
    est = Timezone('America/New_York')
    pdt = Timezone('America/Los_Angeles')


t = Time()

print('Launch of a first man to space:')
t.moscow = datetime(1961, 4, 12, 9, 6, 59)
print(t.utc)        # 1961-04-12 06:06:59+00:00
print(t.warsaw)     # 1961-04-12 07:06:59+01:00
print(t.moscow)     # 1961-04-12 09:06:59+03:00
print(t.est)        # 1961-04-12 01:06:59-05:00
print(t.pdt)        # 1961-04-11 22:06:59-08:00

print('First man set foot on a Moon:')
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.moscow)     # 1969-07-21 05:56:15+03:00
print(t.est)        # 1969-07-20 22:56:15-04:00
print(t.pdt)        # 1969-07-20 19:56:15-07:00

3.6.5. Assignments

Code 3.15. Solution
"""
* Assignment: Datetime Timezone Define
* Complexity: easy
* Lines of code: 5 lines
* Time: 13 min

English:
    1. Use data from "Given" section (see below)
    2. Create `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
    3. Use `List of tz database time zones` [1]
    X. Run doctests - all must succeed

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Stwórz obiekt `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
    3. Użyj `List of tz database time zones` [1]
    X. 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 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)
"""


# Given
from pytz import timezone


utc: timezone = ...  # timezone in UTC
london: timezone = ...  # timezone in London, United Kingdom
moscow: timezone = ...  # timezone in Moscow, Russian Federation
warsaw: timezone = ...  # timezone in Warsaw, Poland
tokyo: timezone = ...  # timezone in Tokyo, Japan
sydney: timezone = ...  # timezone in Sydney, Australia
auckland: timezone = ...  # timezone in Auckland, New Zealand
new_york: timezone = ...  # timezone in New York, USA

cape_canaveral: timezone = ...  # timezone in Cape Canaveral, FL, USA
houston: timezone = ...  # timezone in Houston, TX, USA
bajkonur: timezone = ...  # timezone in Bajkonur Cosmodrome, Kazachstan
north_pole: timezone = ...  # timezone in North Pole
south_pole: timezone = ...  # timezone in South Pole


3.6.6. References

1

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