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 datetime
's 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.
The built-in datetime module
Before jumping into other libraries, let's review how we might convert a date string to a Python datetime object using the datetime
module.
Say we receive this date string from an API and need it to exist as a Python datetime object:
2018-04-29T17:45:25Z
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
. strptime
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 %S
.
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 %S
and %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
The dateutil
module provides extensions to the datetime
module.
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())
The 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 ignoretz=True
to 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
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
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 date()
call.
$ 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)>
Its add
and 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
Maya includes other popular libraries that deal with datetimes in Python, including Humanize
, pytz
, and 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 .datetime()
after maya.parse()
. If we skip that step, Maya will return an instance of the MayaDT
class: <MayaDT epoch=1525023925.0>
.
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
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
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.now()
, 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.
3 Comments