获取本地时区的 Olson TZ 名称?

Posted

技术标签:

【中文标题】获取本地时区的 Olson TZ 名称?【英文标题】:Get the Olson TZ name for the local timezone? 【发布时间】:2011-12-01 23:11:32 【问题描述】:

如何获得与C的localtime调用给出的值对应的Olson timezone name(如Australia/Sydney)?

这是通过TZ、符号链接/etc/localtime 或在与时间相关的系统配置文件中设置TIMEZONE 变量覆盖的值。

【问题讨论】:

所以,明确地说,您想要一种方法来确定 Linux 上当前用户区域设置的 Olson 时区名称(即America/New_York)? @wberry:是的,没错。 在那种情况下,我主演这个因为我也想知道:-) 类似于***.com/questions/1723073/… 以获得一些想法。 我认为我的回答正确列出了可能的匹配项?你认为它在某些情况下会给出错误的结果吗? 【参考方案1】:

我认为最好的办法是遍历所有 pytz 时区并检查哪个与本地时区匹配,每个 pytz 时区对象都包含有关 utcoffset 和 tzname 的信息,例如 CDT、EST,有关本地时间的相同信息可以从 time.timezone/altzonetime.tzname,我认为这足以正确匹配 pytz 数据库中的本地时区,例如

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

输出:

