53

How do I get the Olson timezone name (such as Australia/Sydney) corresponding to the value given by C's localtime call?

This is the value overridden via TZ, by symlinking /etc/localtime, or setting a TIMEZONE variable in time-related system configuration files.

Matt Joiner
  • 112,946
  • 110
  • 377
  • 526

11 Answers11

19

This is kind of cheating, I know, but getting from '/etc/localtime' doesn't work for you? Like following:

>>>  import os
>>> '/'.join(os.readlink('/etc/localtime').split('/')[-2:])
'Australia/Sydney'

Hope it helps.

Edit: I liked @A.H.'s idea, in case '/etc/localtime' isn't a symlink. Translating that into Python:

#!/usr/bin/env python

from hashlib import sha224
import os

def get_current_olsonname():
    tzfile = open('/etc/localtime')
    tzfile_digest = sha224(tzfile.read()).hexdigest()
    tzfile.close()

    for root, dirs, filenames in os.walk("/usr/share/zoneinfo/"):
        for filename in filenames:
            fullname = os.path.join(root, filename)
            f = open(fullname)
            digest = sha224(f.read()).hexdigest()
            if digest == tzfile_digest:
                return '/'.join((fullname.split('/'))[-2:])
            f.close()
        return None

if __name__ == '__main__':
    print get_current_olsonname()
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
Thiago Curvelo
  • 3,711
  • 1
  • 22
  • 38
  • 1
    It works when /etc/localtime is a symlink. This isn't the case most of the time. – Matt Joiner Oct 20 '11 at 20:51
  • 1
    This works surprisingly well. I wonder what would happen if the zoneinfo was updated, but `/etc/localtime` was not. Also I recommend you switch to 4 space indentation instead of tabs (or 8 spaces) immediately. – Matt Joiner Dec 02 '11 at 02:34
  • Well, it was 8 spaces btw... About zoneinfo, I think your system will be tz-less, without /etc/locatime, doesn't it? – Thiago Curvelo Dec 02 '11 at 15:22
  • 1
    No I meant if the zoneinfo files are updated, and /etc/localtime was not a symlink. You may have a localtime that no longer matches any zoneinfo file. – Matt Joiner Dec 03 '11 at 07:30
  • 4
    On my Ubuntu system, this unfortunately returns zoneinfo/localtime. – Matt Joiner Dec 03 '11 at 07:40
  • @MattJoiner: On my Ubuntu system it produces the same result as [Anurag's answer](http://stackoverflow.com/a/8328904/4279). [I've used this code](https://gist.github.com/4136246) – jfs Nov 23 '12 at 16:02
17

I think best bet is to go thru all pytz timezones and check which one matches local timezone, each pytz timezone object contains info about utcoffset and tzname like CDT, EST, same info about local time can be obtained from time.timezone/altzone and time.tzname, and I think that is enough to correctly match local timezone in pytz database e.g.

import time
import pytz
import datetime

local_names = []
if time.daylight:
    local_offset = time.altzone
    localtz = time.tzname[1]
else:
    local_offset = time.timezone
    localtz = time.tzname[0]

local_offset = datetime.timedelta(seconds=-local_offset)

for name in pytz.all_timezones:
    timezone = pytz.timezone(name)
    if not hasattr(timezone, '_tzinfos'):
        continue#skip, if some timezone doesn't have info
    # go thru tzinfo and see if short name like EDT and offset matches
    for (utcoffset, daylight, tzname), _ in timezone._tzinfos.iteritems():
        if utcoffset == local_offset and tzname == localtz:
            local_names.append(name)

print local_names

output:

['America/Atikokan', 'America/Bahia_Banderas', 'America/Bahia_Banderas', 'America/Belize', 'America/Cambridge_Bay', 'America/Cancun', 'America/Chicago', 'America/Chihuahua', 'America/Coral_Harbour', 'America/Costa_Rica', 'America/El_Salvador', 'America/Fort_Wayne', 'America/Guatemala', 'America/Indiana/Indianapolis', 'America/Indiana/Knox', 'America/Indiana/Marengo', 'America/Indiana/Marengo', 'America/Indiana/Petersburg', 'America/Indiana/Tell_City', 'America/Indiana/Vevay', 'America/Indiana/Vincennes', 'America/Indiana/Winamac', 'America/Indianapolis', 'America/Iqaluit', 'America/Kentucky/Louisville', 'America/Kentucky/Louisville', 'America/Kentucky/Monticello', 'America/Knox_IN', 'America/Louisville', 'America/Louisville', 'America/Managua', 'America/Matamoros', 'America/Menominee', 'America/Merida', 'America/Mexico_City', 'America/Monterrey', 'America/North_Dakota/Beulah', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', 'America/Ojinaga', 'America/Pangnirtung', 'America/Rainy_River', 'America/Rankin_Inlet', 'America/Resolute', 'America/Resolute', 'America/Tegucigalpa', 'America/Winnipeg', 'CST6CDT', 'Canada/Central', 'Mexico/General', 'US/Central', 'US/East-Indiana', 'US/Indiana-Starke']

In production you can create such a mapping beforehand and save it instead of iterating always.

Testing script after changing timezone:

$ export TZ='Australia/Sydney'
$ python get_tz_names.py
['Antarctica/Macquarie', 'Australia/ACT', 'Australia/Brisbane', 'Australia/Canberra', 'Australia/Currie', 'Australia/Hobart', 'Australia/Lindeman', 'Australia/Melbourne', 'Australia/NSW', 'Australia/Queensland', 'Australia/Sydney', 'Australia/Tasmania', 'Australia/Victoria']

user7116
  • 63,008
  • 17
  • 141
  • 172
Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
  • This algorithm gives the best results so far, but it doesn't seem as clean as @pcperini's answer. – Matt Joiner Dec 02 '11 at 02:41
  • @Matt Joiner, I think it as as clean code wise and more accurate as instead of country name it compares offsets – Anurag Uniyal Dec 02 '11 at 03:52
  • @MattJoiner, also I don't think country code obtained from a url will change is TZ setting of system is changed? – Anurag Uniyal Dec 02 '11 at 03:54
  • 3
    To find out whether DST is in effect: `if time.daylight and time.localtime().tm_isdst > 0`. Note: `time.daylight` along just tells whether the local timezone has DST at all, it doesn't tell anything about the current state. – jfs Aug 02 '13 at 11:36
  • this loses the history that's represented with an Olson TZ name - if I uses one of the results to convert historical dates recorded in UTC to the corresponding local time, one may end up with incorrect conversion: because one reason for the many names / timezones are that places switched their UTC-offset in different ways. – chichak Jun 25 '23 at 18:37
12

One problem is that there are multiple "pretty names" , like "Australia/Sydney" , which point to the same time zone (e.g. CST).

So you will need to get all the possible names for the local time zone, and then select the name you like.

e.g.: for Australia, there are 5 time zones, but way more time zone identifiers:

     "Australia/Lord_Howe", "Australia/Hobart", "Australia/Currie", 
     "Australia/Melbourne", "Australia/Sydney", "Australia/Broken_Hill", 
     "Australia/Brisbane", "Australia/Lindeman", "Australia/Adelaide", 
     "Australia/Darwin", "Australia/Perth", "Australia/Eucla"

you should check if there is a library which wraps TZinfo , to handle the time zone API.

e.g.: for Python, check the pytz library:

http://pytz.sourceforge.net/

and

http://pypi.python.org/pypi/pytz/

in Python you can do:

from pytz import timezone
import pytz

In [56]: pytz.country_timezones('AU')
Out[56]: 
[u'Australia/Lord_Howe',
 u'Australia/Hobart',
 u'Australia/Currie',
 u'Australia/Melbourne',
 u'Australia/Sydney',
 u'Australia/Broken_Hill',
 u'Australia/Brisbane',
 u'Australia/Lindeman',
 u'Australia/Adelaide',
 u'Australia/Darwin',
 u'Australia/Perth',
 u'Australia/Eucla']

but the API for Python seems to be pretty limited, e.g. it doesn't seem to have a call like Ruby's all_linked_zone_names -- which can find all the synonym names for a given time zone.

Tilo
  • 33,354
  • 5
  • 79
  • 106
  • I agree, I think these were meant so users could set their timezone with less hassle (e.g. not knowing their UTC offset). – Mike Nov 06 '11 at 07:06
  • @Tilo: The bounty requires an algorithm for generating this list of timezone names. – Matt Joiner Dec 02 '11 at 02:26
  • easy in Ruby (I added an answer for Ruby below) ... please check the pytz API on how to do the same with Python. – Tilo Dec 02 '11 at 06:18
  • for Australia's timezones this might be true (I don't know) but many different timezones that have the same time now didn't always have the same time - definitely in europe. Europe/Zurich and Europe/Berlin differ by when DST was observed and when it wasn't. – chichak Jun 25 '23 at 20:08
8

If evaluating /etc/localtime is OK for you, the following trick might work - after translating it to python:

> md5sum /etc/localtime
abcdefabcdefabcdefabcdefabcdefab /etc/localtime
> find /usr/share/zoneinfo -type f |xargs md5sum | grep abcdefabcdefabcdefabcdefabcdefab
abcdefabcdefabcdefabcdefabcdefab /usr/share/zoneinfo/Europe/London
abcdefabcdefabcdefabcdefabcdefab /usr/share/zoneinfo/posix/Europe/London
...

The duplicates could be filtered using only the official region names "Europe", "America" ... If there are still duplicates, you could take the shortest name :-)

