前端中的时间

在前台界面中,前端常常需要对时间进行处理,例如:将后端返回的时间戳格式化 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]'

查看基础组件库时发现,基础组件库的 calendardate-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 标准,将一年的第一周的日期归属到该年份中。

参考阅读

相关推荐
DT——1 小时前
Vite项目中eslint的简单配置
前端·javascript·代码规范
学习ing小白4 小时前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进4 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er4 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063714 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl4 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码4 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347545 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
ch_s_t5 小时前
新峰商城之分类三级联动实现
前端·html
辛-夷5 小时前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js