在开发国际化网站的时候,难免会与时区打交道,通用CMS更是如此,毕竟其潜在用户可能是来自于全球各地的。Django在时区这个问题上下了不少功夫,但是很多资深的开发者都有可能尚未完全屡清楚Django中各种时间的实际意义和使用方法,导致写出错误的代码;作为安全研究人员,时区问题也可能和一些安全问题挂钩,比如优惠券的过期时间、订单的下单与取消时间等,如果没有考虑时区问题,有可能将导致一些逻辑漏洞。
本文就从多个常用模块开始,了解一下Django中的时区究竟是怎么回事,以及在时间的比较中可能出现的一些逻辑错误。
从“两种时间”说起
我们都知道,在Python中表示“时间”的对象是 datetime.datetime
。
其实在Python中,这个对象被分成了两个类型:
- aware datetime
- naive datetime
他们的区别是:如果 datetime
对象的 tzinfo
属性有设置时区值,则这个对象是一个aware
datime;否则它是一个naive datetime。
举个例子,我们平时在编写Python脚本的时候,使用下面这行代码获取当前时间:
from datetime import datetime
t = datetime.now()
此时,t是一个naive datetime,因为我们没有给他设置时区:
naive
的中文意思大家应该都很熟悉,这里的大概意思就是“simple”,这是一个很简单、原始的时间对象。实际上就是指,计算机不知道这个时间,他的时区究竟是什么,它可能代表着北京时间,也可能是UTC时间,因为我们没有指定时区,我们无法“假设”其是计算机系统所在的时区,也无法“假设”其是UTC时区。也就是说,计算机拿到了一个naive
datetime,是无法准确地定位到某一个时间点的,也无法直接转换成一个unix时间戳。
那么相对的,aware
datetime就是计算机能准确知道其时区的时间对象,他是一个准确的时间点,就落在时间轴上的某个地方,不管从哪个时区看,这个点都是绝对固定的。所以,我们可以将一个aware
datetime转换成unix时间戳。
有的同学可能比较好奇,你说naive datetime无法转换成时间戳,那么为什么这个对象有一个 timestamp()
方法呢:
原因我们查文档可以得出结论,如果对象是naive datetime,则会以当前系统本地时区为准。
Django的时区配置
回到Django。由于Django是一个国际化框架,时区相关处理自然是其必不可少的组成部分。Django的配置项中,有下面两个选项与时区相关:
USE_TZ
TIME_ZONE
USE_TZ
用来指定整个项目是否 使用时区 , TIME_ZONE
是默认时区的值。
如果 USE_TZ
的值设置为False,那么Django项目中所有时间都使用naive
datetime(除非有明确指定时区的情况)。也就是说,网站内存储和使用的时间全部是 TIME_ZONE
的值所指定的时区。
这样做有一些弊端:
- 数据库中保存的是naive datetime,导致在跨区域迁移数据的时候,可能无法准确定位到某个时间点
- 国际化企业可能面向不同国家有不同的网站,但后台数据库相同,此时究竟使用哪个时区保存和展示时间,将引起混乱
- 即使是同一个网站的用户,他们可能来自于全球各地,查看到的时间却是统一的服务器时间,对于高交互式的应用十分不友好
- 即使网站面向的用户仅来自于某一个地区,也会涉及到“夏时令”(Daylight Saving Time)相关的问题,每年可能将会导致两次时间误差
默认情况下,用 django-admin
生成的项目,其设置中 USE_TZ
等于True,这也是Django官方建议的配置。此时,在网站内部存储与使用的是UTC时间,而与用户交互时使用 TIME_ZONE
或手工的时区。
我们后文中也以Django的默认配置 USE_TZ=True
为前提条件,否则也没有讨论的必要了。
Django的时间函数
Django的包 django.utils.timezone
中有下面几个常用的时间相关函数:
- now(),返回当前的UTC时间
- localtime(),返回当前的本地时间(默认是TIME_ZONE配置指定的时区时间)
- is_aw