Python Django MySQL,时区、日期、时间戳(USE_TZ=True时的时间存储问题)

Python Django MySQL,时区、日期、时间戳,写下这个标题的时候,头脑里面回荡着火车上的经典广告词:啤酒、饮料、矿泉水,花生、瓜子、八宝粥。固然本文跟这些零食吃喝没有关系,咱们主要来聊聊时间问题。python


环境说明:sql

一、约定:数据库

  本文中的“时间”,如未特别说明均指“日期+时间”,即形如“%Y-%m-%d %H:%M:%S”,或“yyyy-mm-dd HH:MM:SS” 等包含日期和时间点的值,可能包含形如 “.fraction” 的毫秒级的数值以及时区标识。django

二、软件版本:数组

  Python:2.7.10session

  Django:1.11.12 final并发

  MySQL:5.7.18app

三、基础数据:函数

  Django Model:测试

class IcsServiceStatusModel(models.Model): class Meta: db_table = 'ics_service_status' app_label = 'ics_meta' objects = SelfDefinedManager() id = models.AutoField(primary_key=True) pub_id = models.CharField(max_length=15) service_mode = models.SmallIntegerField(max_length=5) current_session = models.CharField(max_length=50) last_msg_dt = models.DateTimeField() created_at = models.DateTimeField() updated_at = models.DateTimeField()

  MySQL 数据表:

CREATE TABLE IF NOT EXISTS `ics_service_status` ( `id` int(20) UNSIGNED NOT NULL AUTO_INCREMENT, `pub_id` varchar(15) NOT NULL, `service_mode` tinyint(5) NOT NULL, `current_session` varchar(50) NOT NULL, `last_msg_dt` datetime NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='智能客服服务状态跟踪表' $$

知识点1、计算机上的时间表示

一、时间的常规表示

时间一般分为日期和时刻,表示为 y 年 m 月 d 日 H 时 M 分S 秒。几个单位之间的换算简单计为1年=365天(闰年366天),1个月=28天、29天、30天或31天(分别对应平年的2月,闰年的2月,三、六、九、11月,一、三、五、七、八、十、12月),1天=24小时,1小时=60分钟,1分钟=60秒。经典格式:yyyy-mm-dd HH:MM:SS。

二、时区

太阳一升一落一天过去了。毛爷爷说“青年人好像早晨八九点钟的太阳”,咱们能够很直观地感觉到八九点正式太阳升起后两三个小时正挂在半空的时候。一样的这句话换做是西半球的美国人、南半球的巴西人、赤道上的印尼人来理解也都是不会有问题的。由于咱们平常生活中采用的计时方式都是参考太阳的移动周期来肯定时刻的(好比中国的日晷):一天分为24个小时,太阳的照射下,物体的影子最短的时候为正午12点,先后均分,各12个小时,由此肯定的时间为地方时,或本地时间Local Time)。地理知识告诉咱们,地球的自传、绕太阳公转使得让物体的影子最短的太阳须要由东到西逐次照射到地球的每一个角落。因此咱们看到的八九点钟的太阳跟外国人眼中的八九点钟的太阳已经不是同一个。这种时间表示方式方便在世界各地的人们相对统一地创建时间与环境的对应关系,由此产生便于沟通交流的意识。然而不一样经度上的人们,尤为是跨度比较大的两个地方,若是都使用各自本地的时间交流的话就要出乱子了。已知华盛顿的正午比北京的正午来的晚12个小时。假如华盛顿飞北京要13个小时,身居北京的小丽晚上8点吃过晚饭后就给美国的好朋友凯特打电话说:“明天晚上8点去我家参加生日派对,傍晚6点去机场接你”。凯特是个很守时的小朋友。他想傍晚6点要飞到北京,总共又要飞13个小时,那岂不是要早晨5点就出发了?因而乎次日凯特起了个大早,5点天还蒙蒙亮飞机准时起飞了。手上捧着精心挑选的礼物,期待着快点送给小丽。13个小时事后飞机平稳地降落在北京的机场,在机场的到达厅他没有看到小丽,太阳刚刚升起,路边的摊贩正在叫卖早餐。凯特打电话给小丽,却被臭骂了一顿“你为何不许时参加个人生日派对?” 这是为啥捏?

