Python Dates Times and Timezones

Dealing with time zones is hard, you may know what time zone you are in but what happens when you want to convert your local time to that in another timezone?

Python makes it easy to forget about time zones and generally when dealing with time in your applications you probably don’t think you need to care about this sort of stuff, but if you want to compare one time with another you do, otherwise you could be in trouble!

The following python examples demonstrate how to deal with times in different time zones.

Before we start working with time zones we need to import a couple of modules.

>>> from datetime import datetime
>>> from pytz import timezone

On Debian Linux (or Raspbian) if you get an error importing pytz you need to install the python-tz package (as a super user).

apt-get install python-tz
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  python-tz
0 upgraded, 1 newly installed, 0 to remove and 120 not upgraded.
Need to get 33.7 kB of archives.
After this operation, 127 kB of additional disk space will be used.
  :
  :
  :
Setting up python-tz (2016.7-0.3) ...
#

Now we can return a timezone aware date time object by just by specifying the timezone (it really is that easy so why wouldn’t you want to ensure that every date time object was timezone aware?)

>>> datetime.now(timezone('UTC'))
datetime.datetime(2018, 2, 2, 22, 23, 12, 882387, tzinfo=)
>>> datetime.now(timezone('Europe/London'))
datetime.datetime(2018, 2, 2, 22, 23, 42, 893842, tzinfo=)
>>>

Obviously it wasn’t summer time when I first wrote this but it can take me a while to tidy this sort of stuff up for publication!

To make the times easier for us humans to read you can specify a format string and by specifying the timezone you can compare the times around the world.

>>> datetime.now(timezone('UTC')).strftime("%Y-%m-%d %H:%M:%S %Z%z")
'2018-02-02 22:23:55 UTC+0000'
>>> datetime.now(timezone('Europe/London')).strftime("%Y-%m-%d %H:%M:%S %Z%z")
'2018-02-02 22:24:08 GMT+0000'
>>> datetime.now(timezone('Pacific/Auckland')).strftime("%Y-%m-%d %H:%M:%S %Z%z")
'2018-02-03 11:24:39 NZDT+1300'
>>> datetime.now(timezone('US/Eastern')).strftime("%Y-%m-%d %H:%M:%S %Z%z")
'2018-02-02 17:25:08 EST-0500'
>>> datetime.now(timezone('US/Pacific')).strftime("%Y-%m-%d %H:%M:%S %Z%z")
'2018-02-02 14:25:22 PST-0800'
>>>

Given a date time object in one time zone, you can convert it to another...

>>> now=datetime.now(timezone('Europe/London'))
>>> now.strftime(“%Y-%m-%d %H:%M:%S %Z%z”)
‘2018-02-02 22:24:08 GMT+0000’
>>> now.astimezone(timezone('Europe/Berlin')).strftime("%Y-%m-%d %H:%M:%S %Z%z")
'2018-02-02 23:24:58 CET+0100'
>>>

Note – While it is usually it is enough to specify the time and the time zone, even this can be ambiguous when comparing the time in different time zones during the transition to and from daylight saving time as the example below shows.

At 2am on the 28 Oct 2018 the clocks in London will go back an hour, so converting a time between 1am and 2am to UTC will give a different result depending on whether or not day light saving time was in force.

>>> now=datetime(2018, 10, 28, 1, 30, 00)
>>> now
datetime.datetime(2018, 10, 28, 1, 30)
>>> timezone('Europe/London').localize(now)
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<DstTzInfo ‘Europe/London’ GMT0:00:00 STD>)
>>> timezone('Europe/London').localize(now, is_dst=True)
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<DstTzInfo ‘Europe/London’ BST+1:00:00 DST>)
>>> 
>>> timezone('Europe/London').localize(now).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> timezone('Europe/London').localize(now, is_dst=True).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 0, 30, tzinfo=<UTC>)
>>> 
>>> timezone('UTC').normalize(timezone('Europe/London').localize(now))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> timezone('UTC').normalize(timezone('Europe/London').localize(now, is_dst=True))
datetime.datetime(2018, 10, 28, 0, 30, tzinfo=<UTC>)
>>> 

However in Berlin the time doesn’t change.

>>> timezone('Europe/Berlin').localize(now)
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<DstTzInfo ‘Europe/Berlin’ CEST+2:00:00 DST>)
>>> timezone('Europe/Berlin').localize(now, is_dst=True)
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<DstTzInfo ‘Europe/Berlin’ CEST+2:00:00 DST>)
>>> 
>>> timezone('Europe/Berlin').localize(now).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> timezone('Europe/Berlin').localize(now, is_dst=True).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> 
>>> timezone('UTC').normalize(timezone('Europe/Berlin').localize(now))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> timezone('UTC').normalize(timezone('Europe/Berlin').localize(now, is_dst=True))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> 

As daylight saving time in Berlin ends at 3am, an hour later the situation is reversed..

>>> now=datetime(2018, 10, 28, 2, 30, 00)
>>> now
datetime.datetime(2018, 10, 28, 2, 30)
>>> timezone('Europe/London').localize(now)
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<DstTzInfo ‘Europe/London’ GMT0:00:00 STD>)
>>> timezone('Europe/London').localize(now, is_dst=True)
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<DstTzInfo ‘Europe/London’ GMT0:00:00 STD>)
>>> 
>>> timezone('Europe/London').localize(now).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<UTC>)
>>> timezone('Europe/London').localize(now, is_dst=True).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<UTC>)
>>> 
>>> timezone('UTC').normalize(timezone('Europe/London').localize(now))
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<UTC>)
>>> timezone('UTC').normalize(timezone('Europe/London').localize(now, is_dst=True))
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<UTC>)
>>> 

>>> timezone('Europe/Berlin').localize(now)
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<DstTzInfo ‘Europe/Berlin’ CET+1:00:00 STD>)
>>> timezone('Europe/Berlin').localize(now, is_dst=True)
datetime.datetime(2018, 10, 28, 2, 30, tzinfo=<DstTzInfo ‘Europe/Berlin’ CEST+2:00:00 DST>)
>>> 
>>> timezone('Europe/Berlin').localize(now).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> timezone('Europe/Berlin').localize(now, is_dst=True).astimezone(timezone('UTC'))
datetime.datetime(2018, 10, 28, 0, 30, tzinfo=<UTC>)
>>> 
>>> timezone('UTC').normalize(timezone('Europe/Berlin').localize(now))
datetime.datetime(2018, 10, 28, 1, 30, tzinfo=<UTC>)
>>> timezone('UTC').normalize(timezone('Europe/Berlin').localize(now, is_dst=True))
datetime.datetime(2018, 10, 28, 0, 30, tzinfo=<UTC>)
>>> 

Hopefully this makes it obvious that comparing two different times isn’t always straight forward! Personally I think that when working with times it is better to stick with UTC and only convert it to the local time format when displaying the time, but if you must use local time don’t forget that you need to know the time zone AND if daylight saving time is in force.

References

This entry was posted in Debian, Linux, Programming, Raspbian, Ubuntu and tagged . Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.