9 | Dealing With Dates and Times in Python


It’s time to talk about dates and times in Python. This area of programming is used extensively in some applications. However, there are other applications in which you’ll never need to use dates and times. Data points often include a date and time component, and this is often an essential part of the data in many real-world applications.

There are several reasons why dealing with dates and times in Python is not as straightforward as you might think. Here are some of those reasons:

  • The way we group days and times is not the most straightforward. Days of the week repeat every seven days, but the number of days in a month doesn’t follow any easily quantifiable rule. We don’t even get a whole number of weeks in a year as 365 is not divisible by 7. And then there are leap years.
  • In the real world, we use many different formats to deal with dates and times. The first day of September of the year two thousand and twenty-one is the same as 01/09/2021 or indeed 09/01/2021, depending on which part of the world you’re in. Or you can use just 21 to represent the year. This date is the same as 1-Sep-21 or September 1, 2021. There are many more options, too.
  • And if this isn’t enough to cause headaches, you can add time zones to the mix. 10:00 am on September 1, 2021 in Greenwich Mean Time/Coordinated Universal Time (GMT/UTC) is not the same as 10:00 am on September 1, 2021 in Eastern Standard Time (EST). And yes, there’s also the switch to daylight saving time in many time zones to consider.

Luckily, there are tools you can use to deal with dates and times in Python without having to work out all these quirks about dates and times from first principles.

Using datetime to Deal With Dates and Times in Python

There’s more than one module that allows you to deal with dates and times in Python. You’ll learn about the most standard of these modules in this Chapter. This module is the built-in datetime module that you can import into your program using the import keyword in the usual manner.

Let’s start using the datetime module and introduce the two key data types that you’ll need to get started:

  • datetime.datetime
  • datetime.timedelta

One of the data types is called datetime which is part of the datetime module! This naming can cause some confusion. Remember that in datetime.datetime, the first datetime is the module name while the second is the name of the data type.

datetime.datetime

The best way to introduce datetime.datetime is through an example. Note that you’re running the examples below in a Python console (interactive environment). The output will look different when you run similar code in a script. You’ll learn why this happens later on in this Chapter:

>>> import datetime
>>> time_now = datetime.datetime.now()

>>> time_now
datetime.datetime(2021, 9, 20, 21, 40, 3, 58773)

>>> type(time_now)
<class 'datetime.datetime'>

You’ve used the method now() of datetime.datetime to get the current date and time and assign it to the variable name time_now. Of course, you can call this variable anything you wish. When you display the value of this variable, you can see numbers that represent the date and time when I ran this code. The numbers shown are the year, month, day, hour, minute, second, and microsecond, in this order.

The output shows that the time was 9:40:03 pm (21:40:03) on the 20th of September, 2021 (20/9/2021, or 9/20/2021 in the US time format). You can even see the microsecond listed. There may be times when you need this level of precision!

The datetime.datetime data type is Python’s way of understanding and dealing with dates and times. You can explore this further by looking at some of the attributes of an object of type datetime.datetime. This Console session carries on from the one you started earlier:

>>> time_now.year
2021

>>> time_now.month
9

>>> time_now.day
20

>>> time_now.weekday()
0

>>> time_now.isoweekday()
1

>>> time_now.isoformat()
'2021-09-20T21:40:03.058773'

The first three attributes contain the integers representing the year, month, and day of the datetime.datetime object. There are also attributes containing the hour, minute, second, and microsecond values. These are the same values included in the output when you displayed time_now.

The examples above also show some of the methods you can use with datetime.datetime. The weekday() method returns 0 as the 20th of September 2021 was a Monday. The days Monday to Sunday are represented by the numbers 0 to 6. There’s also an alternative version of this method which adheres to the date and time international standard ISO 8601. This method is isoweekday() which returns 1 for Monday and 7 for Sunday.

You can also display the date and time in the ISO standard using isoformat(). If you’re likely to use dates and times extensively or you want to learn about the standard formats, you can read more about the ISO 8601 international standard.

datetime.timedelta

You can now create two variables containing two different datetime.datetime objects and compare them:

>>> first_time = datetime.datetime.now()
>>> # Wait a minute, literally...
>>> second_time = datetime.datetime.now()