为了便于不一样地区的人们交流时间,地球人须要一个统一的标准时间(UTC, Coordinated Universal  Time, 协调世界时,诞生于1972年;注1:与世界时UT相差±0.9s;注2:UTC 基于TAI,即International Atomic Time,国际原子时,计算秒)。大伙约定以东经0°(也是西经0°,俗称本初子午线)所在地区的地方时的中午12点做为世界标准时间的0点。同时东经180°(也是西经180°)设为标准日期变动的界线。这么看世界标准时间更像是国际日期变动线上的阿留申群岛居民的地方时。其余地区的本地时间与世界标准时间则用时差进行换算:由本初子午线往东,每隔15°经度时间加1小时;往西,每隔15°经度时间减去1小时。这样以15°为单位划分的360°÷15°=24个区域就是时区。

回到小丽请客的问题,假设他们打电话的时刻是位于东八区的北京时间2018年5月6日晚上8点,那么此时正是世界标准时间的5月6日中午12点,而位于西四区的华盛顿,凯特的家,当地的时间则为5月6日的上午8点。而小丽说的生日派对的时间为北京时间5月7日晚上8点,对应世界标准时间则为5月7日中午12点,华盛顿凯特家的5月7日上午8点。因此凯特应该在当地时间5月6日下午5点出发,才能遇上小丽在机场接他。然而他是华盛顿时间5月7日早晨5点出发的,对应世界标准时间为5月7日上午9点,是小丽所在的北京时间的5月7日下午5点了,一个小时可不是要错过吗?

因此小丽在说时间的时候必定要强调是北京时间的明晚8点,表示为:2018-05-07 20:00:00+8:00:00 (或2018-05-07 20:00:00 UTC+8),而凯特在作行程计划的时候也要记得把约会时间转换为华盛顿的第二天早上8点:2018-05-07 08:00:00-4:00:00,从而推算出发时间是华盛顿时间的当天下午5点,即:2018-05-06 17:00:00-4:00:00。固然,若是他们统一用世界标准时交流的话会更简单。小丽只须要说“2018年5月7日的12点我生日,过来 happy,10点去机场接你”,卡特也只要买好世界标准时间2018年5月6日21点的机票就能够按时赴约了。

总之呢,交流时间的时候声明是哪里的时间是很重要滴。一个明确的日期时间能够表示为:

yyyy-mm-dd HH:MM:SS UTC±n

其中“+”表示“东”,“-”表示“西”,结合 n 一块儿表示时区的编号。UTC±n 声明了前面的时间是哪一个时区的地方时。转换为世界标准时间只须要 ±n 小时便可。若是不写后面的 UTC±n,默认状况下就是 UTC±0,也就是标准世界时间了。

世界时区查询:http://www.shijian.cc/shiqu/

三、时间戳

时间的表示常用一个公认的参考点,好比平常使用中默认的“(公元后)几几年”,须要明确说明的“公元前几几年”、“民国几几年”、“顺治几几年”,它们分别用公认的元年、新国家的成立日期、统治者的上位日期等做为参考点。在计算机里面咱们以 1970年1月1日0点做为参考点,用误差值来记录具体的时间,精确到秒。参考时间点的设置有时是为了便于记忆、也便于使用。而计算机上参考点的设置还受到一个客观因素的约束。

早期的CPU和操做系统以32位为主。若是用一个整数完整地表示公元后的时间,精确到秒,那么y年m月d日H时M时S秒须要用整数

(y-1)×365×24×3600+(m-1)×30×24×3600+(d-1)×24×3600+H×3600+M×60+S)

来表示(假设一年365天、一个月30天),以2018年7月17日15点12分46秒为例,对应的整数值为

2017×365×24×3600+6×30×24×3600+16×24×3600+15×3600+12×60+46 = 63,665,190,766

然而32位处理器可以表示的最大整数值为:无符号数,2^32-1,即 4,294,967,295;有符号数,2^31-1,即 2,147,483,647,远不及 63,665,190,766。一年(按365天算)等于 31,536,000 秒,32位处理器能表示的最大整数值只能表示 (2^31-1)÷(365×24×3600) ≈ 68(年) 也就是只能表示公元前68年到公元68年之间的日期。

