咱们来自字节跳动飞书商业应用研发部(Lark Business Applications),现在咱们在北京、深圳、上海、武汉、杭州、成都、广州、三亚都设立了工作区域。咱们重视的产品范畴主要在企业经验管理软件上,包含飞书 OKR、飞书绩效、飞书招聘、飞书人事等 HCM 范畴体系,也包含飞书审批、OA、法务、财政、收购、差旅与报销等体系。欢迎各位参加咱们。

本文作者:飞书商业应用研发部 俞泽霖

欢迎大家重视飞书技能,每周定期更新飞书技能团队技能干货内容,想看什么内容,欢迎大家评论区留言~

1 布景

依据《State of Frontend 2022》问卷调查, 最受欢迎的前五个东西库中,时刻处理相关的库占有了两席。时刻处理东西为什么如受前端工程师青睐?JS Date 为什么无法满意开发需求?不同的时刻库之间又存在哪些差异?

主流时间库横向对比

2 时刻

2.1 计量规范

时刻包含了时刻和时段两个概念,惯例意义上的时段是能够经过两个时刻核算得到的,但地球自转的不稳定,这种方式得到的时段并不是恒定的,因此诞生了两种时刻计量体系:

  1. 世界时(GMT):依据地球自转的天文丈量得到,并不稳定。
  2. 原子时(IAT):依据原子的单次振动时刻得到,能够认为是恒定不变的。

这两种时刻尺度速率上的不同每一至二年会带来会差大约1秒的差异,因此每隔一定时刻会在原子时的基础上添加或削减1秒,即闰秒,得到接近于世界时的时刻,称为和谐世界时(UTC)。

2.2 时区

全球被划分为24个时区。规则英国的格林威治天文台原址所在经线为基准线,即本初子午线,所在时区为零时区,零时区以东为东 1-12 区,以西为西 1-12 区(东12区和西12区是重合的)。每差一个时区,区时相差一小时,越往东区时越早。为了让世界各地都有一个统一的参照时刻,规则零时区的 UTC 时刻作为规范时刻,简称 UTC 时刻。

主流时间库横向对比

3 new Date()

3.1 YYYY-MM-DD 的时区问题

  • 运用 new Date('YYYY-MM-DD') 实例化 Date 目标时,因为没有指定详细时刻,体系会主动设置一个时刻为 ’00:00:00′ 的 UTC 时刻,并在用户拜访时,返回体系时区的对应时刻。如,坐落东八区的开发者拜访 Date 显现的时刻是 UTC+8。坐落西七区的开发者拜访 Date 显现的时刻是 UTC-7。也就是说,假如直接运用 new Date('YYYY-MM-DD') 设置时刻,坐落不同时区的用户会获取不同的结果,某些情况下会导致意想不到的bug。
  • 运用 new Date('YYYY-MM-DD HH-MM-SS') 或其他官方引荐的日期格局实例化目标,如new Date(DD, MM YYYY),能够防止上述时区问题,创立当时时区下 00:00:00 的 Date 目标。
// 时区为我国
var date1 = new Date('2022-10-24') 
// Mon Oct 24 2022 08:00:00 GMT+0800 (我国规范时刻)
// 时区为美国
var date2 = new Date('2022-10-24') 
// Sun Oct 23 2022 17:00:00 GMT-0700 (北美太平洋夏令时刻)
// 运用 new Date('YYYY-MM-DD HH-MM-SS') 能够创立当时时区的 Date 目标
var date3 = new Date('2022-10-24 00:00:00')
// Mon Oct 24 2022 00:00:00 GMT+0800 (我国规范时刻)
// 传入格局为'DD, MM YYYY'的日期
var date4 = new Date('10, 24 2022')
// Mon Oct 24 2022 00:00:00 GMT+0800 (我国规范时刻)

3.2 Safari 的兼容性问题

Safari 只支撑 YYYY/MM/DDMM/DD/YYYYMMMM DD, YYYY 格局的日期,运用 new Date('YYYY-MM-DD') 会报错。

3.3 无法解析或展示特定格局的日期

特定格局日期的解析需求凭借正则表达式来完结。

// 经过正则表达式解析
const datePattern = /^(\d{2})-(\d{2})-(\d{4})$/;
const [, month, day, year] = datePattern.exec('10-24-2022');
new Date(`${month}, ${day} ${year}`);

4 干流时刻库

4.1 MomentJS

  • 彻底解决解析问题和格局化问题
const date1 = moment('2022-10-24');
console.log(date1.format())   // 2022-10-24T00:00:00+08:00
console.log(date1.toArray())  // [2022, 9, 24, 0, 0, 0, 0],注:月份的开始数为0
console.log(date1.toJSON())   // 2022-10-23T16:00:00.000Z
  • 适配多种甚至自定义的格局写法