>>> first_time
datetime.datetime(2021, 9, 20, 22, 13, 13, 810414)
>>> second_time
datetime.datetime(2021, 9, 20, 22, 14, 14, 112847)

>>> gap = second_time - first_time
>>> gap
datetime.timedelta(seconds=60, microseconds=302433)

>>> type(gap)
<class 'datetime.timedelta'>

>>> gap.seconds
60

>>> gap.total_seconds()
60.302433

>>> second_time > first_time
True

In the example above, I assigned the date and time to a variable called first_time, then waited a minute before assigning the date and time to a second variable named second_time. I couldn’t get the timing perfectly right, so the difference between the two times is not exactly 60 seconds!

When you subtract one datetime.datetime object from another, the value returned is an object of type datetime.timedelta. In this example, the program shows the gap between the two dates and times using the number of seconds and microseconds. You’ll see later than for larger gaps, the number of days will also be displayed.

There are attributes that allow you to extract just one of the relevant values. In the example above, you use gap.seconds to show the whole number of seconds included in the datetime.timedelta. However, this value is not the total number of seconds that have elapsed. The method total_seconds() returns this value.

You can also compare two datetime.datetime objects. This operation doesn’t give a datetime.timedelta but instead returns a Boolean, as is usually the case when using comparison operators. In the context of comparing dates and times, the “greater than” operator represents “later than”, whereas the “less than” operator means “earlier than”.

Converting Dates and Times in Python To and From Strings

You’ve learned about the two main data types in the datetime module that allow you to deal with dates and times in Python. However, so far, you’ve only created datetime.datetime objects using the now() method. This method gives you the date and time at the time of execution. This is useful sometimes. However, in many applications, you’ll need to deal with other dates.

To deal with dates and times in Python from the past, present, or future, you’ll need two other key methods associated with the datetime.datetime class. These methods, quite possibly, win the award for the worst named methods in all of Python’s standard library:

  • strptime()
  • strftime()

You can use these methods to convert a string containing a date and time into a datetime.datetime object, and the other way round. Let’s look at each of these methods and find a way of remembering which one is which!

strptime()

The strptime() method converts a string containing a date and time into a datetime.datetime object. You can remember this using the p between str and time in the method name, which refers to parsing a string into a date and time. The method name only refers to time, but as you’re using the datetime module, this method deals with both dates and times.

The problem is that there are many different formats used to refer to dates and times. Let’s start with the following date, which I’m showing in three different formats that are commonly used around the world:

  • 20/02/1991 (using dd/mm/yyyy)
  • 1991-02-20
  • 20 February, 1991

No, this is not my birthday. It’s Python’s birthday. This is the date the very first version of the language was released.

You can create three variables to represent this date as a string in its three different formats:

>>> date_v1 = "20/02/1991"
>>> date_v2 = "1991-02-20"
>>> date_v3 = "20 February, 1991"

Before you can convert these strings into datetime.datetime objects, you’ll need a way to let the datetime module know which date format you’re using. There are format codes you can use that represent all the possible ways of displaying years, months, and days. There are also codes to refer to time data. However, these examples don’t include a time. Therefore, you can focus just on dates in these examples.

Here’s a selection of the format codes you’ll need for these examples, including a couple of additional ones. You can find the complete list of format codes in the datetime module documentation:

CodeMeaningExample
%dDay of the month using two digits03, 22, 31
%mMonth represented as a number using two digits01, 10, 12
%bMonth represented as an abbreviated name (language will depend on your locale setting)Jan, Oct, Dec
%BMonth represented as a full name (language will depend on your locale setting)January, October, December
%yYear represented using two digits, without the century91, 21, 01
%YYear represented using four digits1991, 2021, 2001

Let’s see how you can use these codes to input the date format you’re using into strptime():

>>> date_v1 = "20/02/1991"
>>> datetime.datetime.strptime(date_v1, "%d/%m/%Y")
datetime.datetime(1991, 2, 20, 0, 0)

