相信许多用过django的朋友都碰到过配置时区的问题。好不容易熬夜lu完代码结果一“runserver”出现各种 naive time 、aware time傻傻分不清楚,要是没有时间操作还好,否则动不动来个“can’t subtract offset-naive and offset-aware datetime”什么的,吓的服务器都500了。我知道其实你的内心是奔溃的(#_#,还能不能让人做一个安静的美程序猿了!! )
所以,我决定去django里探一探TIME_ZONE的究竟。
说到TIME_ZONE就必须得提USE_TZ,这个值决定django到底启不启用时区,如果它是False的,那么其实TIME_ZONE就是个摆设,但如果你的django setting 是django默认的,那么USE_TZ肯定就是True啦,然后你又有在model里用到DateTimeField字段,最后很不幸你又是个强迫症患者,那么当网站一运行时,你就会看到源源不断的类似这样的东东出现在你的server_access_log里时:
RuntimeWarning:DateTimeField xxxx received a naive datetime
你的内心一定是奔溃的 x2。
那么如何让这个烦人的东西消失呢?最简单粗暴的方法就是设置USE_TZ=False,从此以后你和服务器就过上了快乐幸福的生活。。等等,如果只是这样的话我还写这么多废话干什么?!
既然django都提供时区给我们用,web又不是software,说不定哪天真的会有外国友人来访呢?@_@既然如此,让我们来驯服django的时区吧~
首先,把TIME_ZONE改成自己对应的时区,比如我就选择了“Asia/Shanghai”,设置USE_TZ=True。那么现在我们的网站就已经是在使用“Asia/Shanghai”时区了。然后,重中之重的就是如何保持我们网站保存的时间数据是符合我们的时区设置的。在django model 中,DateTimeField用来储存时间数据,那么这个field的本质是存储python的datetime类型数据,所以,只要我们让这些存进DateTimeField的datetime类型数据都乖乖按我们的要求加上时区那么一切就ok了。
说到这里,我们就要暂停一下,说说前面提到的”naive time“和“aware time”了:
Naive Time:
从字面意思上理解,我们就知道,这是个“幼稚的时间”,它还傻傻分不清楚时区是什么gui,是不是可以吃。。。所以你就理解为它是个本地时间,不带时区信息,不能直接用与储存,那么我们一般用的:
import datetime
datetime.datetime.now()
所生成的datetime就是个“naive time”,不能直接储存。那么到这里,有同学会说:我知道我知道,datetime还有个
datetime.datetime.utcnow()
生成一定是不“naive”了的吧?我只能悲伤的告诉你,它也是“naive”的。只是.now()生成的是你当前时区的“naive time”,而.utcnow()则生成的是utc标准时区的“naive time”。
Aware Time
还是从字面意思理解,这是个“清醒的时间”,它是长大了的时间,他已不再“naive”,它明白了时区是什么gui,不能吃。那么这个“aware time”就是我们需要的datetime类型,只要给他正确的时区,存进数据库,那么你网站的时间就永远不会有问题了,服务器日志也不会再有让你内心奔溃的东西出现了~
讲到这里,问题很明了了,让“naive time“长大,变成“aware time”我们就可以用它啦。只剩一步,我们就可以和服务器过上愉快幸福的生活啦!那么此刻,我们就要请出tzinfo君了,它就是来教“naive time”做大人的人!
对于每一个“naive time”我们只需要给他一个正确的tzinfo他就可以被正常使用了,那么这个tzinfo从哪里来呢?别急,不知道大家在安装django的时候有没有注意到他会检测你有没有安装pytz这个库,如果没有他会自动帮你装上。没错,就是他啦:
PYTZ:World timezone definitions, modern and historical
简单说就是一个python的时区管理包,那么以后只要我们每次涉及到时间操作时都这样写:
from pytz import timezone
import datetime
my_zone = timezone("Asia/Shanghai")
my_time = datetime.datetime.now().replace(tzinfo=my_zone)
在timezone里填入你合适的时区,生成的datetime就自己长大啦,哈哈。看到这里,有熟悉django的人肯定要问了,django本身也有timezone啊,干嘛要用pytz的timezone?其实,django utils里的timezone就是用pytz实现的,让我们来看django的源码:
#django.utils.timezone.py
"""
Timezone-related classes and functions.
This module uses pytz when it's available and fallbacks when it isn't.
"""
import sys
import time as _time
from datetime import datetime, timedelta, tzinfo
from threading importlocal
from django.conf import settings
from django.utils import lru_cache, six
from django.utils.decorators importContextDecorator
try:
import pytz
exceptImportError:
pytz =None
__all__ =[
'utc','get_fixed_timezone',
'get_default_timezone','get_default_timezone_name',
'get_current_timezone','get_current_timezone_name',
'activate','deactivate','override',
'localtime','now',
'is_aware','is_naive','make_aware','make_naive',
]
# UTC and local time zones
所以我们直接用pytz生成tzinfo只是更直接一些。但实际在django项目中,我建议大家使用:
django.utils.timezone
不是因为我喜欢装x,而是你的时区配置是在setting里,如果有改变,你就得去代码里一处一处去找时区信息并改为正确得。django的timezone里有个方法:
timezone.get_current_timezone()
可以读取setting里的TIME_ZONE信息并返回相应的tzinfo,这样做我们就可以随意改变时区而不用担心代码里还需要手动更改时区信息啦。当然,如果你用django的话,我建议在生成datetime时就不要用
datetime.now()
datetime.utcnow()
这两个方法了,他们生成的都是“naive time”你要得指定timezone才可以使用,多麻烦,其实django早就想到这一点,为我们提供了:
django.utils.timezone.now()
这个方法默认生成的就是根据setting里TIME_ZONE所配置的时区的正确时间,直接用它我们就可以少很多事啦~
从此后你就可以和服务器过上快乐幸福的生活啦~~~~吗?
现在我只讲了存入datetime,那么如果需要读出datetime并正确显示出来呢?到这里很多人会说,这多简单啊~直接读出datetime然后用strftime或者直接str显示datetime不就可以了?
嗯,如果我们存进的datetime的时间与他的时区和你所在的地方对应,那么这样做没有任何问题,但如果你在存入时间时不幸的使用了:
datetime.datetime.utcnow().raplace(tzinfo=xxx)
是的,虽然你指定了正确的时区,但那只是把utcnow的时间换了一个时区信息而已,系统只是认为utcnow里存的时间是那个时区的,相当于只做一个标示的作用,如果这时候直接用strftime格式化出的时间依然是utc标准时间而非你本地的时间。那么该如何做才可以正确的显示我们需要的时间呢?有两种办法:
- 使用datetime.astimezone()
- 使用django.utils.timezone.localtime()
这两种方法都可以将一个时间转换为另一个时区时间,而 2 中默认转换为django setting里的时区,当然你也可以指定时区转换,后面后此方法的详解。所以,只要这样写,显示你的datetime就没有任何问题啦:
import datetime
from pytz import timezone
utc_zone = timezone("utc")
my_zone = timezone("Asia/Shanghai")
my_time = datetime.datetime.utcnow().replace(tzinfo=utc_zone)
out_time = my_time.astimezone(my_zone)
print out_time.strftime('%Y-%m-%d %H:%M:%S')
我们继续看看django的timezone,讲一些其他常用的方法:
def localtime(value, timezone=None):
"""
Converts an aware datetime.datetime to local time.
Local time is defined by the current time zone, unless another time zone
is specified.
"""
if timezone isNone:
timezone = get_current_timezone()
# If `value` is naive, astimezone() will raise a ValueError,
# so we don't need to perform a redundant check.
value = value.astimezone(timezone)
if hasattr(timezone,'normalize'):
# This method is available for pytz time zones.
value = timezone.normalize(value)
return value
可以看到,timezone.localtime()可以将一个其他时区的“aware time”转换为本地时间,当然,这个本地时间依然是有时区的,不是“naive”的,你也可以指定一个timezone让localtime将“aware time”转换为其他时区的time。
def now():
"""
Returns an aware or naive datetime.datetime, depending on settings.USE_TZ.
"""
if settings.USE_TZ:
# timeit shows that datetime.now(tz=utc) is 24% slower
return datetime.utcnow().replace(tzinfo=utc)
else:
return datetime.now()
timezone.now()可以根据setting中的USE_TZ返回一个有(无)时区的datetime。
def is_aware(value):
"""
Determines if a given datetime.datetime is aware.
The logic is described in Python's docs:
http://docs.python.org/library/datetime.html#datetime.tzinfo
"""
return value.tzinfo isnotNoneand value.tzinfo.utcoffset(value)isnotNone
def is_naive(value):
"""
Determines if a given datetime.datetime is naive.
The logic is described in Python's docs:
http://docs.python.org/library/datetime.html#datetime.tzinfo
"""
return value.tzinfo isNoneor value.tzinfo.utcoffset(value)isNone
那么timezone.is_aware()和timezone.is_naive()分别可以判断一个datetime是否是“aware”(“naive”)。
def make_aware(value, timezone=None, is_dst=None):
"""
Makes a naive datetime.datetime in a given time zone aware.
"""
if timezone isNone:
timezone = get_current_timezone()
if hasattr(timezone,'localize'):
# This method is available for pytz time zones.
return timezone.localize(value, is_dst=is_dst)
else:
# Check that we won't overwrite the timezone of an aware datetime.
if is_aware(value):
raiseValueError(
"make_aware expects a naive datetime, got %s"% value)
# This may be wrong around DST changes!
return value.replace(tzinfo=timezone)
def make_naive(value, timezone=None):
"""
Makes an aware datetime.datetime naive in a given time zone.
"""
if timezone isNone:
timezone = get_current_timezone()
# If `value` is naive, astimezone() will raise a ValueError,
# so we don't need to perform a redundant check.
value = value.astimezone(timezone)
if hasattr(timezone,'normalize'):
# This method is available for pytz time zones.
value = timezone.normalize(value)
return value.replace(tzinfo=None)
timezone.make_aware()与timezone.make_naive()则分别是处理datetime为“aware”和“naive”。
综合运用这些方法,就可以解决大多数timezone遇到的问题啦~
转自https://www.rapospectre.com/blog/24