8.3. Use datetime Instead of time for Local Clocks

Coordinated Universal Time (UTC) is the standard, time-zoneindependent representation of time. UTC works great for computers that represent time as seconds since the UNIX epoch. But UTC isn’t ideal for humans. Humans reference time relative to where they’re currently located. People say “noon” or “8 am” instead of “UTC 15:00 minus 7 hours.” If your program handles time, you’ll probably find yourself converting time between UTC and local clocks for the sake of human understanding.

Python provides two ways of accomplishing time zone conversions. The old way, using the time built-in module, is terribly error prone. The new way, using the datetime built-in module, works great with some help from the community-built package named pytz.

You should be acquainted with both time and datetime to thoroughly understand why datetime is the best choice and time should be avoided.

8.3.1. The time Module

The localtime function from the time built-in module lets you convert a UNIX timestamp (seconds since the UNIX epoch in UTC) to a local time that matches the host computer’s time zone (Pacific Daylight Time in my case). This local time can be printed in human-readable format using the strftime function:

Click here to view code image

>>> import time
>>>
>>> now = 1552774475
>>> local_tuple = time.localtime(now)
>>> time_format = '%Y-%m-%d %H:%M:%S'
>>> time_str = time.strftime(time_format, local_tuple)
>>> print(time_str)
2019-03-16 22:14:35

You’ll often need to go the other way as well, starting with user input in human-readable local time and converting it to UTC time. You can do this by using the strptime function to parse the time string, and then calling mktime to convert local time to a UNIX timestamp:

Click here to view code image

>>> time_tuple = time.strptime(time_str, time_format)
>>> utc_now = time.mktime(time_tuple)
>>> print(utc_now)
1552774475.0

How do you convert local time in one time zone to local time in another time zone? For example, say that I’m taking a flight between San Francisco and New York, and I want to know what time it will be in San Francisco when I’ve arrived in New York.

I might initially assume that I can directly manipulate the return values from the time, localtime, and strptime functions to do time zone conversions. But this is a very bad idea. Time zones change all the time due to local laws. It’s too complicated to manage yourself, especially if you want to handle every global city for flight departures and arrivals.

Many operating systems have configuration files that keep up with the time zone changes automatically. Python lets you use these time zones through the time module if your platform supports it. On other platforms, such as Windows, some time zone functionality isn’t available from time at all. For example, here I parse a departure time from the San Francisco time zone, Pacific Daylight Time (PDT):

Click here to view code image

import os

if os.name == 'nt':

print("This example doesn't work on Windows")

else:

parse_format = '%Y-%m-%d %H:%M:%S %Z' depart_sfo = '2019-03-16 15:45:16 PDT' time_tuple = time.strptime(depart_sfo, parse_format) time_str = time.strftime(time_format, time_tuple) print(time_str)

>>>
2019-03-16 15:45:16

After seeing that ‘PDT’ works with the strptime function, I might also assume that other time zones known to my computer will work. Unfortunately, this isn’t the case. strptime raises an exception when it sees Eastern Daylight Time (EDT), which is the time zone for New York:

arrival_nyc = '2019-03-16 23:33:24 EDT' time_tuple = time.strptime(arrival_nyc, time_format)

>>>
Traceback ...
ValueError: unconverted data remains:  EDT

The problem here is the platform-dependent nature of the time module. Its behavior is determined by how the underlying C functions work with the host operating system. This makes the functionality of the time module unreliable in Python. The time module fails to consistently work properly for multiple local times. Thus, you should avoid using the time module for this purpose. If you must use time, use it only to convert between UTC and the host computer’s local time. For all other types of conversions, use the datetime module.

The datetime Module The second option for representing times in Python is the datetime class from the datetime built-in module. Like the time module, datetime can be used to convert from the current time in UTC to local time.

Here, I convert the present time in UTC to my computer’s local time, PDT:

Click here to view code image

>>> from datetime import datetime, timezone
>>>
>>> now = datetime(2019, 3, 16, 22, 14, 35)
>>> now_utc = now.replace(tzinfo=timezone.utc)
>>> now_local = now_utc.astimezone()
>>> print(now_local)
2019-03-16 22:14:35+00:00

The datetime module can also easily convert a local time back to a UNIX timestamp in UTC:

Click here to view code image

>>> time_str = '2019-03-16 15:14:35'
>>> now = datetime.strptime(time_str, time_format)
>>> time_tuple = now.timetuple()
>>> utc_now = time.mktime(time_tuple)
>>> print(utc_now)
1552749275.0

Unlike the time module, the datetime module has facilities for reliably converting from one local time to another local time. However, datetime only provides the machinery for time zone operations with its tzinfo class and related methods. The Python default installation is missing time zone definitions besides UTC.

Luckily, the Python community has addressed this gap with the pytz module that’s available for download from the Python Package Index (see Item 82: “Know Where to Find Community-Built Modules” for how to install it). pytz contains a full database of every time zone definition you might need.

To use pytz effectively, you should always convert local times to UTC first. Perform any datetime operations you need on the UTC values (such as offsetting). Then, convert to local times as a final step.

For example, here I convert a New York City flight arrival time to a UTC datetime. Although some of these calls seem redundant, all of them are necessary when using pytz:

Click here to view code image

>>> import pytz
>>>
>>> arrival_nyc = '2019-03-16 23:33:24'
>>> nyc_dt_naive = datetime.strptime(arrival_nyc, time_format)
>>> eastern = pytz.timezone('US/Eastern')
>>> nyc_dt = eastern.localize(nyc_dt_naive)
>>> utc_dt = pytz.utc.normalize(nyc_dt.astimezone(pytz.utc))
>>> print(utc_dt)
2019-03-17 03:33:24+00:00

Once I have a UTC datetime, I can convert it to San Francisco local time:

Click here to view code image

>>> pacific = pytz.timezone('US/Pacific')
>>> sf_dt = pacific.normalize(utc_dt.astimezone(pacific))
>>> print(sf_dt)
2019-03-16 20:33:24-07:00

Just as easily, I can convert it to the local time in Nepal:

Click here to view code image

>>> nepal = pytz.timezone('Asia/Katmandu')
>>> nepal_dt = nepal.normalize(utc_dt.astimezone(nepal))
>>> print(nepal_dt)
2019-03-17 09:18:24+05:45

With datetime and pytz, these conversions are consistent across all environments, regardless of what operating system the host computer is running.

8.3.2. Things to Remember

✦ Avoid using the time module for translating between different time zones.

✦ Use the datetime built-in module along with the pytz community module to reliably convert between times in different time zones.

✦ Always represent time in UTC and do conversions to local time as the very final step before presentation.