Skip to main content

世界时区定义,现代和历史

项目描述

作者

斯图尔特主教<斯图尔特@斯图尔特主教>

介绍

pytz 将 Olson tz 数据库引入 Python。该库允许使用 Python 2.4 或更高版本进行准确的跨平台时区计算。它还解决了夏令时结束时时间不明确的问题,您可以在 Python 库参考 ( datetime.tzinfo ) 中了解更多信息。

几乎所有的奥尔森时区都受支持。

安装

该软件包可以使用pip安装,也可以使用标准 Python distutils 从 tarball 安装。

如果您使用pip安装,则无需下载任何内容,因为将从 PyPI 为您下载最新版本:

pip install pytz

如果您从 tarball 安装,请以管理用户身份运行以下命令:

python setup.py install

pytz 企业版

作为 Tidelift 订阅的一部分提供。

pytz 和数以千计的其他软件包的维护者正在与 Tidelift 合作,为您用于构建应用程序的开源依赖项提供商业支持和维护。节省时间、降低风险并改善代码运行状况,同时向维护者支付您使用的确切依赖项的费用。学到更多。.

示例和用法

本地化时间和日期算术

>>> from datetime import datetime, timedelta
>>> from pytz import timezone
>>> import pytz
>>> utc = pytz.utc
>>> utc.zone
'UTC'
>>> eastern = timezone('US/Eastern')
>>> eastern.zone
'US/Eastern'
>>> amsterdam = timezone('Europe/Amsterdam')
>>> fmt = '%Y-%m-%d %H:%M:%S %Z%z'

该库仅支持两种构建本地化时间的方式。第一种是使用pytz库提供的localize()方法。这用于本地化一个简单的日期时间(没有时区信息的日期时间):

>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500

构建本地化时间的第二种方法是使用标准astimezone()方法转换现有本地化时间:

>>> ams_dt = loc_dt.astimezone(amsterdam)
>>> ams_dt.strftime(fmt)
'2002-10-27 12:00:00 CET+0100'

不幸的是,对于许多时区,使用标准日期时间构造函数的 tzinfo 参数对 pytz “不起作用”。

>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt)  # /!\ Does not work this way!
'2002-10-27 12:00:00 LMT+0018'

但是,对于没有夏令时转换的时区是安全的,例如 UTC:

>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt)  # /!\ Not recommended except for UTC
'2002-10-27 12:00:00 UTC+0000'

处理时间的首选方式是始终以 UTC 工作,仅在生成供人类阅读的输出时转换为本地时间。

>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
>>> loc_dt = utc_dt.astimezone(eastern)
>>> loc_dt.strftime(fmt)
'2002-10-27 01:00:00 EST-0500'

该库还允许您使用本地时间进行日期运算,尽管它比在 UTC 中工作更复杂,因为您需要使用normalize()方法来处理夏令时和其他时区转换。在此示例中,loc_dt设置为夏令时在美国/东部时区结束的时刻。

>>> before = loc_dt - timedelta(minutes=10)
>>> before.strftime(fmt)
'2002-10-27 00:50:00 EST-0500'
>>> eastern.normalize(before).strftime(fmt)
'2002-10-27 01:50:00 EDT-0400'
>>> after = eastern.normalize(before + timedelta(minutes=20))
>>> after.strftime(fmt)
'2002-10-27 01:10:00 EST-0500'

创建当地时间也很棘手,不推荐使用当地时间的原因。不幸的是,在构造日期时间时不能只传递tzinfo参数(有关详细信息,请参阅下一节)

>>> dt = datetime(2002, 10, 27, 1, 30, 0)
>>> dt1 = eastern.localize(dt, is_dst=True)
>>> dt1.strftime(fmt)
'2002-10-27 01:30:00 EDT-0400'
>>> dt2 = eastern.localize(dt, is_dst=False)
>>> dt2.strftime(fmt)
'2002-10-27 01:30:00 EST-0500'

使用标准 astimezone 方法更容易在时区之间进行转换。

>>> utc_dt = utc.localize(datetime.utcfromtimestamp(1143408899))
>>> utc_dt.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'
>>> au_tz = timezone('Australia/Sydney')
>>> au_dt = utc_dt.astimezone(au_tz)
>>> au_dt.strftime(fmt)
'2006-03-27 08:34:59 AEDT+1100'
>>> utc_dt2 = au_dt.astimezone(utc)
>>> utc_dt2.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'
>>> utc_dt == utc_dt2
True

在处理时区转换的 UTC 方面时,您可以采取捷径。当没有夏令时转换需要处理时,normalize()localize()并不是真正需要的。

