# Using pytz for timezones

# Timezones

A timezone is a region where the same standard time is used.

For example, a lot of Western Europe uses the same standard time—Spain, France, Italy, Germany, etc. They are all in the "Central European Time" timezone. In Summer, when daylight savings are in effect, they are all in the "Central European Summer Time" timezone.

If you are running Python in a computer that is using CET, then doing datetime.datetime.now() would give you the local time of the computer.

However if you grab a different computer that is using Pacific Standard Time, then datetime.datetime.now() would give you a different time.

That is why it is important to tell the datetime objects what timezone the time they represent is in, so that no matter what computer is running the code, the time represented will always be the same.

The "central" time is called Universal Time Coordinated (UTC). All other timezones can be described by using UTC as a reference. For example, CET is UTC + 1 hour. PST is UTC - 6 hours.

We would thus write CET as +01:00, and PST as -06:00.

We call that the "offset", and it is usually placed after a date and time. For example:

2019-12-23 11:54:13+01:00

The above time is 11:54 with an offset of 1 hour.

That means that that timezone might be CET (or any other timezone with a +1 hour offset from UTC).

We can calculate the UTC time by subtracting 1 hour from the time, which would put us at 10:54.

# Getting the current date and time in UTC

We've learned that you can get the local time like so:

import datetime

today = datetime.datetime.now()
print(today) # 2019-12-23 15:35:56.133138

When doing this you can ask Python to get you the current time, but translated to a different timezone. Below we pass datetime.timezone.utc to .now() so that Python will give us the current time in the UTC timezone.

import datetime

today = datetime.datetime.now(datetime.timezone.utc)
print(today) # 2019-12-23 15:35:56.133138+00:00

You can see that in my case, the time is the same. That's because, at the moment, my timezone also has an offset of +00:00, so it matches UTC.

If your timezone does not match UTC, you'll see those two times are different (indeed, the difference will be your timezone's offset from UTC).

Note that when we did .now() without passing a timezone, our printed string did not contain an offset. But when we did .now(datetime.timezone.utc), the printed string contained the +00:00 offset.

# Naive vs. aware datetimes

If your datetime object knows what timezone the date and time it represents is in, then we call that an aware datetime object. If it doesn't have timezone information, we call that a naive object.

Aware objects represent specific points in time in specific places in the world. Naive objects only represent specific points in time.

Note that working with naive objects can be dangerous because different pieces of code may interpret the naive object to be using a different timezone. For example, some of your code might use a naive object as if it were in UTC. Some other code might use a naive object as if it were in the current, local timezone.

Therefore it's almost always a good idea to store timezone information in your datetime objects.

# Converting from one timezone to another using pytz

You can convert from one timezone to another using the built-in datetime module, but you'll need to tell Python what timezones exist and can be used by writing a class for each timezone that subclasses the datetime.tzinfo class. You'll also have to do a lot of manual work so that your timezone classes keep track of daylight savings.

Alternatively you can use (and probably should use) the pytz module for working with timezones in your Python code.

First of all, install pytz by following their installation instructions (opens new window) or using pip install pytz.

# Getting a timezone from pytz

The pytz module comes with a whole database of timezones, accessible by their official names (opens new window):

import pytz

eastern = pytz.timezone("US/Eastern")
amsterdam = pytz.timezone("Europe/Amsterdam")

# Adding timezone information to a native datetime

If you have a naive datetime object (from the built-in module), you can use pytz to add timezone information:

import datetime
import pytz

eastern = pytz.timezone("US/Eastern")

local_time = datetime.datetime.now()
print(local_time)  # 2020-02-12 11:47:21.817708
eastern_time = eastern.localize(local_time)
print(eastern_time)  # 2020-02-12 11:47:21.817708-05:00

Note that doing this does not change the time information in the local_time object.

# Converting from one timezone to another

If you have an aware datetime object and you want to translate it to another timezone, then you can also do this with pytz:

import datetime
import pytz

eastern = pytz.timezone("US/Eastern")
amsterdam = pytz.timezone("Europe/Amsterdam")

local_time = datetime.datetime.now()
eastern_time = eastern.localize(local_time)
print(eastern_time)  # 2020-02-12 11:47:21.817708-05:00

amsterdam_time = eastern_time.astimezone(amsterdam)
print(amsterdam_time)  # 2020-02-12 17:47:21.817708+01:00

Now the displayed time has changed because it's the same date and time, but in a different timezone. Both eastern_time and amsterdam_time refer to the same exact point in time.

You can make sure of that by converting each to UTC yourself. Add 5 hours to eastern_time and subtract 1 hour from amsterdam_time to reach a +00:00 offset. You'll see the times are identical.

# Dealing with daylight savings

The pytz module deals with daylight savings with you. For example the US/Eastern timezone may sometimes be at -05:00 and other times at -04:00, depending on the time of year.

# Getting current local time from users

The way to construct a current local time is to:

import datetime
import pytz

local_time = datetime.datetime.now()
utc_local_time = datetime.datetime.now(tz=pytz.utc)

print(local_time)  # datetime.datetime(2020, 4, 26, 11, 52, 56, 47126)
print(utc_local_time)  # datetime.datetime(2020, 4, 26, 10, 52, 56, 47126, tzinfo=<UTC>)

Note that when using .now() together with tz=pytz.utc, the system is able to determine that the time right now if we were in the UTC time zone would be 10:52, and not 11:52 (which is the local time with the current timezone).

# Working with dates and times in software applications

I would recommend to always work with UTC everywhere in your applications:

  1. Ask users for a local time and their timezone.
  2. Use their timezone to localize the local time.
  3. Convert to UTC before saving to the database.

Then, always store UTC in your database. Only change back to the user's local time when you are displaying a date and time to them (and only if your users want to see times in their local time).

That way you will not have to worry about timezones within your application logic; only when dealing with user interaction.

import datetime
import pytz

eastern = pytz.timezone("US/Eastern")

user_date = eastern.localize(datetime.datetime(2020, 4, 15, 6, 0, 0))
utc_date = user_date.astimezone(pytz.utc)
print(utc_date)  # 2020-04-15 10:00:00+00:00

As long as you always work with UTC, and only convert to the user's local timezone when you are displaying information, it will be relatively simple!