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:測試
MySQL 數據表:
知識點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)建立表,插入數據:
可見,數據類型爲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。
通常不跨時區的應用,能夠不使用時區,即在settings.py設置 USE_TZ=False
啓用 USE_TZ = True
後,處理時間方面,有兩條 「黃金法則」:
保證存儲到數據庫中的是 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/
參考連接: