Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Using Python's datetime Module
Working with dates and times is one of the biggest challenges in programming. Between dealing with time zones, daylight saving time, and different written date formats, it can be tough to keep track of which days and times you’re referencing. Fortunately, the built-in Python datetime
module can help you manage the complex nature of dates and times.
In this tutorial, you’ll learn:
- Why programming with dates and times is such a challenge
- Which functions are available in the Python
datetime
module - How to print or read a date and time in a specific format
- How to do arithmetic with dates and times
Plus, you’re going to develop a neat application to count down the time remaining until the next PyCon US!
Free Bonus: Click here to get our free Python Cheat Sheet that shows you the basics of Python 3, like working with data types, dictionaries, lists, and Python functions.
Let’s get started!
Programming With Dates and Times
If you’ve ever worked on software that needed to keep track of times across several geographic areas, then you probably have a sense of why programming with time can be such a pain. The fundamental disconnect is that computer programs prefer events that are perfectly ordered and regular, but the way in which most humans use and refer to time is highly irregular.
Note: If you want to learn more about why time can be so complicated to deal with, then there are many great resources available on the web. Here are a few good places to start:
One great example of this irregularity is daylight saving time. In the United States and Canada, clocks are set forward by one hour on the second Sunday in March and set back by one hour on the first Sunday in November. However, this has only been the case since 2007. Prior to 2007, clocks were set forward on the first Sunday in April and set back on the last Sunday in October.
Things get even more complicated when you consider time zones. Ideally, time zone boundaries would follow lines of longitude exactly. However, for historical and political reasons, time zone lines are rarely straight. Often, areas that are separated by large distances find themselves in the same time zone, and adjacent areas are in different time zones. There are some time zones out there with pretty funky shapes.
How Computers Count Time
Nearly all computers count time from an instant called the Unix epoch. This occurred on January 1, 1970, at 00:00:00 UTC. UTC stands for Coordinated Universal Time and refers to the time at a longitude of 0°. UTC is often also called Greenwich Mean Time, or GMT. UTC is not adjusted for daylight saving time, so it consistently keeps twenty-four hours in every day.
By definition, Unix time elapses at the same rate as UTC, so a one-second step in UTC corresponds to a one-second step in Unix time. You can usually figure out the date and time in UTC of any given instant since January 1, 1970, by counting the number of seconds since the Unix epoch, with the exception of leap seconds. Leap seconds are occasionally added to UTC to account for the slowing of the Earth’s rotation but are not added to Unix time.
Note: There’s an interesting bug associated with Unix time. Since many older operating systems are 32-bit, they store the Unix time in a 32-bit signed integer.
This means that at 03:14:07 on January 19, 2038, the integer will overflow, resulting in what’s known as the Year 2038 problem, or Y2038. Similar to the Y2K problem, Y2038 will need to be corrected to avoid catastrophic consequences for critical systems.
Nearly all programming languages, including Python, incorporate the concept of Unix time. Python’s standard library includes a module called time
that can print the number of seconds since the Unix epoch:
>>> import time
>>> time.time()
1579718137.550164
In this example, you import the time
module and execute time()
to print the Unix time, or number of seconds (excluding leap seconds) since the epoch.
In addition to Unix time, computers need a way to convey time information to users. As you saw in the last example, Unix time is nearly impossible for a human to parse. Instead, Unix time is typically converted to UTC, which can then be converted into a local time using time zone offsets.
The Internet Assigned Numbers Authority (IANA) maintains a database of all of the values of time zone offsets. IANA also releases regular updates that include any changes in time zone offsets. This database is often included with your operating system, although certain applications may include an updated copy.
The database contains a copy of all the designated time zones and how many hours and minutes they’re offset from UTC. So, during the winter, when daylight saving time is not in effect, the US Eastern time zone has an offset of -05:00, or negative five hours from UTC. Other regions have different offsets, which may not be integer hours. The UTC offset for Nepal, for example, is +05:45, or positive five hours and forty-five minutes from UTC.
How Standard Dates Can Be Reported
Unix time is how computers count time, but it would be incredibly inefficient for humans to determine the time by calculating the number of seconds from an arbitrary date. Instead, we work in terms of years, months, days, and so forth. But even with these conventions in place, another layer of complexity stems from the fact that different languages and cultures have different ways of writing the date.
For instance, in the United States, dates are usually written starting with the month, then the day, then the year. This means that January 31, 2020, is written as 01-31-2020. This closely matches the long-form written version of the date.
However, most of Europe and many other areas write the date starting with the day, then the month, then the year. This means that January 31, 2020, is written as 31-01-2020. These differences can cause all sorts of confusion when communicating across cultures.
To help avoid communication mistakes, the International Organization for Standardization (ISO) developed ISO 8601. This standard specifies that all dates should be written in order of most-to-least-significant data. This means the format is year, month, day, hour, minute, and second:
YYYY-MM-DD HH:MM:SS
In this example, YYYY
represents a four-digit year, and MM
and DD
are the two-digit month and day, starting with a zero if necessary. After that, HH
, MM
, and SS
represent the two-digit hours, minutes, and seconds, starting with a zero if necessary.
The advantage of this format is that the date can be represented with no ambiguity. Dates written as DD-MM-YYYY
or MM-DD-YYYY
can be misinterpreted if the day is a valid month number. You’ll see a little later on how you can use the ISO 8601 format with Python datetime
.
How Time Should Be Stored in Your Program
Most developers who have worked with time have heard the advice to convert local time to UTC and store that value for later reference. In many cases, especially when you’re storing dates from the past, this is enough information to do any necessary arithmetic.
However, a problem can happen if a user of your program inputs a future date in their local time. Time zone and daylight saving time rules change fairly frequently, as you saw earlier with the 2007 change in daylight saving time for the United States and Canada. If the time zone rules for your user’s location change before the future date that they inputted, then UTC won’t provide enough information to convert back to the correct local time.
Note: There are a number of excellent resources available to help you determine the appropriate way to store time data in your application. Here are a few places to start:
In this case, you need to store the local time, including the time zone, that the user inputted as well as the version of the IANA time zone database that was in effect when the user saved the time. This way, you’ll always be able to convert the local time to UTC. However, this approach won’t always allow you to convert UTC to the correct local time.
Using the Python datetime
Module
As you can see, working with dates and times in programming can be complicated. Fortunately, you rarely need to implement complicated features from scratch these days since many open-source libraries are available to help out. This is definitely the case in Python, which includes three separate modules in the standard library to work with dates and times:
calendar
outputs calendars and provides functions using an idealized Gregorian calendar.datetime
supplies classes for manipulating dates and times.time
provides time-related functions where dates are not needed.
In this tutorial, you’ll focus on using the Python datetime
module. The main focus of datetime
is to make it less complicated to access attributes of the object related to dates, times, and time zones. Since these objects are so useful, calendar
also returns instances of classes from datetime
.
time
is less powerful and more complicated to use than datetime
. Many functions in time
return a special struct_time
instance. This object has a named tuple interface for accessing stored data, making it similar to an instance of datetime
. However, it doesn’t support all of the features of datetime
, especially the ability to perform arithmetic with time values.
datetime
provides three classes that make up the high-level interface that most people will use:
datetime.date
is an idealized date that assumes the Gregorian calendar extends infinitely into the future and past. This object stores theyear
,month
, andday
as attributes.datetime.time
is an idealized time that assumes there are 86,400 seconds per day with no leap seconds. This object stores thehour
,minute
,second
,microsecond
, andtzinfo
(time zone information).datetime.datetime
is a combination of adate
and atime
. It has all the attributes of both classes.
Creating Python datetime
Instances
The three classes that represent dates and times in datetime
have similar initializers. They can be instantiated by passing keyword arguments for each of the attributes, such as year
, date
, or hour
. You can try the code below to get a sense of how each object is created:
>>> from datetime import date, time, datetime
>>> date(year=2020, month=1, day=31)
datetime.date(2020, 1, 31)
>>> time(hour=13, minute=14, second=31)
datetime.time(13, 14, 31)
>>> datetime(year=2020, month=1, day=31, hour=13, minute=14, second=31)
datetime.datetime(2020, 1, 31, 13, 14, 31)
In this code, you import the three main classes from datetime
and instantiate each of them by passing arguments to the constructor. You can see that this code is somewhat verbose, and if you don’t have the information you need as integers, these techniques can’t be used to create datetime
instances.
Fortunately, datetime
provides several other convenient ways to create datetime
instances. These methods don’t require you to use integers to specify each attribute, but instead allow you to use some other information:
date.today()
creates adatetime.date
instance with the current local date.datetime.now()
creates adatetime.datetime
instance with the current local date and time.datetime.combine()
combines instances ofdatetime.date
anddatetime.time
into a singledatetime.datetime
instance.
These three ways of creating datetime
instances are helpful when you don’t know in advance what information you need to pass into the basic initializers. You can try out this code to see how the alternate initializers work:
>>> from datetime import date, time, datetime
>>> today = date.today()
>>> today
datetime.date(2020, 1, 24)
>>> now = datetime.now()
>>> now
datetime.datetime(2020, 1, 24, 14, 4, 57, 10015)
>>> current_time = time(now.hour, now.minute, now.second)
>>> datetime.combine(today, current_time)
datetime.datetime(2020, 1, 24, 14, 4, 57)
In this code, you use date.today()
, datetime.now()
, and datetime.combine()
to create instances of date
, datetime
, and time
objects. Each instance is stored in a different variable:
today
is adate
instance that has only the year, month, and day.now
is adatetime
instance that has the year, month, day, hour, minute, second, and microseconds.current_time
is atime
instance that has the hour, minute, and second set to the same values asnow
.
On the last line, you combine the date information in today
with the time information in current_time
to produce a new datetime
instance.
Warning: datetime
also provides datetime.utcnow()
, which returns an instance of datetime
at the current UTC. However, the Python documentation recommends against using this method because it doesn’t include any time zone information in the resulting instance.
Using datetime.utcnow()
may produce some surprising results when doing arithmetic or comparisons between datetime
instances. In a later section, you’ll see how to assign time zone information to datetime
instances.
Using Strings to Create Python datetime
Instances
Another way to create date
instances is to use .fromisoformat()
. To use this method, you provide a string with the date in the ISO 8601 format that you learned about earlier. For instance, you might provide a string with the year, month, and date specified:
2020-01-31
This string represents the date January 31, 2020, according to the ISO 8601 format. You can create a date
instance with the following example:
>>> from datetime import date
>>> date.fromisoformat("2020-01-31")
datetime.date(2020, 1, 31)
In this code, you use date.fromisoformat()
to create a date
instance for January 31, 2020. This method is very useful because it’s based on the ISO 8601 standard. But what if you have a string that represents a date and time but isn’t in the ISO 8601 format?
Fortunately, Python datetime
provides a method called .strptime()
to handle this situation. This method uses a special mini-language to tell Python which parts of the string are associated with the datetime
attributes.
To construct a datetime
from a string using .strptime()
, you have to tell Python what each of the parts of the string represents using formatting codes from the mini-language. You can try this example to see how .strptime()
works:
1>>> date_string = "01-31-2020 14:45:37"
2>>> format_string = "%m-%d-%Y %H:%M:%S"
On line 1, you create date_string
, which represents the date and time January 31, 2020, at 2:45:37 PM. On line 2, you create format_string
, which uses the mini-language to specify how the parts of date_string
will be turned into datetime
attributes.
In format_string
, you include several formatting codes and all of the dashes (-
), colons (:
), and spaces exactly as they appear in date_string
. To process the date and time in date_string
, you include the following formatting codes:
Component | Code | Value |
---|---|---|
Year (as four-digit integer ) | %Y |
2020 |
Month (as zero-padded decimal) | %m |
01 |
Date (as zero-padded decimal) | %d |
31 |
Hour (as zero-padded decimal with 24-hour clock) | %H |
14 |
Minute (as zero-padded decimal) | %M |
45 |
Second (as zero-padded decimal) | %S |
37 |
A complete listing of all of the options in the mini-language is outside the scope of this tutorial, but you can find several good references on the web, including in Python’s documentation and on a website called strftime.org.
Now that date_string
and format_string
are defined, you can use them to create a datetime
instance. Here’s an example of how .strptime()
works:
3>>> from datetime import datetime
4>>> datetime.strptime(date_string, format_string)
5datetime.datetime(2020, 1, 31, 14, 45, 37)
In this code, you import datetime
on line 3 and use datetime.strptime()
with date_string
and format_string
on line 4. Finally, line 5 shows the values of the attributes in the datetime
instance created by .strptime()
. You can see that they match the values shown in the table above.
Note: There are more advanced ways to create datetime
instances, but they involve using third-party libraries that must be installed. One particularly neat library is called dateparser
, which allows you to provide natural language string inputs. The input is even supported in a number of languages:
1>>> import dateparser
2>>> dateparser.parse("yesterday")
3datetime.datetime(2020, 3, 13, 14, 39, 1, 350918)
4>>> dateparser.parse("morgen")
5datetime.datetime(2020, 3, 15, 14, 39, 7, 314754)
In this code, you use dateparser
to create two datetime
instances by passing two different string representations of time. On line 1, you import dateparser
. Then, on line 2, you use .parse()
with the argument "yesterday"
to create a datetime
instance twenty-four hours in the past. At the time of writing, this was March 13, 2020, at 2:39 PM.
On line 3, you use .parse()
with the argument "morgen"
. Morgen is the German word for tomorrow, so dateparser
creates a datetime
instance twenty-four hours in the future. At the time of writing, this was March 15 at 2:39 PM.
Starting Your PyCon Countdown
Now you have enough information to start working on a countdown clock for next year’s PyCon US! PyCon US 2021 will start on May 12, 2021 in Pittsburgh, PA. With the 2020 event having been canceled, many Pythonistas are extra excited for next year’s gathering. This is a great way to keep track of how long you’ll need to wait and boost your datetime
skills at the same time!
To get started, create a file called pyconcd.py
and add this code:
# pyconcd.py
from datetime import datetime
PYCON_DATE = datetime(year=2021, month=5, day=12, hour=8)
countdown = PYCON_DATE - datetime.now()
print(f"Countdown to PyCon US 2021: {countdown}")
In this code, you import datetime
from datetime
and define a constant, PYCON_DATE
, that stores the date of the next PyCon US. You don’t expect the date of PyCon to change, so you name the variable in all caps to indicate that it’s a constant.
Next, you compute the difference between datetime.now()
, which is the current time, and PYCON_DATE
. Taking the difference between two datetime
instances returns a datetime.timedelta
instance.
timedelta
instances represent the change in time between two datetime
instances. The delta in the name is a reference to the Greek letter delta, which is used in science and engineering to mean a change. You’ll learn more later about how to use timedelta
for more general arithmetic operations.
Finally the printed output, as of April 9, 2020 at a little before 9:30 PM is:
Countdown to PyCon US 2021: 397 days, 10:35:32.139350
Only 397 days until PyCon US 2021! This output is a little clunky, so later on you’ll see how you can improve the formatting. If you run this script on a different day, you’ll get a different output. If you run the script after May 12, 2021 at 8:00 AM, you’ll get a negative amount of time remaining!
Working With Time Zones
As you saw earlier, storing the time zone in which a date occurs is an important aspect of ensuring your code is correct. Python datetime
provides tzinfo
, which is an abstract base class that allows datetime.datetime
and datetime.time
to include time zone information, including an idea of daylight saving time.
However, datetime
does not provide a direct way to interact with the IANA time zone database. The Python datetime.tzinfo
documentation recommends using a third-party package called dateutil
. You can install dateutil
with pip
:
$ python -m pip install python-dateutil
Note that the name of the package that you install from PyPI, python-dateutil
, is different from the name that you use to import the package, which is just dateutil
.
Using dateutil
to Add Time Zones to Python datetime
One reason that dateutil
is so useful is that it includes an interface to the IANA time zone database. This takes the hassle out of assigning time zones to your datetime
instances. Try out this example to see how to set a datetime
instance to have your local time zone:
>>> from dateutil import tz
>>> from datetime import datetime
>>> now = datetime.now(tz=tz.tzlocal())
>>> now
datetime.datetime(2020, 1, 26, 0, 55, 3, 372824, tzinfo=tzlocal())
>>> now.tzname()
'Eastern Standard Time'
In this example, you import tz
from dateutil
and datetime
from datetime
. You then create a datetime
instance set to the current time using .now()
.
You also pass the tz
keyword to .now()
and set tz
equal to tz.tzlocal()
. In dateutil
, tz.tzlocal()
returns a concrete instance of datetime.tzinfo
. This means that it can represent all the necessary time zone offset and daylight saving time information that datetime
needs.
You also print the name of the time zone using .tzname()
, which prints 'Eastern Standard Time'
. This is the output for Windows, but on macOS or Linux, your output might read 'EST'
if you’re in the US Eastern time zone during the winter.
You can also create time zones that are not the same as the time zone reported by your computer. To do this, you’ll use tz.gettz()
and pass the official IANA name for the time zone you’re interested in. Here’s an example of how to use tz.gettz()
:
>>> from dateutil import tz
>>> from datetime import datetime
>>> London_tz = tz.gettz("Europe/London")
>>> now = datetime.now(tz=London_tz)
>>> now
datetime.datetime(2020, 1, 26, 6, 14, 53, 513460, tzinfo=tzfile('GB-Eire'))
>>> now.tzname()
'GMT'
In this example, you use tz.gettz()
to retrieve the time zone information for London, United Kingdom and store it in London_tz
. You then retrieve the current time, setting the time zone to London_tz
.
On Windows, this gives the tzinfo
attribute the value tzfile('GB-Eire')
. On macOS or Linux, the tzinfo
attribute will look something like tzfile('/usr/share/zoneinfo/Europe/London)
, but it might be slightly different depending on where dateutil
pulls the time zone data from.
You also use tzname()
to print the name of the time zone, which is now 'GMT'
, meaning Greenwich Mean Time. This output is the same on Windows, macOS, and Linux.
In an earlier section, you learned that you shouldn’t use .utcnow()
to create a datetime
instance at the current UTC. Now you know how to use dateutil.tz
to supply a time zone to the datetime
instance. Here’s an example modified from the recommendation in the Python documentation:
>>> from dateutil import tz
>>> from datetime import datetime
>>> datetime.now(tz=tz.UTC)
datetime.datetime(2020, 3, 14, 19, 1, 20, 228415, tzinfo=tzutc())
In this code, you use tz.UTC
to set the time zone of datetime.now()
to the UTC time zone. This method is recommended over using utcnow()
because utcnow()
returns a naive datetime
instance, whereas the method demonstrated here returns an aware datetime
instance.
Next, you’ll take a small detour to learn about naive vs aware datetime
instances. If you already know all about this, then you can skip ahead to improve your PyCon countdown with time zone information.
Comparing Naive and Aware Python datetime
Instances
Python datetime
instances support two types of operation, naive and aware. The basic difference between them is that naive instances don’t contain time zone information, whereas aware instances do. More formally, to quote the Python documentation:
An aware object represents a specific moment in time that is not open to interpretation. A naive object does not contain enough information to unambiguously locate itself relative to other date/time objects. (Source)
This is an important distinction for working with Python datetime
. An aware datetime
instance can compare itself unambiguously to other aware datetime
instances and will always return the correct time interval when used in arithmetic operations.
Naive datetime
instances, on the other hand, may be ambiguous. One example of this ambiguity relates to daylight saving time. Areas that practice daylight saving time turn the clocks forward one hour in the spring and backward one hour in the fall. This typically happens at 2:00 AM local time. In the spring, the hour from 2:00 AM to 2:59 AM never happens, and in the fall, the hour from 1:00 AM to 1:59 AM happens twice!
Practically, what happens is that the offset from UTC in these time zones changes throughout the year. IANA tracks these changes and catalogs them in the different database files that your computer has installed. Using a library like dateutil
, which uses the IANA database under the hood, is a great way to make sure that your code properly handles arithmetic with time.
Note: In Python, the difference between naive and aware datetime
instances is determined by the tzinfo
attribute. An aware datetime
instance has the tzinfo
attribute equal to a subclass of the datetime.tzinfo
abstract base class.
Python 3.8 and below provide one concrete implementation of tzinfo
called timezone
. However, timezone
is limited to expressing fixed offsets from UTC that cannot change throughout the year, so it isn’t that useful when you need to account for changes such as daylight saving time.
Python 3.9 includes a new module called zoneinfo
that provides a concrete implementation of tzinfo
that tracks the IANA database, so it includes changes like daylight saving time. However, until Python 3.9 becomes widely used, it probably makes sense to rely on dateutil
if you need to support multiple Python versions.
dateutil
also provides several concrete implementations of tzinfo
in the tz
module that you used earlier. You can check out the dateutil.tz
documentation for more information.
This doesn’t mean that you always need to use aware datetime
instances. But aware instances are crucial if you’re comparing times with each other, especially if you’re comparing times in different parts of the world.
Improving Your PyCon Countdown
Now that you know how to add time zone information to a Python datetime
instance, you can improve your PyCon countdown code. Earlier, you used the standard datetime
constructor to pass the year, month, day, and hour that PyCon will start. You can update your code to use the dateutil.parser
module, which provides a more natural interface for creating datetime
instances:
# pyconcd.py
from dateutil import parser, tz
from datetime import datetime
PYCON_DATE = parser.parse("May 12, 2021 8:00 AM")
PYCON_DATE = PYCON_DATE.replace(tzinfo=tz.gettz("America/New_York"))
now = datetime.now(tz=tz.tzlocal())
countdown = PYCON_DATE - now
print(f"Countdown to PyCon US 2021: {countdown}")
In this code, you import parser
and tz
from dateutil
and datetime
from datetime
. Next, you use parser.parse()
to read the date of the next PyCon US from a string. This is much more readable than the plain datetime
constructor.
parser.parse()
returns a naive datetime
instance, so you use .replace()
to change the tzinfo
to the America/New_York
time zone. PyCon US 2021 will take place in Pittsburgh, Pennsylvania, which is in the US Eastern time zone. The canonical name for that time zone is America/New_York
since New York City is the largest city in the time zone.
PYCON_DATE
is an aware datetime
instance with the time zone set to US Eastern time. Since May 12 is after daylight saving time takes effect, the time zone name is 'EDT'
, or 'Eastern Daylight Time'
.
Next, you create now
to represent the current instant of time and give it your local time zone. Last, you find the timedelta
between PYCON_DATE
and now
and print the result. If you’re in a locale that does not adjust the clocks for daylight saving time, then you may see the number of hours remaining until PyCon change by an hour.
Doing Arithmetic With Python datetime
Python datetime
instances support several types of arithmetic. As you saw earlier, this relies on using timedelta
instances to represent time intervals. timedelta
is very useful because it’s built into the Python standard library. Here’s an example of how to work with timedelta
:
>>> from datetime import datetime, timedelta
>>> now = datetime.now()
>>> now
datetime.datetime(2020, 1, 26, 9, 37, 46, 380905)
>>> tomorrow = timedelta(days=+1)
>>> now + tomorrow
datetime.datetime(2020, 1, 27, 9, 37, 46, 380905)
In this code, you create now
, which stores the current time, and tomorrow
, which is a timedelta
of +1
days. Next, you add now
and tomorrow
to produce a datetime
instance one day in the future. Note that working with naive datetime
instances, as you are here, means that the day
attribute of the datetime
increments by one and does not account for any repeated or skipped time intervals.
timedelta
instances also support negative values as the input to the arguments:
>>> yesterday = timedelta(days=-1)
>>> now + yesterday
datetime.datetime(2020, 1, 25, 9, 37, 46, 380905)
In this example, you provide -1
as the input to timedelta
, so when you add now
and yesterday
, the result is a decrease by one in the days
attribute.
timedelta
instances support addition and subtraction as well as positive and negative integers for all arguments. You can even provide a mix of positive and negative arguments. For instance, you might want to add three days and subtract four hours:
>>> delta = timedelta(days=+3, hours=-4)
>>> now + delta
datetime.datetime(2020, 1, 29, 5, 37, 46, 380905)
In this example, you add three days and subtract four hours, so the new datetime
is at January 29 at 5:37 AM. timedelta
is very useful in this way, but it’s somewhat limited because it cannot add or subtract intervals larger than a day, such as a month or a year. Fortunately, dateutil
provides a more powerful replacement called relativedelta
.
The basic syntax of relativedelta
is very similar to timedelta
. You can provide keyword arguments that produce changes of any number of years, months, days, hours, seconds, or microseconds. You can reproduce the first timedelta
example with this code:
>>> from dateutil.relativedelta import relativedelta
>>> tomorrow = relativedelta(days=+1)
>>> now + tomorrow
datetime.datetime(2020, 1, 27, 9, 37, 46, 380905)
In this example, you use relativedelta
instead of timedelta
to find the datetime
corresponding to tomorrow. Now you can try adding five years, one month, and three days to now
while subtracting four hours and thirty minutes:
>>> delta = relativedelta(years=+5, months=+1, days=+3, hours=-4, minutes=-30)
>>> now + delta
datetime.datetime(2025, 3, 1, 5, 7, 46, 380905)
Notice in this example that the date ends up as March 1, 2025. This is because adding three days to now
would be January 29, and adding one month to that would be February 29, which only exists in a leap year. Since 2025 is not a leap year, the date rolls over to the next month.
You can also use relativedelta
to calculate the difference between two datetime
instances. Earlier, you used the subtraction operator to find the difference between two Python datetime
instances, PYCON_DATE
and now
. With relativedelta
, instead of using the subtraction operator, you need to pass the two datetime
instances as arguments :
>>> now
datetime.datetime(2020, 1, 26, 9, 37, 46, 380905)
>>> tomorrow = datetime(2020, 1, 27, 9, 37, 46, 380905)
>>> relativedelta(now, tomorrow)
relativedelta(days=-1)
In this example, you create a new datetime
instance for tomorrow
by incrementing the days
field by one. Then, you use relativedelta
and pass now
and tomorrow
as the two arguments. dateutil
then takes the difference between these two datetime
instances and returns the result as a relativedelta
instance. In this case, the difference is -1
days, since now
happens before tomorrow
.
dateutil.relativedelta
objects have countless other uses. You can use them to find complex calendar information, such as the next year in which October the 13th falls on a Friday or what the date will be on the last Friday of the current month. You can even use them to replace attributes of a datetime
instance and create, for example, a datetime
one week in the future at 10:00 AM. You can read all about these other uses in the dateutil
documentation.
Finishing Your PyCon Countdown
You now have enough tools in your belt to finish your PyCon 2021 countdown clock and provide a nice interface to use as well. In this section, you’ll use relativedelta
to calculate the time remaining until PyCon, develop a function to print the time remaining in a nice format, and show the date of PyCon to the user.
Using relativedelta
in Your PyCon Countdown
First, replace the plain subtraction operator with relativedelta
. With the subtraction operator, your timedelta
object couldn’t count intervals of time larger than a day. However, relativedelta
allows you to show the years, months, and days remaining:
1# pyconcd.py
2
3from dateutil import parser, tz
4from dateutil.relativedelta import relativedelta
5from datetime import datetime
6
7PYCON_DATE = parser.parse("May 12, 2021 8:00 AM")
8PYCON_DATE = PYCON_DATE.replace(tzinfo=tz.gettz("America/New_York"))
9now = datetime.now(tz=tz.tzlocal())
10
11countdown = relativedelta(PYCON_DATE, now)
12print(f"Countdown to PyCon US 2021: {countdown}")
The only change that you made in this code was to replace line 11 with countdown = relativedelta(PYCON_DATE, now)
. The output from this script should tell you that PyCon US 2021 will happen in about one year and one month, depending on when you run the script.
However, that output isn’t very pretty since it looks like the signature of relativedelta()
. You can build up some prettier output by replacing line 11 in the previous code with the code below:
11def time_amount(time_unit: str, countdown: relativedelta) -> str:
12 t = getattr(countdown, time_unit)
13 return f"{t} {time_unit}" if t != 0 else ""
14
15countdown = relativedelta(PYCON_DATE, now)
16time_units = ["years", "months", "days", "hours", "minutes", "seconds"]
17output = (t for tu in time_units if (t := time_amount(tu, countdown)))
18print("Countdown to PyCon US 2021:", ", ".join(output))
This code requires Python 3.8 because it uses the new walrus operator. You can make this script work on older versions of Python by using a traditional for
loop in place of line 17.
In this code, you define time_amount()
, which takes two arguments, the unit of time and the relativedelta
instance from which the time units should be retrieved. If the amount of time is not equal to zero, then time_amount()
returns a string with the amount of time and the time unit. Otherwise, it returns an empty string.
You use time_amount()
in the comprehension on line 17. That line creates a generator storing the non-empty strings returned from time_amount()
. It uses the walrus operator to assign the return value of time_amount()
to t
and includes t
only if it is True
.
Finally, line 18 prints the final output using .join()
on the generator. Next, you’ll take a look at including the PyCon date in the output from your script.
Showing the PyCon Date in Your PyCon Countdown
Earlier, you learned about creating datetime
instances using .strptime()
. This method uses a special mini-language within Python to specify how the date string is formatted.
Python datetime
has an additional method called .strftime()
that allows you to format a datetime
instance to a string. In a sense, it’s the reverse operation of parsing using .strptime()
. You can differentiate between the two methods by remembering that the p
in .strptime()
stands for parse, and the f
in .strftime()
stands for format.
In your PyCon countdown, you can use .strftime()
to print output to let the user know the date on which PyCon US will start. Remember, you can find the formatting codes that you want to use on strftime.org. Now add this code on line 18 of your PyCon countdown script:
18pycon_date_str = PYCON_DATE.strftime("%A, %B %d, %Y at %H:%M %p %Z")
19print(f"PyCon US 2021 will start on:", pycon_date_str)
20print("Countdown to PyCon US 2021:", ", ".join(output))
In this code, line 18 uses .strftime()
to create a string representing the starting date of PyCon US 2021. The output includes the weekday, month, day, year, hour, minute, AM or PM, and time zone:
Wednesday, May 12, 2021 at 08:00 AM EDT
On line 19, you print this string for the user to see with some explanatory text. The last line prints the amount of time remaining until the PyCon start date. Next, you’ll finish your script to make it easier for other people to reuse.
Finalizing Your PyCon Countdown
The final step that you’ll want take is to follow Python best practices and put the code that produces output into a main()
function. You can check out the full, final code after applying all these changes:
1# pyconcd.py
2
3from dateutil import parser, tz
4from dateutil.relativedelta import relativedelta
5from datetime import datetime
6
7PYCON_DATE = parser.parse("May 12, 2021 8:00 AM")
8PYCON_DATE = PYCON_DATE.replace(tzinfo=tz.gettz("America/New_York"))
9
10def time_amount(time_unit: str, countdown: relativedelta) -> str:
11 t = getattr(countdown, time_unit)
12 return f"{t} {time_unit}" if t != 0 else ""
13
14def main():
15 now = datetime.now(tz=tz.tzlocal())
16 countdown = relativedelta(PYCON_DATE, now)
17 time_units = ["years", "months", "days", "hours", "minutes", "seconds"]
18 output = (t for tu in time_units if (t := time_amount(tu, countdown)))
19 pycon_date_str = PYCON_DATE.strftime("%A, %B %d, %Y at %H:%M %p %Z")
20 print(f"PyCon US 2021 will start on:", pycon_date_str)
21 print("Countdown to PyCon US 2021:", ", ".join(output))
22
23if __name__ == "__main__":
24 main()
In this code, you move print()
and the code used for the generator into main()
. On line 23, you use the guard clause to make sure that main()
only runs when this file is executed as a script. This allows other people to import your code and reuse PYCON_DATE
, for instance, if they’d like.
Now you can modify this script as much as you want. One neat thing to do might be to allow the user to change the time zone associated with now
by passing a command-line argument. You could also change the PYCON_DATE
to something closer to home, say PyCon Africa or EuroPython.
To get even more excited about PyCon, check out Real Python at PyCon US 2019 and How to Get the Most Out of PyCon!
Alternatives to Python datetime
and dateutil
Python datetime
and dateutil
are a powerful combination of libraries when you’re working with dates and times. dateutil
is even recommended in the Python documentation. However, there are many other libraries that you can use to work with dates and times in Python. Some of these rely on datetime
and dateutil
, while others are completely independent replacements:
- pytz provides time zone information similar to
dateutil
. It uses a somewhat different interface than the standarddatetime.tzinfo
, so be aware of the potential problems if you decide to use it. - Arrow provides a drop-in replacement for
datetime
. It’s inspired bymoment.js
, so if you’re coming from web development, then this might be a more familiar interface. - Pendulum provides another drop-in replacement for
datetime
. It includes a time zone interface and an improvedtimedelta
implementation. - Maya provides a similar interface as
datetime
. It relies on Pendulum for parts of the parsing library. - dateparser provides an interface to generate
datetime
instances from human-readable text. It’s flexible and supports many languages.
In addition, if you work heavily with NumPy, Pandas, or other data science packages, then there are a few options that might be useful to you:
- NumPy provides a similar API to the built-in Python
datetime
library, but the NumPy version can be used in arrays. - Pandas provides support for time-series data in DataFrames, usually sequential values of time-based events, by using the NumPy
datetime
module. - cftime provides support for calendars other than the proleptic Gregorian calendar as well as other time units conforming to the Climate and Forecasting (CF) conventions. It’s used by the
xarray
package to provide time-series support.
Further Reading
Since programming with time can be so complicated, there are many resources on the web to help you learn more about it. Fortunately, this is a problem that many people who work in every programming language have thought about, so you can usually find information or tools to help with any problem you may have. Here’s a selected list of articles and videos that I found helpful in writing this tutorial:
- Daylight saving time and time zone best practices
- Storing UTC is not a Silver Bullet
- How to save datetimes for future events
- Coding Best Practices Using DateTime in the .NET Framework
- Computerphile: The Problem with Time & Timezones
- The Complexity of Time Data Programming
In addition, Paul Ganssle is a core contributor to CPython and the current maintainer of dateutil
. His articles and videos are a great resource for Python users:
Conclusion
In this tutorial, you learned about programming with dates and times and why it often leads to errors and confusion. You also learned about the Python datetime
and dateutil
modules as well as how to work with time zones in your code.
Now you can:
- Store dates in a good, future-proof format in your programs
- Create Python
datetime
instances with formatted strings - Add time zone information to
datetime
instances withdateutil
- Perform arithmetic operations with
datetime
instances usingrelativedelta
In the end, you created a script that counts down the time remaining until the next PyCon US so you can get excited for the biggest Python gathering around. Dates and times can be tricky, but with these Python tools in your arsenal, you’re ready to tackle the toughest problems!
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Using Python's datetime Module