MySQL日期与时间函数(日期/时间格式化、增减、对比、时区、UTC和UNIX时间)

读完本文,您将掌握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()等;不涉及运算、抽取时间的部分内容
  • 时间格式转换:包括DATE_FORMAT()STR_TO_DATE()、STR_TO_TIME();不涉及运算、抽取时间的指定部分输出为字符串
  • 时间增减:包括DATE_ADD()DATE_SUB()LAST_DAY等;运算、得出具体时间
  • 时间比较:包括DATEDIFF()TIMEDIFF()TIMESTAMPDIFF()DAYOFYEAR()等;运算、得出数值/差值

时间抽取

包括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()MONTHQUARTER()YEAR():抽取指定时间的是星期几、月份、第几季度、年份
      • DAY():等价于DAYOFMONTH(),获取指定时间是当月的第几天;类似的还有DAYOFWEEK()/DAYOFYEAR()/MONTH()
      • DAYNAME()MONTHNAME():获取星期几名称和月名称,如’Saturday’、‘February’。

相比于其他函数,抽取函数职能单一,这类函数平时使用会略少些。但正因为单一,该函数是其他函数构成的基础。

时间格式转换(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'})函数,可以快速获取各种风格的格式定义。

STR_TO_DATE的入参不合法时会返回NULL

  • 格式字符串不合法、时间字符串和格式字符串不匹配,都会判定为不合法

    格式字符串是可以包括可见字符、格式标识符的
    但格式字符串匹配时间字符串时,这些字符在后者中与格式位置要一致;匹配到格式标识符时、时间字符串里对应位置的字符必需是时间的一个部分。

  • sql_mode系统变量配置了NO_ZERO_DATENO_ZERO_IN_DATE时(可通过show variables like "sql_mode";查看),没有日期或日期为0会认为是不合法日期,见下图
    在这里插入图片描述

更多格式化的介绍,可参考《官方文档: DATE_FORMAT》《官方文档: STR_TO_DATE》

时间增减(DATE_ADD等)

包括DATE_ADD()DATE_SUB()LAST_DAY等;
运算、得出具体时间

  • 通用计算函数:对指定时间进行增减、获取计算后的时间,主要是DATE_ADDDATE_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                 |
    +-------------------------------------+----------------------------+
    

    unitEXTRACT()可选的基本一致,建议使用前阅读 附录二:时间间隔速查表及注意事项

  • 特定计算函数:

    • LAST_DAY:获取指定时间当月的最后一天;如果传入的时间不合法,则返回NULL。

时间比较(DATEDIFF等)

包括DATEDIFF()TIMEDIFF()TIMESTAMPDIFF()DAYOFYEAR()等;
运算、得出数值/差值

  • 通用比较函数:获取任意两个时间的差值。主要有DATEDIFFTIMEDIFFTIMESTAMPDIFF;

    • DATEDIFF(expr1, expr2)
      表示expr1expr2多几,即expr1 - expr2。返回结果的单位是天。
      入参不严格、可自动转换为天。
      DATEDIFF('2019-10-24 23:59:59', '2019-10-23');,计算结果是1

    • TIMEDIFF(expr1, expr2)
      表示expr1 - expr2。返回结果是时间表达式hh:mm:ss,但如果入参有毫秒,则返回结果也会包括毫秒。
      入参相对不严格,但必需包含时间部分(无时间部分会返回NULL)、且符合时间表达式定义,可以是DATETIMETIME。可以查看下例:

      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_expr1datetime_expr2,需是日期或日期时间表达式(可以一个是日期、一个是日期时间)。在必要时,会将DATE转换为时间为00:00:00DATETIME
      例: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.0000002038-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_tzto_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 ‘年数-月数’

《官方文档: 时间间隔》


  1. 《MySQL官方文档 - 日期与时间函数》 ↩︎

  2. 《MySQL官方文档: UNIX_TIMESTAMP》 ↩︎

  3. 《MySQL官方文档: CONVERT_TZ》 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章