>>> utc_dt = datetime.utcfromtimestamp(1143408899).replace(tzinfo=utc)
>>> utc_dt.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'
>>> au_tz = timezone('Australia/Sydney')
>>> au_dt = au_tz.normalize(utc_dt.astimezone(au_tz))
>>> au_dt.strftime(fmt)
'2006-03-27 08:34:59 AEDT+1100'
>>> utc_dt2 = au_dt.astimezone(utc)
>>> utc_dt2.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'

tzinfo API

timezone()函数返回的tzinfo实例已通过向utcoffset()dst() && tzname()方法添加is_dst 参数来扩展以应对不明确的时间。

>>> tz = timezone('America/St_Johns')
>>> normal = datetime(2009, 9, 1)
>>> ambiguous = datetime(2009, 10, 31, 23, 30)

对于大多数时间戳, is_dst参数被忽略。它仅在 DST 过渡模棱两可期间使用,以解决该模棱两可的问题。

>>> print(tz.utcoffset(normal, is_dst=True))
-1 day, 21:30:00
>>> print(tz.dst(normal, is_dst=True))
1:00:00
>>> tz.tzname(normal, is_dst=True)
'NDT'
>>> print(tz.utcoffset(ambiguous, is_dst=True))
-1 day, 21:30:00
>>> print(tz.dst(ambiguous, is_dst=True))
1:00:00
>>> tz.tzname(ambiguous, is_dst=True)
'NDT'
>>> print(tz.utcoffset(normal, is_dst=False))
-1 day, 21:30:00
>>> tz.dst(normal, is_dst=False).seconds
3600
>>> tz.tzname(normal, is_dst=False)
'NDT'
>>> print(tz.utcoffset(ambiguous, is_dst=False))
-1 day, 20:30:00
>>> tz.dst(ambiguous, is_dst=False)
datetime.timedelta(0)
>>> tz.tzname(ambiguous, is_dst=False)
'NST'

如果未指定is_dst,不明确的时间戳将引发pytz.exceptions.AmbiguousTimeError异常。

>>> print(tz.utcoffset(normal))
-1 day, 21:30:00
>>> print(tz.dst(normal))
1:00:00
>>> tz.tzname(normal)
'NDT'
>>> import pytz.exceptions
>>> try:
...     tz.utcoffset(ambiguous)
... except pytz.exceptions.AmbiguousTimeError:
...     print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
>>> try:
...     tz.dst(ambiguous)
... except pytz.exceptions.AmbiguousTimeError:
...     print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
>>> try:
...     tz.tzname(ambiguous)
... except pytz.exceptions.AmbiguousTimeError:
...     print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00

本地时间问题

我们必须处理的主要问题是某些日期时间可能在一年中出现两次。例如,在 10 月最后一个星期日早上的美国/东部时区,会发生以下顺序:

  • 01:00 EDT 发生

  • 1 小时后,而不是凌晨 2:00,时钟拨回 1 小时,01:00 再次发生(这次是美国东部标准时间 01:00)

事实上,01:00 和 02:00 之间的每一瞬间都会出现两次。这意味着,如果您尝试使用标准日期时间语法在“美国/东部”时区创建时间,则无法指定您的意思是在夏令时结束时间转换之前还是之后。使用 pytz 自定义语法,你能做的最好的就是做出有根据的猜测:

>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 1, 30, 00))
>>> loc_dt.strftime(fmt)
'2002-10-27 01:30:00 EST-0500'

如您所见,系统已为您选择了一个,并且有 50% 的机会在 1 小时后退出。对于某些应用程序,这无关紧要。但是,如果您尝试安排与不同时区的人会面或分析日志文件,则这是不可接受的。

最好和最简单的解决方案是坚持使用 UTC。pytz 包通过在 Python 文档中包含基于标准 Python 参考实现的特殊 UTC 实现,鼓励使用 UTC 表示内部时区。

UTC 时区 unpickles 成为同一个实例,并且 pickle 到比其他 pytz tzinfo 实例更小的大小。UTC 实现可以通过 pytz.utc、pytz.UTC 或 pytz.timezone('UTC') 获得。

>>> import pickle, pytz
>>> dt = datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
>>> naive = dt.replace(tzinfo=None)
>>> p = pickle.dumps(dt, 1)
>>> naive_p = pickle.dumps(naive, 1)
>>> len(p) - len(naive_p)
17
>>> new = pickle.loads(p)
>>> new == dt
True
>>> new is dt
False
>>> new.tzinfo is dt.tzinfo
True
>>> pytz.utc is pytz.UTC is pytz.timezone('UTC')
True

请注意,其他一些时区通常被认为是相同的(GMT、格林威治、通用等)。UTC 的定义与这些其他时区不同,它们并不等同。因此,它们在 Python 中不会进行相同的比较。

>>> utc == pytz.timezone('GMT')
False

请参阅下面的什么是 UTC部分。

