在前台界面中,前端常常需要对时间进行处理,例如:将后端返回的时间戳格式化
YYYY-MM-DD HH:mm:ss
; 活动倒计时;两个时间计算时间差;日期选择三个月后、近半年;等等
首先,我们日常说的时间其实是时刻,两个时刻之间为一个时间间隔。
js的内置 Date 对象
创建一个 JavaScript Date
实例,该实例呈现时间中的某个时刻。Date
对象则基于 Unix Time Stamp,即自 1970 年 1 月 1 日
(UTC)起经过的毫秒数。(许多计算机系统使用Unix时间戳来记录时间,这种时间戳的起始日期是1970年1月1日,比之更早的时间是可以表示的,只是时间戳是负数)
js
new Date('1901').getTime() // -2177452800000
new Date('1970').getTime() // 0
创建 Date 对象
Date 对象由 Date()
构造函数创建。
有 4 种方法创建新的日期对象:
- new Date()
日期对象是静态的。计算机时间正在滴答作响,但日期对象不会,默认情况下,JavaScript 将使用浏览器的时区并将日期显示为全文本字符串。
js
new Date() // Sun Jul 14 2024 11:23:07 GMT+0800 (中国标准时间)
- new Date(year, month, day, hours, minutes, seconds, milliseconds)
7个数字分别指定年、月、日、小时、分钟、秒和毫秒(按此顺序):
js
new Date(2024, 8, 14) // Sat Sep 14 2024 00:00:00 GMT+0800 (中国标准时间)
new Date(2024, 8, 14, 23, 59, 59) // Sat Sep 14 2024 23:59:59 GMT+0800 (中国标准时间)
注意: JavaScript 从 0 到 11 计算月份,一月是 0,十二月是11;一位和两位数年份将被解释为 19xx 年;
- new Date(milliseconds)
当参数是一个时,会被认定为毫秒,将创建一个零时加毫秒的新日期对象,例如:
js
new Date(100000000000) // Sat Mar 03 1973 17:46:40 GMT+0800 (中国标准时间) {}
一天(24 小时)是 24 * 60 * 60 * 1000 = 86 400 000 毫秒
- new Date(date string)
js
new Date('2024')
// Mon Jan 01 2024 08:00:00 GMT+0800 (中国标准时间)
new Date('2024-10-01')
// Tue Oct 01 2024 08:00:00 GMT+0800 (中国标准时间)
new Date('2024/10/01 10:10:10')
// Tue Oct 01 2024 10:10:10 GMT+0800 (中国标准时间)
Date 日期格式
ISO 8601 是表现日期和时间的国际标准,日期和时间间使用 T 分隔,UTC 时间通过大写字母 Z 来分隔 注意: 这两个将得到不同的结果,在日期-时间字符串中省略 T 或 Z,在不同浏览器中也会产生不同结果
js
new Date('2024-07-14T11:53')
// Sun Jul 14 2024 11:53:00 GMT+0800 (中国标准时间)
new Date('2024-07-14Z11:53')
// Sun Jul 14 2024 19:53:00 GMT+0800 (中国标准时间)
在设置日期时,如果不规定时区,则 JavaScript 会使用浏览器的时区。当获取日期时,如果不规定时区,则结果会被转换为浏览器时区。
注意:在某些浏览器中,不带前导零的月或其会产生错误
js
new Date('2024-7-14') // 危险写法
常用的日期处理方法
以下说的都是js
的内置Date
对象的方法,其他库创建的对象,不一定有以下方法
日期获取方法
获取方法用于获取日期的某个部分(来自日期对象的信息)。下面是最常用的方法:
方法 | 描述 |
---|---|
getDate() | 以数值返回天(1-31) |
getDay() | 以数值获取周名(0-6,周日为0) |
getFullYear() | 获取四位的年(yyyy) |
getHours() | 获取小时(0-23) |
getMilliseconds() | 获取毫秒(0-999) |
getMinutes() | 获取分(0-59) |
getMonth() | 获取月(0-11) |
getSeconds() | 获取秒(0-59) |
getTime() | 获取时间(从 1970 年 1 月 1 日至该日期对象所代表时间的毫秒数) |
日期设置方法
设置方法用于设置日期的某个部分。下面是最常用的方法:
方法 | 描述 |
---|---|
setDate() | 以数值(1-31)设置日 |
setFullYear() | 设置年(可选月和日) |
setHours() | 设置小时(0-23) |
setMilliseconds() | 设置毫秒(0-999) |
setMinutes() | 设置分(0-59) |
setMonth() | 设置月(0-11) |
setSeconds() | 设置秒(0-59) |
setTime() | 设置时间(从 1970 年 1 月 1 日至今的毫秒数) |
其他常用方法
方法 | 描述 |
---|---|
valueOf() | 获取时间(从 1970 年 1 月 1 日至该日期对象所代表时间的毫秒数)同getTime() |
静态方法
上面讲的都是实例方法,需要通过实例调用,下面则是静态方法,通过类名调用
方法 | 描述 |
---|---|
Date.now() | 获取时间(从 1970 年 1 月 1 日至该日期对象所代表时间的毫秒数) |
Date.parse() | 返回从 1970-1-1 00:00:00 UTC 到该日期对象(该日期对象的 UTC 时间)的毫秒数 |
日期处理库dayjs、date-fns等
js自带的Date
对象和方法已经很完善了,为啥还需要额外的日期处理库呢?主要原因在
(1)不可靠,参数理解成本大
js
new Date("2024-10-28")
// Fri Oct 28 2024 08:00:00 GMT+0800 (中国标准时间)
new Date(2024, 10, 28) // 这里的 10 其实是 11月了
// Mon Nov 28 2024 00:00:00 GMT+0800 (中国标准时间)
(2)格式化不方便
通常会使用date的api做一些简单的封装;
ini
2024-10-28 => 2024年10月28日
2024/10/28 09:00:00 => 2024年10月28日 09:00:00
(3)时刻的加减运算不方便
不能计算几天前,几小时后,几个月前,半年内这种简单的运算,通常也要自己封装;
moment.js
新项目已经不推荐使用了,原因:(1)可变对象 (2)包体积过大 (3)官方已停止维护
dayjs
(1)和moment.js相同的API
和moment.js相同的API、相同的链式操作。
js
// 最近一周,(不含今天)
const NEAR_WEEK = [dayjs().subtract(7, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')]
// 最近一个月 (按照31天算)
const NEAR_MONTH = [dayjs().subtract(31, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')]
(2)不可变
上面提到,可变性是使用momentJs时最大的问题之一。在day.js完全消除了这个问题,它支持不变性。
(3)体积小
Day.js 虽然仅有 2kb 大小,但是功能一点都没有阉割,包含了时间处理的全部常用方法。
date-fns
(1)模块化、按需引入 (2)不可变 (3) 同时支持 Flow 和 TypeScript
官网举例:
js
import { format, compareAsc } from "date-fns";
format(new Date(2014, 1, 11), "MM/dd/yyyy");
//=> '02/11/2014'
const dates = [
new Date(1995, 6, 2),
new Date(1987, 1, 11),
new Date(1989, 6, 10),
];
dates.sort(compareAsc);
//=> [
// Wed Feb 11 1987 00:00:00,
// Mon Jul 10 1989 00:00:00,
// Sun Jul 02 1995 00:00:00
// ]
date-fns中的日期处理函数还是很多的,我们一般可以使用他的一些api然后二次封装成日期相关utils
js
import { format } from "date-fns";
const formatDate = (e, t = "yyyy-MM-dd HH:mm:ss") => format(e, t)
// ...
const date = {
formatDate,
}
export {
date
}
[注意:] 传递给任何Date-fns
函数的所有Date
参数都由toDate
处理,必须是DateType | number | string
类型, 当参数不合法时,会报错
js
import type { GenericDateConstructor } from "../types.js";
/**
* @name toDate
* @category Common Helpers
* @summary Convert the given argument to an instance of Date.
*
* @description
* Convert the given argument to an instance of Date.
*
* If the argument is an instance of Date, the function returns its clone.
*
* If the argument is a number, it is treated as a timestamp.
*
* If the argument is none of the above, the function returns Invalid Date.
*
* **Note**: *all* Date arguments passed to any *date-fns* function is processed by `toDate`.
*
* @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc).
*
* @param argument - The value to convert
*
* @returns The parsed date in the local time zone
*
* @example
* // Clone the date:
* const result = toDate(new Date(2014, 1, 11, 11, 30, 30))
* //=> Tue Feb 11 2014 11:30:30
*
* @example
* // Convert the timestamp to date:
* const result = toDate(1392098430000)
* //=> Tue Feb 11 2014 11:30:30
*/
export function toDate<DateType extends Date>(
argument: DateType | number | string,
): DateType {
const argStr = Object.prototype.toString.call(argument);
// Clone the date
if (
argument instanceof Date ||
(typeof argument === "object" && argStr === "[object Date]")
) {
// Prevent the date to lose the milliseconds when passed to new Date() in IE10
return new (argument.constructor as GenericDateConstructor<DateType>)(
+argument,
);
} else if (
typeof argument === "number" ||
argStr === "[object Number]" ||
typeof argument === "string" ||
argStr === "[object String]"
) {
// TODO: Can we get rid of as?
return new Date(argument) as DateType;
} else {
// TODO: Can we get rid of as?
return new Date(NaN) as DateType;
}
}
在这里如果使用dayjs创建的日期对象,使用date-fns的函数就报错了;
js
const date1 = dayjs()
console.log(typeof date1) // 'object'
console.log(Object.prototype.toString.call(date1)) // '[object Object]'
解法:valueOf(), valueOf 能得到dayjs的时间戳,也同样使用于js普通的Date对象
js
console.log(Object.prototype.toString.call(date1.valueOf())) // '[object Number]'
查看基础组件库时发现,基础组件库的 calendar
和 date-picker
均使用的 dayjs,最后返回的日期经过了 local()
或者 valueOf()
处理,并非返回 dayjs对象
其他细节
(1)yyyy-MM-dd HH:mm:ss 和 yyyy-MM-dd HH:mm:ss 的区别
-
yyyy-MM-dd HH:mm:ss 格式不能很好地处理周年份。如果一年的第一天是周日,那么这一年的第一天会被解析为上一年的最后一天。
-
YYYY-MM-dd HH:mm:ss 格式能够正确处理周年份。它会根据 ISO 8601 标准,将一年的第一周的日期归属到该年份中。