Python Date Time Intro

This is a brief introduction to date and time in Python.

Definitions

Here are some commonly used terms:

  • Greenwich Mean Time: abbreviated GMT. This was the zero offset
    timezone. It has now been superseded by Universal Coordinated Time
    (see next term) although the two terms are often used
    interchangeably. See
    Wikipedia.
  • Coordinated Universal Time: abbreviated UTC. This is zero offset
    timezone but should be used instead of GMT, because it is more
    precise than GMT, and not just to keep the French happy. See
    Wikipedia.

Storing date and time values

Zone-Aware Date and Time

The zoneinfo module uses your operating system\’s time zone database if it is available, or the tzdata Python package if it is unavailable. Create a new ZoneInfo object as follows and use it to get the current timestamp or a datetime of your choosing:

from datetime import datetime
from zoneinfo import ZoneInfo

now = datetime.now(ZoneInfo("Australia/Sydney"))
# Start date of Le Tour de France:
tdf_start_date = datetime(2021,6,28,9,0,0,0,ZoneInfo("Europe/Paris"))

A Python datetime object can alo be created by parsing an ISO datetime string, or a string in a format of your choice, according to C date and time formatting conventions:

# End date of Le Tour de France, ISO style:
tdf_end_date = datetime.fromisoformat("2021-07-18T18:00:00.000+02:00")
# or in a custom format:
tdf_end_date = datetime.strptime("18-07-2021 18:00:00.00 +0200", "%d-%m-%Y %H:%M:%S.%f %z")

The Python standard library has only limited ISO datetime string parsing features, and is not fully compatible with ISO 8601. Use third-party packages for full compatibility.

The timezone of your now object is stored in the tzinfo attribute as shown:

now.tzinfo
# The utcoffset() method below returns a timedelta with a seconds attribute.
# The next line prints 36000.
now.tzinfo.utcoffset(now).seconds

Here are some other useful methods you can call on your datetime object:

# The weekday() method returns an integer number for the day of 
# the week, where Monday = 0.
now.weekday()
# The isoformat() method returns a datetime string, 
# eg "2021-07-02T17:18:12.646789+10:00"
now.isoformat()
# The dst() indicates whether daylight savings applies to 
# the datetime object.  It returns a timedelta with a seconds 
# attribute, which is zero during standard time.
now.dst()

"Naive" (Zone-Unaware) Date and Time

If you don\’t need to deal with offsets and regions then Python lets you omit them, and create "naive" datetime objects:

now = datetime.now()

Naturally, the tzinfo attribute of such a datetime object will be None:

print(now.tzinfo)

Date and Time

If you only need to deal with whole days then the date class is very handy. Similarly if you only need to deal with times, then Python provides the time class.

from datetime import date, time, timezone
from zoneinfo import ZoneInfo
today = date.today()
tdf_start_date = date(2021,6,28)
now_naive = time()
now_zoned = time(tzinfo=timezone.utc)
tdf_start_time_naive = time(9,0,0,0)
tdf_start_time_zoned = time(9,0,0,0,tzinfo=ZoneInfo("Europe/Paris"))

Manipulating Dates and Times

Suppose you want to watch the final stage of Le Tour de France on Sunday 18th July 2021, sixteen days from today (at the time of writing), at 11:30 pm. You could calculate it as follows:

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
sixteen_days = timedelta(days=16)
tdf_final_sydney = datetime.now(ZoneInfo("Australia/Sydney")) \
                      .replace(hour=23,minute=30,second=0) \
                      + sixteen_days
tdf_final_paris = tdf_final_sydney.astimezone(ZoneInfo("Europe/Paris"))

Line 3 creates a time difference object, a timedelta. Different units of time can be supplied as named arguments, and these objects can be used to perform date and time arithmetic with datetime objects and with other timedelta objects. Line 4 gets today\’s date in Sydney. Line 5 sets the time to 11:30 pm. Line 6 then uses the timedelta to advance today\’s date (Sydney time) sixteen days into the future, when the final will be. Line 7 calculates what time the start will be in Paris by adjusting the timezone, so you know what time it will be for the riders.

You can prove that two objects tdf_final_sydney and tdf_final_paris, which represent the start of the race in Sydney and Paris respectively, represent the same moment in time and have no time difference between them, by subtracting one from the other. Open a Python console and create the two variables with the code above, then run the code below:

>>> tdf_final_sydney - tdf_final_paris
datetime.timedelta(0)

Traps and Pitfalls

Zone unaware datetimes sometimes can produce wrong values in some versions of Python if you try to add zone information to them in the wrong way. eg:

from datetime import datetime, timezone
bad_apr01 = datetime(2022,4,1).astimezone(timezone.utc) # WRONG!
apr01 = datetime(2022,4,1, tzinfo=timezone.utc) # CORRECT

Similarly, be careful when obtaining the current moment. Don’t use utcnow() at all.

from datetime import datetime, timezone
now_wrong = datetime.utcnow() # WRONG!  Is zone unaware!
now_correct = datetime.now(tz=timezone.utc) # CORRECT

If you use the PyTz third party timezone library, don’t use a PyTz timezone object as an argument to the datetime constructor since it can give inconsistent results. There is more information about this on the package information page on PyPi but here is the short version:

import pytz
from datetime import datetime
sydney_tz = pytz.timezone("Australia/Sydney")
bad_apr01 = datetime(2022,4,1 tzinfo=sydney_tz) # WRONG!
apr01 = sydney_tz.localize(datetime(2022,4,1)) # CORRECT

Further Reading

https://docs.python.org/3/library/datetime.html

https://docs.python.org/3/library/zoneinfo.html

https://pypi.org/project/pytz/

Acknowledgements

The author acknowledges the traditional custodians of the Daruk and the
Eora People and pays respect to the Elders past and present.

Python® is a registered trademark of the Python Software Foundation and/or its
affiliates.