Working with Datetime Objects and Timezones in Python
Share
Interests
Posted in these interests:
Working with dates and times can be tricky, especially when dealing with timezone conversions. This guide will provide an overview of Python’s datetime module with an emphasis on timezone related functions.
1 – What is a datetime object?
First, if you’ve seen datetime used in Python, you’ll notice that it’s both the name of a module and one of many classes within the module. So the datetime module can be imported like this:
import datetime
# datetime.datetime
# datetime.timedelta
# datetime.timezone (python 3.2+)
Or you can simply import the datetime classes you care about:
from datetime import datetime, timedelta
A datetime object is an instance of the datetime.datetime class that represents a single point in time. If you are familiar with object oriented programming, a datetime object is created by instantiating the datetime class with a date.
An easy way to get a datetime object is to use datetime.now.
import datetime
datetime.datetime.now()
> datetime.datetime(2016, 11, 15, 9, 59, 25, 608206)
As you can see, the now method returned a datetime object that represents the point in time when now was called.
You can also create a datetime object by specifying which date you want to represent. At a minimum, instantiating datetime requires at least 3 arguments – year, month, and day.
Let’s instantiate my birthday.
import datetime
datetime.datetime(1985, 10, 20)
> datetime.datetime(1985, 10, 20, 0, 0)
From here, we’ll talk about manipulating, formatting and doing timezone conversions on datetime objects.
2 – Formatting datetime objects
According to the documentation, the “focus of the implementation [of the datetime library] is on efficient attribute extraction for output formatting and manipulation”. So we will discuss extracting attributes and formatting dates.
For this example, we’ll choose a random date.
import datetime
d = datetime.datetime(1984, 1, 10, 23, 30)
There are many occasions where we’ll want to format a datetime object in a specific way. For this, the strftime method comes in very handy. This method allows you to print a string formatted using a series of formatting directives. This is best understood with examples.
d.strftime("%B %d, %Y")
> 'January 10, 1984'
d.strftime("%Y/%m/%d")
> '1984/01/10'
d.strftime("%d %b %y")
> '10 Jan 84'
d.strftime("%Y-%m-%d %H:%M:%S")
> '1984-01-10 23:30:00'
As you can hopefully tell, the same datetime object is used to generate each date format. The format is specified using various formatting directives. For example, %Y corresponds to the full four digit year, while %m corresponds to the two digit decimal number representing the month. See the documentation for a full list of formatting directives.
It’s also possible to access various attributes of the datetime object directly.
d.year
> 1984
d.month
> 1
d.day
> 10
When discussing formatting, it’s valuable to be familiar with ISO 8601, which is an international standard for the representation of dates and times. Python has a method for quickly generating an ISO 8601 formatted date/time:
d.isoformat()
> '1984-01-10T23:30:00'
Now we’ll discuss the opposite of strftime, which is strptime. This is where you create a datetime object from a string. But since the string can be formatted in any way, it’s necessary to tell datetime what format to expect. Using the same set of formatting directives, we can pass in a string and the expected format to create a datetime object.
import datetime
datetime.datetime.strptime("December 25, 2010", "%B %d, %Y")
> datetime.datetime(2010, 12, 25, 0, 0)
Notice how the pattern matches the string exactly. If you use a formatting directives or date doesn’t make sense it will raise an exception.
3 – Enter timezones
So far, instantiating and formatting datetime objects is fairly easy. However, timezones add a little bit of complexity to the equation.
naive vs aware
So far we’ve been dealing only with naive datetime objects. That means the object is naive to any sort of timezone. So a datetime object can be either offset naive or offset aware.
A timezone’s offset refers to how many hours the timezone is from Coordinated Universal Time (UTC).
A naive datetime object contains no timezone information. The easiest way to tell if a datetime object is naive is by checking tzinfo. tzinfo will be set to None of the object is naive.
import datetime
naive = datetime.datetime.now()
naive.tzinfo
> None
To make a datetime object offset aware, you can use the pytz library. First, you have to instantiate a timezone object, and then use that timezone object to “localize” a datetime object. Localizing simply gives the object timezone information.
import datetime
import pytz
d = datetime.datetime.now()
timezone = pytz.timezone("America/Los_Angeles")
d_aware = timezone.localize(d)
d_aware.tzinfo
> <DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>
A naive datetime object is limited in that it cannot locate itself in relation to offset aware datetime objects. For instance:
import datetime
import pytz
d_naive = datetime.datetime.now()
timezone = pytz.timezone("America/Los_Angeles")
d_aware = timezone.localize(d_naive)
d_naive < d_aware
> TypeError: can't compare offset-naive and offset-aware datetimes
When dealing with datetime objects, I’ve come across two pieces of advice with which I generally agree. First, always use “aware” datetime objects. And second, always work in UTC and do timezone conversion as a last step.
More specifically, as pointed out by user jarshwah on reddit, you should store datetimes in UTC and convert on display.
Once you’re familiar with aware datetime objects, timezone conversions are relatively easy. Let’s create a datetime object with a UTC timezone, and convert it to Pacific Standard.
import datetime
import pytz
utc_now = pytz.utc.localize(datetime.datetime.utcnow())
pst_now = utc_now.astimezone(pytz.timezone("America/Los_Angeles"))
pst_now == utc_now
> True
So pst_now and utc_now are different datetime objects with different timezones, yet they are equal. To be certain, we can print the time of each:
utc_now.isoformat()
> '2016-11-16T22:31:18.130822+00:00'
pst_now.isoformat()
> '2016-11-16T14:31:18.130822-08:00'
4 – Measuring duration with timedelta
Often we’ll be working with multiple datetime objects, and we’ll want to compare them. The timedelta class is useful for finding the difference between two dates or times. While datetime objects represent a point in time, timedelta objects represents a duration, like 5 days or 10 seconds.
Suppose I want to know exactly how much older I am than my brother. I’ll create datetime object for each of us representing the day and time of our birth.
import datetime
import pytz
my_birthday = datetime.datetime(1985, 10, 20, 17, 55)
brothers_birthday = datetime.datetime(1992, 6, 25, 18, 30)
Since we like to work with offset aware objects, we’ll add timezone information.
indy = pytz.timezone("America/Indianapolis")
my_birthday = indy.localize(my_birthday)
brothers_birthday = indy.localize(brothers_birthday)
To see how much older I am than my brother, we can simply subtract the two datetime objects. And to see the answer in a human readable way, we can simple print the difference.
diff = brothers_birthday - my_birthday
print(diff)
> 2440 days, 0:35:00
The diff variable is actually a timedelta object that looks like this datetime.timedelta(2440, 2100).
Subtracting a datetime object from another yields a timedelta object, so as you might suspect, subtracting a timedelta object from a datetime object yields a datetime object.
datetime - datetime = timedelta
# and
datetime - timedelta = datetime
Of course the same is true for addition.
This is useful for answering questions like “what was the date 3 weeks ago from yesterday?” or “what day of the week is 90 days from today?”.
To answer the second question, we need to have two things – first, a datetime object representing today and second, a timedelta object representing 90 days.
import datetime
today = datetime.datetime.now()
ninety_days = datetime.timedelta(days=90)
Then we can simply do the calculation.
target_date = today + ninety_days
And since we want to know the day of the week, we can use strftime.
target_date.strftime("%A")
> 'Wednesday'
5 – Conclusion
Dates and times can be tricky, but Python’s datetime class should make things a little bit easier. Hopefully you found this guide to be useful. If you think there are any other essentially examples or topics related to datetime objects and timezones please comment below, and I will try to add them to the guide.