const date2 = moment('10/24/2022', 'MM/DD/YYYY');
const date3 = moment('2022-10-24-4-30', 'YYYY-MM-DD-HH-mm');
  • 包体积大:MomentJS 包体积非常庞大,接近 300kb,且基于 OOP(Object Oriented Programming)的设计需求先引进 moment 目标,再运用目标中的办法,导致无法经过 tree-shaking 压缩体积,引证后会打包一切办法,简单引发首屏加载的功用问题。
  • 时刻目标是可变的(mutable):对时刻目标的核算操作会改动目标自身,一般需求复制后操作。
  const startDate = moment(); // Sun Oct 23 2022 23:11:34 GMT+0800   const endDate = startDate.add(1, 'year'); // Mon Oct 23 2023 23:11:34 GMT+0800
  console.log(startDate === endDate);   // true

现在 moment.js 因为前史包袱晋级困难, 加上更好的替代品出现,现已中止保护。关于深度运用 moment.js 但希望替换时刻库的项目,能够安装 eslint-plugin-you-dont-need-momentjs 来帮助晋级。装备方式如下:

// package.json
"extends" : ["plugin:you-dont-need-momentjs/recommended"]

4.2 DayJS

是 Moment.js 的轻量化计划,具有相同强大的 API,但包体积只要 6.5KB。

  • 不可变(Immutable)

  • 体积小。为了减小体积,day.js 将一些复杂功用抽离到插件中,运用时需额外引进

  • 具有和 MomentJS 相同的 API,搬迁成本低。但搬迁时需注意:

    • 注1:涉及到更改时刻目标的操作,不能简单地替换。
    • import moment from "moment";
      const timeEntity = moment();
      timeEntity.add(1, "d"); // 天数加1
      import dayjs from "dayjs";
      const timeEntity = moment();
      timeEntity = timeEntity.add(1, "d"); // 天数加1
      
  • 假如项目中大量依靠此类逻辑的话,Day.js 插件提供了适配计划用,虽然官方并不引荐。
  • var badMutable = require('dayjs/plugin/badMutable')
    dayjs.extend(badMutable) // with BadMutable plugin  const today = dayjs()
    today.add(1, 'day') // immutable
    
  • 注2:一些特别功用需求经过插件额外引进,并进行装备。
  • import dayjs from "dayjs";
    import dayOfYear from "dayjs/plugin/dayOfYear";
    import objectSupport from "dayjs/plugin/objectSupport";
    // 装备插件
    dayjs.extend(dayOfYear);
    dayjs.extend(objectSupport);
    export default dayjs;
    

4.3 Date-fns

Date-fns 的 API 是基于 FP(Functional Programming)的,能够按需导入函数。

  • 不可变(Immutable)
  • 函数导入,但相比目标导入,调用不够灵活,每个东西函数都要从指定途径引进。
export addDays from 'date-fns/addDays/index.js'
const newDate = addDays(new Date(), 7);

优化:将需求用到的东西函数引进到模块文件中,再对外暴露办法。

// custom-date-fns.js
export { default as add } from 'date-fns/add/index.js'
export { default as addBusinessDays } from 'date-fns/addBusinessDays/index.js'
export { default as addDays } from 'date-fns/addDays/index.js'
export { default as addHours } from 'date-fns/addHours/index.js'
  • 支撑 tree-shaking,某些情况下具有更小的引进体积

    • add 操作,2KB
    • 主流时间库横向对比
    • format + add 操作,24.1KB
    • 主流时间库横向对比
    • parse 解析日期字符串,98.5KB
    • 主流时间库横向对比

4.4 横向对比

主流时间库横向对比

主流时间库横向对比

主流时间库横向对比

5 总结

  • Native Date 无法直接解析自定义格局的时刻字符串,且简单引进时区问题。不引荐
  • Moment.js 包体积过大,且时刻目标存在 mutable 问题,源代码也早已中止保护。不引荐
  • Day.js 克服了 moment.js 的缺点,且 api 与 moment.js 高度符合,从 moment.js 搬迁成本低。可是部分功用需求经过插件引进。引荐
  • Date-fns 相同克服了 moment.js 的缺点,并支撑 tree-shaking,独自运用某些功时,引进的包体积甚至小于 day.js。但需求从目标目录导入所需的东西函数,上手难度大。在引进了多种东西函数或涉及解析时刻字符串时,还会导致包体积过大。引荐存在轻度需求时运用。

参加咱们

扫码发现职位 & 投递简历:

主流时间库横向对比

官网投递:job.toutiao.com/s/FyL7DRg