讀完本文,您將掌握MySQL的各類時間函數。
附錄提供了格式標識、時間間隔兩個速查表,便於日常工作中快速查找。
本文會持續更新、補充,建議您收藏。
更多MySQL函數介紹,可查看《MySQL函數和運算符》
MySQL提供了比較多的、靈活的時間函數,而且基本穩定、已經多個版本不曾修改過了1。
文章目錄
獲取當前時間
獲取當前時間很簡單,常用的有CURDATE()
/CURTIME()
/NOW()
三個函數。
下面表格說明了其功能和區別:
功能 | 推薦函數 | 同義函數 | 小數位 | 效果 |
---|---|---|---|---|
獲取當前日期 | CURDATE() |
CURRENT_DATE CURRENT_DATE() UTC時間參考時區與UTC時間 |
不支持 | 默認YYYY-MM-DD 若上下文有數字則返回 YYYYMMDD |
獲取當前時間 | CURTIME() |
CURRENT_TIME CURRENT_TIME() UTC時間參考時區與UTC時間 |
默認無 可傳參指定至多6位 如 CURTIME(3) 表示精確到毫秒 |
默認hh:mm:ss 若上下文有數字則返回 hhmmss |
獲取當前日期+時間 | NOW() |
CURRENT_TIMESTAMP CURRENT_TIMESTAMP() LOCALTIME LOCALTIME() LOCALTIMESTAMP LOCALTIMESTAMP() UTC時間參考時區與UTC時間 |
默認無 可傳參指定至多6位 如 NOW(3) 表示精確到毫秒 |
默認YYYY-MM-DD hh:mm:ss 若上下文有數字則返回 YYYYMMDDhhmmss |
上下文有數字,指的是函數前後有數字運算,如我現在執行的
CURDATE() + 0
返回了20191023
如果對“現在”的定義精確一點:上述函數取的都是SQL開始執行的時間,只是輸出格式有所變化。而在存儲過程或觸發器中,返回的是存儲過程或觸發器開始執行的時間。
除了上面的函數,MySQL還提供了更細緻的“現在”定義:SYSDATE()
函數,每一行/每個列數據準備時才獲取時間,如下面樣例所示:
mysql> SELECT CURDATE(), CURTIME(6), NOW(6), SYSDATE(6) AS 'SYS1', SLEEP(2), SYSDATE(6) AS 'SYS2' FROM sales limit 5;
+------------+-----------------+----------------------------+----------------------------+----------+----------------------------+
| CURDATE() | CURTIME(6) | NOW(6) | SYS1 | SLEEP(2) | SYS2 |
+------------+-----------------+----------------------------+----------------------------+----------+----------------------------+
| 2019-10-23 | 23:40:29.086893 | 2019-10-23 23:40:29.086893 | 2019-10-23 23:40:29.092738 | 0 | 2019-10-23 23:40:31.092824 |
| 2019-10-23 | 23:40:29.086893 | 2019-10-23 23:40:29.086893 | 2019-10-23 23:40:31.092868 | 0 | 2019-10-23 23:40:33.098602 |
| 2019-10-23 | 23:40:29.086893 | 2019-10-23 23:40:29.086893 | 2019-10-23 23:40:33.098663 | 0 | 2019-10-23 23:40:35.098779 |
| 2019-10-23 | 23:40:29.086893 | 2019-10-23 23:40:29.086893 | 2019-10-23 23:40:35.098840 | 0 | 2019-10-23 23:40:37.098912 |
| 2019-10-23 | 23:40:29.086893 | 2019-10-23 23:40:29.086893 | 2019-10-23 23:40:37.098959 | 0 | 2019-10-23 23:40:39.099050 |
+------------+-----------------+----------------------------+----------------------------+----------+----------------------------+
可以通過開啓
--sysdate-is-now
選項,將SYSDATE()
變爲NOW()
的別名,但不建議這麼做,保留一個額外的函數、多一個選擇。
注意:WHERE index_column = SYSDATE()
的寫法,查詢時不走索引。因爲SYSDATE()
是執行時動態計算時間,假設通過索引檢索數據、每掃到一行都會生成新值,索引也就失去了比較的意義。
以上就是獲取當前時間的函數。
上面講的是獲取本地時間(服務器時區的時間),要獲取UTC時間需通過
UTC_DATE()
/UTC_TIME()
/UTC_TIMESTAMP()
,在後面時區與UTC時間 部分有介紹。
如果不瞭解時區、UTC時間,閱讀《時區是怎麼劃分的?世界各時區的時間如何統一表達?》即可瞭解
當前周/月/年的獲取,MySQL沒有內置函數,需要通過運算得出。接下來時間計算中會講到。
時間計算/轉換
先看一個獲取當前月、當月第一天/最後一天的例子(碼字的時候是2019-10-23):
mysql> SELECT CURDATE() as '今天', DATE_FORMAT(CURDATE(), '%Y-%m') AS '當月',
DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY) as '當月第一天',
LAST_DAY(CURDATE()) as '當月最後一天';
+------------+---------+-----------------+--------------------+
| 今天 | 當月 | 當月第一天 | 當月最後一天 |
+------------+---------+-----------------+--------------------+
| 2019-10-23 | 2019-10 | 2019-10-01 | 2019-10-31 |
+------------+---------+-----------------+--------------------+
這個例子中用到的幾種函數,幾乎覆蓋了MySQL時間計算的形式:
DATE_FORMAT
:將時間轉換成指定格式的字符串;反向函數有STR_TO_DATE()、STR_TO_TIME()
;DATE_SUB
:將指定時間減去指定的週期長度,類似的還有DATE_ADD
;DAY
:等價於DAYOFMONTH()
,獲取該天是當月的第幾天;類似概念的還有DAYOFYEAR()
(該年的第幾天)、WEEK()
(該年的第幾周)、MONTH()
(該年的第幾月)LAST_DAY
:獲取指定時間當月的最後一天,默認輸出的是YYYY-MM-DD
格式。單獨提供了這個函數,應該是出於每月天數不同、便於計算的考慮。
這些函數大致可以分爲四類:
時間抽取
包括
EXTRACT()
、DATE()
、HOUR()
等;
不涉及運算、抽取時間的部分內容
- 通用抽取函數
EXTRACT(unit FROM date)
:- 例
EXTRACT(YEAR_MONTH FROM NOW())
,表示抽取當前時間的年月,結果爲201910
;其中YEAR_MONTH
就是unit
unit
有20多個枚舉值,常用的有DAY
/MONTH
/YEAR
等,但在表達式使用、不同枚舉值和不同函數等方面有一些坑,建議使用前閱讀 附錄二:時間間隔速查表及注意事項
EXTRACT()
函數的更多細節,可參考《MySQl官方文檔 - EXTRACT》 - 例
- 特定抽取函數:
DATE()
:抽取指定時間的日期。默認返回YYYY-MM-DD
,若上下文有數字則返回YYYYMMDD
TIME()
:抽取指定時間的時分秒。HOUR()
、MINITE()
、SECOND()
、MICROSECOND()
分別抽取指定時間的小時(0~23)、分鐘、秒、微妙。- 抽取時有一定計算的函數:
WEEK()
、MONTH
、QUARTER()
、YEAR()
:抽取指定時間的是星期幾、月份、第幾季度、年份DAY()
:等價於DAYOFMONTH()
,獲取指定時間是當月的第幾天;類似的還有DAYOFWEEK()
/DAYOFYEAR()
/MONTH()
DAYNAME()
、MONTHNAME()
:獲取星期幾名稱和月名稱,如’Saturday’、‘February’。- 該項雖然列在抽取函數中,實際上有一定的格式化作用。
- 具體名稱由系統變量
lc_time_names
控制,可參考《官方文檔:服務器區域設置支持》
相比於其他函數,抽取函數職能單一,這類函數平時使用會略少些。但正因爲單一,該函數是其他函數構成的基礎。
時間格式轉換(DATE_FORMAT / STR_TO_DATE)
包括
DATE_FORMAT()
、STR_TO_DATE()、STR_TO_TIME()
等函數;
不涉及運算、抽取時間的指定部分輸出爲字符串
- 時間 => 指定格式的字符串:使用
DATE_FORMAT(date,format)、TIME_FORMAT(date,format)
函數。 - 日期/時間字符串 => 日期/時間:使用
STR_TO_DATE(str,format)、STR_TO_TIME(str,format)
。用法與DATE_FORMAT
類似,其本來就是DATE_FORMAT
的逆向函數。
在MySQL中,日期/時間對象輸出時會自動轉爲字符串,而運算時字符串又會自動轉爲日期/時間對象完成運算,這中間就是時間轉換函數在發揮作用。
這類函數本身簡單,但有很多格式、難記憶。好在這些格式是各類語言基本通用的,而且也有列表速查(可參考附錄一:時間格式速查表)。
這裏提供一個樣例,包含了常用的格式:
mysql> SELECT temp.*, STR_TO_DATE(`str1`, `format1`) FROM
(SELECT `TIME`, `format1`, `format2`,
DATE_FORMAT(`TIME`, `format1`) AS 'str1',
DATE_FORMAT(`TIME`, `format2`) AS 'str2'
FROM
(SELECT NOW(6) AS 'TIME', '%Y-%m-%d %H:%i:%s.%f' AS 'format1',
'%a %b %d %Y %H:%i:%s.%f' AS 'format2'
) t
) temp;
+----------------------------+----------------------+-------------------------+----------------------------+---------------------------------+--------------------------------+
| TIME | format1 | format2 | str1 | str2 | STR_TO_DATE(`str1`, `format1`) |
+----------------------------+----------------------+-------------------------+----------------------------+---------------------------------+--------------------------------+
| 2019-10-24 15:29:16.357909 | %Y-%m-%d %H:%i:%s.%f | %a %b %d %Y %H:%i:%s.%f | 2019-10-24 15:29:16.357909 | Thu Oct 24 2019 15:29:16.357909 | 2019-10-24 15:29:16.357909 |
+----------------------------+----------------------+-------------------------+----------------------------+---------------------------------+--------------------------------+
MySQL還提供的
GET_FORMAT({DATE|TIME|DATETIME}, {'EUR'|'USA'|'JIS'|'ISO'|'INTERNAL'})
函數,可以快速獲取各種風格的格式定義。
- 例:
GET_FORMAT(DATETIME,'ISO')
會返回'%Y-%m-%d %H:%i:%s'
,這與上面例子第一個格式類似。- 具體可參考《官方文檔:GET_FORMAT》
STR_TO_DATE
的入參不合法時會返回NULL
:
- 格式字符串不合法、時間字符串和格式字符串不匹配,都會判定爲不合法
格式字符串是可以包括可見字符、格式標識符的
但格式字符串匹配時間字符串時,這些字符在後者中與格式位置要一致;匹配到格式標識符時、時間字符串裏對應位置的字符必需是時間的一個部分。 sql_mode
系統變量配置了NO_ZERO_DATE
或NO_ZERO_IN_DATE
時(可通過show variables like "sql_mode";
查看),沒有日期或日期爲0會認爲是不合法日期,見下圖
更多格式化的介紹,可參考《官方文檔: DATE_FORMAT》、《官方文檔: STR_TO_DATE》
時間增減(DATE_ADD等)
包括
DATE_ADD()
、DATE_SUB()
、LAST_DAY
等;
運算、得出具體時間
-
通用計算函數:對指定時間進行增減、獲取計算後的時間,主要是
DATE_ADD
、DATE_SUB
函數。
語法爲DATE_ADD(date,INTERVAL expr unit)
,也可以寫作date + INTERVAL expr unit,如獲取昨天(今天是2019-10-24):mysql> SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY), -> CURDATE() - INTERVAL 1 DAY; +-------------------------------------+----------------------------+ | DATE_SUB(CURDATE(), INTERVAL 1 DAY) | CURDATE() - INTERVAL 1 DAY | +-------------------------------------+----------------------------+ | 2019-10-23 | 2019-10-23 | +-------------------------------------+----------------------------+
unit
與EXTRACT()
可選的基本一致,建議使用前閱讀 附錄二:時間間隔速查表及注意事項 -
特定計算函數:
LAST_DAY
:獲取指定時間當月的最後一天;如果傳入的時間不合法,則返回NULL。
時間比較(DATEDIFF等)
包括
DATEDIFF()
、TIMEDIFF()
、TIMESTAMPDIFF()
、DAYOFYEAR()
等;
運算、得出數值/差值
-
通用比較函數:獲取任意兩個時間的差值。主要有
DATEDIFF
、TIMEDIFF
、TIMESTAMPDIFF
;-
DATEDIFF(expr1, expr2)
:
表示expr1
比expr2
多幾天,即expr1 - expr2
。返回結果的單位是天。
入參不嚴格、可自動轉換爲天。
如DATEDIFF('2019-10-24 23:59:59', '2019-10-23');
,計算結果是1
。 -
TIMEDIFF(expr1, expr2)
:
表示expr1 - expr2
。返回結果是時間表達式hh:mm:ss
,但如果入參有毫秒,則返回結果也會包括毫秒。
入參相對不嚴格,但必需包含時間部分(無時間部分會返回NULL
)、且符合時間表達式定義,可以是DATETIME
或TIME
。可以查看下例:mysql> SELECT TIMEDIFF("2019-11-08 09:16", "2019-11-08 09/17"), TIMEDIFF('2019-11-08 09:16', "2019-11-08 09/17/05.123")
返回結果是
-00:01:00
,-00:01:05.123
,可以看到單位與入參是有關的。 -
TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2)
:
表示datetime_expr2 - datetime_expr1
,注意是參數2在前,與上面兩個函數是反過來的。返回一個整數數字,單位由unit
參數指定。單位支持
MICROSECOND (microseconds), SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR
,與EXTRACT()
使用的unit
推薦表達式有一定相似性,可以參考 附錄二:時間間隔速查表及注意事項入參
datetime_expr1
和datetime_expr2
,需是日期或日期時間表達式(可以一個是日期、一個是日期時間)。在必要時,會將DATE
轉換爲時間爲00:00:00
的DATETIME
。
例:SELECT TIMESTAMPDIFF(MONTH,'2019-11-01','2019-10-01')
結果爲1
上面例子的入參,分別使用了不同的表達式,但最終還是計算了結果出來。
這就是前面提過的時間對象自動轉換:所有寫作字符串的時間,運算前都會通過STR_TO_DATE()
等函數轉換爲時間對象,因此入參支持什麼樣格式,取決於STR_TO_DATE()
等函數的支持範圍。具體可參考時間轉換函數 -
-
特定比較函數:
DAYOFYEAR(expr)
:獲取指定時間是當年的第幾天WEEKOFYEAR(expr)
:獲取指定時間是當年第幾周
時區、UTC時間、UNIX時間戳
如果不瞭解時區、UTC,可參考《時區是怎麼劃分的?世界各時區的時間如何統一表達?GMT、UTC有什麼區別?》
獲取UTC時間
UTC_DATE()
/UTC_TIME()
/UTC_TIMESTAMP()
:返回當前UTC日期/時間/日期+時間。格式、小數處理,與CURDATE()
/CURTIME()
/NOW()
一致、僅因時區差別顯示值不同。
獲取UNIX時間戳
如不瞭解UNIX時間戳,可參考《UNIX時間、UNIX 2038問題》
UNIX_TIMESTAMP([date])
:用於返回指定時間的UNIX時間戳(自1970-01-01T00:00:00
後的秒數)2
- 若未指定時間,則默認返回當前時間的時間戳,相當於
UNIX_TIMESTAMP(NOW())
- 指定的時間,服務器會按客戶端會話時區來解釋(默認與服務器時區一致,除非手工設定),將其轉換爲UTC的內部Unix時間戳值並返回
- 指定的時間如果不包含毫秒微秒,則返回時間戳也不包含
- 指定時間的範圍與
TIMESTAMP
數據類型不同,TIMESTAMP
只支持1970-01-01T00:00:01.000000
到2038-01-19T03:14:07.999999
,但大於該時間的值,UNIX_TIMESTAMP()
也可以計算出時間戳來。MySQL日期取值範圍可參考《MySQL所有類型的字段長度和存儲開銷》
查看和設置時區
可通過SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;
查看當前服務器、客戶端會話的時區。
結果可能有三種:
SYSTEM
,表示服務器時區與系統時區相同。- UTC偏移量,如 ‘+10:00’或’-6:00’。
- 時區名,例如 ‘Europe/Helsinki’, 'US/Eastern’或 ‘MET’。僅當MySQL已經創建並填寫了數據庫中的時區信息表時,才能使用命名時區 。
MySQL啓動時會自主確定主機所在時區,作爲系統時區。也可以手工指定。
客戶端會話時區默認與服務器時區一致,也可以在會話中使用set time_zone
自行設定,但該設定僅在當前會話有效。
關於系統時區默認值、填充時區,可以參考《官方文檔:時區變量》
時區轉換
CONVERT_TZ(dt,from_tz,to_tz)
:用於將日期時間從一個時區轉換到另外一個時區3
- 例:從UTC轉到東十區,
SELECT CONVERT_TZ('2019-01-01 12:00:00','+00:00','+10:00');
結果爲'2019-01-01 22:00:00'
from_tz
和to_tz
可以使用UTC偏移量或時區名
關於MySQL對時區的支持,可參考《官方文檔: MySQL服務器時區支持》
以上。感謝您的閱讀。
附錄
附錄一:格式標識符速查表
標識符 | 描述 |
---|---|
%Y | 年,4 位 |
%y | 年,2 位 |
%m | 月,數值(00-12) |
%c | 月,數值 |
%b | 縮寫月名 |
%M | 月名 |
%d | 月的天,數值(00-31) |
%D | 帶有英文前綴的月中的天 |
%e | 月的天,數值(0-31) |
%j | 年的天 (001-366) |
%H | 小時 (00-23) |
%h | 小時 (01-12) |
%k | 小時 (0-23) |
%I(大寫i) | 小時 (01-12) |
%l(小寫L) | 小時 (1-12) |
%i | 分鐘,數值(00-59) |
%S | 秒(00-59) |
%s | 秒(00-59) |
%f | 微秒 |
%p | AM 或 PM |
%r | 時間,12-小時(hh:mm:ss AM 或 PM) |
%T | 時間, 24-小時 (hh:mm:ss) |
%a | 縮寫星期名 |
%U | 周 (00-53) 星期日是一週的第一天 |
%u | 周 (00-53) 星期一是一週的第一天 |
%V | 周 (01-53) 星期日是一週的第一天,與 %X 使用 |
%v | 周 (01-53) 星期一是一週的第一天,與 %x 使用 |
%W | 星期名 |
%w | 周的天 (0=星期日, 6=星期六) |
%X | 年,其中的星期日是周的第一天,4 位,與 %V 使用 |
%x | 年,其中的星期一是周的第一天,4 位,與 %v 使用 |
附錄二:時間間隔速查表及注意事項
注意:
- 參數表達式是字符串;其支持各種分隔符,下表中列出的只是推薦的分隔符,比如
:
也可以寫作/
- 表達式傳入數字時會自動轉換,但轉換結果可能與預期不同,可以參考下面例子:
SELECT temp.*, DATE_ADD(`time`, INTERVAL `str` HOUR_MINUTE) AS 'addByStr', DATE_ADD(`time`, INTERVAL `number` HOUR_MINUTE) AS 'addByNumber', DATE_ADD(`time`, INTERVAL `castedNumber` HOUR_MINUTE) AS 'addByCastedNumber' FROM (SELECT STR_TO_DATE('2019-06-01', '%Y-%m-%d %H') AS 'time', '6/4' AS 'str', 6/4 AS 'number', CAST(6/4 AS DECIMAL(3,1)) as 'castedNumber' ) temp; +---------------------+-----+--------+--------------+---------------------+---------------------+---------------------+ | time | str | number | castedNumber | addByStr | addByNumber | addByCastedNumber | +---------------------+-----+--------+--------------+---------------------+---------------------+---------------------+ | 2019-06-01 00:00:00 | 6/4 | 1.5000 | 1.5 | 2019-06-01 06:04:00 | 2019-06-04 12:20:00 | 2019-06-01 01:05:00 | +---------------------+-----+--------+--------------+---------------------+---------------------+---------------------+
6/4
在sql中運算結果是1.5000,匹配單位HOUR_MINUTE
時,小數點被作爲分隔符,小數點前是小時數、爲1,小數點後是分鐘數、爲5000,鎖DATE_ADD
後增加了1小時、5000分鐘(≈83h),所以2019-06-01 00:00:00
變成了2019-06-04 12:20:00
- 列表中的單位,在個別函數中會有出入,比如
DAY_
類在EXTRACT()
時並不返回天。
時間間隔的單位 | 傳入參數表達式 (建議使用字符串,否則容易換算錯誤) |
---|---|
MICROSECOND | 微秒數 |
SECOND | 秒數 |
MINUTE | 分鐘數 |
HOUR | 小時數 |
DAY | 天數 |
WEEK | 週數 |
MONTH | 月數 |
QUARTER | 季度數 |
YEAR | 年數 |
SECOND_MICROSECOND | ‘秒數.微秒數’ |
MINUTE_MICROSECOND | ‘分鐘數:秒數.微秒數’ |
MINUTE_SECOND | ‘分鐘數:秒數’ |
HOUR_MICROSECOND | ‘小時數:分鐘數:秒數.微秒數’ |
HOUR_SECOND | ‘小時數:分鐘數:秒數’ |
HOUR_MINUTE | ‘小時數:分鐘數’ |
DAY_MICROSECOND | ‘天數 小時數:分鐘數:秒數.微秒數’ , EXTRACT 時不會返回天 |
DAY_SECOND | ‘天數 小時數:分鐘數:秒數’, EXTRACT 時不會返回天 |
DAY_MINUTE | ‘天數 小時數:分鐘數’, EXTRACT 時不會返回天 |
DAY_HOUR | ‘天數 小時數’, EXTRACT 時不會返回天 |
YEAR_MONTH | ‘年數-月數’ |