日期時間類型中包含以下幾種數據類型:
各類型都有具體的取值范圍,超出或非法的其他值時,MySQL 會回退到 0。TIMESTAMP 類型是個例外,給它設置一個超出范圍的值時,將保存上該類型允許的最大值。
MySQL 按標准格式 YYYY-MM-DD hh:mm:ss[.fraction]
輸出日期時間,但設置或進行日期時間相關的比較時卻支持靈活的多種格式,會自動解析。具體支持的輸入格式可參見 Section 9.1.3, “Date and Time Literals”。其中 fraction
部分為秒后面的小數部分,取值范圍為 0~6 位。
雖然 MySQL 支持多種格式進行日期時間的設置,但日期部分要求必須是 年-月-日 的形式才能正確解析。比如 98-09-04
是按年月日順序解析的,而不是英文里常用的月日年,或者日月年。
年在只給了兩位數的情況下,MySQL 嘗試使用以下規則來補全:
- 給定的兩位數為 70~99 時解析成 1970 ~ 1999。
- 給定為 00 ~ 69 時解析成 2000 ~ 2069。
所以,為了避免不可預測的結果,使用時還是指定全一些。
- 在需要使用數字的語境下,MySQL 會將日期時間自動轉成數字。同理,在需要日期時間的相關操作語境下,會嘗試將數字解析成日期時間。
- 通過設置 MySQL 相關參數,日期類型可保存原本非法的值,比如開啟 ALLOW_INVALID_DATES 設置項時,可設置日期類型保存一個
2009-11-31
值,但正常情況下我們知道 11 月哪來什么 31 號。此時 MySQL 僅僅只是不檢查月分與日期的關聯性,但月分的取值范圍 112 及日期的取值范圍 131 還是要單獨各自做校驗的。
mysql> INSERT INTO todo (title,created_on) VALUES ('blah','2019-09-31');
ERROR 1292 (22007): Incorrect date value: '2019-09-31' for column 'created_on' at row 1
mysql> SET SESSION sql_mode = 'ALLOW_INVALID_DATES';
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO todo (title,created_on) VALUES ('blah','2019-09-31');
Query OK, 1 row affected, 1 warning (0.01 sec)
mysql> SELECT * FROM todo;
+----+------+------------+
| id | title | created_on |
+----+------+------------+
| 1 | blah | 2019-09-31 |
+----+------+------------+
1 rows in set (0.00 sec)
某些場景下你可能需要保存部分日期,比如用戶只輸入了年沒輸入月日。所以 MySQL 是支持將月日設置成 0,比如 2019-00-00
。但這種情況下就無法從日期相關的操作中獲得到准確的結果,比如使用 DATE_SUB()
或 DATE_ADD()
函數時。禁用月日的零值可通過開啟 MySQL 的 NO_ZERO_IN_DATE
模式。
除了月日可零,MySQL 還支持設置年月日都零的值 0000-00-00
,對於日期非必填的情況比較有用,因為此時它比單純的 NULL
更有語義。可通過開啟 MySQL 的 NO_ZERO_DATE 模式來禁用這個全零的值。
各日期時間零值格式如下,但實際時用時,直接簡寫成一個 0 效果是等效的。
Data Type | “Zero” Value |
---|---|
DATE | '0000-00-00' |
TIME | '00:00:00' |
DATETIME | '0000-00-00 00:00:00' |
TIMESTAMP | '0000-00-00 00:00:00' |
YEAR | 0000 |
DATE,DATETIME,及 TIMESTAMP
三者具有相關性,都支持多種格式的自動解析,詳見 Date and Time Literals。
DATE
日期格式不帶時間 TIME
部分,查詢時輸出格式為 YYYY-MM-DD
,取值范圍為 1000-01-01
到 9999-12-31
。
DATETIME
包含日期及時間,輸出格式為 YYYY-MM-DD hh:mm:ss
,取值范圍 1000-01-01 00:00:00
到 9999-12-31 23:59:59
。
TIMESTAMP
同 DATETIME
,但取值范圍基於 UTC 時間,較 DATETIME
要小,為 1970-01-01 00:00:01
UTC 到 2038-01-19 03:14:07
UTC。所以使用 TIMESTAMP 格式的時間,到 2038 年會溢出,這就是 Year 2038 problem。關於該問題的討論和解決可參見這個 StackOverflow 的回答。
既然如此,為何要使用這個取值范圍更小的呢。TIMESTAMP
存儲的值是帶時區的。在存儲時會根據當前時區轉成 UTC(universal time zone) 存儲,查詢時也會根據時區從 UTC 轉換到具體的時間。對於支持多語及國際化全球部署的應用來說,顯得尤為方便。需要注意的是,這里操作基於的時區默認為服務器的時區,可通過改變 time_zone
SET GLOBAL time_zone=time_zone
來修改。時區的設置也可以是以連接為單位,這樣來自不同時區的請求可得到不同的時間。
TIMESTAMP
和 DATETIME
都可包含至多 6 位的小數來表示時間中毫秒(microseconds)的部分。所以,帶上毫秒時完整的格式是 YYYY-MM-DD hh:mm:ss[.fraction]
。前者取值范圍為 1970-01-01 00:00:01.000000
到 2038-01-19 03:14:07.999999
,后者為 1000-01-01 00:00:00.000000
到 9999-12-31 23:59:59.999999
。
在寫入時,對於非法的日期時間值,將自動存成零值,即 '0000-00-00' 或 '0000-00-00 00:00:00'。
關於日期時間需要注意的點:
- 因為 MySQL 支持比較寬松的格式來設置日期時間,所以理論上你可以用你想用的值來做為數字之間的分界符,但使用時需要關注其解析的原理。比如給一個日期格式的列設置
10:11:12
,雖然這個值看起來像時間類型,但還是可以正確在被解析成目標列的格式,即日期。如果這這個日期列設置10:45:15
則會認為是非法值,因為45
不是一個合法的月份值,所以存儲時變成零值0000-00-00
。 - 日期時間與毫秒的分界符必需是小數點。
- 默認 MySQL 除了檢查日月值是否有有效范圍 1~ 31,1~12。還會將兩者結合進來檢查,比如 4 月沒有 31。所以對於日期
2004-04-31
算是非法的,會變成零值0000-00-00
。如果不需要這樣的約束檢查,可開啟 MySQL 的ALLOW_INVALID_DATES
模式。
日期時間的自動初始化及更新
TIMESTAMP
和 DATETIME
還支持自動初始化(auto-initialized)和更新到當前時間(auto-updated)。
- 創建表定義列時,指定
DEFAULT CURRENT_TIMESTAMP
來使相應的日期時間列自動初始化。 - 指定
ON UPDATE CURRENT_TIMESTAMP
來使相應的日期時間列自動更新。
兩者可同時作用於一個日期時間列,表示插入記錄時自動初始化成當前時間,后續記錄更新時自動更新到當前時間。
其中 CURRENT_TIMESTAMP
指代當前時間,與其有相同效果的還有 CURRENT_TIMESTAMP()
, NOW()
, LOCALTIME
, LOCALTIME()
, LOCALTIMESTAMP
以及 LOCALTIMESTAMP()
。
DEFAULT
除了可指定成當前時間外,也可指定一個任意的固定值,比如 DEFAULT 0
或 `DEFAULT '2000-01-01 00:00:00'。
對於指定了自動初始化的列,插入時如果沒指定該列的值,則會自動設置為當前的時間。
對於指定為自動更新的列,一旦一條記錄中有字段變更,該日期會自動更新成變更時的時間。如果不想它更新,可在插入其他值時手動設置一下該日期列為原有的值,讓其保持不變。
TIMESTAMP
和 DATETIME
在列的定義時,如果指定了小數部分,那么在配合使用 CURRENT_TIMESTAMP(fsp)
時,這個小數部分的精度需要保持一致。比如:
-- ✅
CREATE TABLE t1 (
ts TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
);
-- 🚨
CREATE TABLE t1 (
ts TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(3)
);
TIME
時間 TIME
格式即日期時間中時間的部分,輸出格式為 hh:mm:ss
或時間較大時為 hhh:mm:ss
,取值范圍 -838:59:59
到 838:59:59
。同樣地,也是支持帶至多 6 位小數表示毫秒。
設置時也是支持將多種格式自動解析。對於帶冒號的情況,比如 11:12
解析成 11:12:00
而不是 00:11:12
。不帶冒號的情況,將最右邊的兩位數字解析成秒(按逝去的時間來解析),比如 '1112'
和 1112
不是 11:12:00
而會解析成 00:11:12
。同理,'12'
和 12
會解析成 00:00:12
。
YEAR
YEAR
表示日期中年的部分,是一個 1 字節大小的類型,可通過 YEAR
或 YEAR(4)
來聲明,其展示寬度(display width)為 4。查詢時輸出格式為 YYYY
,取值范圍 1901 到 2155。 0000
也是合法的值。
支持使用以下格式進行設置:
- 使用 1901 ~ 2155 間的四位數字值。
- 或將上面的數字以字符串形式給定。
- 1 ~ 99 之間的數字,此時 1 ~ 69 解析成 2001 ~ 2069,70 ~ 99 解析成 1970 ~ 1999.
- 其他可返回合法值的方法,比如
NOW()
。