ᚱᛗ
© 2022
Powered by Hugo

Datetime Module

Table of Contents

Summary

Notes on the datetime module from the Python documentation .

Aware and Naive Objects

Date and time objects may be categorized as “aware” or “naive” depending on whether or not they include timezone information.

  • Aware: With sufficient knowledge of applicable algorithmic and political time adjustments, such as time zone and daylight saving time information, an aware object can locate itself relative to other aware objects. An aware object represents a specific moment in time that is not open to interpretation.

  • Naive: A naive object does not contain enough information to unambiguously locate itself relative to other date/time objects. Whether a naive object represents Coordinated Universal Time (UTC) , local time, or time in some other timezone is purely up to the program, just like it is up to the program whether a particular number represents metres, miles, or mass. Naive objects are easy to understand and to work with, at the cost of ignoring some aspects of reality.

Datetime Classes

  1. datetime.date – An idealized naive date, assuming the current Gregorian calendar always was, and always will be, in effect. Attributes: year, month, and day.
  2. datetime.time – An idealized time, independent of any particular day, assuming that every day has exactly 24x60x60 seconds. (There is no notion of “leap seconds” here.) Attributes: hour, minute, second, microsecond, and tzinfo.
  3. datetime.datetime – A combination of a date and a time. Attributes: year, month, day, hour, minute, second, microsecond, and tzinfo.
  4. datetime.timedelta – A duration expressing the difference between two date, time, or datetime instances to microsecond resolution.
  5. datetime.tzinfo – An abstract base class for time zone information objects. These are used by the datetime and time classes to provide a customizable notion of time adjustment (for example, to account for time zone and/or daylight saving time).
  6. datetime.timezone – A class that implements the tzinfo abstract base class as a fixed offset from the UTC. However timezone is limited to expressing fixed offsets from UTC only, and it doesn’t track changes such as daylight saving time.

Initializing Instances of date, time and datetime

# imports
from datetime import date, time, datetime

date(year=1969, month=7, day=20)
# datetime.date(1969, 7, 20)
time(hour=20, minute=17, second=1)
# datetime.time(20, 17, 1)
datetime(year=1969, month=7, day=20, hour=20, minute=17, second=1)
# datetime.datetime(1969, 7, 20, 20, 17, 1)

# Note: arguments must be passed as integers

Commonly Used Built-in Methods – today() and now()

# Today's date
today = date.today()
today
# datetime.date(2021, 9, 20)
print(today)
# 2021-09-20
# Now's date and time
now = datetime.now()
now
# datetime.datetime(2021, 9, 20, 11, 32, 54, 640613)
print(now)
# 2021-09-20 11:32:54.640613

Attributes of a Datetime Object

# datetime.datetime(2021, 9, 20, 11, 32, 54, 640613)
now.year  # 2021
now.month  # 9
now.day  # 20
now.hour  # 11
now.minute  # 32
now.second  # 54
now.microsecond  # 640613; 1 second == 1000000 microseconds

ISO 8601 – Format Standardization

The purpose of standard ISO 8601 is to provide a well-defined, unambiguous method of representing calendar dates and times in worldwide communications. In general, ISO 8601 applies to these representations and formats: dates, in the Gregorian calendar (including the proleptic Gregorian calendar); times, based on the 24-hour timekeeping system, with optional UTC offset; time intervals; and combinations thereof.

In representations that adhere to the ISO 8601 interchange standard, dates and times are arranged such that the greatest temporal term (typically a year) is placed at the left and each successively lesser term is placed to the right of the previous term.

Date Type Example
Date 2021-09-21
Date and time in UTC 2021-09-21T17:00:55+00:00
2021-09-21T17:00:55Z
20210921T170055Z
Week 2021-W38
Week w/ weekday 2021-W38-2
Date w/o year –09-21
Ordinal date 2021-264

From ISO 8601 Strings to datetime Object – fromisoformat()

# date only
date.fromisoformat("1969-07-20")
# datetime.date(1969, 7, 20)
assert date(1969, 7, 20) == date.fromisoformat("1969-07-20")

# datetime
datetime.fromisoformat("2021-09-21T17:00:55+00:00")
# datetime.datetime(2021, 9, 21, 17, 0, 55, tzinfo=datetime.timezone.utc)

Parsing Non-ISO Strings to datetime Object – strptime()

Fortunately, non-standard string formats can also be parsed using custom rules. Below is a selection of some common codes. The full list can be found in the documentation .

Code Meaning Example
%Y Year (4-digit number) 0001, …, 2020, …, 9999
%y Year (last 2 digits) 01, …, 20, …, 99
%m Month (2-digit number) 01, 02, …, 12
%B Month (locale’s full name) January, February, …, December
%b Month (locale’s abbreviation) Jan, Feb, …, Dec
%d Day of the month (2 digits) 01, 02, … 31
%w Weekday (Sunday:0, Saturday:6) 0, 1, …, 6
%A Weekday (locale’s full name) Sunday, Monday, …, Saturday
%a Weekday (locale’s abbreviation) Sun, Mon, …, Sat
%j Day of the year (3 digits) 001, 002, …, 366
%H Hour (24-h clock, 2 digits) 00, 01, …, 23
%M Minute (2 digits) 00, 01, …, 59
%S Second (2 digits) 00, 01, …, 59
# straightforward case
date_string = "07-20-1969 20:17:01"
fmt = "%m-%d-%Y %H:%M:%S"
datetime.strptime(date_string, fmt)
# datetime.datetime(1969, 7, 20, 20, 17, 1)
# more complex case
awful_date_string = "Jul---20/1969 @ 20::17::01"
fmt = "%b---%d/%Y @ %H::%M::%S"
datetime.strptime(awful_date_string, fmt)
# datetime.datetime(1969, 7, 20, 20, 17, 1)