如果你坚持使用当地时间,这个库提供了一个明确构建它们的工具:

>>> loc_dt = datetime(2002, 10, 27, 1, 30, 00)
>>> est_dt = eastern.localize(loc_dt, is_dst=True)
>>> edt_dt = eastern.localize(loc_dt, is_dst=False)
>>> print(est_dt.strftime(fmt) + ' / ' + edt_dt.strftime(fmt))
2002-10-27 01:30:00 EDT-0400 / 2002-10-27 01:30:00 EST-0500

如果您将 None 作为 is_dst 标志传递给 localize(),如果您尝试构建模棱两可或不存在的时间,pytz 将拒绝猜测并引发异常。

例如,2002 年 10 月 27 日凌晨 1:30 在美国/东部时区发生了两次,当时时钟在夏令时结束时放回:

>>> dt = datetime(2002, 10, 27, 1, 30, 00)
>>> try:
...     eastern.localize(dt, is_dst=None)
... except pytz.exceptions.AmbiguousTimeError:
...     print('pytz.exceptions.AmbiguousTimeError: %s' % dt)
pytz.exceptions.AmbiguousTimeError: 2002-10-27 01:30:00

同样,2002 年 4 月 7 日凌晨 2:30 在美国/东部时区根本没有发生,因为在凌晨 2:00 提出的时钟跳过了整个小时:

>>> dt = datetime(2002, 4, 7, 2, 30, 00)
>>> try:
...     eastern.localize(dt, is_dst=None)
... except pytz.exceptions.NonExistentTimeError:
...     print('pytz.exceptions.NonExistentTimeError: %s' % dt)
pytz.exceptions.NonExistentTimeError: 2002-04-07 02:30:00

这两个异常共享一个公共基类以使错误处理更容易:

>>> isinstance(pytz.AmbiguousTimeError(), pytz.InvalidTimeError)
True
>>> isinstance(pytz.NonExistentTimeError(), pytz.InvalidTimeError)
True

一个特殊情况是国家/地区更改其时区定义而没有夏令时切换。例如,1915 年华沙从华沙时间切换到中欧时间,没有夏令时转换。因此,在 1915 年 8 月 5 日午夜钟声敲响时,时钟回拨了 24 分钟,从而产生了一个模棱两可的时间段,如果不参考时区缩写或实际的 UTC 偏移量,就无法指定该时间段。在这种情况下,午夜发生了两次,都不是夏令时期间的时间。pytz 通过将切换之前的不明确时间段视为夏令时并将之后的不明确时间段视为标准时间来处理此转换。

>>> warsaw = pytz.timezone('Europe/Warsaw')
>>> amb_dt1 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=True)
>>> amb_dt1.strftime(fmt)
'1915-08-04 23:59:59 WMT+0124'
>>> amb_dt2 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=False)
>>> amb_dt2.strftime(fmt)
'1915-08-04 23:59:59 CET+0100'
>>> switch_dt = warsaw.localize(datetime(1915, 8, 5, 00, 00, 00), is_dst=False)
>>> switch_dt.strftime(fmt)
'1915-08-05 00:00:00 CET+0100'
>>> str(switch_dt - amb_dt1)
'0:24:01'
>>> str(switch_dt - amb_dt2)
'0:00:01'

在不明确的时间段内创建时间的最佳方法是从另一个时区(例如 UTC)转换:

>>> utc_dt = datetime(1915, 8, 4, 22, 36, tzinfo=pytz.utc)
>>> utc_dt.astimezone(warsaw).strftime(fmt)
'1915-08-04 23:36:00 CET+0100'

处理所有这些歧义的标准 Python 方法不是处理它们,例如在此示例中使用 Python 文档中的美国/东部时区定义进行演示(请注意,此实现仅适用于 1987 年至 2006 年之间的日期 - 它包含在仅测试!):

>>> from pytz.reference import Eastern # pytz.reference only for tests
>>> dt = datetime(2002, 10, 27, 0, 30, tzinfo=Eastern)
>>> str(dt)
'2002-10-27 00:30:00-04:00'
>>> str(dt + timedelta(hours=1))
'2002-10-27 01:30:00-05:00'
>>> str(dt + timedelta(hours=2))
'2002-10-27 02:30:00-05:00'
>>> str(dt + timedelta(hours=3))
'2002-10-27 03:30:00-05:00'

注意到前两个结果了吗?乍一看,您可能认为它们是正确的,但考虑到 UTC 偏移量,您会发现它们实际上是两个小时的间隔,而不是我们要求的 1 小时。

>>> from pytz.reference import UTC # pytz.reference only for tests
>>> str(dt.astimezone(UTC))
'2002-10-27 04:30:00+00:00'
>>> str((dt + timedelta(hours=1)).astimezone(UTC))
'2002-10-27 06:30:00+00:00'