strptime() is a class method that’s bound to the class rather than to individual instances of the class. You haven’t read about class methods, but you don’t need to know more at this stage. The second line is quite a mouthful. Let’s break it down to review what you’ve covered so far:

  • The first datetime in datetime.datetime is the name of the module which you import
  • The second datetime is the name of the class that creates objects. The objects are of type datetime.datetime–the class datetime that’s in the module datetime. It would have been clearer if the module and class names were different, but unfortunately, they’re not
  • strptime() is a method of the datetime.datetime class

The strptime() method takes two arguments:

  • a string containing the date (or date and time)
  • a string containing the format which uses the format codes

The date you’re using has the day, month, and four-digit year separated by the forward-slash /, and therefore the format code which represents this date is the string "%d/%m/%Y". Note that a capital Y is used to represent the four-digit year as the lowercase version refers to a two-digit year representation. You can refer to the table above describing the format codes or the full table of format codes in the documentation.

strptime() returns a datetime.datetime object showing the year, month, and day. The values for second and microsecond are 0, as the string you used did not include any time information.

You can now try and convert the remaining two strings into datetime.datetime objects. Once you’ve tried this, you can read on…

Although the three strings shown above are different strings, they represent the same date. You can confirm this by checking for equality after you convert to datetime.datetime:

>>> date_v1 = "20/02/1991"
>>> date_v1_as_dt = datetime.datetime.strptime(date_v1, "%d/%m/%Y")
>>> date_v1_as_dt
datetime.datetime(1991, 2, 20, 0, 0)

>>> date_v2 = "1991-02-20"
>>> date_v2_as_dt = datetime.datetime.strptime(date_v2, "%Y-%m-%d")
>>> date_v2_as_dt
datetime.datetime(1991, 2, 20, 0, 0)

>>> date_v3 = "20 February, 1991"
>>> date_v3_as_dt = datetime.datetime.strptime(date_v3, "%d %B, %Y")
>>> date_v3_as_dt
datetime.datetime(1991, 2, 20, 0, 0)

>>> # Compare the three strings
>>> date_v1 == date_v2 == date_v3
False

>>> # Compare the three datetime.datetime objects
>>> date_v1_as_dt == date_v2_as_dt == date_v3_as_dt
True

In the first three blocks of code above, you’re converting the three strings to datetime.datetime objects using strptime() and the correct format codes for each string. In the third case, you include a space between %d and %B and a comma and a space between %B and %Y.

The strings are different, and therefore, you get False when you check the three strings for equality. Python doesn’t know yet that these strings represent dates. It’s just checking whether the characters in the strings are identical. They’re not.

However, the equality check for the three datetime.datetime objects returns True. These are indeed the same dates.

strftime()

The second of the “two worst named methods in Python” is strftime(). This method performs the reverse of the method you’ve just learned about. The f in the method name shows that you’re obtaining a string from a date and time object. This method is an instance method. Therefore, it’s bound to an instance of the class and has access to the data contained within that instance. These are the methods you learned about in Chapter 7 on object-oriented programming in Python.

You can create a date and time object directly by creating an instance of the datetime.datetime class:

>>> new_date = datetime.datetime(year=2008, month=12, day=3)
>>> new_date
datetime.datetime(2008, 12, 3, 0, 0)

As you learned in the earlier Chapter about OOP, this is the direct way to create an instance of a class. Recall that the second datetime in datetime.datetime is the name of a class, even though it doesn’t follow the naming convention of using upper camel case. When you create an instance of a class, you’re calling the __init__() method for this class. In this case, you’re passing three named arguments to the __init__() method, which creates an instance of the datetime.datetime class.

The date used in this example is when Python 3.0 was released.

You can convert this datetime.datetime object into a string using strftime(). You can choose to create a string using any format you wish:

>>> new_date.strftime("%Y-%m-%d")
'2008-12-03'

>>> new_date.strftime("Python 3.0 was released in %B %Y")
'Python 3.0 was released in December 2008'

strftime() only requires one explicit argument, which is the string containing the format code. The method also has access to the object new_date itself. The full signature of this method is strftime(self, fmt). self is the implied first argument, as is the case for all instance methods.

If you wish, you can also use strftime() using the same style as strptime():

>>> datetime.datetime.strftime(new_date, "%Y-%m-%d")
'2008-12-03'