Datetime Object to Formatted String – strftime()

# Printing the date and time of the first lunar landing
dt = datetime(1969, 7, 20, 20, 17, 1)
dt_formatted = dt.strftime("%A, %B %d, %Y at %H:%M")
print(f"The Eagle landed on {dt_formatted}")
# The Eagle landed on Sunday, July 20, 1969 at 20:17

Timezones with dateutil

The datetime module has a basic timezone class (for handling arbitrary fixed offsets from UTC) and its timezone.utc attribute (a UTC timezone instance). However, datetime does not provide a direct way to interact with the IANA time zone database.

dateutil.tz library brings the IANA timezone database (also known as the Olson database) to Python, and its usage is recommended.

The Time Zone Database (often called tz, tzdata or zoneinfo) contains code and data that represent the history of local time for many representative locations around the globe. It is updated periodically to reflect changes made by political bodies to time zone boundaries, UTC offsets, and daylight-saving rules.

Local and Specific Time Zones

# imports
from dateutil import tz
# local time zone
now = datetime.now(tz=tz.tzlocal())
now
# datetime(2021, 9, 22, 14, 59, 58, 563927, tzinfo=tzlocal())
now.tzname()
# 'EDT'
# Eastern Daylight Time (EDT)
# specific time zone
paris_tz = tz.gettz("Europe/Paris")
type(paris_tz)  # <class 'dateutil.tz.tz.tzfile'>
now = datetime.now(tz=paris_tz)
now
# datetime.datetime(2021, 9, 23, 0, 11, 21, 508957, tzinfo=tzfile('/usr/share/zoneinfo/Europe/Paris'))
now.tzname()
# 'CEST'
# Central European Summer Time (CEST)

GMT (UTC 0)

now = datetime.now(tz=tz.UTC)
# Greenwich Mean Time (GMT) by default
print(now)
# 2021-09-22 23:34:39.362606+00:00

Converting to Different Time Zones – dt.astimezone()

# astimezone() with no arguments returns the local time zone
print(now.astimezone().isoformat())
# 2021-09-22T19:34:39.362606-04:00
print(now.astimezone().tzname())
# EDT
# Eastern Daylight Time (EDT)

# define time zones of interest
PST = tz.gettz("US/Pacific")
EST = tz.gettz("US/Eastern")
JST = tz.gettz("Asia/Tokyo")
NZST = tz.gettz("Pacific/Auckland")

# view the same timestamp as a different time zone
print(now.astimezone(JST))
# 2021-09-23 08:34:39.362606+09:00
now.astimezone(JST).isoformat()
# '2021-09-23T08:34:39.362606+09:00'
assert now.astimezone(JST).tzname() == 'JST'
now.astimezone(JST).tzinfo  # file location with tz info
# tzfile('/usr/share/zoneinfo/Asia/Tokyo')

Note: full list of IANA names

Time deltas

Time deltas represent durations of time, timedelta() is able to capture these durations down to the microsecond. Characteristics of datetime.timedelta objects:

  • Time deltas can only be constructed using days and/or smaller time units (hours, minutes, seconds, microseconds). It’s not possible to specify a month parameter directly, however, this can be bypassed by inputting the equivalent time in days, eg. timedelta(days=30)
  • It is possible to create intervals by combining days, hours, minutes, seconds and microseconds
  • It is possible to add or subtract intervals. Note that arithmetic rules are followed: the sign can be placed both inside the parameter and as an operand. Eg., subtracting a negative 48-hour period would actually add 48 hours (negative * negative = positive).
  • It is possible to both add and subtract different parameters at the same time. Eg., add hours while subtracting minutes.
# imports
from datetime import datetime, timedelta
# combination example
delta = timedelta(days=12, hours=3, minutes=15, seconds=7)
# datetime.timedelta(days=12, seconds=11707)
# hours and minutes get converted to seconds
assert (3*60*60 + 15*60 + 7) == delta.seconds
# arithmetic example -- going back 48 hours
t = datetime(2021, 1, 1, 12, 0, 0)
t.isoformat()
# '2021-01-01T12:00:00'
delta_minus_48h = timedelta(hours=-48)
delta_plus_48h = timedelta(hours=+48)
(t + delta_minus_48h).isoformat()
# '2020-12-30T12:00:00'
# subtracting a positive delta == adding a negative delta
assert (t - delta_plus_48h) == (t + delta_minus_48h)
# subtracting a negative delta adds time
(t - delta_minus_48h).isoformat()
# '2021-01-03T12:00:00'
# add and subtract different parameters at the same time
timedelta(hours=10, minutes=-1)
# datetime.timedelta(seconds=35940)
# both arguments are converted to seconds

The following example computes the start and end of a subscription purchase, assuming the purcase is made at time datetime.now(), using the local time zone.

# compute the end of a 30-day subscription interval
subscription_start = datetime.now(tz=tz.tzlocal())
# datetime.datetime(2021, 9, 23, 10, 53, 8, 377276, tzinfo=tzlocal())
subscription_length = timedelta(days=30)
# datetime.timedelta(days=30)
subscription_end = subscription_start + subscription_length
# datetime.datetime(2021, 10, 23, 10, 53, 8, 377276, tzinfo=tzlocal())
assert (subscription_end - subscription_start).days == 30

Further Reading