那么该怎么知足使用计算机处理时间的需求呢?“计算机计时元年” 的概念由此诞生。UNIX操做系统考虑到计算
机产生的年代和应用的时限综合取了1970年1月1日做为UNIX TIME的纪元时间。因而y年m月d日H时M时S秒,用该时间与纪元时间的整数差值表示为

(y年m月d日与1970年1月1日的日期差)×24×3600+H×3600+M×60+S

该差值也称为时间戳。一样的,哪怕是时间差值,32位的处理器也只能表示1970年1月1日先后68年的时间,也就是 1901年12月13日20时45分52秒到2038年01月19日03时14分07秒。因此呢,使用32位处理器的老机器们届时将面临相似“千年虫”的“2038年问题”。

知识点2、MySQL 中的时间表示

一、datetime 和 timestamp

在 MySQL 中时间能够用 datetime 和 timestamp 两种类型的字段表示。(date 类型能够存储日期,time 类型能够存储时间)

两者的相同点:可经过设置默认值自动更新和初始化,默认显示格式都为:YYYY-MM-dd HH:mm:ss

两者的不一样点:

    ① timestamp 类型的字段实际存储的是距离1970年1月1日0点的秒数,用4字节存储,能够根据时区设置转换为指定时区的时间值,存储范围从 '1970-01-01 00:00:01' UTC 到 '2038-01-19 03:14:07' UTC。

    ② datetime 类型的字段用8字节存储,对时区设置无感知,存储范围从 '1000-01-01 00:00:00' 到 '9999-12-31 23:59:59'。

二、MySQL 中获取时间能够用如下函数

    ① now():当前日期时间,例如:"2018-07-18 16:07:23"

    ② curdate():当天日期,例如:"2018-07-18"

    ③ curtime():当前时间,例如:"16:07:23"

    ④ timestamp、current_timestamp、current_timestamp()、localtime()、localtimestamp()、unix_timestamp(now())

    ⑤ date_sub(curdate(),interval 1 day):日期减(date_add、timediff)

三、在实验中理解 datetime 和 timestamp 的不一样

(1)查看 explicit_defaults_for_timestamp,即,“是否明确地给 timestamp 类型的字段设置默认值”:

    注:MySQL 5.6.6 版本启用了系统变量 explicit_defaults_for_timestamp,高于 5.6.6版本的 MySQL 则有该特性。

    当 explicit_defaults_for_timestamp=false 时,按照以下规则"初始化":

    ① 未明确声明为 NULL 属性的 TIMESTAMP 列被分配为 NOT NULL 属性。 (其余数据类型的列,若是未显式声明为 NOT NULL,则容许 NULL 值。)将此列设置为NULL将其设置为当前时间戳。

    ② 表中的第一个 TIMESTAMP 列(若是未声明为 NULL 属性或显式 DEFAULT 或 ON UPDATE 子句)将自动分配 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 属性。

    ③ 第一个以后的 TIMESTAMP 列(若是未声明为 NULL 属性或显式 DEFAULT 子句)将自动分配 DEFAULT '0000-00-00 00:00:00'(“零”时间戳)。 对于不指定此列的显式值的插入行,该列将分配 “0000-00-00 00:00:00”,而且不会发生警告。

    当 explicit_defaults_for_timestamp=true 时,按照以下规则"初始化":

    ① 未明确声明为 NOT NULL 的 TIMESTAMP 列容许 NULL 值。 将此列设置为 NULL,而不是当前时间戳。

    ② 没有 TIMESTAMP 列自动分配 DEFAULT CURRENT_TIMESTAMP 或 ON UPDATE CURRENT_TIMESTAMP 属性。 必须明确指定这些属性。

    ③ 声明为 NOT NULL 且没有显式 DEFAULT 子句的 TIMESTAMP 列被视为没有默认值。 对于不为此列指定显式值的插入行,结果取决于 SQL 模式。 若是启用了严格的 SQL 模式,则会发生错误。 若是未启用严格的 SQL 模式,则会为列分配隐式默认值 “0000-00-00 00:00:00”,并发出警告。 这相似于 MySQL 如何处理其余时间类型,如 DATETIME。

    不一样 TIMESTAMP 默认值的做用:

    ① CURRENT_TIMESTAMP

        在建立新记录的时候把这个字段设置为当前时间,但之后修改时,再也不刷新它

    ② ON UPDATE CURRENT_TIMESTAMP

        在建立新记录的时候把这个字段设置为0,之后修改时刷新它

    ③ CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  

        在建立新记录和修改现有记录的时候都对这个数据列刷新

    ④ ‘yyyy-mm-dd hh:mm:ss’ ON UPDATE CURRENT_TIMESTAMP

        在建立新记录的时候把这个字段设置为给定值,之后修改时刷新它