A.H.
  • 63,967
  • 15
  • 92
  • 126
7

Install pytz

import pytz
import time
#import locale
import urllib2

yourOlsonTZ = None
#yourCountryCode = locale.getdefaultlocale()[0].split('_')[1]
yourCountryCode = urllib2.urlopen('http://api.hostip.info/country.php').read()

for olsonTZ in [pytz.timezone(olsonTZ) for olsonTZ in pytz.all_timezones]:
    if (olsonTZ._tzname in time.tzname) and (str(olsonTZ) in pytz.country_timezones[yourCountryCode]):
        yourOlsonTZ = olsonTZ
        break

print yourOlsonTZ

This code will take a best-guess crack at your Olson Timezone based both on your Timezone Name (as according to Python's time module), and your Country Code (as according to Python's locale module the hostip.info project, which references your IP address and geolocates you accordingly).

For example, simply matching the Timzone Names could result in America/Moncton, America/Montreal, or America/New_York for EST (GMT-5). If your country is the US, however, it will limit the answer to America/New_York.

However, if your country is Canada, the script will simply default to the topmost Canadian result (America/Moncton). If there is a way to further refine this, please feel free to leave suggestions in comments.

Patrick Perini
  • 22,555
  • 12
  • 59
  • 88
  • Can you rework the determination of the country code to get this value from somewhere more reliable? In Australia it's not uncommon to have `en_US`, despite the fact that `en_AU` exists. On top of that, my tzname is `'EST'`, so your algorithm gives me `America/New_York`, but I'm in `Australia/Sydney`. – Matt Joiner Dec 02 '11 at 02:30
  • Working on it. Can I assume web access? – Patrick Perini Dec 02 '11 at 03:36
