13

Using pytz, I know how to get a listing a Timezone names, but I would like to get all possible Timezone abbreviations for each Timezone name:

import pytz
list(pytz.common_timezones)
['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa',...]

What I am looking for is given any Timezone abbreviation, example PST or PDT, ignoring current datetime (e.g. now), return the all possible Timezone name, in this case a list that would include America/Los_Angeles.

Thanks

jfs
  • 399,953
  • 195
  • 994
  • 1,670
jeff00seattle
  • 1,291
  • 5
  • 17
  • 31

5 Answers5

9

Since you wish to ignore the current datetime, it sounds like you want to find any timezone which ever used the given abbreviation at any time in the past. That information is in the Olson database and accessible through pytz. However, pytz stores this information in the private attribute, tzone._transition_info:

import collections
import datetime as DT
import pytz

tzones = collections.defaultdict(set)
abbrevs = collections.defaultdict(set)

for name in pytz.all_timezones:
    tzone = pytz.timezone(name)
    for utcoffset, dstoffset, tzabbrev in getattr(
            tzone, '_transition_info', [[None, None, DT.datetime.now(tzone).tzname()]]):
        tzones[tzabbrev].add(name)
        abbrevs[name].add(tzabbrev)

The reason for the third (default) argument to gettattr is to handle a few timezones, such as Africa/Bujumbura, which never had any transitions. So the abbreviation in these cases is the current abbreviation.


In [94]: tzones['PST']
Out[94]: 
{'America/Bahia_Banderas',
 'America/Boise',
 'America/Creston',
 'America/Dawson',
 'America/Dawson_Creek',
 'America/Ensenada',
 'America/Hermosillo',
 'America/Inuvik',
 'America/Juneau',
 'America/Los_Angeles',
 'America/Mazatlan',
 'America/Metlakatla',
 'America/Santa_Isabel',
 'America/Sitka',
 'America/Tijuana',
 'America/Vancouver',
 'America/Whitehorse',
 'Canada/Pacific',
 'Canada/Yukon',
 'Mexico/BajaNorte',
 'Mexico/BajaSur',
 'PST8PDT',
 'Pacific/Pitcairn',
 'US/Pacific',
 'US/Pacific-New'}

In [95]: tzones['PDT']
Out[95]: 
{'America/Boise',
 'America/Dawson',
 'America/Dawson_Creek',
 'America/Ensenada',
 'America/Juneau',
 'America/Los_Angeles',
 'America/Metlakatla',
 'America/Santa_Isabel',
 'America/Sitka',
 'America/Tijuana',
 'America/Vancouver',
 'America/Whitehorse',
 'Canada/Pacific',
 'Canada/Yukon',
 'Mexico/BajaNorte',
 'PST8PDT',
 'US/Pacific',
 'US/Pacific-New'}

In [97]: abbrevs['America/Los_Angeles']
Out[97]: {'LMT', 'PDT', 'PPT', 'PST', 'PWT'}

As Paul points out, note that timezone abbreviations are ambiguous -- they do not necessarily map to timezones with the same utcoffset. For example, both Asia/Shanghai and US/Central use the CST timezone abbreviation.

In [242]: 'Asia/Shanghai' in tzones['CST']
Out[242]: True

In [243]: 'US/Central' in tzones['CST']
Out[243]: True
Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • @J.F.Sebastian: Thanks very much for the improvements. – unutbu Mar 18 '16 at 17:57
  • 2
    It might be worth noting that these don't necessarily actually all refer to the same zone. Consider that `tzones['CST']` includes `'Cuba'`, `'Asia/Shanghai'` and `'US/Central'` – Paul Mar 18 '16 at 18:07
  • This seems to be missing half of all timezones... `tzones['CSD']` => `set()` – Cerin May 03 '23 at 14:04
5

I like unutbu's answer, which led me to the answer that worked for me. I have the locale of the user, so I found that I can use that to remove the ambiguity between time zone abbreviations.

In other words, this fixes the problem with the following:

In [242]: 'Asia/Shanghai' in tzones['CST']
Out[242]: True

In [243]: 'US/Central' in tzones['CST']
Out[243]: True

This function requires a two letter country code:

