读完本文,您将掌握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 | ‘年数-月数’ |