python pytz时区设置模块

时间:2022-11-22 15:55:55

如果你的程序要考虑时区,可以使用pytz。
pytz官方文档:http://pytz.sourceforge.net/
我使用的python版本:3.7.1

datetime模块中有tzinfo相关的东西,但是它是一个抽象类,文档上说:
tzinfo is an abstract base clase, meaning that this class should not be instantiated directly. You need to derive a concrete subclass, and (at least) supply implementations of the standard tzinfo methods needed by the datetime methods you use. The datetime module does not supply any concrete subclasses of tzinfo.

上面是说tzinfo是一个抽象类,不应该被直接实例化。你需要派生子类,提供相应的标准方法。datetime模块并不提供tzinfo的任何子类。
所以你可能会使用pytz这个模块。通过easy_install可以安装。

关于时区使用的几点想法:
1. 如果你的网站可能有来自其它时区的,可能你要考虑这个问题。都是一个地区的话,还要看服务器是否与用户在一个地区,如果不在,也要考虑。
2. 因此,基本上要考虑服务器时区与用户时区。服务器时区可以配置在系统中,全局生效。而用户时区则与用户相关,可以由用户自已进行设置。
3. 在生成相关时间对象时要加入时区的信息,并在输出时进行合适的转换。

而pytz提供了创建某个时区对象的方法,如:
# 查看中国时区
>>> import pytz
>>> pytz.country_timezones('cn')
['Asia/Shanghai', 'Asia/Urumqi']

可以看到,中国的时区有:'Asia/Shanghai', 'Asia/Urumqi'。

# 创建一个时区对象:
>>> tz = pytz.timezone('Asia/Shanghai')
# 然后在创建时间对象时进行指定:
>>> import datetime
>>> datetime.datetime.now(tz)
datetime.datetime(2009, 2, 21, 15, 12, 33, 906000, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)
>>> datetime.datetime(2009, 2, 21, 15, 12, 33, tzinfo=tz)
datetime.datetime(2009, 2, 21, 15, 12, 33, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>)
>>> datetime.date(2009, 2, 21, tzinfo=tz)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tzinfo' is an invalid keyword argument for this function
>>> datetime.time(15, 12, 33, tzinfo=tz)
datetime.time(15, 12, 33, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>)

从上面可以看出now(), datetime(), time()都是可以指定tzinfo信息的,而date是不行的,不知道为什么。所以最好的方法是内部使用datetime对象,需要时进行时区转换,然后再输出。

时区转换:
>>> utc = pytz.utc
>>> n = datetime.datetime.now(tz)
>>> n
datetime.datetime(2009, 2, 21, 15, 16, 41, 843000, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)
>>> new = n.astimezone(utc)
>>> new
datetime.datetime(2009, 2, 21, 7, 16, 41, 843000, tzinfo=<UTC>)
>>> utc.normalize(n.astimezone(utc))
datetime.datetime(2009, 2, 21, 7, 16, 41, 843000, tzinfo=<UTC>)

utc是世界标准时间。
上面的代码通过astimezone(utc)将中国时间转为utc标准时间,可以看到不同的时区时间已经不一样了。不过在pytz的文档上说:
Converting between timezones also needs special attention. This also needs to use the normalize method to ensure the conversion is correct.

要注意什么呢?是 daylight savings time,中文叫【日光节约时间】或【夏令时】。对于有采用了夏时制的要使用normzlize来处理,不采用的,直接使用astimezone来处理。所以在通常情况下使用astimezone()就足够了。

另外pytz还提供了全部的timezone信息,如:

>>> from pytz import all_timezones
>>> len(all_timezones)
591
>>> from pytz import common_timezones
>>> len(common_timezones)
439

时区转换时发现的问题:
>>> import pytz, datetime
>>> tz = pytz.timezone('Asia/Shanghai')
>>> tz
<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>

可以看到,它有一个LMT,这是Local Mean Time的缩写,网上查一查意思是本地平均时。而且时间是+8:06,说明与UTC的时差不是8个小时整。先不管它,让我们转换一下试试。

>>> d = datetime.datetime(2009,2,21,23,18,5,tzinfo=tz)
>>> d
datetime.datetime(2009, 2, 21, 23, 18, 5, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>)

这时,时区与tz是一样的,没问题。

>>> x = d.astimezone(pytz.utc)
>>> x
datetime.datetime(2009, 2, 21, 15, 12, 5, tzinfo=<UTC>)

我们转为UTC时区,时间上有差异,没问题。

再转回来。

>>> x.astimezone(tz)
datetime.datetime(2009, 2, 21, 23, 12, 5, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)

奇怪,看到了吧,变成了CST了。时差也成了+8:00了。CST就是Central Standard Time的意思。但这样就造成了转换的不一致。我们应该使用CST标准才对。

让我们再看一下:

>>> datetime.datetime.now(tz)
datetime.datetime(2009, 2, 22, 11, 11, 2, 125000, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)
>>> datetime.time(23, 18, 5, tzinfo=tz)
datetime.time(23, 18, 5, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>)

可以看到now()函数得到的是CST的,而time传入tzinfo是LMT的。(date不支持tzinfo参数)所以我们要进行修订,怎么做,使用timezone对象的localize()方法,如:

>>> d = datetime.datetime(2009,2,21,23,18,5)
>>> tz.localize(d)
datetime.datetime(2009, 2, 21, 23, 18, 5, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)

所以我才明白pytz的文档上说的:

Creating localtimes is also tricky, and the reason why working with local times is not recommended. Unfortunately, you cannot just pass a ‘tzinfo’ argument when constructing a datetime.

所以我的建议是生成带时区的时间时,一定要使用timezone.localize()来生成。不要在时间对象的构造函数中传入tzinfo的方式来实现,为些我封装了一些函数放在了uliweb/utils/date.py中。

另外关于北京时间。在pytz中,我无法找到Asia/Beijing和GMT+8这样的时区设置,但是有些时间转换的工具却有。按理说pytz使用的是标准的时区数据库,我特意下载了查看,的确是没有。

时区处理的确是挺麻烦的事。象有些数据库也支持这样的功能,如postgres支持set timezone的命令,这是在django中看到的。

done!