日期處理是業務開發高頻痛點。本文按場景分類梳理 MySQL 核心日期函數:NOW() vs SYSDATE() 行為差異、DATE_FORMAT 萬能格式化、截取函數族、DATE_ADD 通用運算、DATEDIFF/TIMEDIFF 比較、TO_DAYS/UNIX_TIMESTAMP 轉換,並給出生產環境常見避坑指南與組合招範例。
SELECT NOW(), SLEEP(3), NOW(); -- 2015-10-28 22:43:34 | 0 | 2015-10-28 22:43:34 (固定) SELECT SYSDATE(), SLEEP(3), SYSDATE(); -- 2015-10-28 22:46:48 | 0 | 2015-10-28 22:46:52 (實時)
差異本質**:NOW() 在語句開始執行時確定,存儲函數/觸發器/複製場景下行為確定;SYSDATE() 每次調用取實時值。**生產建議統一使用 NOW()**,除非極少數需要實時時間戳的審計場景。
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d'); -- 2015-10-28 SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); -- 2015-10-28 22:10:56 SELECT DATE_FORMAT(NOW(), '%W, %M %D, %Y'); -- Wednesday, October 28th, 2015
| 符號 | 含義 | 示例 |
|---|---|---|
| %Y | 4 位年份 | 2015 |
| %y | 2 位年份 | 15 |
| %m | 月份 01-12 | 10 |
| %c | 月份 1-12 | 10 |
| %d | 日期 01-31 | 28 |
| %e | 日期 1-31 | 28 |
| %H | 小時 00-23 | 22 |
| %k | 小時 0-23 | 22 |
| %h/%I | 小時 01-12 | 10 |
| %i | 分鐘 00-59 | 10 |
| %s/%S | 秒 00-59 | 56 |
| %W | 星期全名 | Wednesday |
| %a | 星期縮寫 | Wed |
| %M | 月份全名 | October |
| %b | 月份縮寫 | Oct |
| %r | 12 小時制時間 | 10:10:56 PM |
| %T | 24 小時制時間 | 22:10:56 |
避坑**:DATE_FORMAT 返回字符串,不可直接與 DATE/DATETIME 列比較。錯誤:WHERE create_date >= DATE_FORMAT(NOW(), '%Y-%m-%d')。正確:WHERE create_date >= DATE(NOW()) 或 WHERE create_date >= CURDATE()。
SELECT DAYOFWEEK(NOW()); -- 1=週日...7=週六
SELECT DAYOFMONTH('2015-10-28'); -- 28
SELECT DAYOFYEAR('2015-10-28'); -- 301
SELECT YEAR('2015-10-28'); -- 2015
SELECT QUARTER('2015-10-28'); -- 4
SELECT MONTH('2015-10-28'); -- 10
SELECT WEEK('2015-10-28'); -- 43 (預設週日為週首)
SELECT WEEK('2015-10-28', 1); -- 44 (週一為週首)
SELECT HOUR('2015-10-28 23:11:56'); -- 23
SELECT MINUTE('2015-10-28 23:11:56'); -- 11
SELECT SECOND('2015-10-28 23:11:56'); -- 56
SELECT DATE('2015-10-28 23:11:56'); -- 2015-10-28
SELECT TIME('2015-10-28 23:11:56'); -- 23:11:56
SELECT DATE_ADD('2015-10-28 23:11:56', INTERVAL 4 DAY); -- 2015-11-01 23:11:56
SELECT DATE_ADD('2015-10-28 23:11:56', INTERVAL -3 DAY); -- 2015-10-25 23:11:56
SELECT DATE_ADD('2015-10-28 23:11:56', INTERVAL 1 YEAR); -- 2016-10-28 23:11:56
SELECT DATE_ADD('2015-10-28 23:11:56', INTERVAL '1 3' DAY_HOUR); -- 2015-10-27 20:11:56
SELECT DATE_ADD('2015-10-28 23:11:56', INTERVAL '-1 1:1:1' DAY_SECOND); -- 2015-10-27 22:10:55
| 單位關鍵字 | 含義 | 表達式格式 |
|---|---|---|
| SECOND/MINUTE/HOUR/DAY/MONTH/YEAR | 單一單位 | 數字 |
| MINUTE_SECOND | 分:秒 | 'MIN:SEC' |
| HOUR_MINUTE | 時:分 | 'HOUR:MIN' |
| DAY_HOUR | 天 時 | 'DAY HOUR' |
| YEAR_MONTH | 年-月 | 'YEAR-MONTH' |
| HOUR_SECOND | 時:分:秒 | 'HOUR:MIN:SEC' |
| DAY_MINUTE | 天 時:分 | 'DAY HOUR:MIN' |
| DAY_SECOND | 天 時:分:秒 | 'DAY HOUR:MIN:SEC' |
規則**:正數加、負數減;缺少高位單位時從右補齊(如 DAY_SECOND 缺時分視為 MINUTE_SECOND);月份天數溢出自動截斷到月末。
SELECT DATEDIFF('2015-10-28', '2015-10-26'); -- 2 (天數差)
SELECT TIMEDIFF('2015-10-28 23:11:56', '2015-10-28 20:11:56'); -- 03:00:00 (時分秒差)
SELECT TO_DAYS('2015-10-28 23:11:56'); -- 736264 (西元 0 年至今天數)
SELECT TO_DAYS('0000-00-00 00:00:00'); -- 0
SELECT UNIX_TIMESTAMP('2015-10-28 23:11:56'); -- 1446045116 (1970-01-01 UTC 起秒數)
SELECT TIME_TO_SEC('01:00:05'); -- 3605
SELECT SEC_TO_TIME(3605); -- 01:00:05
-- 本月第一天 00:00:00 SET @month_start = DATE(DATE_FORMAT(NOW(), '%Y-%m-01')); -- 本月最後一天 23:59:59 SET @month_end = DATE_SUB(DATE_ADD(@month_start, INTERVAL 1 MONTH), INTERVAL 1 SECOND); -- 本週一 00:00:00 (模式 1: 週一為週首) SET @week_start = DATE_SUB(NOW(), INTERVAL WEEKDAY(NOW()) DAY); SET @week_start = DATE(@week_start); -- 本週日 23:59:59 SET @week_end = DATE_ADD(@week_start, INTERVAL 6 DAY); SET @week_end = DATE_SUB(@week_end, INTERVAL -1 SECOND); -- 去年同期對比 SELECT * FROM sales WHERE sale_date BETWEEN DATE_SUB(@month_start, INTERVAL 1 YEAR) AND DATE_SUB(@month_end, INTERVAL 1 YEAR);
將此速查表釘在工位,下次寫日期邏輯直接複製粘貼修改參數,告別「這個函數怎麼用」的現場查文檔。