def GetTimeZoneName(timezone, country_code):

    #see if it's already a valid time zone name
    if timezone in pytz.all_timezones:
        return timezone

    #if it's a number value, then use the Etc/GMT code
    try:
        offset = int(timezone)
        if offset > 0:
            offset = '+' + str(offset)
        else:
            offset = str(offset)
        return 'Etc/GMT' + offset
    except ValueError:
        pass

    #look up the abbreviation
    country_tzones = None
    try:
        country_tzones = pytz.country_timezones[country_code]
    except:
        pass

    set_zones = set()
    if country_tzones is not None and len(country_tzones) > 0:
        for name in country_tzones:
            tzone = pytz.timezone(name)
            for utcoffset, dstoffset, tzabbrev in getattr(tzone, '_transition_info', [[None, None, datetime.datetime.now(tzone).tzname()]]):
                if tzabbrev.upper() == timezone.upper():
                    set_zones.add(name)

        if len(set_zones) > 0:
            return min(set_zones, key=len)

        # none matched, at least pick one in the right country
        return min(country_tzones, key=len)


    #invalid country, just try to match the timezone abbreviation to any time zone
    for name in pytz.all_timezones:
        tzone = pytz.timezone(name)
        for utcoffset, dstoffset, tzabbrev in getattr(tzone, '_transition_info', [[None, None, datetime.datetime.now(tzone).tzname()]]):
            if tzabbrev.upper() == timezone.upper():
                set_zones.add(name)

    return min(set_zones, key=len)

This returns the correct time zones for CST:

>>> GetTimeZoneName('CST','CN')
'Asia/Shanghai'

>>> GetTimeZoneName('CST','US')
'America/Detroit'
HansG600
  • 260
  • 4
  • 12
  • Your example raised an interesting problem, is the `date +%Z` command returned value will be both CST, if called in a Shanghai machine and in a Detroit machine ? – intijk Apr 02 '19 at 17:23
  • @intijk I'm not 100% sure I understand your question. I am not using a date +%Z time. I get the abbreviated time zone from the device. I get "CST" from both a Shanghai machine and a Detroit machine. I'm trying to use this to get the correct pytz time zone. – HansG600 Apr 03 '19 at 18:08
  • I believe Detroit is in eastern time and not central. – James VB Mar 24 '22 at 18:25
5

Updating for py3.9+ (as discussed in PEP615), the new zoneinfo module may help:

from collections import defaultdict
from datetime import datetime as dt
from zoneinfo import available_timezones, ZoneInfo

now = dt.utcnow()
tz_key = lambda tz: ZoneInfo(tz).tzname(now)
tz_map = defaultdict(list)

for tz in available_timezones():
    tz_map[tz_key(tz)].append(tz)
tz_map = {k: sorted(v) for k, v in tz_map.items()}

e.g., print(tz_map['PDT']) would yield: ['America/Ensenada', 'America/Los_Angeles', 'America/Santa_Isabel', 'America/Tijuana', 'America/Vancouver', 'Canada/Pacific', 'Mexico/BajaNorte', 'PST8PDT', 'US/Pacific', 'US/Pacific-New']

Note: timezone draws from locally-installed timezone data. You can also add the 1st party tzdata library (not in standard lib, but maintained by python's core devs: use pip install tzdata).

GG_Python
  • 3,436
  • 5
  • 34
  • 46
  • 2
    note: for a given geographic time zone, different UTC offsets might apply over the course of a year due to DST being active or inactive. Therefore, using `datetime.now()` (no need to use [utcnow](https://blog.ganssle.io/articles/2019/11/utcnow.html) btw.) gives you only part of the full picture (thus only a subsection of all the abbreviations). – FObersteiner Jun 21 '21 at 13:03
1

I did this using the datetime module's strftime function, as it has a %Z format specifier that returns the timezone abbreviation. Here is a filter statement that does what you need:

>>> filter(lambda x: datetime.datetime.now(pytz.timezone(x)).strftime("%Z") == "PDT", pytz.common_timezones)
['America/Dawson',
'America/Los_Angeles',
'America/Tijuana',
'America/Vancouver',
'America/Whitehorse',
'Canada/Pacific',
'US/Pacific']

You can replace "PDT" with whatever timezone you need. Remember you'll also need to import datetime.

Aaron Christiansen
  • 11,584
  • 5
  • 52
  • 78
  • Thanks for the reply. However, this is using `datetime.now()`, which will only give those timezone names that currently match provide timezone abbreviation. For example, _America/Los_Angeles_ currently matches _PST_ but not _PDT_. – jeff00seattle Mar 17 '16 at 17:41
  • `>>> list(filter(lambda x: datetime.now(pytz.timezone(x)).strftime("%Z") == "PDT", pytz.common_timezones)) ['America/Dawson', 'America/Los_Angeles', 'America/Tijuana', 'America/Vancouver', 'America/Whitehorse', 'Canada/Pacific', 'US/Pacific']` – jeff00seattle Mar 17 '16 at 17:46
  • `>>> list(filter(lambda x: datetime.now(pytz.timezone(x)).strftime("%Z") == "PST", pytz.common_timezones)) ['Pacific/Pitcairn']` – jeff00seattle Mar 17 '16 at 17:47
0
import pytz
for tzname in list(pytz.common_timezones):
    print(pytz.timezone(tzname)._tzname)

Similar question

Community
  • 1
  • 1
naleves
  • 11