5

The tzlocal module for Python is aimed at exactly this problem. It produces consistent results under both Linux and Windows, properly converting from Windows time zone ids to Olson using the CLDR mappings.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
1

Here's another posibility, using PyICU instead; which is working for my purposes:

>>> from PyICU import ICUtzinfo
>>> from datetime import datetime
>>> datetime(2012, 1, 1, 12, 30, 18).replace(tzinfo=ICUtzinfo.getDefault()).isoformat()
'2012-01-01T12:30:18-05:00'
>>> datetime(2012, 6, 1, 12, 30, 18).replace(tzinfo=ICUtzinfo.getDefault()).isoformat()
'2012-06-01T12:30:18-04:00'

Here it is interpreting niave datetimes (as would be returned by a database query) in the local timezone.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
1

This will get you the time zone name, according to what's in the TZ variable, or localtime file if unset:

#! /usr/bin/env python

import time

time.tzset
print time.tzname
Diego Torres Milano
  • 65,697
  • 9
  • 111
  • 134
  • 1
    Unfortunately it returns a tuple of the 3 letter timezone codes, not the full timezone name. – Matt Joiner Oct 06 '11 at 04:25
  • 3
    Even if you build a lookup table for this, it will be ambiguous. "EST" can be `America/New_York` or `Australia/Sydney`. "BST" can be `Europe/London` or `Asia/Dhaka`. – wberry Oct 10 '11 at 19:37
  • 2
    He's right. The only thing the system knows about its timezone is the GMT offset and DST settings. On linux those are set by either the TZ environment variable (usually not set however), or the /etc/localtime file, which can either be a link to one of the timezone definitions found in /usr/share/zoneinfo or a copy of one of them. There is just no way to could get this value generically unless you know for sure that the system you're running your script on is configured using a symbolic link or the environment variable. Then you could probably run system commands from within python to get it. – Yanick Girouard Oct 19 '11 at 16:55
  • @MattJoiner: Well if you want to get the environment variable's content, you could use os.getenv("TZ"), or parse the return of `ls -l /etc/localtime` and extract the link path from the result. There's a whole thread about how to do that here: http://stackoverflow.com/questions/89228/how-to-call-external-command-in-python (the calling of ls that is, not the parsing path) – Yanick Girouard Dec 03 '11 at 05:43
