How to List All Time Zones in Python

In the (time) zone.

Python 3.9 introduced zoneinfo into the standard library. This module reads your operating system’s copy of the tz database. On older versions of Python, you can install the backports.zoneinfo package to use zoneinfo without upgrading.

Note that on some operating systems, notably Windows, there is no accessible copy of the tz database. In this case you should also install the official tzdata package which zoneinfo will read from.

Call zoneinfo.available_timezones() to list all available time zones:

In [1]: import zoneinfo

In [2]: zoneinfo.available_timezones()
Out[1]:
{'Africa/Abidjan',
 'Africa/Accra',
 ...
 'WET',
 'Zulu'}

As the documentation notes, this function opens a lot of files, and recalculates the list on every call. Therefore, it’s best to avoid calling it at import time, and to cache the results. We can create a caching wrapper with functools.lru_cache:

import zoneinfo
from functools import lru_cache


@lru_cache(maxsize=None)
def get_timezones():
    return zoneinfo.available_timezones()

This returns the same results, but caches them in memory on first call:

In [3]: get_timezones()
Out[3]:
{'Africa/Abidjan',
 'Africa/Accra',
 ...
 'WET',
 'Zulu'}

On my computer, the caching cuts the runtime from about 40ms to 1μs, so it’s definitely worth it.

Since the timezone names come from the standard tz database, they are never removed. This means it’s safe to store them as-is to in your application’s database.

With pytz

Before zoneinfo, the Python ecosystem relied on pytz for time zones. If you can migrate to zoneinfo, it’s better, as the pytz API has ambiguities, and it bundles its own copy of the tz database that is not always up to date.

But, if you are using pytz, you can access its list of all time zones in the attribute pytz.all_timezones:

In [1]: import pytz

In [2]: pytz.all_timezones
Out[2]: ['Africa/Abidjan', 'Africa/Accra', ..., 'WET', 'Zulu']

Deprecated names

The zoneinfo documentation notes:

Although it is a somewhat common practice to expose these to end users, these values are designed to be primary keys for representing the relevant zones and not necessarily user-facing elements. Projects like CLDR (the Unicode Common Locale Data Repository) can be used to get more user-friendly strings from these keys.

That said, it’s often easiest to use the time zone names in a UI, such as in a dropdown list. The CLDR project would be ideal, but at time of writing it doesn’t seem like there’s a good package for using it from within Python.

If you are using the time zone names in your UI, you should be aware that some names are deprecated. This is because they have been renamed to follow regional changes. For example, Asia/Calcutta was renamed to Asia/Kolkata, as city’s official name was decolonialized in 2001. To retain backwards compatibility, the tz database maintains old names indefinitely, but encourages using only the new ones.

If presenting the list of time zone names to a user, it would be ideal to filter out the old names. Unfortunately, the copy of the tz database that both zoneinfo and pytz use does not have this information, and contains both deprecated and current names. Fortunately, we can obtain the list of deprecated names from the source database, and filter it from our output.

Download the latest data only distribution of the tz database from the IANA site. Inside it is a file called backward, which is a tab-separated text format. It starts like so:

# tzdb links for backward compatibility

# This file is in the public domain, so clarified as of
# 2009-05-17 by Arthur David Olson.

# This file provides links between current names for timezones
# and their old names.  Many names changed in late 1993.

# Link  TARGET          LINK-NAME
Link    Africa/Nairobi      Africa/Asmera
Link    Africa/Abidjan      Africa/Timbuktu

We can parse this with a little Python code:

import re


def deprecated_aliases():
    aliases = set()
    with open("backward", "r") as fp:
        for line_full in fp:
            line = line_full.strip()
            if not line.startswith("Link\t"):
                continue
            _link, _dest, source = re.split("\t+", line)
            aliases.add(source)
    return aliases

And then update our zoneinfo-based get_timezones() function to remove those deprecated aliases:

import zoneinfo
from functools import lru_cache


@lru_cache(maxsize=None)
def get_timezones():
    return zoneinfo.available_timezones() - deprecated_aliases()

You’ll want to do similar if using pytz.

Now we see fewer names in the result, for example the deprecated name Zulu no longer appears:

In [16]: get_timezones()
Out[16]:
{'Africa/Abidjan',
 'Africa/Accra',
 ...
 'Pacific/Wallis',
 'WET'}

We need to include the backward file with our application. It will need updating in the future, but since deprecations are rare we can do so infrequently—perhaps annually.

Fin

May time be kind to you,

—Adam


Improve your Django develompent experience with my new book.


Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: