• Ukieweb

    佳的博客

    曾梦想仗剑天涯,后来工作忙没去。

彻底理解 Django 中的时区 TIME_ZONE 和 USE_TZ 的设置 timezone的使用

开始之前你需要了解下面的知识!!!

1. 时区 以及 UTC 和DST

时区:由于世界各国家与地区经度不同,地方时也有所不同,因此会划分为不同的时区。【参考百度百科

UTC:  可以视为一个世界统一的时间,以原子时为基础,其他时区的时间都是根据自己所在的时区在这个基础上增加或减少的,比如中国的时区就为 UTC+8。【参考百度百科

DST(夏时制, day saving time)则是为了充分利用夏天日照长的特点,充分利用光照节约能源而人为调整时间的一种机制。在夏天将时间人为的向前加一小时,使人们早睡早起节约能源。虽然很多西方国家都采用了DST,但是中国不采用DST。【参考百度百科

在现实环境中,存在有多个时区。用户之间很有可能存在于不同的时区(不同时区时间UTC +差值),并且许多国家都拥有自己的一套夏令时系统(用到夏时令时也是要进行+-1)。所以如果网站面向的是多个时区用户,只以当前本地时间为标准开发,是会对不同时区的用户产生影响的。

2.Naive 和 Aware 类型的 datetime 对象

Python 的 datatime.datetime对象有一个 tzinfo 属性,该属性是 datetime.tzinfo 子类的一个实例,他被用来存储时区信息

当某个 datetime 对象的 tzinfo 属性被设置为一个时区,并给出一个时间偏移量时,我们称该 datetime 对象是 aware (已知) 的。否则称其为 naive (原生) 的。

  • naive 是tzinfo=None 的datetime 对象,

  • aware 是 tzinfo=某个时区 的 datetime对象

可以使用  from django.utils import timezone  类的 is_aware() 和 is_naive() 方法来判断某个 datetime 对象是 aware 类型或 naive 类型。

3.datetime类和 timezone 类

python 自带的 datetime模块下的 datetime 类 和 Django 的 utils 模块下的 timezone 类

  • datetime 类: 这个是python 自带的类,当然也可以在 django 的代码中使用

  • timezone类:是django 中 处理 datetime 时候用的类,是对 dateime 类的二次封装

4.Django 中的 TIME_ZONE 和 USE_TZ 参数

在 settings 中 TIME_ZOME 和 USE_TZ 两个参数会对django 在使用处理 时间日期时起到影响

4.1.TIME_ZONE

默认即不设置时time_zone时,值为America/Chicago ;设置time_zone为某个时区时, 则为设置的值

TIME_ZONE所做的动作:

  • 在Unix环境(time.tzset()已实现)上,Django将os.environ['TZ'] 变量设置为 您在TIME_ZONE设置中指定的时区 。因此,您所有的视图和模型都将在该时区自动运行。但是,TZ 如果您使用手动配置的选项(settings.configure(DEBUG=True)),则Django不会设置环境变量。如果Django没有设置TZ 环境变量,则取决于您的进程在正确的环境中运行。

  • 在 windows 中,TIME_ZONE 要和系统的设置为一样,因为它不能在windows 中设定 time zones 环境变量

USE_TZ 对 TIME_ZONE 的影响:

  • USE_TZ=True:当设置为 true 时候,则在 template 和 forms 中时间显示会自动转为这个timezone的navie 类型的datetime对象

  • USE_TZ=False:不使用 aware 类型的datetime ;django 用到的所有地方都是这个timezone 的 navie 类型的 datetime对象

我们看看 Django 源码,在django初始化时设置的 TIME_ZONE 具体做了什么

from django.conf import settings

# 看到如下代码

class Settings:
    def __init__(self, settings_module):
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))
        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module
        mod = importlib.import_module(self.SETTINGS_MODULE)
        tuple_settings = (
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
        )
        self._explicit_settings = set()
        for setting in dir(mod):
            if setting.isupper():
                setting_value = getattr(mod, setting)
                if (setting in tuple_settings and
                        not isinstance(setting_value, (list, tuple))):
                    raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
                setattr(self, setting, setting_value)
                self._explicit_settings.add(setting)
        if not self.SECRET_KEY:
            raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")
        if self.is_overridden('FILE_CHARSET'):
            warnings.warn(FILE_CHARSET_DEPRECATED_MSG, RemovedInDjango31Warning)
        if hasattr(time, 'tzset') and self.TIME_ZONE:
            # When we can, attempt to validate the timezone. If we can't find
            # this file, no check happens and it's harmless.
            zoneinfo_root = Path('/usr/share/zoneinfo')
            zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split('/'))
            if zoneinfo_root.exists() and not zone_info_file.exists():
                raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
            # Move the time zone info into os.environ. See ticket #2315 for why
            # we don't do this unconditionally (breaks Windows).
            os.environ['TZ'] = self.TIME_ZONE
            time.tzset()

毫无疑问,首先会访问django.conf.__init__.py文件。在这里settings是一个lazy object,当访问settings的时候,真正实例化的是以下这一个Settings类。

可以看到如果设置了 TIME_ZONE 会将os.environ['TZ'] 设置为 TIME_ZOME 变量的值

4.2. USE_TZ 

一个布尔值,用于指定datetime对象 默认情况下是否是 aware 类型

  • 如果设置为False,django 使用 naive 类型的时间

  • 如果设置为True,Django使用 aware即带时区的日期时间。

4.3.TIME_ZONE 和 USE_TZ 的影响面

有了上面的知识,下面就来说说 django 中的时区问题,即对 settings 中的 TIME_ZONE 和 USE_TZ 设置及其产生的影响。

仅对 datetime类和 timezone类分析:

  • TIME_ZONE 的实质是改变Django 运行环境的时区;所以它会影响到 datetime 和timezone的时区。

  • USE_TZ 它只作用于timezone,不会影响 datetime 类,datetime.now() 它返回的永远都是一个 naive 类型的 datetime 对象。

from django.utils.timeout import now
# 源码如下

def now():
    """
    Return 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()

要看 TIME_ZONE 和 USE_TZ 对 DJANGO 最全面影响----读源码

方法:在 Django 模块中

cd /home/vagrant/.pyenv/versions/3.8.3/lib/python3.8/site-packages/django 

grep "TIME_ZONE" ./ -r
grep "USE_TZ" ./ -r

5.timezone类使用

In [67]: dir(timezone)    
Out[67]: 
['ContextDecorator',
 'FixedOffset',
 'Local',
 'RemovedInDjango31Warning',
 'ZERO',
 '__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_active',
 '_get_timezone_name',
 'activate',
 'datetime',
 'deactivate',
 'functools',
 'get_current_timezone',
 'get_current_timezone_name',
 'get_default_timezone',
 'get_default_timezone_name',
 'get_fixed_timezone',
 'is_aware',
 'is_naive',
 'localdate',
 'localtime',
 'make_aware',
 'make_naive',
 'now',
 'override',
 'pytz',
 'settings',
 'template_localtime',
 'timedelta',
 'timezone',
 'tzinfo',
 'utc',
 'warnings']

这里不做全面赘述,只将几个重点方法:

  • is_aware: 是否是一个 aware 类型的 datetime(即带时区)

  • is_naive:是否是一个 naive类型的 datetime

  • make_aware:将 naive 转为 aware

  • make_naive:将 aware 转为 naive

  • now

    • 若USE_TZ=True返回一个时区是 utc的 aware datetime对象;

    • 若USE_TZ=False 等同于 datetime.datetime.now() 返回一个 naive 的datetime对象

  • localtime localdate: 

    • 若USE_TZ=True返回一个时区是 TIME_ZONE的 aware datetime对象

    • 若USE_TZ=False 报错

  • datetime: 导入的是 python datetime,即 timezone.datetime == datetime.datetime

6.总结

在Django 中:

  • 建议 开启 USE_TZ;

  • 代码要用到 datetime,使用 django 的timzone 函数

在所有的系统中:

  • 强烈建议在代码和数据库中统一使用 UTC 时间

  • 仅在与最终用户进行展示时使用本地时间




0
0
下一篇:git tag 的 附注标签 和 轻量标签

0 条评论

老佳啊

85后,大专学历,中原人士,家里没矿。

由于年轻时长的比较帅气,导致在别人眼里,我一直不谈恋爱的原因是清高,实则是自己的小自卑。最大的人生目标就是找一个相知相爱相容的人,共度余生。

和人相处时如果能感受到真诚,会非常注重彼此的关系,对别人没有什么心机,即使有利益冲突,一般也会以和为贵,因为在这个世界上,物质的东西,从来不会吸引到我。

特别迷恋那些大山大水,如果现在还能隐居,可能早就去了。对那些宏伟的有底蕴的人文景观比较不感冒。

从事于IT行业,却一直对厨房念念不忘,由于身材魁梧,总觉得自己上辈子是个将军,可惜这辈子没当兵,也不会打架。