Using Python datetime to Work With Dates and Times

Using Python datetime to Work With Dates and Times

by Bryan Weber May 04, 2020 intermediate

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!

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.

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.

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.

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:

  1. calendar outputs calendars and provides functions using an idealized Gregorian calendar.
  2. datetime supplies classes for manipulating dates and times.
  3. 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:

  1. datetime.date is an idealized date that assumes the Gregorian calendar extends infinitely into the future and past. This object stores the year, month, and day as attributes.
  2. datetime.time is an idealized time that assumes there are 86,400 seconds per day with no leap seconds. This object stores the hour, minute, second, microsecond, and tzinfo (time zone information).
  3. datetime.datetime is a combination of a date and a time. 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:

  1. date.today() creates a datetime.date instance with the current local date.
  2. datetime.now() creates a datetime.datetime instance with the current local date and time.
  3. datetime.combine() combines instances of datetime.date and datetime.time into a single datetime.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:

  1. today is a date instance that has only the year, month, and day.
  2. now is a datetime instance that has the year, month, day, hour, minute, second, and microseconds.
  3. current_time is a time instance that has the hour, minute, and second set to the same values as now.

On the last line, you combine the date information in today with the time information in current_time to produce a new datetime instance.

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.

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.

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 standard datetime.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 by moment.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 improved timedelta 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:

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 with dateutil
  • Perform arithmetic operations with datetime instances using relativedelta

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!

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Bryan Weber

Bryan Weber Bryan Weber

Bryan is a mechanical engineering professor and a core developer of Cantera, the open-source platform for thermodynamics, chemical kinetics, and transport.

» More about Bryan

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Real Python Comment Policy: The most useful comments are those written with the goal of learning from or helping out other readers—after reading the whole article and all the earlier comments. Complaints and insults generally won’t make the cut here.

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Keep Learning

Related Tutorial Categories: intermediate