['America/Atikokan', 'America/Bahia_Banderas', 'America/Bahia_Banderas', 'America/Belize', 'America/Cambridge_Bay', “美国/坎昆”、“美国/芝加哥”、“美国/奇瓦瓦”、 'America/Coral_Harbour'、'America/Costa_Rica'、'America/El_Salvador'、 '美国/韦恩堡','美国/危地马拉', '美国/印第安纳/印第安纳波利斯','美国/印第安纳/诺克斯', '美国/印第安纳州/马伦戈','美国/印第安纳州/马伦戈', '美国/印第安纳州/彼得堡','美国/印第安纳州/Tell_City', '美国/印第安纳州/Vevay','美国/印第安纳州/文森斯', “美国/印第安纳/威纳马克”、“美国/印第安纳波利斯”、“美国/伊魁特”、 '美国/肯塔基/路易斯维尔','美国/肯塔基/路易斯维尔', '美国/肯塔基/蒙蒂塞洛','美国/诺克斯_IN', “美国/路易斯维尔”、“美国/路易斯维尔”、“美国/马那瓜”、 “美国/马塔莫罗斯”、“美国/梅诺米尼”、“美国/梅里达”、 '美国/墨西哥城','美国/蒙特雷', 'America/North_Dakota/Beulah', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', 'America/Ojinaga', 'America/Pangnirtung'、'America/Rainy_River'、'America/Rankin_Inlet'、 “美国/坚决”、“美国/坚决”、“美国/特古西加尔巴”、 “美国/温尼伯”、“CST6CDT”、“加拿大/中部”、“墨西哥/一般”、 'US/Central'、'US/East-Indiana'、'US/Indiana-Starke']

在生产中,您可以预先创建这样的映射并保存它,而不是总是迭代。

更改时区后的测试脚本:

$ export TZ='澳大利亚/悉尼' $ python get_tz_names.py ['南极洲/麦格理','澳大利亚/ACT','澳大利亚/布里斯班', “澳大利亚/堪培拉”、“澳大利亚/居里”、“澳大利亚/霍巴特”、 “澳大利亚/林德曼”、“澳大利亚/墨尔本”、“澳大利亚/新南威尔士州”、 “澳大利亚/昆士兰”、“澳大利亚/悉尼”、“澳大利亚/塔斯马尼亚”、 '澳大利亚/维多利亚']

【讨论】:

这个算法给出了迄今为止最好的结果,但它似乎不像@pcperini 的答案那么干净。 @Matt Joiner,我认为它像干净的代码一样明智且更准确,因为它比较偏移量而不是国家名称 @MattJoiner,我认为从 url 获得的国家代码不会改变系统的 TZ 设置吗? 查看夏令时是否生效:if time.daylight and time.localtime().tm_isdst > 0。注意:time.daylight along 只是告诉当地时区是否有夏令时,它不告诉任何关于当前状态的信息。【参考方案2】:

这有点作弊,我知道,但是从'/etc/localtime' 获取对您不起作用? 如下:

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

希望对你有帮助。

编辑:我喜欢@A.H. 的想法,以防'/etc/localtime' 不是符号链接。将其翻译成 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()

【讨论】:

当 /etc/localtime 是符号链接时有效。大多数时候情况并非如此。 这出奇的好。我想知道如果 zoneinfo 更新了会发生什么,但 /etc/localtime 没有更新。另外我建议您立即切换到 4 个空格缩进而不是制表符(或 8 个空格)。 嗯,顺便说一句,它是 8 个空格...关于 zoneinfo,我认为您的系统将是 tz-less,没有 /etc/locatime,不是吗? 不,我的意思是如果 zoneinfo 文件已更新,并且 /etc/localtime 不是符号链接。您的本地时间可能不再与任何 zoneinfo 文件匹配。 在我的 Ubuntu 系统上,这很遗憾返回 zoneinfo/localtime。【参考方案3】:

一个问题是有多个“漂亮的名字”,例如“Australia/Sydney”,它们指向同一个时区(例如 CST)。

因此,您需要获取当地时区的所有可能名称,然后选择您喜欢的名称。

例如:对于澳大利亚,有 5 个时区,但时区标识符要多得多:

     "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"

您应该检查是否有一个 包装 TZinfo 的库,以处理时区 API。

例如:对于 Python,请检查 pytz 库:

http://pytz.sourceforge.net/

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

在 Python 中你可以这样做:

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']

但 Python 的 API 似乎非常有限,例如它似乎没有像 Ruby 的 all_linked_zone_names 这样的调用——它可以找到给定时区的所有同义词。

【讨论】:

我同意,我认为这些是为了让用户可以更轻松地设置他们的时区(例如,不知道他们的 UTC 偏移量)。 @Tilo:赏金需要一个算法来生成这个时区名称列表。 在 Ruby 中很容易(我在下面添加了 Ruby 的答案)...请查看 pytz API,了解如何使用 Python 执行相同操作。【参考方案4】:

如果评估 /etc/localtime 对您来说没问题,那么在将其转换为 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
...

可以仅使用官方地区名称“欧洲”、“美国”过滤重复项...如果仍有重复项,您可以使用最短的名称 :-)

【讨论】:

【参考方案5】:

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

此代码将根据您的时区名称(根据 Python 的 time 模块)和您的国家代码(根据 Python 的 locale 模块hostip.info 项目,它引用您的 IP 地址并相应地对您进行地理定位)。

例如,简单地匹配 Timzone 名称可能会导致 America/MonctonAmerica/MontrealAmerica/New_York 用于 EST (GMT-5)。但是,如果您所在的国家/地区是美国,则会将答案限制为America/New_York

但是,如果您所在的国家/地区是加拿大,则脚本将默认为加拿大最高的结果 (America/Moncton)。如果有进一步完善的方法,请随时在 cmets 中留下建议。

【讨论】:

你能重新确定国家代码以从更可靠的地方获得这个值吗?在澳大利亚,en_US 并不少见,尽管en_AU 存在。最重要的是,我的 tzname 是'EST',所以你的算法给了我America/New_York,但我在Australia/Sydney 正在处理它。我可以假设网络访问吗?【参考方案6】:

Python 的tzlocal 模块正是针对这个问题。它在 Linux 和 Windows 下产生一致的结果,使用 CLDR 映射正确地将 Windows 时区 id 转换为 Olson。

【讨论】:

【参考方案7】:

