Once upon a time, one of us (Lacey) had spent more than an hour staring at the table in the Python docs that describes date and time formatting strings. I was having a hard time understanding one specific piece of the puzzle as I was trying to write the code to translate a datetime string from an API into a Python datetime object, so I asked for help.
"Why don't you just use
dateutil?" someone asked.
Reader, if you take nothing away from this month's Python column other than there are easier ways than
strptime to convert datetime strings into datetime objects, we will consider ourselves successful.
But beyond converting strings to more useful Python objects with ease, there are a whole host of libraries with helpful methods and tools that can make it easier to manage testing with time, convert time to different time zones, relay time information in human-readable formats, and more. If this is your first foray into dates and times in Python, take a break and read How to work with dates and time with Python. To understand why dealing with dates and times in programming is hard, read Falsehoods programmers believe about time.
This article will introduce you to:
Feel free to skip the ones you're already familiar with and focus on the libraries that are new to you.
Before jumping into other libraries, let's review how we might convert a date string to a Python datetime object using the
Say we receive this date string from an API and need it to exist as a Python datetime object:
This string includes:
- The date in YYYY-MM-DD format
- The letter "T" to indicate that a time is coming
- The time in HH:II:SS format
- A time zone designator "Z," which indicates this time is in UTC (read more about datetime string formatting)
To convert this string to a Python datetime object using the
datetime module, you would start with
datetime.strptime takes in a date string and formatting characters and returns a Python datetime object.
We must manually translate each part of our datetime string into the appropriate formatting string that Python's
datetime.strptime can understand. The four-digit year is represented by
%Y. The two-digit month is
%m. The two-digit day is
%d. Hours in a 24-hour clock are
%H, and zero-padded minutes are
%M. Zero-padded seconds are
Much squinting at the table in the documentation is required to reach these conclusions.
Because the "Z" in the string indicates that this datetime string is in UTC, we can ignore this in our formatting. (Right now, we won't worry about time zones.)
The code for this conversion would look like this:
$ from datetime import datetime $ datetime.strptime('2018-04-29T17:45:25Z', '%Y-%m-%dT%H:%M:%SZ') datetime.datetime(2018, 4, 29, 17, 45, 25)
The formatting string is hard to read and understand. I had to manually account for the letters "T" and "Z" in the original string, as well as the punctuation and the formatting strings like
%m. Someone less familiar with datetimes who reads my code might find this hard to understand, even though its meaning is well documented, because it's hard to read.
Let's look at how other libraries handle this kind of conversion.
dateutil module provides extensions to the
To continue with our parsing example above, achieving the same result with
dateutil is much simpler:
$ from dateutil.parser import parse $ parse('2018-04-29T17:45:25Z') datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=tzutc())
dateutil parser will automatically return the string's time zone if it's included. Since ours was in UTC, you can see that the datetime object returned that. If you want
parse to ignore time zone information entirely and return a naive datetime object, you can pass the parameter
parse like so:
$ from dateutil.parser import parse $ parse('2018-04-29T17:45:25Z', ignoretz=True) datetime.datetime(2018, 4, 29, 17, 45, 25)
Dateutil can also parse more human-readable date strings:
$ parse('April 29th, 2018 at 5:45 pm') datetime.datetime(2018, 4, 29, 17, 45)
dateutil also offers tools like
relativedelta for calculating the time difference between two datetimes or adding/removing time to/from a datetime,
rrule for creating recurring datetimes, and
tz for dealing with time zones, among other tools.
Arrow is another library with the goal of making manipulating, formatting, and otherwise dealing with dates and times friendlier to humans. It includes
dateutil and, according to its docs, aims to "help you work with dates and times with fewer imports and a lot less code."
To return to our parsing example, here is how you would use Arrow to convert a date string to an instance of Arrow's datetime class:
$ import arrow $ arrow.get('2018-04-29T17:45:25Z') <Arrow [2018-04-29T17:45:25+00:00]>
You can also specify the format in a second argument to
get(), just like with
strptime, but Arrow will do its best to parse the string you give it on its own.
get() returns an instance of Arrow's datetime class. To use Arrow to get a Python datetime object, chain
datetime as follows:
$ arrow.get('2018-04-29T17:45:25Z').datetime datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=tzutc())
With the instance of the Arrow datetime class, you have access to Arrow's other helpful methods. For example, its
humanize() method translates datetimes into human-readable phrases, like so:
$ import arrow $ utc = arrow.utcnow() $ utc.humanize() 'seconds ago'
Read more about Arrow's useful methods in its documentation.
Moment's creator considers it "alpha quality," but even though it's in early stages, it is well-liked and we wanted to mention it.
Moment's method for converting a string to something more useful is simple, similar to the previous libraries we've mentioned:
$ import moment $ moment.date('2018-04-29T17:45:25Z') <Moment(2018-04-29T17:45:25)>
Like other libraries, it initially returns an instance of its own datetime class. To return a Python datetime object, add another
$ moment.date('2018-04-29T17:45:25Z').date datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=<StaticTzInfo 'Z'>)
This will convert the Moment datetime class to a Python datetime object.
Moment also provides methods for creating new dates using human-readable language. To create a date for tomorrow:
$ moment.date("tomorrow") <Moment(2018-04-06T11:24:42)>
subtract commands take keyword arguments to make manipulating your dates simple, as well. To get the day after tomorrow, Moment would use this code:
$ moment.date("tomorrow").add(days=1) <Moment(2018-04-07T11:26:48)>
Maya includes other popular libraries that deal with datetimes in Python, including
pendulum, among others. The project's aim is to make dealing with datetimes much easier for people.
Maya's README includes several useful examples. Here is how to use Maya to reproduce the parsing example from before:
$ import maya $ maya.parse('2018-04-29T17:45:25Z').datetime() datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=<UTC>)
Note that we have to call
maya.parse(). If we skip that step, Maya will return an instance of the
Because Maya folds in so many helpful datetime libraries, it can use instances of its
MayaDT class to do things like convert timedeltas to plain language using the
slang_time() method and save datetime intervals in an instance of a single class. Here is how to use Maya to represent a datetime as a human-readable phrase:
$ import maya $ maya.parse('2018-04-29T17:45:25Z').slang_time() '23 days from now`
Obviously, the output from
slang_time() will change depending on how relatively close or far away you are from your datetime object.
Delorean, named for the time-traveling car in the Back to the Future movies, is particularly helpful for manipulating datetimes: converting datetimes to other time zones and adding or subtracting time.
Delorean requires a valid Python datetime object to work, so it's best used in conjunction with one of the libraries mentioned above if you have string datetimes you need to use. To use Delorean with Maya, for example:
$ import maya $ d_t = maya.parse('2018-04-29T17:45:25Z').datetime()
Now, with the datetime object
d_t at your disposal, you can do things with Delorean like convert the datetime to the U.S. Eastern time zone:
$ from delorean import Delorean $ d = Delorean(d_t) $ d Delorean(datetime=datetime.datetime(2018, 4, 29, 17, 45, 25), timezone='UTC') $ d.shift('US/Eastern') Delorean(datetime=datetime.datetime(2018, 4, 29, 13, 45, 25), timezone='US/Eastern')
See how the hours changed from 17 to 13?
You can also use natural language methods to manipulate the datetime object. To get the next Friday following April 29, 2018 (the date we've been using):
$ d.next_friday() Delorean(datetime=datetime.datetime(2018, 5, 4, 13, 45, 25), timezone='US/Eastern')
Read more about Delorean in its documentation.
Freezegun is a library that helps you test with specific datetimes in your Python code. Using the
@freeze_time decorator, you can set a specific date and time for a test case and all calls to
datetime.datetime.utcnow(), etc. will return the date and time you specified. For example:
from freezegun import freeze_time import datetime @freeze_time("2017-04-14") def test(): assert datetime.datetime.now() == datetime.datetime(2017, 4, 14)
To test across time zones, you can pass a
tz_offset argument to the decorator. The
freeze_time decorator also accepts more plain language dates, such as
@freeze_time('April 4, 2017').
Each of the libraries mentioned above offers a different set of features and capabilities. It might be difficult to decide which one best suits your needs. Maya's creator, Kenneth Reitz, says, "All these projects complement each other and are friends."
These libraries share some features, but not others. Some are good at time manipulation, others excel at parsing. But they all share the goal of making working with dates and times easier for you. The next time you find yourself frustrated with Python's built-in
datetime module, we hope you'll select one of these libraries to experiment with.