In this case, you need two arguments. The first is the datetime.datetime object and the second the format code. However, the OOP form you used earlier is clearer and more concise, so it’s often the preferred option.

You can now easily find out the time elapsed between the release of the first version of Python and Python 3.0. You can also find how old Python is today:

>>> new_date - date_v1_as_dt
datetime.timedelta(days=6496)

>>> datetime.datetime.now() - date_v1_as_dt
datetime.timedelta(days=11171, seconds=40363, microseconds=426860)

In both of these examples, you’re subtracting one datetime.datetime object from another. As you’ve seen earlier, this operation returns a datetime.timedelta which can include days, seconds, and microseconds.

What Day of The Week Were You Born?

You can now write a short program to find what day of the week you were born. The steps you’ll need are:

  • Choose a format you’d like to use for the date
  • Input the date in that format as a string
  • Convert the string to a datetime.datetime object using strptime()
  • Either use the weekday() or isoweekday() methods to get the day of the week, or use strftime() to get the name of the weekday straight away

I will use the dd/mm/yyyy format in this example which is represented by the format code string "%d/%m/%Y". You can use input() to ask the user to enter the date of birth:

# find_day_of_birth.py

import datetime

format = "%d/%m/%Y"

dob = input("Enter your date of birth (dd/mm/yyyy): ")
dob_as_dt = datetime.datetime.strptime(dob, format)

print(dob_as_dt)

When you run this script, you’re asked to enter a date of birth. I’ll use Python’s date of birth again in the example below:

Enter your date of birth (dd/mm/yyyy): 20/02/1991
1991-02-20 00:00:00

You’ll have noticed that you’re using a script for this example rather than the Python Console. Therefore, you’ve had to use print() to display the datetime.datetime object. The output from print() shows the date and time but it’s displayed differently from when you displayed datetime.datetime objects in the Console. In the Snippets section at the end of this Chapter, you can read about the different ways to display an object. The built-in functions str() and repr() return the object’s string representation and object representation, respectively.

Displaying dates and times in Python using str() and repr()

For this section, a short explanation will be sufficient. You can look in the Snippets section for more details.

In this case, print() returns the string representation of a datetime.datetime object, which displays the date and time in a form that the user of the program easily understands. This is the same output returned by str(). The date and time format displayed when using print() or str() is the ISO international standard format you have already come across.

When you display the value of an object in the Python Console or other interactive environments, Python displays the object representation. This output is the value returned by repr(), and it’s a string showing information that’s specific to the Python object. It’s aimed at the programmer and not the user of a program.

Let’s compare the two representations in a Console session using the same new_date variable you used earlier, which is Python 3.0’s date of birth:

>>> new_date = datetime.datetime(year=2008, month=12, day=3)
>>> str(new_date)
2008-12-03 00:00:00

>>> repr(new_date)
'datetime.datetime(2008, 12, 3, 0, 0)'

When you convert new_date into a string using str(), the function returns the ISO standard format. Note that this string has no reference to Python data types. It’s a format that everyone can understand as long as they’re familiar with the standard format for dates and times. They don’t need to be familiar with Python.

However, repr() returns the object representation. This representation gives you information about the Python data type, which str() didn’t do. When you use repr(), you can see that you have a datetime.datetime object. Indeed, repr() usually returns a string that shows valid Python syntax, and you can copy and paste this to recreate the object. This information is intended for a programmer who understands Python and not for the user of a program.

print() returns the same output as str() in this case. However, when you type the name of an object in the Console and hit Return/Enter, Python displays the value returned by repr(). You can read more about these two types of representations, str() and repr(), and the special methods __str__() and __repr__() in the Snippets section at the end of this Chapter.

Completing the day-you-were-born program

Before adding the final steps to the program to find the day of the week you were born, try rerunning the script and input the date in the wrong format to see what happens:

Enter your date of birth (dd/mm/yyyy): 1991-2-20
Traceback (most recent call last):
  ...
ValueError: time data '1991-2-20' does not match format '%d/%m/%Y'

The string you input doesn’t have the same format as the one defined in strptime(). This input raises a ValueError.