这将为您获取时区名称,根据 TZ 变量中的内容,如果未设置,则为本地时间文件:

#! /usr/bin/env python

import time

time.tzset
print time.tzname

【讨论】:

不幸的是,它返回一个由 3 个字母的时区代码组成的元组,而不是完整的时区名称。 即使你为此建立一个查找表,它也会是模棱两可的。 “EST”可以是America/New_YorkAustralia/Sydney。 “BST”可以是Europe/LondonAsia/Dhaka 他是对的。系统唯一知道其时区的是 GMT 偏移量和 DST 设置。在 linux 上,这些是由 TZ 环境变量(通常不设置)或 /etc/localtime 文件设置的,该文件可以是 /usr/share/zoneinfo 中找到的时区定义之一的链接,也可以是其中之一。除非您确定运行脚本的系统是使用符号链接或环境变量配置的,否则无法一般地获取此值。然后你可能可以从 python 中运行系统命令来获取它。 @MattJoiner:如果你想获取环境变量的内容,你可以使用os.getenv("TZ"),或者解析ls -l /etc/localtime的返回值,然后从结果中提取链接路径.这里有一个关于如何做到这一点的完整主题:***.com/questions/89228/…(调用 ls 即不是解析路径)【参考方案8】:

这是另一种可能性,使用 PyICU 代替;这对我的目的有用:

>>> 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'

这里解释的是本地时区的 nave 日期时间(数据库查询将返回)。

【讨论】:

ICU 有什么问题是人们不喜欢的答案吗?【参考方案9】:

我更喜欢遵循 ​​_xxx 值而不是探索 _xxx 值

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()

【讨论】:

使用 TZ 环境变量仅适用于 Linux(或 Unix),不适用于 Windows。 另一件事是它在我目前的 Fedora (F20) 上根本不起作用,没有定义这样的变量 TZ。【参考方案10】:

在大多数情况下,我更改了 tcurvelo 的脚本以找到正确的时区形式 (Continent/..../City),但如果失败则全部返回

#!/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()

【讨论】:

【参考方案11】:

This javascript project 尝试在浏览器客户端解决相同的问题。它通过在区域设置中播放“二十个问题”来工作,询问某些过去时间的 UTC 偏移量(以测试夏季时间边界等)并使用这些结果来推断本地时区必须是什么。不幸的是,我不知道有任何等效的 Python 包,因此如果有人想使用此解决方案,则必须将其移植到 Python。

虽然每次(在最坏的情况下)更新 TZ 数据库时都需要更新此公式,但结合此算法和 Anurag Uniyal 提出的解决方案(仅保留两种方法返回的可能性)在我看来是最可靠的方法计算有效的本地时区。只要任意两个时区中至少一个本地时间的UTC偏移量存在一定的差异,这样的系统就可以正确地在它们之间进行选择。

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review 两段解释都是链接回答? 我可以选择“这是一条评论,不回答问题。当您有足够的代表时,您将能够对问题发表评论。”但是,您有超过 10,000 个代表,所以感觉居高临下。 我认为它很好地回答了这个问题,否则我不会浪费时间给它。将过去关键日期和时间的 UTC 偏移量视为您的真实语言环境的线索是此处任何其他答案都没有提供的方法。您肯定不希望我在答案中复制确切的算法吗? 如果“此 JavaScript 项目”不复存在,您的回答将几乎毫无用处。目前尚不清楚比较任意数量的 UTC 日期如何得出Australia/Sydney 的结果。

以上是关于获取本地时区的 Olson TZ 名称?的主要内容,如果未能解决你的问题,请参考以下文章

使用 boost 或 std 获取特定时区的当前本地时间

如何从安卓手机获取时区?

在c#中获取时区列表[重复]

如何使用纬度和经度坐标从某个位置获取时区?

如何使用纬度和经度坐标从某个位置获取时区?

如何使用纬度和经度坐标从某个位置获取时区?