(2)查看 time_zone,即,时区设置:

(3)建立表,插入数据:

SHOW VARIABLES LIKE '%explicit_defaults_for_timestamp%'; SHOW VARIABLES LIKE '%time_zone%'; CREATE DATABASE test; USE test; CREATE TABLE IF NOT EXISTS `z_test` ( `msg_dt` varchar(19) NOT NULL, `create_dt` datetime NOT NULL, `update_dt` TIMESTAMP NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试时间表示'; INSERT INTO z_test VALUES (now(), now(), now()); INSERT INTO z_test VALUES (unix_timestamp(now()), unix_timestamp(now()), unix_timestamp(now())); INSERT INTO z_test VALUES ('2018-07-18 11:55:32', '2018-07-18 11:55:32', '2018-07-18 11:55:32'); INSERT INTO z_test VALUES (localtime, localtime, localtime); INSERT INTO z_test VALUES (localtimestamp(), localtimestamp(), localtimestamp()); SELECT * FROM fspis_meta.z_test; SET time_zone = '+4:00'; SELECT * FROM fspis_meta.z_test; SET time_zone = 'SYSTEM'; UPDATE z_test SET msg_dt='plain text'; SELECT * FROM fspis_meta.z_test; ------------------------------------------------------------------------------ SET explicit_defaults_for_timestamp = 'ON'; -- 再作一遍上述操做 

 

    可见,数据类型为TIMESTAMP 的 update_dt 字段被设置了默认值“CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP”

(4)时区修改成“+4:00”后的查询结果:

(5)将时区还原为“SYSTEM”,即“+8:00”,更新 msg_dt 字段:

    因为 update_dt 字段的 default 值为 “CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP”,因而自动更新。


(6)当 explicit_defaults_for_timestamp=true 时,实验结果:

    更新 msg_dt 字段:

    因为 update_dt 字段的 default 值为 null,此时更新记录并无影响 update_dt 字段的值

知识点3、Django 中时间的使用

一、Python 中的时间函数

    import datetime, time, pytz 模块

    (1)datetime 模块提供:datetime、date、time、timezone、tzinfo、timedelta、struct_time 等类

        其中 datetime 类包含:now()、utcnow()、utcoffset()、tzname()、time()、timestamp()、date()、ctime() 等函数用于操做时间,datetime.now() 以数组的形式即 struct_time 表示时间;

    (2)time 模块提供:struct_time 等类;其中,time.time() 以时间戳的形式表示时间,time.localtime() 以数组的形式即 struct_time 表示时间;

    (3)pytz 提供:timezone() 等函数

    (4)时间格式化代码

代码 做用 代码 做用
%a 星期几的简写 %A 星期几的全称
%b 月分的简写 %B 月份的全称
%c 标准的日期的时间串 %C 年份的后两位数字
%d 十进制表示的每个月的第几天 %D 月/天/年
%e 在两字符域中,十进制表示的每个月的第几天 %F 年-月-日
%g 年份的后两位数字,使用基于周的年 %G 年分,使用基于周的年
%h 简写的月份名 %H 24小时制的小时
%I 12小时制的小时 %j 十进制表示的每一年的第几天
%m 十进制表示的月份 %M 十时制表示的分钟数
%n 新行符 %p 本地的AM或PM的等价显示
%r 12小时的时间 %R 显示小时和分钟:hh:mm
%S 十进制的秒数 %t 水平制表符
%T 显示时分秒:hh:mm:ss %u 每周的第几天,星期一为第一天 (值从0到6,星期一为0)
%U 第年的第几周,把星期日作为第一天(值从0到53) %V 每一年的第几周,使用基于周的年
%w 十进制表示的星期几(值从0到6,星期天为0) %W 每一年的第几周,把星期一作为第一天(值从0到53)
%x 标准的日期串 %X 标准的时间串
%y 不带世纪的十进制年份(值从0到99) %Y 带世纪部分的十制年份
%z ,%Z 时区名称,若是不能获得时区名称则返回空字符。 %% 百分号

    Python 的 datatime.datetime对象有一个 tzinfo 属性,该属性是 datetime.tzinfo 子类的一个实例,他被用来存储时区信息。当某个 datetime 对象的 tzinfo 属性被设置并给出一个时间偏移量时,咱们称该 datetime 对象是 aware (已知) 的。不然称其为 naive (原生) 的。 可使用 is_aware() 和 is_naive() 函数来判断某个 datetime 对象是 aware 类型或 naive 类型。

二、Django Utils 中的时间函数

    import django.utils import timezone, tzinfo, datetime_safe 模块

    timezone 模块也提供了 datetime、tzinfo、timedelta 等类和 local() 等函数

    timezone.make_aware(datetime.datetime.now(), timezone.get_default_timezone()) 能够为 naive 类型的 datetime 添加时区属性

三、Django 默认关闭时区支持,开启时区支持,须要在 settings 中设置 USE_TZ = True 。最好同时安装 pytz 模块(pip install pytz) 。Django 的 settings.py 中与时间相关的设置

    TIME_ZONE = 'Asia/Shanghai'

    USE_TZ = True

    当设置了 TIME_ZONE 则 Django(Django 默认的 TIME_ZONE = 'America/Chicago' 或 system 时区)将使用指定的时区,它将影响 datetime.locale、now()等函数的返回值。

    当设置了 USE_TZ 为 True 时,Django 与其余系统或服务的交流将强制使用 UTC 时间。

    可能踩的坑:

    当 USE_TZ=True 时,把时间存储到数据库的时候 “INSERT INTO table_name VALUES('datetime_str' 或 datetime实例)” Django 将会把 'datetime_str' 和 datetime 实例转换为 UTC 时间。因为MySQL 的 datetime 类型字段对时区是无感知的,因此会直接存储由 Django 传递过去的 UTC 形式的时间。在中国,这个问题表现为存储到数据库里面的时间会晚8个小时。

    解决方法:dt.replace(tzinfo=pytz.utc),也就是在存储前将 datetime 的时区信息改成 UTC。

def datetime_for_db(dt=None, dt_str=None): if dt: return dt.replace(tzinfo=pytz.utc) elif dt_str: try: dt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') return dt.replace(tzinfo=pytz.utc) except Exception: raise Exception else: return datetime.now().replace(tzinfo=pytz.utc)

    通常不跨时区的应用,能够不使用时区,即在settings.py设置 USE_TZ=False

启用 USE_TZ = True 后,处理时间方面,有两条 “黄金法则”:

  1. 保证存储到数据库中的是 UTC 时间;

  2. 在函数之间传递时间参数时,确保时间已经转换成 UTC 时间;

好比,一般获取当前时间用的是:

  import datetime

  now = datetime.datetime.now()

启用 USE_TZ = True 后,须要写成:

  import datetime

  from django.utils.timezone import utc

  now = datetime.datetime.utcnow().replace(tzinfo=utc)

或:

  from django.utils import timezone

  now = timezone.now()

保证 now 变量存放的是 UTC 时间。

再如 fromtimestamp() 这个函数,启用 USE_TZ = True 后应使用 utcfromtimestamp() 函数替代。

附录

Django 官方网站对 timezone 的说明:

https://docs.djangoproject.com/en/1.11/topics/i18n/timezones/

参考连接:

https://blog.csdn.net/wy00703/article/details/45071277

https://blog.csdn.net/qq_37049781/article/details/79347278

相关文章
相关标签/搜索