流行數(shù)據(jù)庫系統(tǒng) MySQL 的新老用戶常常會(huì)對(duì)數(shù)據(jù)庫處理時(shí)間值的方式感到困惑。有時(shí)用戶不會(huì)費(fèi)心去了解時(shí)間值的數(shù)據(jù)類型。這可能是因?yàn)樗麄冇X得本身也沒有什么好了解的。日期就是日期,對(duì)吧?好吧,并非總是如此。花幾分鐘時(shí)間了解 MySQL 如何存儲(chǔ)和顯示日期和時(shí)間是有益的。學(xué)習(xí)如何最好地利用數(shù)據(jù)庫表中的時(shí)間值可以幫助你成為更好的編碼者。
MySQL 時(shí)間值類型
當(dāng)你在 MySQL 中新建表時(shí),選擇合適的數(shù)據(jù)類型(INT
、FLOAT
、CHAR
等)高效地保存插入到表中的數(shù)據(jù)。MySQL 為時(shí)間值提供了五種數(shù)據(jù)類型。它們是 DATE
、TIME
、DATETIME
、TIMESTAMP
和 YEAR
。
MySQL 使用 ISO 8601
格式來存儲(chǔ)以下格式的值(LCTT 譯注:國際標(biāo)準(zhǔn) ISO 8601,是國際標(biāo)準(zhǔn)化組織的日期和時(shí)間的表示方法,全稱為《數(shù)據(jù)存儲(chǔ)和交換形式·信息交換·日期和時(shí)間的表示方法》):
DATE
:YYYY-MM-DD
TIME
:HH:MM:SS
TIMESTAMP
:YYYY-MM-DD HH:MM:SS
YEAR
:YYYY
DATETIME 與 TIMESTAMP 的比較
你可能已經(jīng)注意到 日期時(shí)間DATETIME 和 時(shí)間戳TIMESTAMP
首先,可以使用的日期范圍不同。DATETIME
可以保存 1000-01-01 00:00:00 和 9999-12-31 23:59:59 之間的日期,而 TIMESTAMP
的范圍更有限,從 1970-01-01 00:00:01 到 2038-01-19 03:14:07 UTC。
其次,雖然兩種數(shù)據(jù)類型都允許你 自動(dòng)初始化auto_initialize 或 自動(dòng)更新auto_update 它們各自的值(分別用 DEFAULT CURRENT_TIMESTAMP
和 ON UPDATE CURRENT_TIMESTAMP
),但在 5.6.5 版本之前,對(duì) DATETIME
值不能這樣操作。如果你要用 DATETIME
,你可以使用 CURRENT_TIMESTAMP
的 MySQL 同義詞之一,例如 NOW()
或 LOCALTIME()
。
如果你對(duì)一個(gè) DATETIME
值使用 ON UPDATE CURENT_TIMESTAMP
(或其同義詞之一),但沒有使用 DEFAULT CURRENT_TIMESTAMP
子句,那么這個(gè)列的默認(rèn)值為 NULL
。除非你在表的定義中包含 NOT NULL
,在這種情況下,它默認(rèn)為 0。
另一件需要記住的重要事情是,盡管通常情況下,除非你聲明一個(gè)默認(rèn)值,否則 DATETIME
和 TIMESTAMP
列都沒有一個(gè)默認(rèn)值,但這個(gè)規(guī)則有一個(gè)例外。如果沒有指定 DEFAULT CURRENT_TIMESTAMP
和 ON UPDATE CURRENT_TIMESTAMP
這兩個(gè)子句,并且禁用 explicit_defaults_for_timestamp
這個(gè)變量,那么你表中的第一個(gè) TIMESTAMP
列將被隱式創(chuàng)建。
要檢查這個(gè)變量的狀態(tài),請運(yùn)行:
mysql> show variables like 'explicit_default%';
如果你想打開或關(guān)閉它,運(yùn)行這段代碼(用 0 表示關(guān)閉,用 1 表示打開):
mysql> set explicit_defaults_for_timestamp = 0;
TIME
MySQL 的 時(shí)間TIME
首先要注意的是,雖然 TIME
經(jīng)常被認(rèn)為是一天中的時(shí)間,但它實(shí)際上是經(jīng)過的時(shí)間。換句話說,它可以是一個(gè)負(fù)值,或者可以大于 23:59:59。在 MySQL 中,一個(gè) TIME
值的范圍可以是 -838:59:59 到 838:59:59。
另外,如果你縮寫一個(gè)時(shí)間值,MySQL 會(huì)因你是否使用冒號(hào)作出不同解釋。例如,10:34 這個(gè)值被 MySQL 看作是 10:34:00。也就是說,十點(diǎn)過后的 34 分鐘。但是,如果你不使用冒號(hào)寫作 1034
,MySQL 將其視為 00:10:34,意思是 10 分鐘 34 秒。
最后,你應(yīng)該知道 TIME
值(以及 DATETIME
和 TIMESTAMP
字段的時(shí)間部分)從 5.6.4 版本開始,可以取一個(gè)小數(shù)部分。要使用它,請?jiān)跀?shù)據(jù)類型定義的結(jié)尾處添加一個(gè)整數(shù)(最大值為 6)的圓括號(hào)。
time_column TIME(2)
時(shí)區(qū)
時(shí)區(qū)變化不僅在現(xiàn)實(shí)世界中產(chǎn)生混亂和疲勞,而且也會(huì)在數(shù)據(jù)庫系統(tǒng)中制造麻煩。地球被劃分為 24 個(gè)獨(dú)立的時(shí)區(qū),通常每隔 15 度經(jīng)度就會(huì)發(fā)生變化。我說通常是因?yàn)橐恍﹪倚惺路绞讲煌@缰袊辉谝粋€(gè)時(shí)區(qū)運(yùn)作,而不是預(yù)期的五個(gè)時(shí)區(qū)。
你如何處理處于不同時(shí)區(qū)的數(shù)據(jù)庫系統(tǒng)的用戶就成了一個(gè)問題。幸運(yùn)的是,MySQL 并沒有使這個(gè)問題變得太困難。
要檢查你的會(huì)話時(shí)區(qū),請運(yùn)行:
mysql> select @@session.time_zone;
如果結(jié)果顯示 System
,這意味著它正在使用你的 my.cnf
配置文件中設(shè)置的時(shí)區(qū)。如果你在本地計(jì)算機(jī)上運(yùn)行你的 MySQL 服務(wù)器,這可能就是你會(huì)得到的,你不需要做任何改變。
如果你想改變你的會(huì)話的時(shí)區(qū),請運(yùn)行如下命令:
mysql> set time_zone = '-05:00';
這將你的時(shí)區(qū)設(shè)置為 美國/東部US/Eastern,比 協(xié)調(diào)世界時(shí)UTC
獲得一周的日期
為了跟上本教程后面部分的代碼,你應(yīng)該在你的系統(tǒng)中創(chuàng)建一個(gè)帶有日期值類型的表。比如:
mysql> create table test
( row_id smallint not null auto_increment primary key,
the_date date not null);
然后使用 ISO 8601 格式在表中插入一些隨機(jī)日期,如
mysql> insert into test (the_date) VALUES ('2022-01-05');
我在我的 test
表中插入了四行日期值,你插入多少行都可以。
有時(shí)你可能想知道某一天是星期幾。MySQL 給了你幾種實(shí)現(xiàn)方法。
第一種,也是最顯而易見的方法,是使用 DAYNAME()
函數(shù)。如下示例表所展示,DAYNAME()
函數(shù)可以告訴你每個(gè)日期是星期幾:
mysql> SELECT the_date, DAYNAME(the_date) FROM test;
+------------+-------------------------------+
| the_date | DAYNAME(the_date) |
+------------+-------------------------------+
| 2021-11-02 | Tuesday |
| 2022-01-05 | Wednesday |
| 2022-05-03 | Tuesday |
| 2023-01-13 | Friday |
+------------+-------------------------------+
4 rows in set (0.00 sec)
另外兩種獲取星期幾的方法是返回整數(shù)值,而不是星期幾的名稱,分別是 WEEKDAY()
和 DAYOFWEEK()
。他們都返回?cái)?shù)字,卻又各不相同。WEEKDAY()
函數(shù)返回從 0 到 6 的數(shù)字,其中 0 代表星期一,6 代表星期日。而 DAYOFWEEK()
則返回從 1 到 7 的數(shù)字,其中 1 代表星期日,7 代表星期六。
mysql> SELECT the_date, DAYNAME(the_date),
WEEKDAY(the_date), DAYOFWEEK(the_date) FROM test;
+------------+------------------+------------------+--------------------+
| the_date | DAYNAME(the_date)| WEEKDAY(the_date)| DAYOFWEEK(the_date)|
| 2021-11-02 | Tuesday | 1 | 3 |
| 2022-01-05 | Wednesday | 2 | 4 |
| 2022-05-03 | Tuesday | 1 | 3 |
| 2023-01-13 | Friday | 4 | 6 |
+------------+------------------+------------------+--------------------+
4 rows in set (0.00 sec)
當(dāng)你只想獲取日期的一部分時(shí)
有時(shí)你可能在 MySQL 表中存儲(chǔ)了一個(gè)日期,但是你只想獲取日期的一部分。這并不是問題。
MySQL 中有幾個(gè)顧名思義的函數(shù),可以輕松獲取日期對(duì)象的特定部分。以下是一些示例:
mysql> SELECT the_date, YEAR(the_date), MONTHNAME(the_date),
DAYOFMONTH(the_date) FROM test ;
+-----------+---------------+-------------------+---------------------+
| the_date | YEAR(the_date)|MONTHNAME(the_date)| DAYOFMONTH(the_date)|
+-----------+---------------+-------------------+---------------------+
| 2021-11-02| 2021 | November | 2 |
| 2022-01-05| 2022 | January | 5 |
| 2022-05-03| 2022 | May | 3 |
| 2023-01-13| 2023 | January | 13 |
+-----------+---------------+-------------------+---------------------+
4 rows in set (0.00 sec)
MySQL 也允許你使用 EXTRACT()
函數(shù)來獲取日期的一部分。你提供給函數(shù)的參數(shù)是一個(gè)單位說明符(確保是單數(shù)形式)、FROM
和列名。因此,為了從我們的 test 表中僅獲取年份,你可以寫:
mysql> SELECT EXTRACT(YEAR FROM the_date) FROM test;
+----------------------------------------------+
| EXTRACT(YEAR FROM the_date) |
+----------------------------------------------+
| 2021 |
| 2022 |
| 2022 |
| 2023 |
+----------------------------------------------+
4 rows in set (0.01 sec)
插入和讀取不同格式的日期
正如之前提到的,MySQL 使用 ISO 8601
格式存儲(chǔ)日期和時(shí)間值。但是如果你想以另一種方式存儲(chǔ)日期和時(shí)間值,例如 MM-DD-YYYY
格式,怎么辦?首先,不要嘗試這樣做。MySQL 以 8601 格式存儲(chǔ)日期和時(shí)間,就是這樣。不要嘗試更改它。但是,這并不意味著你必須在將數(shù)據(jù)輸入到數(shù)據(jù)庫之前將數(shù)據(jù)轉(zhuǎn)換為特定的格式,或者你不能以任何你想要的格式展示數(shù)據(jù)。
如果你想要將非 ISO 的格式的日期輸入到表中,你可以使用 STR_TO_DATE()
函數(shù)。第一個(gè)參數(shù)是你想要存儲(chǔ)在數(shù)據(jù)庫中的日期的字符串值。第二個(gè)參數(shù)是格式化字符串,它讓 MySQL 知道日期的組織方式。讓我們看一個(gè)簡單的例子,然后我將更深入地研究這個(gè)看起來很奇怪的格式化字符串是什么。
mysql> insert into test (the_date) values (str_to_date('January 13, 2023','%M %d, %Y'));
Query OK, 1 row affected (0.00 sec)
你將格式化字符串放在引號(hào)中,并在每個(gè)特殊字符前加上百分號(hào)。上面代碼中的格式序列告訴 MySQL 我的日期由一個(gè)完整的月份名稱 %M
,后跟一個(gè)兩位數(shù)的日期%d
,然后是一個(gè)逗號(hào),最后由一個(gè)四位數(shù)的年份 %Y
組成。請注意,大寫很重要。
一些其他常用的格式化字符串字符是:
%b
縮寫月份的名稱(例如:Jan
)%c
數(shù)字月份(例如: 1)%W
星期名稱(例如: `Saturday)%a
星期名稱的縮寫(例如:Sat
)%T
24 小時(shí)制的時(shí)間(例如:22:01:22
)%r
帶 AM/PM 的 12 小時(shí)制的時(shí)間(例如:10:01:22 PM
)%y
兩位數(shù)的年份(例如: 23)
請注意,對(duì)于兩位數(shù)年份 %y
,年份范圍是 1970 到 2069。因此,從 70 到 99 的數(shù)字被假定為 20 世紀(jì),而從 00 到 69 的數(shù)字被假定為 21 世紀(jì)。
如果你有一個(gè)日期存儲(chǔ)在你的數(shù)據(jù)庫中,你想用不同的格式顯示它,你可以使用這個(gè) DATE_FORMAT()
函數(shù):
mysql> SELECT DATE_FORMAT(the_date, '%W, %b. %d, %y') FROM test;
+-----------------------------------------+
| DATE_FORMAT(the_date, '%W, %b. %d, %y') |
+-----------------------------------------+
| Tuesday, Nov. 02, 21 |
| Wednesday, Jan. 05, 22 |
| Tuesday, May. 03, 22 |
| Friday, Jan. 13, 23 |
+-----------------------------------------+
4 rows in set (0.00 sec)
總結(jié)
本教程應(yīng)該為你提供了一個(gè)關(guān)于 MySQL 中的日期和時(shí)間值的有用的概述。我希望本文教會(huì)了您一些新知識(shí),使您能夠更好地控制和理解 MySQL 數(shù)據(jù)庫如何處理時(shí)間值。
(題圖:MJ/76b6481a-a271-4e81-bc17-dd7fbe08a240)