国家信息

提供了一种机制来访问特定国家/地区常用的时区,使用 ISO 3166 国家/地区代码进行查找。它返回一个字符串列表,可用于使用pytz.timezone()检索相关的 tzinfo 实例:

>>> print(' '.join(pytz.country_timezones['nz']))
Pacific/Auckland Pacific/Chatham

Olson 数据库带有 ISO 3166 国家代码到英语国家名称的映射,pytz 将其公开为字典:

>>> print(pytz.country_names['nz'])
New Zealand

什么是UTC

'UTC' 是协调世界时。它是格林威治标准时间 (GMT) 和世界时的各种定义的继承者,但又有所不同。UTC 现在是调节时钟和时间测量的全球标准。

所有其他时区都是相对于 UTC 定义的,并且包括诸如 UTC+0800 之类的偏移量——从 UTC 中添加或减去小时以得出本地时间。UTC 中没有夏令时,因此它是执行日期算术的有用时区,而无需担心夏令时转换、您所在国家/地区更改其时区或在多个时区漫游的移动计算机造成的混乱和歧义。

帮手

提供了两个时区列表。

all_timezones是可以使用的时区名称的详尽列表。

>>> from pytz import all_timezones
>>> len(all_timezones) >= 500
True
>>> 'Etc/Greenwich' in all_timezones
True

common_timezones是有用的当前时区列表。它不包含已弃用的区域或历史区域,除了一些我认为常用的区域,例如美国/东部(如果您认为其他时区值得包含在此处,请打开错误报告)。它也是一个字符串序列。

>>> from pytz import common_timezones
>>> len(common_timezones) < len(all_timezones)
True
>>> 'Etc/Greenwich' in common_timezones
False
>>> 'Australia/Melbourne' in common_timezones
True
>>> 'US/Eastern' in common_timezones
True
>>> 'Canada/Eastern' in common_timezones
True
>>> 'Australia/Yancowinna' in all_timezones
True
>>> 'Australia/Yancowinna' in common_timezones
False

common_timezones和all_timezones都字母顺序排序:

>>> common_timezones_dupe = common_timezones[:]
>>> common_timezones_dupe.sort()
>>> common_timezones == common_timezones_dupe
True
>>> all_timezones_dupe = all_timezones[:]
>>> all_timezones_dupe.sort()
>>> all_timezones == all_timezones_dupe
True

all_timezonescommon_timezones也可以作为集合使用。

>>> from pytz import all_timezones_set, common_timezones_set
>>> 'US/Eastern' in all_timezones_set
True
>>> 'US/Eastern' in common_timezones_set
True
>>> 'Australia/Victoria' in common_timezones_set
False

您还可以使用country_timezones()函数检索特定国家/地区使用的时区列表。它需要一个 ISO-3166 两个字母的国家代码。

>>> from pytz import country_timezones
>>> print(' '.join(country_timezones('ch')))
Europe/Zurich
>>> print(' '.join(country_timezones('CH')))
Europe/Zurich

国际化 - i18n/l10n

Pytz 是 IANA 数据库的接口,它使用 ASCII 名称。Unicode 联盟的Unicode 语言环境 (CLDR) 项目提供翻译。Thomas Khyn 的 l18n包可用于从 Python 访问这些翻译。

执照

麻省理工学院许可证。

此代码也可作为 Zope 3 的一部分在 Zope 公共许可证版本 2.1 (ZPL) 下使用。

如有必要,我很乐意重新许可此代码以包含在其他开源项目中。

最新版本

此软件包将在 Olson 时区数据库发布后更新。最新版本可以从Python Package Index下载。用于生成此分发的代码托管在 launchpad.net 上,可使用 git:

git clone https://git.launchpad.net/pytz

github 上的镜像也可在https://github.com/stub42/pytz获得

在Launchpad上发布新版本的公告 ,并在 那里托管Atom 提要。

错误、功能请求和补丁

可以使用Launchpad Bugs报告错误。

安全问题

有关安全问题的报告可以通过Tidelift 进行

问题和限制

  • 与 UTC 的偏移量四舍五入到最接近的整分钟,因此 1937 年之前的欧洲/阿姆斯特丹等时区将最多延迟 30 秒。这是 Python 日期时间库的限制。

  • 如果您认为时区定义不正确,我可能无法修复它。pytz 是 Olson 时区数据库的直接翻译,需要对这个源进行时区定义的更改。如果您发现错误,应将它们报告给时区邮件列表,链接自http://www.iana.org/time-zones

延伸阅读

关于时区的更多信息: https ://data.iana.org/time-zones/tz-link.html

接触

斯图尔特主教<斯图尔特@斯图尔特主教>