The final step in your program is to find the weekday that is associated with the datetime.datetime object. The first option you have is to use one of two methods you’ve seen already: weekday() or isoweekday(). The only difference between the two methods is how the days are labelled. I’ll use isoweekday() since it uses the integers 1 to 7 rather than 0 to 6 to represent the days of the week:

# find_day_of_birth.py

import datetime

format = "%d/%m/%Y"

dob = input("Enter your date of birth (dd/mm/yyyy): ")
dob_as_dt = datetime.datetime.strptime(dob, format)

print(dob_as_dt.isoweekday())

This code gives the following output:

Enter your date of birth (dd/mm/yyyy): 20/02/1991
3

Python was born on a Wednesday since 1 represents Monday in the ISO format, and therefore, 3 represents Wednesday.

You can also use strftime() to display the weekday directly, using the format codes shown in the documentation for datetime. You’ll find the code %A close to the top of the table presented in the documentation. This format code represents the full name of the weekday:

# find_day_of_birth.py

import datetime

format = "%d/%m/%Y"

dob = input("Enter your date of birth (dd/mm/yyyy): ")
dob_as_dt = datetime.datetime.strptime(dob, format)

print(dob_as_dt.isoweekday())
print(dob_as_dt.strftime("%A"))

You’re now converting the datetime.datetime object into a string, but you’re choosing to display only the weekday name. The output from this code is the following:

Enter your date of birth (dd/mm/yyyy): 20/02/1991
3
Wednesday

This output confirms that Python was indeed born on a Wednesday!

Dealing With Time Zones

Let’s go back to the very first example you used to create a datetime.datetime object:

>>> import datetime
>>> time_now = datetime.datetime.now()
>>> time_now
datetime.datetime(2021, 9, 21, 12, 36, 9, 58234)

As you can see, I was writing this part of the Chapter on the 21st of September, 2021, at 12:36 pm. But what time zone was I in? There are times when we don’t need to know information about time zones. In the datetime module, dates and times that don’t include information about the time zone are called naive objects.

By default, now() returns a naive object. Therefore, it’s not possible to know what time zone I was in when I created the datetime.datetime object time_now.

Date and time objects can also be aware. Aware date and time objects have information about the time zone. In the example below, you’ll first create an offset with respect to GMT/UTC. The offset is a time difference and therefore is of type datetime.timedelta:

>>> offset = datetime.timedelta(hours=1)
>>> offset
datetime.timedelta(seconds=3600)

>>> time_now = datetime.datetime.now(datetime.timezone(offset))
>>> time_now
datetime.datetime(2021, 9, 21, 12, 48, 42, 220369, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600)))

As I write, I’m in the British Summer Time (BST) time zone which is one hour later than GMT/UTC, or +01:00 in ISO standard format. In this case, there is an argument passed to now(). This is an object of type datetime.timezone which is defined using the offset with respect to GMT/UTC.

When you display the datetime.datetime object returned by the method now(), you can see an additional term with the name tzinfo. This term shows that the date and time object is aware as it has information about the time zone.

This is good news if you want to make sure your dates and times are aware of the time zone. However, defining time zones in this manner is not straightforward. You’ll still need to know what the offsets from GMT/UTC are, and consider whether daylight saving applies, for example.

There are many ways you can deal with time zones in Python. In this section, I won’t try to outline each and every option. Instead, I’ll introduce one of the newest additions to Python. The zoneinfo module was introduced to the language in Python 3.9.

Using zoneinfo

The zoneinfo module does as its name implies. It provides information about specific time zones. The module provides a class called ZoneInfo which you can use to create objects that can provide the time zone information required to make a date and time object aware.

Unlike the case with datetime.datetime where the module name and class name are identical, the zoneinfo module follows the Python naming convention: the class name uses upper camel case while the module name is in lowercase. This makes things a bit clearer when writing zoneinfo.ZoneInfo.

Here’s an example. You’ll also need to import zoneinfo in addition to datetime:

# using_time_zones.py

import datetime
import zoneinfo

london_now = datetime.datetime.now(zoneinfo.ZoneInfo("Europe/London"))

print("London time zone")
print(london_now)
print(repr(london_now))

You’ve used both print(london_now) which shows the string representation of the date and time, and print(repr(london_now)) which shows the Python-specific object representation. This code gives the following output, although you’ll get a different value when you run this:

