Skip to main content

格式化日期和时间

平时开发的时候对日期和时间进行运算和格式化,可能经常使用 new Date(),但是它的用法和一些怪异设定并不是很方便好用,一不小心就踩坑。

new Date() 的四宗罪

浏览器不兼容

new Date('2023-1-1')

上面这行代码在 Safari 浏览器,返回的都是 Invalid Date。Safari 浏览器目前还理解不了 YYYY-MM-DD 这样的格式,只支持 YYYY/MM/DD。这就造成在 Windows 环境下的代码正常原型,而 Macbook 和 iPhone 用户异常显示;

月份的索引起点

new Date(2023, 1, 1) // 2023年2月1日

月份的索引是以 0 为起点的,而年份、日期却不是,同样的,对应的方法 setMonth() 也是从 0 开始设置的;

年份初始化不统一

当需要格式化公元元年至公元 100 年之间的时间,需要特殊的写法:

new Date(2023, 1, 1) // Wed Feb 01 2023 00:00:00 GMT+0800 (中国标准时间)
new Date(50, 2, 1) // Wed Mar 01 1950 00:00:00 GMT+0800 (中国标准时间)
new Date('0050-02-01') // Tue Feb 01 0050 08:05:43 GMT+0805 (中国标准时间) √

new Date() 第一个参数年份小于 100 的话,会自动加上 1900 年,所以需要用 YYYY-MM-DD 格式的时间字符串作为第一个参数...但是如果给公元 100 年前添加上时间,又有不一样的表现:

new Date('0050-02-01 00:00:00') // Wed Feb 01 1950 00:00:00 GMT+0800 (中国标准时间)

当以 YY-MM-DD 这种字符串格式作为参数的时候,构造函数把最后面那个当作年份,而且默认它为 20xx 年

new Date('10-11-12') // Thu Oct 11 2012 00:00:00 GMT+0800 (中国标准时间)

日期初始化存在时区差异

new Date('2018-01-01') // Mon Jan 01 2018 08:00:00 GMT+0800 (中国标准时间)
new Date('2018/01/01') // Mon Jan 01 2018 00:00:00 GMT+0800 (中国标准时间)

上面两种格式返回的时间是不同的,查了北京时间与格林尼治时间,时差 8 个小时。

介于 new Date() 中的这些怪异设定,而且不能方便的对时间进行计算、比较、格式化,所以日常开发中一般使用其他工具。

Intl.DateTimeFormat()

Intl 对象的设计目的是使特定位置的数据更容易国际化。DateTimeFormat() 是一种用于格式化日期和时间的方法。

首先创建一个实例,第一个参数 locale 告诉方法将日期和时间格式化为哪种语言:

const formatter = new Intl.DateTimeFormat('zh-CN') // 中文
const formatter = new Intl.DateTimeFormat('en-US') // 美式英语
const formatter = new Intl.DateTimeFormat('en-GB') // 英式英语

使用一系列 options 作为第二个参数,可以对输出的格式进行更细粒度的控制:

const now = new Date()

// 使用 format 格式化为字符串
new Intl.DateTimeFormat('zh-CN', { dateStyle: 'full' }).format(now) // '2022年3月30日星期三'
new Intl.DateTimeFormat('zh-CN', { dateStyle: 'long' }).format(now) // '2022年3月30日'
new Intl.DateTimeFormat('zh-CN', { dateStyle: 'medium' }).format(now) // '2022年3月30日'
new Intl.DateTimeFormat('zh-CN', { dateStyle: 'short' }).format(now) // '2022/3/30'
new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }).format(now) // 'March 30, 2022'

new Intl.DateTimeFormat('en-CA', { dateStyle: 'short' }).format(now) // '2022-03-30'

另一个例子:

const options = { year: 'numeric', month: 'long', day: 'numeric' }
const date = new Date('2021-10-01')
const formatter = new Intl.DateTimeFormat('zh-CN', options) // 创建实例

console.log(formatter.format(date)) // 2021年10月1日

// 使用 formatToParts 方法打印出格式化部件
formatter.formatToParts(date).forEach(part => console.log(part.value))
// 分别输出:2021 - 年 - 10 - 月 - 1 - 日

浏览器兼容性:

Moment.js

直接看例子:

// 引入 moment.js 库
const moment = require('moment')

// 格式化当前日期和时间
const currentDate = moment()
console.log(currentDate.format('YYYY-MM-DD HH:mm:ss'))

// 解析日期字符串为 moment 对象
const parsedDate = moment('2023-06-04')
console.log(parsedDate.format('YYYY-MM-DD HH:mm:ss'))

// 计算两个日期之间的差异
const startDate = moment('2023-06-01')
const endDate = moment('2023-06-05')
const duration = moment.duration(endDate.diff(startDate))
console.log(duration.asDays())

// 添加和减去时间
const date = moment('2023-06-01')
console.log(date.add(7, 'days').format('YYYY-MM-DD'))
console.log(date.subtract(1, 'months').format('YYYY-MM-DD'))

