最近本人在全栈开发的过程中,需要将日期存入数据库。而这篇文章就是作者不断踩坑后的产出,希望可以帮助到大家。
在大家阅读之前,可以先思考一个问题:
我想将用户输入的日期,通过后端接口,写入数据库中。大家会怎么做?
作者总结出了两种方法,同时介绍了各自的优缺点。科普了在工作中可能会用到的日期小知识,现在大家继续看下去吧!
JS的日期数据类型
在JS中,处理日期和时间主要是通过Date对象。接下来我将手把手教你如何创建一个Date实例,同时告诉你各个部分的含义。准备好了吗?我们开始了
创建Date实例
在JS中,通过new关键字和Date()构造函数来创建Date实例:
let now = new Date()
console.log(now)
现在我们创建了一个Date实例,如果在Node.js中输出它的话,会得到什么结果呢?
我们发现,console.log()输出Date实例,会输出一串字符串。那么这段字符串是什么意思呢?我们现在来看看:
- 2024-04-10:日期部分,遵循
YYYY-MM-DD
的格式 - T:分割符,主要的作用就是分割日期和时间,让计算机和用户知道,接下来要显示时间了
- 06:08:01.522:时间部分,遵循
HH:MM:SS.mmm
其中的mmm
为毫秒部分 - Z:标识符,用于标明时区,标识符为Z,说明这个时间是按照UTC时区表示的。
- UTC时区,即 协调世界时时区(Coordinated Universal Time),是世界时间标准的基础。UTC不属于任何特定的地理时区,而是通过原子时钟精确计时,通过科学方法确定的。无论身处世界的哪一个时区,UTC时间都是一致的
- Z实际上指的是Zulu时间,即另一种说法的UTC。标识为Z说明这个时间是相对于UTC的,没有时区偏差。
我猜你现在有点云里雾里了😵,日期部分、T分隔符和时间部分还能理解,这个时区又是个啥?为啥UTC时区要用Z表示?不要担心,现在我们开始说人话:
时区
高中地理说过,整个地球被分为了24个时区,如果你坐飞机一路向东,每经过一个时区,你的手表上的时间都应该修改一小时。
假设我们在中国是12号的12点,要吃午饭了🍚。而按照时区的划分,美国中部现在却是11号的19点,还是晚上。
如果按照当地时间的话,地球上不同时区的时间都是不同的
这个问题可就太头疼了,假如我要做外商,需要做一个限时抢购功能。我如果使用的是中国时区,从10号14:00点开抢,那么美国的用户应该在几点开抢呢?想想就头痛。
为了解决时区不同,时间不同的问题,科学家们引入了UTC时区,即协调世界时(Coordinated Universal Time)
UTC时区
UTC时区消除了世界各地的时间差异,不管你身处地球何处,你的UTC时区的时间与其他任何地方的时间都是一样的。
为什么呢?因为UTC是建立在原子时间的基础上,感兴趣的小伙伴可以自行了解
为什么UTC时区要用Z来标识呢?
这其实就涉及到历史背景了,在过去,使用 NATO(北大西洋公约组织)音标字母(也称为国际音标字母)来代表不同的时区。例如,“Alpha”代表 UTC+1,”Bravo” 代表 UTC+2,以此类推。在这个系统中,”Zulu” 代表了 0 时区,即 UTC。
总是用大写字母Z来标识UTC时区,其实是一个历史遗留叫法,就好比JavaScript里面有个Java,你能说JavaScript和Java有关系吗?
浏览器输出Date实例
上文中为了介绍JS日期的字符串表达方式,我们使用的是Node.js环境,输出的是一行字符串。而我们知道Date实例实际上是一个对象,通过log输出得到的字符串日期其实是Date实例序列化之后的结果。
那么我们如果在浏览器环境输出Date实例,会发生什么呢?
let browserNow = new Date()
console.log(browserNow)
欸!神奇的事情发生了,为什么在Node环境下和浏览器环境下输出的结果不一样呢?
别慌别急!带好墨镜🕶,跟着老司机,我们继续深入了解
让我们先了解输出的这段字符串是什么意思:
- Web:表示今天是星期三
- Apr:表示现在是四月份April
- 10:表示现在是十号
- 2024:表示今年是2024年
- 14:31:23:表示现在是下午14点32分23秒
- GMT+0800:是一个时区标志
- GMT:格林尼治标准时间
- +0800:表示相对于格林尼治标准时间的偏移量,偏移量为+0800。中国的时区统一使用东八区,这整个
GMT+0800
的意思是,当前所在时区比GMT时区要快8小时
- (中国标准时间):提供了时区具体名称,中国整体使用东八区时间
为什么Node.js环境与浏览器环境Date实例输出不同
首先我们要知道,Date实例是对象。如果直接输出的话,应当输出一个对象才对。但是我们通过console.log()输出的都是字符串,这时为什么呢?
console.log()输出的Date类型与环境有关。在浏览器中,会默认先调用Date.prototyoe.toString()
方法,转化为我们在浏览器中看到的格式
为了验证我们的说法,我们在Node.js环境下,手动调用Date.prototype.toString()
方法再输出
let now = new Date()
console.log(now.toString())
这时候我们发现,Node.js环境下的输出与浏览器一致了!
那么在Node.js中,是不是调用Date.prototpye.toUTCString()
的方法,转化为UTC格式的字符串输出呢?
同样的,为了验证我们的说法,我们在浏览器环境下,手动调用Date.prototype.toUTCString()
let now = new Date()
console.log(now.toUTCString())
我们发现,欸?!怎么回事,为什么不是2024-04-10T07:28:03.611Z
的格式?别急!让我细细道来
首先同样是输出当前日期,将为什么调用Date实例的toUTCString()方法后,输出的时间是4月10号的早上7:26;而调用Date实例的toString()方法,输出的时间是4月10号的下午15:11呢?
Date实例的三种格式转化方法
在JS中,Date实例有三种格式转换的方法,它们分别为:
-
toString():将Date对象转化为字符串,同时使用浏览器所在本地时区的时间表示。在中国的话会显示中国时区的时间,美国的话会显示美国时区的时间
-
toUTCString():将Date对象转化为符合
RFC 1123
规范的字符串,统一按照UTC时区显示时间。所以与中国时区的时间不同。其中的GMT是格林威治标准时间,GTM是基于地球自转和太阳相对于格林威治天文台的位置来计算的时间。在1972年以前,它曾经作为全球时间标准
为什么调用toUTCSting(),最后的时区标识是GMT?这其实又是一个JS的历史遗留问题,因为GMT术语用的更多,这是一个历史习惯问题。实际上的时间,还是基于UTC时区的。JS真的好多历史遗留问题……
- toISOString():将Date对象转化为符合
ISO 8601
规范的字符串,同时这个字符串是基于UTC时区的。而这个格式就是之前在node.js环境下输出的日期字符串格式
let now = new Date()
console.log(now.toISOStirng())
node.js环境:
浏览器环境:
获取时间戳
在JS中,我们可以通过调用Date实例的.getTime()
方法,将当前Date实例转化为对应的时间戳
let now = new Date()
console.log(now.getTime())
那么什么是时间戳呢?在JS里时间戳表示的是从UTC时区的1970年1月1日到现在所经过的毫秒数。注意是毫秒数哦!这点很重要。
时间戳1712735944258
的意思是,按照UTC时区的1970年1月1日后,经过1712735944258
毫秒
我们也可以通过Date.now()
的方法,直接获取当前时间戳
let now = Date.now()
console.log(now)
要注意的是,时间戳是number数字类型,不是对象也不是字符串,注意是number类型!
JS的Date日期类型总结
我们可以通过new关键字+Date构造函数创建Date实例,该Date实例默认储存当前时间。
Date实例的字符串输出有三种格式:
- toString():转化为符合当地规范的字符串,不同地区规范不同,时区为浏览器本地时区,不同地区时间不同
- toUTCString():转化为符合
RFC 1123
规范的字符串,时区为UTC时区,不同地区时间相同 - toISOString():转化为符合
ISO 8601
规范的字符串,时区为UTC时区,不同地区时间相同
RFC 1123规范的时间字符串
- Web:表示今天是星期三
- 10:表示现在是十号
- Apr:表示现在是四月份April
- 2024:表示今年是2024年
- 07:38:16:表示现在是下午14点32分23秒
- GMT:是一个时区标志
- GMT:格林尼治标准时间
- 由于JS历史遗留原因,虽然标识是GMT,但实际的时间还是按照UTC时区标准的
ISO 8601规范的时间字符串
- 2024-04-10:日期部分,遵循
YYYY-MM-DD
的格式 - T:分割符,主要的作用就是分割日期和时间,让计算机和用户知道,接下来要显示时间了
- 07:47:28.199:时间部分,遵循
HH:MM:SS.mmm
其中的mmm
为毫秒部分 - Z:标识符,用于标明时区,标识符为Z,说明这个时间是按照UTC时区表示的。
- UTC时区,即 协调世界时时区(Coordinated Universal Time),不同地区时间相同
- Z实际上指的是Zulu时间,即另一种说法的UTC。(又是一个历史遗留问题……)
Mysql的日期数据类型
现在我们认识了JS中的Date日期类型,但是这还不够,因为我们的目标是星辰大海⛵!
为了成为全栈工程师,我们也需要了解Mysql的日期数据类型,这样在全栈项目中,需要存入日期数据时,我们就能得心应手,老板看我们这么厉害,工资💴也水涨船高
话不多说,我们现在来看看Mysql中的日期数据类型
在Mysql中有五种常用的日期数据类型:
- DATE:用于存储日期,字符串格式为
YYYY-MM-DD
,范围从1000-01-01
到9999-12-31
INSERT
`time` (date)
VALUES (
'2024-4-10'
)
- TIME:用于存储时间值,字符串格式为
HH:MM:SS
,范围为从-838:59:59
到838:59:59
。在Mysql5.6.4版本后支持微秒部分,即HH:MM:SS.mmm
INSERT
`time` (time, datetime)
VALUES (
'16:05:30.222'
)
- DATETIME:用于存储日期和时间,字符串格式为
YYYY-MM-DD HH:MM:SS
。在Mysql5.6.4版本后支持微秒部分
INSERT
`time` (datetime)
VALUES (
'2024-04-10 16:05:30.222'
)
TIMESTAMP Mysql的时间戳
为什么要将Mysql里面的timestamp单独讲呢?因为这一段跟JS中的时间戳有着很大的区别,如果没有仔细区分的话,新手非常容易踩坑(没错,说的就是我/(ㄒoㄒ)/)
我们之前说过,在JS中,时间戳是从1970年1月1日到现在所经过的毫秒数,是number类型的数据
但是在mysql中,时间戳是从1970年1月1日到现在所经过的秒数,注意是秒,不是毫秒
但是在储存的时候,却与datetime相似,储存的是YYYY-MM-DD HH:MM:SS
格式的字符串
简单来说,JS的时间戳是按毫秒计时的数字;
而Mysql的时间戳却是按秒计数的数字,存储的却是字符串
我知道你很奇怪,也很迷茫,但你先别迷茫🙅,看完这几个例子就懂了
如果直接将JS的时间戳写入Mysql中,是肯定会报错的,例如:
INSERT
`time` (timestamp)
VALUES (
1712735944258
)
首先JS的时间戳以毫秒为单位,而mysql时间戳以秒为单位,单位不同肯定存不进去。
将1712735944258除以1000后取整数,得到1712735944,这样就将毫秒为单位的时间戳,转化为了以秒为单位的时间戳。现在我们是不是就可以存入mysql了呢?我们试试:
INSERT
`time` (timestamp)
VALUES (
1712735944
)
还是报错了!这时候不要气馁,我们离正确答案以及很近了。
我们报错的原因在于,mysql的timestamp类型的日期数据,存储的时候还是按照YYYY-MM-DD HH:MM:SS
格式的字符串存储的,所以正确的写法是,写入YYYY-MM-DD HH:MM:SS
的时间字符串
INSERT
`time` (timestamp)
VALUES (
'2024-4-10 20:18:30'
)
mysql的TIMESTAMP类型到底是这么个事?
看到这里,不知道你有没有这样的疑问:TIMESTAMP类型和DATETIME类型有什么区别?为什么说Mysql的时间戳是以秒为单位的,但却不能直接写入TIMESTAMP类型的日期中?
好的,我这就来解答您的疑问。但请放轻松,饭要一口一口吃,学习我们也要一步一步的来
TIMESTAMP类型和DATETIME类型有什么区别
从数据写入的角度来看,没有任何区别。写入DATETIME类型和写入TIMESTAMP类型的mysql语法都是一样的
INSERT
`time` (datetime, timestamp)
VALUES (
'2024-04-10 16:05:30.222', '2024-04-10 16:05:30.222'
)
但是这两者在使用方面,确实存在区别,这两者最大的区别在于时区处理:
- DATETIME不存储任何与时区相关的信息,存进去是多少,取出来就是多少
- TIMESTAMP存储时,会将时间字符串自动转化为UTC时区,取出来时会转化为服务器的本地时区,例如中国的东八区。因此TIMESTAMP适合存储跨时区的日期和时间,如果你的用户面向全球,你最好用TIMESTAMP类型存储日期。
为什么说Mysql的时间戳是以秒为单位的,但以秒为单位的时间戳却不能直接写入TIMESTAMP类型的日期中?
mysql的TIMESTAMP类型背后的概念和存储机制确实是以秒为单位的,但是在存入数据的时候,mysql要求为YYYY-MM-DD HH:MM:SS
格式的字符串。
虽然我们不能直接讲JS的毫秒时间戳 直接存入mysql,但是借助mysql的FROM_UNIXTIME()函数,我们可以间接做到:
INSERT
`time` (timestamp)
VALUES (
FROM_UNIXTIME(1712735944258/1000)
)
在上面的代码中,我们先将JS的以毫秒为单位的时间戳 除以 1000,转化以秒为单位的时间戳。
然后将以秒为单位的时间戳,通过FROM_UNIXTIME()函数转化为了YYYY-MM-DD HH:MM:SS
格式的字符串
将JS的Date类型写入Mysql的两种方法
根据上文的学习,现在我们来到实战环节。总的来说我们有两种方法将JS的Date类型写入mysql:
- post传递日期字符串
- post传递时间戳
日期字符串
首先JS中,最接近Mysql的日期格式YYYY-MM-DD HH:MM:SS
的,就是ISO 8601规范的时间字符串YYYY-MM-DDTHH:MM:SS.mmm
在mysql5.6.8版本后,你可以写入毫秒。但是在这个版本之前,写入会报错。请读者分辨自己的mysql版本
所以我们在post传递给后端接口的时候,可以先调用Date实例的.toISOString()
方法,然后将返回的YYYY-MM-DDTHH:MM:SS.mmmZ
格式的字符串进一步处理就好了
我说的进一步处理,指的是:
- 将分隔符T用空格代替
- 去掉末尾的时区标识符Z
最终转化为YYYY-MM-DD HH:MM:SS.mmm
的格式,就可以直接写入mysql数据库啦
let now = new Date()
let tempStr = now.toISOString()
let prasedDate = tempStr.replace("T", " ").replace("Z", "")
console.log(prasedDate)
然后将这个字符串通过post传递参数,后端直接写入数据库就可以了,具体的实现方法就不讲了,感兴趣的同学可以去学express框架或者nest框架
INSERT
`time` (timestamp)
VALUES (
'2024-04-10 13:49:01.796'
)
时间戳转换
另一种方法就是前面介绍的,将JS的以毫秒为单位的时间戳转化为以秒为单位的时间戳,然后作为参数传递给后端。后端再通过mysql的FROM_UNIXTIME()函数解析成对应的日期字符串格式
let now = new Date()
let timeStampBySeconds = Math.floor(now.getTime()/1000)
console.log(timeStampBySeconds)
后端接收到后,调用FROM_UNIXTIME()函数
INSERT
`time` (timestamp)
VALUES (
FROM_UNIXTIME(1712757304)
)
到这里,我们的教程就写完了,最后再来一个彩蛋,附带上我最常用的JS的Date实例操作
JS常见的Date日期操作
创建特定的日期
通过时间字符串创建
通过Date()构造函数,根据上文的格式,我们可以自己写出特定的时间
let now = new Date("2024-04-09T15:26:00")
console.log(now)
//2024-04-09T07:26:00.000Z
通过年、月、日等参数
我们可以通过向Date()构造函数依次传入年、月、日,可选传入时、分、秒、毫秒
必须按照顺序依次输入,不能省略年份,只传入月份和日
- year
- month:注意!月份从0开始!0表示1月
- day
- ?hour
- ?minute
- ?second
- ?millisecond
let today = new Date(2024, 3, 9)
console.log(today)
//2024-04-08T16:00:00.000Z
let now = new Date(2024, 3, 9, 15, 34, 20, 111)
console.log(now)
//2024-04-09T07:34:20.111Z
获取特定年份、月份、日等
比如说,我想获取当前的年份,我可以先创建一个Date对象,在这个Date对象中包含年份、月份、日等
然后通过方法去获取年份、月份、日等
- .getFullYear()
- .getMonth() + 1:获取月份,月份从0开始,1月为0,12月为11
- .getDate():获取一个月中的第几天,1号为1,31号为31
- .getDay():获取一周中的第几天,周日为0,周六为6
- .getHours()
- .getMinutes()
- .getSeconds()
- .getMilliseconds()
这些方法都是根据本地时间计算的,如果想获取UTC时区,可以使用UTC版本,如 - .getUTCFullYear()
- .getUTCMonth()
- 以此类推
时间戳
可以使用Date实例的getTime()方法将日期和时间转化为时间戳。时间戳表示的是从UTC时间的1970年1月1日到现在所经过的毫秒数。
let now = new Date()
let timestamp = now.getTime()
console.log(timestamp)
获取当前时间戳
通过Date.now()方法,返回当前时间戳
let currentTimestamp = Date.now()
console.log(currentTimestamp)
将时间戳转化为Date对象
只需将时间戳作为参数,传入Date()构造函数,就会返回对应的Date实例
let timestamp = 1704067200000
let date = new Date(timestamp)
console.log(date.toString())
原文链接:https://juejin.cn/post/7356080597600043046 作者:Icicle_Crino