时间是人们根据自然现象而进行的一种抽象概念。人类通过观察日月星辰、季节交替、动植物生长等自然现象,逐渐认识到时间的存在,并进行了一系列的时间计量。
世界时(Universal Time,简称UT)是以地球自转周期作为基础的时间计量标准,UT可以根据不同的定义方式分为UT0、UT1、UT2、UT1R等不同的形式。UT的定义是基于天文观测数据,主要通过观测地球自转的角速度来确定时间单位。UT是地球物理学中使用最广泛的时间系统之一,但它受到地球自转速率的不断变化的影响,因此不够稳定和准确。
为了弥补UT的不足,国际标准化组织(ISO)于1970年提出了协调世界时(UTC)。
协调世界时(Coordinated Universal Time,简称UTC),又称世界统一时间,世界标准时间,是目前国际上通用的时间标准,它是以原子钟为基础,通过对全球范围内的原子钟信号进行加权平均而得出的一种时间标准。
协调世界时采用24小时制,一天被划分为24个小时,每小时包括60分钟,每分钟包括60秒。起点是1970年1月1日0时0分0秒,与Unix时间戳相同。
协调世界时以地球自转为基础,以本初子午线上的标准子午线时为基准,使地球各地的时间保持一致。UTC引入了闰秒来纠正由于地球自转速率的微小变化所导致的时间偏差。
国际原子时(International Atomic Time,简称TAI)是一个标准的原子时计量系统,以铯原子的振荡频率为基础,是世界上精度最高的时间计量方式之一。TAI通过一个网络由多个原子钟测量得出,并被国际计量组织(BIPM)所管理。
国际原子时与世界时(UT)之间存在微小的偏差,这是由于地球自转速度的变化导致的。因此,国际地球自转服务(IERS)会不定期地宣布添加或减少闰秒,以保持TAI和UT之间的同步。
时区是按照一定的经度范围将地球划分成若干个区域,使得同一个时区内的时间是相同的,以方便人们进行时间交流和统一管理。世界上总共分为24个主要时区,每个主要时区相差15度经度。
通常,时区以协调世界时(UTC)的正负偏移量来表示,偏移量为0的时区被称为“格林威治标准时间”(GMT),也就是世界标准时间。
全球24个标准时区及其相应的偏差(以协调世界时(UTC)为基准):
中国所在的时区是指中国范围内划分的标准时区,中国的大致经度范围是73°33′E至135°05′E,本应跨越5个时区,但为了统一管理、保证全国统一时间,中国只采用了一个时区,即东八区(UTC+8)。中国除了采用UTC+8表示时区,还可以使用中国标准时间(China Standard Time,CST)来表示。
在Ubuntu 20.04系统中,有几个命令是和时间、时区有关的。
可以通过date
命令来查看时间,在输出结果中同时会显示当前时间是按什么时间计时的。
wux_labs@wux-labs-vm:~$ date
Wed Feb 22 06:39:29 UTC 2023
从输出结果可以看到,当前是UTC时间,2023-02-22。
还可以使用timedatectl
命令查看时间的详细信息。
wux_labs@wux-labs-vm:~$ timedatectl
Local time: Wed 2023-02-22 06:39:35 UTC
Universal time: Wed 2023-02-22 06:39:35 UTC
RTC time: Wed 2023-02-22 06:39:35
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
从结果中可以看到当前时间的详细信息,以及时区信息:
Time zone: Etc/UTC (UTC, +0000)
使用timedatectl list-timezones
命令可以列出所有可使用的时区。
使用timedatectl set-timezone
命令可以修改系统的时区。
以下命令将系统时区修改为Asia/Shanghai
:
wux_labs@wux-labs-vm:~$ sudo timedatectl set-timezone Asia/Shanghai
修改完成后再次查看一下时间信息:
wux_labs@wux-labs-vm:~$ date
Wed Feb 22 14:39:48 CST 2023
wux_labs@wux-labs-vm:~$ timedatectl
Local time: Wed 2023-02-22 14:39:52 CST
Universal time: Wed 2023-02-22 06:39:52 UTC
RTC time: Wed 2023-02-22 06:39:52
Time zone: Asia/Shanghai (CST, +0800)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
在TiDB数据库中,通过参数time_zone
可以设置数据库的时区。TiDB中的参数的作用域分为Global级别和Session级别,每个级别都可以设置time_zone
参数,TiDB在取值的时候优先取Session级别的,Session级别没设置再取Global级别的,具体的优先级为:
Session > Global > 操作系统 > UTC
在TiDB中,可以通过以下语句来查看当前集群中的参数。
mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| SYSTEM | SYSTEM |
+--------------------+---------------------+
1 row in set (0.00 sec)
在当前集群中time_zone
都未设置,所以采用的是操作系统的时区。
在TiDB中,有一些与时间有关的函数,其函数返回值会受时区的影响。
mysql> select now(),curtime();
+---------------------+-----------+
| now() | curtime() |
+---------------------+-----------+
| 2023-02-22 07:17:45 | 07:17:45 |
+---------------------+-----------+
1 row in set (0.00 sec)
由于TiDB数据库集群在启动的时候,操作系统采用的时间是UTC时间,所以当前启动的TiDB数据库集群也采用的UTC时间。
下面修改一下Session级别的参数。
mysql> set session time_zone = '+8:00';
Query OK, 0 rows affected (0.00 sec)
修改完成后,再看看当前时区参数以及函数返回值,都会发生变化。
mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| SYSTEM | +8:00 |
+--------------------+---------------------+
1 row in set (0.00 sec)
mysql> select now(),curtime();
+---------------------+-----------+
| now() | curtime() |
+---------------------+-----------+
| 2023-02-22 15:18:06 | 15:18:06 |
+---------------------+-----------+
1 row in set (0.00 sec)
在TiDB中,有一些数据类型也与时间有关。
下面创建一个表用来看看不同数据类型受时区的影响情况。
mysql> create table temp(f1 datetime, f2 timestamp, f3 time);
Query OK, 0 rows affected (0.14 sec)
插入数据,都取当前时间。
mysql> insert into temp values(now(), now(), curtime());
Query OK, 1 row affected (0.01 sec)
查看时区及时间。
mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| SYSTEM | SYSTEM |
+--------------------+---------------------+
1 row in set (0.00 sec)
mysql> select * from temp;
+---------------------+---------------------+----------+
| f1 | f2 | f3 |
+---------------------+---------------------+----------+
| 2023-02-22 07:27:13 | 2023-02-22 07:27:13 | 07:27:13 |
+---------------------+---------------------+----------+
1 row in set (0.00 sec)
以上结果是采用UTC时间的值,数据是持久化到表中的,固定不变的值。
接下来,切换一下时区,再来看看表中的数据情况。
mysql> set session time_zone = '+8:00';
Query OK, 0 rows affected (0.00 sec)
mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| SYSTEM | +8:00 |
+--------------------+---------------------+
1 row in set (0.00 sec)
mysql> select * from temp;
+---------------------+---------------------+----------+
| f1 | f2 | f3 |
+---------------------+---------------------+----------+
| 2023-02-22 07:27:13 | 2023-02-22 15:27:13 | 07:27:13 |
+---------------------+---------------------+----------+
1 row in set (0.01 sec)
从结果可以看到,对于已经持久化保存的固定数据:
避坑1:
在数据库中,使用Timestamp数据类型,一定要小心,一定要确定好时区,分析影响性。
如果可以,使用datetime类型,而不使用timestamp类型。
在TiDB数据库中,有一些关于日期的函数、计算:
curdate(),用来获取当前时间
date(),将字符串转换成日期
day(),获取日期的天
+interval 1 day,日期计算,加1天
=,可用于判断两个日期是否相等,相等返回1,不等返回0
datediff(),用来计算两个日期之间相差的天数
比如下面的代码用来做日期相关的计算:
mysql> select curdate(), curdate() + interval 5 day, day(curdate()), date('2023-02-22'), curdate() = date('2023-02-22'), curdate() = date('2023-02-23');
+------------+----------------------------+----------------+--------------------+--------------------------------+--------------------------------+
| curdate() | curdate() + interval 5 day | day(curdate()) | date('2023-02-22') | curdate() = date('2023-02-22') | curdate() = date('2023-02-23') |
+------------+----------------------------+----------------+--------------------+--------------------------------+--------------------------------+
| 2023-02-22 | 2023-02-27 | 22 | 2023-02-22 | 1 | 0 |
+------------+----------------------------+----------------+--------------------+--------------------------------+--------------------------------+
1 row in set (0.00 sec)
注意:
date()函数仅能将有效的日期字符串转换成日期,如果日期不合法将会返回NULL。
在下面的例子中,data将正确的日期进行了转换,错误的日期返回了NULL。
mysql> select date('2023-02-22'),date('20230222'),date('2023-02-29'),date('20230229');
+--------------------+------------------+--------------------+------------------+
| date('2023-02-22') | date('20230222') | date('2023-02-29') | date('20230229') |
+--------------------+------------------+--------------------+------------------+
| 2023-02-22 | 2023-02-22 | NULL | NULL |
+--------------------+------------------+--------------------+------------------+
1 row in set, 2 warnings (0.00 sec)
避坑2:
不要以为用date()函数可以成功转换的日期都是正常的日期,有能够成功转换的也是错误日期。
比如,你见过日为00的日期吗?
在下面的案例中,'2023-03-00’是一个错误的日期,但是经过date()函数转换后并不是NULL,而确实是一个日期。但是这个日期,并不等于2023-03-01的前一天。
mysql> select date('2023-03-00'),date('2023-03-01'),date('2023-03-01') - interval 1 day, date('2023-03-00') = date('2023-03-01') - interval 1 day;
+--------------------+--------------------+-------------------------------------+----------------------------------------------------------+
| date('2023-03-00') | date('2023-03-01') | date('2023-03-01') - interval 1 day | date('2023-03-00') = date('2023-03-01') - interval 1 day |
+--------------------+--------------------+-------------------------------------+----------------------------------------------------------+
| 2023-03-00 | 2023-03-01 | 2023-02-28 | 0 |
+--------------------+--------------------+-------------------------------------+----------------------------------------------------------+
1 row in set (0.00 sec)
这个日期的天是0,并且它不是2023-03-01的前一天,也不是2023-02-28的后一天。
mysql> select day(date('2023-03-00')),day(date('2023-03-01')),date('2023-03-01') - interval 1 day, date('2023-03-00') = date('2023-02-28') + interval 1 day;
+-------------------------+-------------------------+-------------------------------------+----------------------------------------------------------+
| day(date('2023-03-00')) | day(date('2023-03-01')) | date('2023-03-01') - interval 1 day | date('2023-03-00') = date('2023-02-28') + interval 1 day |
+-------------------------+-------------------------+-------------------------------------+----------------------------------------------------------+
| 0 | 1 | 2023-02-28 | 0 |
+-------------------------+-------------------------+-------------------------------------+----------------------------------------------------------+
1 row in set (0.00 sec)
由于它不是一个正常的日期,所以它无法计算日期之间的差值。
mysql> select datediff(date('2023-02-28'),date('1970-01-01')),datediff(date('2023-03-00'),date('1970-01-01')),datediff(date('2023-03-01'),date('1970-01-01'));
+-------------------------------------------------+-------------------------------------------------+-------------------------------------------------+
| datediff(date('2023-02-28'),date('1970-01-01')) | datediff(date('2023-03-00'),date('1970-01-01')) | datediff(date('2023-03-01'),date('1970-01-01')) |
+-------------------------------------------------+-------------------------------------------------+-------------------------------------------------+
| 19416 | NULL | 19417 |
+-------------------------------------------------+-------------------------------------------------+-------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
在TiDB数据库中,可以通过日期的计算函数来计算当年是否是闰年,当2月的最后一天是29则是闰年,28则不是闰年。
mysql> select last_day(date('2023-02-01')), case when day(last_day(date('2023-02-01'))) = 29 then '是闰年' else '不是闰年' end;
+------------------------------+-------------------------------------------------------------------------------------------+
| last_day(date('2023-02-01')) | case when day(last_day(date('2023-02-01'))) = 29 then '是闰年' else '不是闰年' end |
+------------------------------+-------------------------------------------------------------------------------------------+
| 2023-02-28 | 不是闰年 |
+------------------------------+-------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select last_day(date('2024-02-01')), case when day(last_day(date('2024-02-01'))) = 29 then '是闰年' else '不是闰年' end;
+------------------------------+-------------------------------------------------------------------------------------------+
| last_day(date('2024-02-01')) | case when day(last_day(date('2024-02-01'))) = 29 then '是闰年' else '不是闰年' end |
+------------------------------+-------------------------------------------------------------------------------------------+
| 2024-02-29 | 是闰年 |
+------------------------------+-------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
MySQL数据库中的时区与TiDB数据库中的时区一致,这里就不重复演示了。
还是需要注意Timestamp数据类型的使用。
在MySQL中也有与TiDB相同的关于日期的函数,这里也就不重复演示了。
但是有一个不太一样的,就是date(),TiDB中会将date(‘2023-03-00’)成功转换成日期,虽然该日期非法,但不为NULL。而在MySQL中date(‘2023-03-00’)是非法的,返回NULL。
mysql> select date('2023-03-00'),date('2023-03-01'),date('2023-03-01') - interval 1 day, date('2023-03-00') = date('2023-03-01') - interval 1 day;
+--------------------+--------------------+-------------------------------------+----------------------------------------------------------+
| date('2023-03-00') | date('2023-03-01') | date('2023-03-01') - interval 1 day | date('2023-03-00') = date('2023-03-01') - interval 1 day |
+--------------------+--------------------+-------------------------------------+----------------------------------------------------------+
| NULL | 2023-03-01 | 2023-02-28 | NULL |
+--------------------+--------------------+-------------------------------------+----------------------------------------------------------+
1 row in set, 2 warnings (0.00 sec)
在TiDB数据库中不支持存储过程、自定义函数、触发器、定时器等。
在MySQL数据库中,可以创建定时器。
下面的语句创建一个定时器,每10秒钟往表中插入一条记录,5分钟后结束定时器功能。
mysql> create event insert_event
-> on schedule every 10 second
-> ends now() + interval 5 minute
-> do insert into temp values(now(), now(), curtime());
Query OK, 0 rows affected (0.02 sec)
定时器创建完成后,每隔一段时间查看数据库表的数据,可以看到数据的增长。
好了,以上就是数据库中关于日期、时间的使用,在TiDB中有几个坑、MySQL中也有几个坑,你遇到过吗?使用的时候一定要注意。
最后我们实现了在数据库中判断闰年、定时器等功能。