Article Overview: In a previous post, we discussed the datetime
module in Python and used it to iterate through a range of dates. Today, let’s learn how to manage timezones in Python using the datetime
module and a third-party package called dateutil
.ย I aim to choose a range of world cities and print the local time in each of those cities. I will also print my local time and UTC, or Coordinated Universal Time. If you’ve not used the datetime
module before, I suggest a quick review of this post before continuing.
The Python datetime module and dateutil package
The Python datetime module dramatically eases the coding complexities when dealing with timezones and leap years. The three most common classes of the datetime module are:
datetime.date()
This class uses the Gregorian calendar, which started in 1582 following a slight adjustment to the Julian calendar. However, datetime.date()
assumes the Gregorian calendar extends infinitely into the past and the future, and it takes the attributes, year, month, and day.
datetime.time()
This class is independent of any particular day and assumes that a day consists of 24 x 60 x 60 seconds and ignores leap seconds. It accepts attributes, hour, minute, second, microsecond, and time zone information, abbreviated to tzinfo
.
datetime.datetime()
This class is a combination of the two classes above and utilises the same attributes of both.
Today we’ll be using only the datetime()
class from the datetime
module.
However, we will introduce some greater functionality with a package called dateutil
, a third-party package that adds power and utility to the standard datetime
module in Python.ย If you don’t have dateutil
installed, you can do so with the following command:
pip install python-dateutil
Dateutil has several modules; however, today, we’ll use the tz
module, allowing timezone implementation by using information from the datetime.tzinfo()
class. If you wish to know more about the dateutil
package, you can find it here.
Are your datetimes aware or naive?
When we speak of someone being naive in English, we usually mean they lack sophistication, are simple, or innocent. In Python, we distinguish between dates and times as naive or aware, meaning that a naive date or time instance does not contain timezone information, whilst an aware one does.
The Python documentation states that an aware date or time instance is a specific moment in time not open to interpretation, and it can locate itself in relation to other aware date or time objects. If I did arithmetic with aware dates or times from different timezones, Python would deliver the correct time interval. If I tried it with a naive object, it would return the wrong information.
For this reason, we use datetime
objects which contain an attribute called tzinfo
, or timezone information, that creates awareness. Tzinfo captures information about the offset from UTC, the timezone names, and whether daylight saving time is in force.
Let’s write some code!
We’ll begin with importing the datetime
module, and then the tz
module from the dateutil
package. As we code, I’ll introduce, use, and explain a few classes found within the tz
module. Then we’ll find the current UTC, or time at zero degrees longitude.
from datetime import datetime as dt from dateutil.tz import * print(dt.now(tzlocal())) # Result # 2021-05-13 16:51:04.250732+01:00
In this code, we imported the datetime
class from the datetime
module and we gave it an alias of dt
just to save me writing the class out longhand each time. We then used the asterisk notation to import all classes of the tz
module which forms part of the dateutil
package.
I called the now()
method which forms part of the datetime
module. The now()
method returns the current date and time of the computer system during the execution of the now statement. However, we passed to the now
method the method tzlocal()
which is in the tz
module and it returns the timezone information, or tzinfo
. This gives access to the timezone and daylight saving information needed to make the datetime
object aware. You can see the returned value is 16:51 on the afternoon of 13 May 2021, and the timezone is +1 hour from UTC. Now weโll see what the time is at zero degrees longitude by calling for the current UTC.
from datetime import datetime as dt from dateutil.tz import * print(dt.now(tzlocal()), '\n') print(dt.now(tzutc())) # Result # 2021-05-13 17:01:16.626317+01:00 # 2021-05-13 16:01:16.627316+00:00
Here weโve introduced another method from the dateutil tz
module, which is called tzutc()
. As youโd expect, it returns an aware datetime
instance in UTC of datetime.now()
. So you see my time in London, which is 17:01 GMT Summer Time, and the time in UTC, which is 16:01 UTC.
Success โ but not that readable
We’ve achieved a result, but it’s not that user friendly. I’m going to format the output from these two lines of code to give the timezone, and we’ll then format the output to be more readable.
from datetime import datetime as dt from dateutil.tz import * local = dt.now(tzlocal()).tzname() print('London: ', dt.now(tzlocal()), local, '\n') utc = dt.now(tzutc()).tzname() print('UTC: ', '\t', dt.now(tzutc()), utc) # Result # London: 2021-05-13 17:22:29.135941+01:00 GMT Summer Time # UTC: 2021-05-13 16:22:29.135941+00:00 UTC
There are a few things happening in this last bit of code. Let’s unpick it. I’ve introduced a new method from the dateutils tz
module, called tzname()
. This method returns the timezone name from the datetime
instances for my time and UTC time, and I’ve passed it to two variables, local and utc respectively. Then in the print line, I’ve simply called the variable after the datetime
instance to print out the timezone info. Of course, I’ve put in the name of the city or zone upfront as a string to enhance readability.
So readability is improving, but we can do more. Let’s format the output of the datetime
instance to be more human friendly.
from datetime import datetime as dt from dateutil.tz import * local = dt.now(tzlocal()).tzname() lon = dt.now(tzlocal()) print('London: ', lon.strftime('%A %d %b %Y %H:%M hrs'), local, '\n') utc = dt.now(tzutc()).tzname() base = dt.now(tzutc()) print('UTC: ', '\t', base.strftime('%A %d %b %Y %H:%M hrs'), utc) # Result # London: Thursday 13 May 2021 17:38 hrs GMT Summer Time # UTC: Thursday 13 May 2021 16:38 hrs UTC
Now that’s a vast improvement. In this code, I introduced the strftime()
method from the datetime
module. This method does a string format on the datetime
instance, hence the name.
I created a datetime
instance for each location and passed it to variables lon
and base
, respectively. I then called the datetime
instance using strftime()
and used specific codes in a string to format the data to be returned. For the complete list of codes, go to this site. To explain the ones we’ve used, here’s a list:
%A | Weekday as localeโs full name. | Thursday |
%d | Day of the month as a zero-padded decimal number. | 13 |
%B | Month as localeโs full name. | May |
%Y | Year with century as a decimal number. | 2021 |
%H | Hour (24-hour clock) as a zero-padded decimal number. | 16 |
%-M | Minute as a decimal number. (Platform specific) | 38 |
Now we have readable format giving all the information we need, we can add in those other cities in which we wish to know the time. Here’s the final bit of code with some reconfiguration and some new methods.
from datetime import datetime as dt from dateutil.tz import * utc = dt.now(tzutc()).tzname() base = dt.now(tzutc()) print('UTC: ', '\t\t\t\t', base.strftime('%A %d %b %Y %H:%M hrs'), utc, '\n') print('East of UTC', '\n') local = dt.now(tzlocal()).tzname() lon = dt.now(tzlocal()) print('London: ', '\t\t\t', lon.strftime('%A %d %b %Y %H:%M hrs'), local) jburg = dt.now(tzoffset('SAST', 7200)) sa = dt.tzname(jburg) print('Johannesburg: ', '\t\t', jburg.strftime('%A %d %b %Y %H:%M hrs'), sa) tokyo = dt.now(tzoffset('JST', 32400)) jp = dt.tzname(tokyo) print('Tokyo: ', '\t\t\t', tokyo.strftime('%A %d %b %Y %H:%M hrs'), jp) kiri = dt.now(tzoffset('LINT', 50400)) ci = dt.tzname(kiri) print('Kiribati: ', '\t\t\t', kiri.strftime('%A %d %b %Y %H:%M hrs'), ci, '\n') print('West of UTC', '\n') wash_dc = dt.now(tzoffset('EDT', -14400)) us = dt.tzname(wash_dc) print('Panama: ', '\t\t\t', wash_dc.strftime('%A %d %b %Y %H:%M hrs'), us) pana = dt.now(tzoffset('PAB', -18000)) pan = dt.tzname(pana) print('Washington DC: ', '\t', pana.strftime('%A %d %b %Y %H:%M hrs'), pan) ckt = dt.now(tzoffset('CKT', -36000)) rar = dt.tzname(ckt) print('Rarotonga: ', '\t\t', ckt.strftime('%A %d %b %Y %H:%M hrs'), rar) alo = dt.now(tzoffset('NUT', -39600)) nut = dt.tzname(alo) print('Niue: ', '\t\t\t\t', alo.strftime('%A %d %b %Y %H:%M hrs'), nut)
Output:
# Result UTC: Friday 14 May 2021 10:34 hrs UTC East of UTC London: Friday 14 May 2021 11:34 hrs GMT Summer Time Johannesburg: Friday 14 May 2021 12:34 hrs SAST Tokyo: Friday 14 May 2021 19:34 hrs JST Kiribati: Saturday 15 May 2021 00:34 hrs LINT West of UTC Panama: Friday 14 May 2021 06:34 hrs EDT Washington DC: Friday 14 May 2021 05:34 hrs PAB Rarotonga: Friday 14 May 2021 00:34 hrs CKT Niue: Thursday 13 May 2021 23:34 hrs NUT
In this code, I’ve introduced another method of the tz
class, which is tzoffset()
. With tzoffset()
you pass two attributes, the name of the timezone and the offset in seconds from UTC. Note that for timezones west of UTC, the offset number is positive; it’s negative for those East of UTC.
I find the world clock website to be useful for time, date, and abbreviation information.
In Summary
In this article, we introduced the datetime
module in Python and the third-party package dateutils
. We aimed to output a list of times in chosen world cities compared to UTC and our local time.
We learned about the datetime.datetime()
class in Python, and the tz
class in dateutils
. We also looked at naive and aware datetime
instances.
Classes we utilised are:
datetime.now() | Returns the current date and time of the computer system during the execution of the now statement. |
tz.local() | Returns the timezone information or tzinfo. |
tz.utc() | Returns an aware datetime instance in UTC |
tz.name() | Returns the timezone name from a datetime instance |
tz.offset() | Accepts two attributes; timezone name and its offset in seconds from UTC |
strftime() | Does a string format on the datetime instance from entered codes |
Finally, we produced a list of chosen cities worldwide and printed formatted dates and times, with their timezone name appended.
I trust this article has been helpful to you. Thanks for reading.