// 开始和结束日期
console.log(moment().startOf('year').format('YYYY-MM-DD'))
console.log(moment().endOf('month').format

// 时区转换
const dateInLosAngeles = moment().tz('America/Los_Angeles')
const dateInNewYork = dateInLosAngeles.clone().tz('America/New_York')
console.log(dateInLosAngeles.format())
console.log(dateInNewYork.format())

// 多语言支持
moment.locale('zh-cn')
console.log(moment().format('LL'))

Day.js

Day.js 是一个轻量级的 JavaScript 库,用于处理和显示日期和时间。它提供了与 Moment.js 类似的 API,但比 Moment.js 更小、更快,并且使用起来也更加直观。

下面列出常用的方法:

dayjs() // 返回当前时间 dayjs 对象
dayjs('1995-12-25') // 可以解析传入的一个标准的ISO 8601时间字符串
dayjs(new Date(2018, 8, 18)) // 可以解析传入的一个 Javascript Date 对象
dayjs(1318781876406) // 可以解析传入的一个 Unix 时间戳 (13位数字)
dayjs(dayjs()) // 获得 dayjs 对象的一个拷贝对象

检测当前 Dayjs 对象是否是一个有效的时间

dayjs('2022-01-33').isValid() // true
dayjs('some invalid string').isValid() // false

计算日期和时间

// 第一个参数是要增加或减少的数量(可以为负数),第二个参数表示要增加或减少的单位
dayjs().add(1, 'day') // 在当前的基础上加1天
dayjs().add(1, 'hour')
dayjs().add(30, 'minute')

// 也可以接收时间单位的缩写形式作为第二个参数
dayjs().subtract(1, 'y') // 减少一年
dayjs().subtract(1, 'M') // 减少一个月
dayjs().subtract(1, 'w') // 减少一周
dayjs().subtract(1, 'd') // 减少一天
dayjs().subtract(1, 'h') // 减少一小时
dayjs().subtract(1, 'm') // 减少一分钟
dayjs().subtract(1, 's') // 减少一秒
dayjs().subtract(1, 'ms') // 减少一毫秒

格式化时间

dayjs('2019-01-25').format('YYYY-MM-DD HH:mm:ss')
dayjs().format('YYYY-MM-DD')
dayjs().format('YYYY/MM/DD')
dayjs().format('YYYY年M月D日')
dayjs().format('dddd, MMMM D, YYYY') // Monday, June 5, 2023
dayjs().format('h:mm A') // 12:30 AM

获取两个 Dayjs 对象的时间差,默认毫秒

const date1 = dayjs('2019-01-25')
const date2 = dayjs('2018-06-05')
date1.diff(date2) // 20214000000
date1.diff(date2, 'month') // 7
date1.diff(date2, 'month', true) // 7.645161290322581
date1.diff(date2, 'day') // 233

获取日期的开始和结束时间的 Dayjs 对象

dayjs().startOf('year') // 获取当前年份的开始时间
dayjs().startOf('month') // 获取当前月份的开始时间
dayjs().startOf('week') // 获取本周(以星期日为开头)的开始时间
dayjs().startOf('day') // 获取当天的开始时间

dayjs().endOf('hour') // 获取当前小时的结束时间
dayjs().endOf('minute') // 获取当前分钟的结束时间
dayjs().endOf('second') // 获取当前秒数的结束时间

比较两个 Dayjs 对象的时间前后

dayjs().isSame(dayjs()) // true 尽管两个 Dayjs 对象不一样且毫秒不一样,但是还是认为时间相同
dayjs().isSame(dayjs().add(1, 'ms')) // false 手动修改时间总是不相同
dayjs().isSame(dayjs(), 'year') // true

dayjs().isBefore(dayjs()) // false
dayjs().isBefore(dayjs(), 'year') // false

dayjs().isAfter(dayjs()) // false
dayjs().isAfter(dayjs(), 'year') // false

获取或设置年份

dayjs().year() // 2023
dayjs().year(2000) // 返回此时此刻2000年的 dayjs 对象

获取或设置月份,从 0 开始

dayjs().month() // 5 实际是6月
dayjs().month(0) // 返回此时此刻1月份的 dayjs 对象

// 有另一种等价的写法
dayjs().get('month')
dayjs().set('month', 0)

获取或设置日期,从 1 开始

dayjs().date() // 4 4号
dayjs().date(1) // 返回此时此刻1号的 dayjs 对象

获取或设置星期,从星期天 0 开始

dayjs().day() // 0 星期天
dayjs().day(0) // 会强行设置为上周日

获取或设置小时、分钟、秒、毫秒

dayjs().hour()
dayjs().hour(12)

dayjs().minute()
dayjs().minute(59)

dayjs().second()
dayjs().second(1)

dayjs().millisecond()
dayjs().millisecond(1)

其他方法

dayjs().clone() // 拷贝 dayjs 对象,等价于 dayjs(dayjs())
dayjs().valueOf() // 返回 Unix 时间戳 (毫秒)
dayjs().unix() // 返回 Unix 时间戳 (秒)
dayjs().daysInMonth() // 返回月份的天数
dayjs().toDate() // 返回原生的 Date 对象
dayjs().toJSON() // 返回 ISO8601 格式的字符串 "2023-06-04T16:25:05.292Z"
dayjs().toISOString() // 等价于 dayjs().toJSON()
dayjs().toString() // Sun, 04 Jun 2023 16:26:42 GMT

使用插件进行时区转换

const utcDate = dayjs.utc('2023-06-04 10:00:00')
const localDate = utcDate.local()
console.log(utcDate.format('YYYY-MM-DD HH:mm:ss'))
console.log(localDate.format('YYYY-MM-DD HH:mm:ss'))

添加本地化插件

const localizedDay = require('dayjs/locale/zh-cn')
dayjs.locale(localizedDay)
console.log(dayjs().format('LL'))