London time zone
2021-09-21 13:07:34.746661+01:00
datetime.datetime(2021, 9, 21, 13, 7, 34, 746661, tzinfo=zoneinfo.ZoneInfo(key='Europe/London'))

The string representation shows the time in ISO format. You can see the +01:00 at the end showing the offset with respect to GMT/UTC. This offset is consistent with London’s time zone on September 21 which is BST, one hour later than GMT/UTC.

The object representation shows the time zone info indicating that this date and time object is aware.

Let’s finish off by using now() to find the time in two different time zones, and then finding the difference between those two times:

# using_time_zones.py

import datetime
import zoneinfo

london_now = datetime.datetime.now(zoneinfo.ZoneInfo("Europe/London"))
new_york_now = datetime.datetime.now(zoneinfo.ZoneInfo("America/New_York"))

print("Print string and object representations of both datetimes")
print("London time zone")
print(london_now)
print(repr(london_now))

print("\nNew York time zone")
print(new_york_now)
print(repr(new_york_now))

print("\nShow time difference between the two datetimes")
print(new_york_now - london_now)

The now() method returns the current time but in two different time zones. For clarity, you can print out both the string and object representations. Finally, you can find the difference between the two times. The output of this code when I ran it gives:

Print string and object representations of both datetimes
London time zone
2021-09-21 13:16:26.738785+01:00
datetime.datetime(2021, 9, 21, 13, 16, 26, 738785, tzinfo=zoneinfo.ZoneInfo(key='Europe/London'))

New York time zone
2021-09-21 08:16:26.740564-04:00
datetime.datetime(2021, 9, 21, 8, 16, 26, 740564, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))

Show time difference between the two datetimes
0:00:00.001779

It was 1:16 pm (13:16) in London and 8:16 am (08:16) in New York when I ran this script. You can see the GMT/UTC offsets for both time zones in the ISO representation of the dates and times: +01:00 for London and -04:00 for New York.

Even though the times are different–it’s 1:16 pm in London, but 8:16 am in New York–when the two times are subtracted from each other, the time difference is virtually zero. That’s because the program executed the now() method twice at roughly the same time. Even though the date and time is displayed differently because they’re showing different time zones, both datetime.datetime objects represent the same date and time, except for a few microseconds!

Conclusion

Time’s up for this Chapter. As with all topics in programming and Python, there’s a lot more we can say about dealing with dates and times in Python. However, trying to squeeze everything in at the first time of asking won’t do anyone any good.

This Chapter aimed to give you a solid understanding of how to start using dates and times in Python using the datetime module.

There are other modules in Python that you can use to deal with dates and times. Some can be used as a replacement for datetime, while others are an extension. However, datetime is the starting point for any work with dates and times, and it’s a module that’s part of the standard library. In most cases, you won’t need to look any further, but if you do, you’ll find that many of the concepts you learned about in datetime reoccur in other modules, too.

If you use modules such as NumPy and Pandas extensively, you’ll also find functionality directly within those packages to deal with dates and times.

In this Chapter, you’ve learned about:

  • Creating date and time objects using datetime.datetime
  • Using datetime.timedelta for time intervals
  • Parsing strings into date and time objects using strptime()
  • Creating strings from date and time objects using strftime()
  • Using time zones when working with dates and times in Python

In many applications where you’ll need to use dates and times, you’ll find you’ll follow similar steps. Once you read in data from an external source which includes dates and times, you’ll convert these strings into datetime.datetime. This conversion means that Python understands these data as dates and times and not just as strings of characters. The datetime module gives Python the context to make sense of dates and times.

Once you perform all the operations you need, you can finally convert your dates and times back into strings using any format required. You can now export these strings back to the outside world where Python is no longer needed.

You’re now ready to start exploring dates and times in Python.


Snippets


1 | Different Representations of Objects: Using str() and repr(), and __str__() and __repr()__

Coming soon…

Sign-Up For Updates

This site was launched in May 2021. I’ll be adding chapters regularly over the coming weeks and months.

Sign-up for updates and you’ll also join the Codetoday Forum where you can ask me questions as you go through this journey to learn Python coding.