0

I prefer following a slightly better than poking around _xxx values

import time, pytz, os

cur_name=time.tzname
cur_TZ=os.environ.get("TZ")

def is_current(name):
   os.environ["TZ"]=name
   time.tzset()
   return time.tzname==cur_name

print "Possible choices:", filter(is_current, pytz.all_timezones)

# optional tz restore
if cur_TZ is None: del os.environ["TZ"]
else: os.environ["TZ"]=cur_TZ
time.tzset()
korc
  • 249
  • 2
  • 2
0

I changed tcurvelo's script to find the right form of time zone (Continent/..../City), in most of cases, but return all of them if fails

#!/usr/bin/env python

from hashlib import sha224
import os
from os import listdir
from os.path import join, isfile, isdir

infoDir = '/usr/share/zoneinfo/'

def get_current_olsonname():
    result = []
    tzfile_digest = sha224(open('/etc/localtime').read()).hexdigest()

    test_match = lambda filepath: sha224(open(filepath).read()).hexdigest() == tzfile_digest

    def walk_over(dirpath):
        for root, dirs, filenames in os.walk(dirpath):
            for fname in filenames:
                fpath = join(root, fname)
                if test_match(fpath):
                    result.append(tuple(root.split('/')[4:]+[fname]))

    for dname in listdir(infoDir):
        if dname in ('posix', 'right', 'SystemV', 'Etc'):
            continue
        dpath = join(infoDir, dname)
        if not isdir(dpath):
            continue
        walk_over(dpath)

    if not result:
        walk_over(join(infoDir))

    return result


if __name__ == '__main__':
    print get_current_olsonname()
saeedgnu
  • 4,110
  • 2
  • 31
  • 48
-1

This JavaScript project attempts to solve the same issue in the browser client-side. It works by playing "twenty questions" with the locale, asking for the UTC offset of certain past times (to test for summer time boundaries, etc.) and using those results to deduce what the local time zone must be. I am not aware of any equivalent Python package unfortunately, so if someone wanted to use this solution it would have to be ported to Python.

While this formula will require updating every time (at worst) the TZ database is updated, a combination of this algorithm and the solution proposed by Anurag Uniyal (keeping only possibilities returned by both methods) sounds to me like the surest way to compute the effective local timezone. As long as there is some difference between the UTC offset of at least one local time in any two time zones, such a system can correctly choose between them.

wberry
  • 18,519
  • 8
  • 53
  • 85
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/19761703) – CJ Dennis May 18 '18 at 03:40
  • Two paragraphs of explanation is a link-only answer? – wberry May 20 '18 at 21:42
  • I could have chosen "This is a comment and doesn't answer the question. When you have enough rep you will be able to comment on the question." However, you have over 10,000 rep, so that felt condescending. – CJ Dennis May 20 '18 at 21:49
  • I think it answers the question perfectly well, else I would not have wasted my time giving it. Considering the UTC offset of key past dates and times as clues to your true locale is an approach not offered by any other answer here. Surely you're not expecting me to duplicate the exact algorithm in my answer? – wberry May 20 '18 at 22:18
  • If "This JavaScript project" ceases to exist, your answer will be almost useless. It's not at all clear how comparing any number of UTC dates can give a result of `Australia/Sydney`. – CJ Dennis May 20 '18 at 22:48