大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

作者:jjjona0215

https://juejin.cn/post/7151050708094189582

前言

本文将简要介绍前端常用日期处理库官方停止维护的moment.js无缝代替moment.js的day.js逐渐流行的date-fns,最后基于date-fns封装常用日期处理的utils

2022年了,如果项目中有用moment.js的可以用day.js代替减少体积做优化,新项目可以直接选择date-fns

本文所有的utils均在github[1]实现

大纲

  • 常用日期处理库

    • 官方停止维护的moment.js

    • 无缝代替moment.js的day.js

    • 逐渐流行的date-fns

  • 基于date-fns封装utils

一、常用日期处理库

1.1 官方停止维护的moment.js

github:https://github.com/moment/moment

moment.js是一个大而全的时间日期库,极大方便了我们在 JavaScript 中计算时间和日期,每周下载量超过 1200 万,已成功用于数百万个项目中。

但是,作为一个诞生于2011年的元老级明星项目,以现在的眼光来看moment.js并非完美无缺,官方总结了两大问题:

(1) 可变对象moment对象是可变对象(mutable),简单点说,任何时间上的加减等计算都改变了其本身。这种设计让代码变得十分不可控,而且很容易带来各种隐蔽且难以调试的 bug。以至于我们在每步修改之前,都要先调用 .clone 克隆一次才能放心操作。

(2)包体积过大因为Momnet.js将全部的功能和所有支持的语言都打到一个包里,包的大小也是到了 280.9 kB 这样一个夸张的数字,而且对于Tree shaking无效。如果要使用时区相关的功能,包体积更是有 467.6 kB 的大小。简单点说,我们可能只需要一个 .format 格式化时间的方法,用户就需要加载数百 kB 的库,这是十分不划算的。

2020年9月,moment.js官方宣布停止开发,进入维护状态(如下图),后续不会再为其增加新功能,并建议新项目不要使用moment.js,推荐使用更现代的库或JavaScript目前的实验性提案Temporal。moment团队提供的替代方案包括:LuxonDay.jsdate-fnsjs-Joda。他们还说,希望未来有一天能够完全不需要JavaScript的日期和时间库,而是使用语言本身的功能。所以他们还推荐了尚处于实验性阶段的Temporal

所以2022年了,当我们在技术选型的时候,可以果断抛弃moment.js

1.2 无缝代替moment.js的day.js

github:https://github.com/iamkun/dayjs

day.js基本用法如下:

dayjs().startOf('month').add(1, 'day').set('year', 2018).format('YYYY-MM-DD HH:mm:ss');

(1)和moment.js相同的API和moment.js相同的API、相同的链式操作。

(2)不可变上面提到,可变性是使用momentJs时最大的问题之一。在day.js完全消除了这个问题,它支持不变性。

(3)体积小Day.js 虽然仅有 2kb 大小,但是功能一点都没有阉割,包含了时间处理的全部常用方法。

1.3 逐渐流行的date-fns

github:https://github.com/date-fns/date-fns

(1)模块化、按需引入date-fns库包含多个函数,有200多种功能,适用于几乎所有场合。并且是模块化的,可以根据需要单独导入这些函数。适用于webpackBrowserifyRollup,还支持 tree-shaking。例如,如果要计算2个日期之间的差值,只需要导入formatDistancesubDays函数。

import { formatDistance, subDays } from ‘date-fns’formatDistance(subDays(new Date(), 3), new Date())

(2)不可变上面提到,可变性是使用 momentJs 时最大的问题之一。在date-fns完全消除了这个问题,它支持不变性。

const startDate = new Date();
const endDate = add(startDate, {years: 2});
console.log(startDate) // 2022-03-13T13:39:07+01:00
console.log(endDate)   // 2024-03-13T13:39:07+01:00

(3) 同时支持 Flow 和 TypeScript

二、基于date-fns封装utils

为什么要封装?一般library都是提供了基础实现

1、日期这种需要跟业务的UI规范结合起来
比如我们默认的日期格式是yyyy-MM-dd HH:mm,而不是yyyy/MM/dd HH:mm:ss
比如日期间隔的默认格式是为yyyy/MM/dd - yyyy/MM/dd,而不是yyyy-MM-dd ~ yyyy-MM-dd等,提供一个日期间隔的utils,而不是每次自己拼装
 ...
在封装的时候写好默认格式(其实真正考虑多语言场景是不能写死格式的,因为不同国家日期的表达其实是不一样的,后面解决了这个问题,再写文章补充吧),在使用的时候就可以避免不同的人写出来的格式不一样

2、日期涉及到多语言
比如时长:22小时17分钟1秒22h17m1s22시간17분1초
比如多久之前:5 天前5 days ago
...
date-fns提供的多语言不一定能百分百契合,在封装的时候处理好

2.1 getTimestamp

获取当前时间或者某个时间的秒级时间戳

import { getUnixTime } from 'date-fns';/*** @author zxyue25* @desc 获取当前时间或者某个时间的秒级时间戳;* 如果入参是毫秒秒级时间戳(13位),则去除最后三位1000返回毫秒级(13位)时间戳;主要场景在前端需要入参拿秒级时间戳给后端作为入参* @param date - Date | Number* @returns 返回格式化后的秒级时间戳 - Number* @example* ```* getTimestamp() // 1658320260* getTimestamp(new Date()) // 1658320260* getTimestamp(new Date().getTime()) // 1658320260* getTimestamp(Date.parse(new Date())) // 1658320260* getTimestamp(1658312707) // 1658312* getTimestamp(1) // 0* ```*/export const getTimestamp = (date: number | Date = new Date()): number => getUnixTime(date);

2.2 getMilliTimestamp

获取当前时间或者某个时间的毫秒级时间戳

import { getTime } from 'date-fns';/*** @author zxyue25* @desc 获取当前时间或者某个时间的毫秒级时间戳;* 如果入参是秒级时间戳(10位),则乘以1000返回毫秒级(13位)时间戳;主要场景在server返回了秒级时间戳,前端先乘以1000转换成日期展示* TODO:这里传入了其他数字怎么处理?直接返回还是补齐13位返回?date-fns是直接返回,建议直接返回* @param date - Date | Number* @returns 返回格式化后的毫秒级时间戳 - Number* @example* ```* getMilliTimestamp() // 1658320372160* getMilliTimestamp(new Date()) // 1658320372160* getMilliTimestamp(new Date().getTime()) // 1658320372160* getMilliTimestamp(Date.parse(new Date())) // 1658320372000* getMilliTimestamp(1658312707) // 1658312707000* getMilliTimestamp(1) // 1* ```*/
export const getMilliTimestamp = (date?: number | Date): number => {if (!date) {return getTime(new Date());} else {if (date instanceof Date) {return getTime(date);} else {if (date.toString().length === 10) {return getTime(date * 1000);} else {return date;}}}
};

2.3 formatDate

将时间戳转换为指定格式的日期;入参可以是秒级时间戳、毫秒级时间戳;

import { format } from 'date-fns';/*** @author zxyue25* @desc 将时间戳转换为指定格式的日期;入参可以是秒级时间戳、毫秒级时间戳;* 如果入参是秒级时间戳(10位),会乘以1000转换;格式默认为'yyyy-MM-dd HH:mm';* 主要场景:通常情况下时间戳转换日期不传格式,而是用默认格式;后端返回的时间戳一般是秒级时间戳,如果直接用date-fns需要自己乘1000传入* @param date - Date | Number* @param formatStr - String* @returns 返回格式化后的秒级时间戳 - Number* @example* ```* formatDate(1658320372161) // 2022-07-20 20:32* format(1658320372, 'yyyy-MM-dd HH:mm') // 1970-01-20 12:38* formatDate(1658320372) // 2022-07-20 20:32* formatDate(new Date()) //  2022-07-21 11:28* formatDate(1658320372000, 'yyyy/MM/dd HH:mm:ss') // 2022/07/20 20:32:52* ```*/export const formatDate = (date: number | Date, formatStr = 'yyyy-MM-dd HH:mm') => {if (typeof date === 'number' && date.toString().length === 10) {return format(date * 1000, formatStr);} else {return format(date, formatStr);}
};

2.4 formatDateRange

将两个时间戳或者Date日期转换为指定格式的日期,并用指定连接符连接;

import { formatDate } from './formatDate';/*** @author zxyue25* @desc 将两个时间戳或者Date日期转换为指定格式的日期,并用指定连接符连接;* 入参可以是秒级时间戳、毫秒级时间戳或者Date日期;如果入参是秒级时间戳(10位),会乘以1000转换;* 格式默认为`${yyyy/MM/dd} - `${yyyy/MM/dd}';如果想更改格式,可传入第三个参数(日期的格式),第四个参数(连接符的格式)* @param startDate - Date | Number* @param endDate - Date | Number* @param formatStr - String 默认格式'yyyy/MM/dd'* @param joinStr - String 默认连接符'-'* @returns 返回格式化后的日期区间字符串* @example* ```* formatDateRange(1658320372161, 1658717927699) // 2022/07/20 - 2022/07/25* formatDateRange(1658320372, 1658717927) // 2022/07/20 - 2022/07/25* formatDateRange(1658320372, 1658717927, '', '~') // 2022/07/20 ~ 2022/07/25* formatDateRange(1658320372, 1658717927, 'yyyy/MM/dd HH:mm', '~') // 2022/07/20 20:32 ~ 2022/07/25 10:58* ```*/export const formatDateRange = (startDate: number | Date,endDate: number | Date,formatStr = 'yyyy/MM/dd',joinStr = '-',
) => `${formatDate(startDate, formatStr || 'yyyy/MM/dd')} ${joinStr} ${formatDate(endDate, formatStr || 'yyyy/MM/dd')}`;

2.5 formatDateDistance

获取指定时间距离当前时间或者指定时间多远

import { formatDistance } from 'date-fns';import { getMilliTimestamp } from './getMilliTimestamp';
import { LANGUAGE_DATE_FNS_MAP } from './locale';/*** @author zxyue25* @desc 获取指定时间距离当前时间或者指定时间多远;* @param date - Date | Number* @param baseDate - Date | Number,默认为当前时间* @param options - 扩展项,可以配置语言,有两种方式:传入语言类型lang;或者直接传入Locale;* @returns 返回描述“指定时间距离当前时间或者指定时间多远”的字符串,有多语言处理* @example* ```* formatDateDistance(1658320372161, 1658717927699, { lang: 'zh-CN' }) // 5 天前* formatDateDistance(1658320372161, 1658717927699, { lang: 'zh-CN', addSuffix: 'false' }) // 5 天* formatDateDistance(1658320372, 1658717927) // 5 days ago'* formatDateDistance(new Date('2022-07-12'), new Date('2022-07-17')) // 5 days ago* formatDateDistance(new Date('2022-07-05'), new Date('2022-07-12')) // 7 days ago* formatDateDistance(new Date('2022-06-12'), new Date('2022-07-12')) // about 1 month ago* formatDateDistance(new Date('2021-07-12'), new Date('2022-07-12')) // about 1 year ago* formatDateDistance(1658320372, 1658717927, { locale: ko }) // 5일 전* ```*/type OptionType = {locale?: Locale;addSuffix?: boolean;lang?: keyof typeof LANGUAGE_DATE_FNS_MAP;
};export const formatDateDistance = (date: Date | number = 0,baseDate: Date | number = new Date(),options?: OptionType,
): string => {const initOptions = {addSuffix: options?.addSuffix || true,locale: options?.locale || LANGUAGE_DATE_FNS_MAP[options?.lang || 'en'],...options,};if ((typeof date === 'number' && date.toString().length === 10) ||(typeof baseDate === 'number' && baseDate.toString().length === 10)) {return formatDistance(getMilliTimestamp(date), getMilliTimestamp(baseDate), initOptions);}return formatDistance(date, baseDate, initOptions);
};

2.6 formatDateDuration

将指定秒转为‘H小时M分钟S秒’,H、M、S为0时,默认不展示;如果想更改格式可传入第二个扩展参数options

import { formatDuration } from 'date-fns';import { LANGUAGE_DATE_FNS_MAP } from './locale';/*** @author zxyue25* @desc 将指定秒转为‘H小时M分钟S秒’,H、M、S为0时,默认不展示;如果想更改格式可传入第二个扩展参数options* @param second - Number,多少秒* @param options - 扩展项,可以配置语言,有两种方式:传入语言类型lang;或者直接传入Locale;* @returns 返回描述“H小时M分钟S秒”的字符串,有多语言处理* @example* ```* formatDateDuration(71) // 1minute11seconds* formatDateDuration(71, { lang: 'zh-CN' }) // 1分钟11秒* formatDateDuration(3604, { lang: 'zh-CN' }) // 1小时4秒* formatDateDuration(80221, { lang: 'zh-CN' }) // 22小时17分钟1秒* formatDateDuration(80221, { locale: ko }) // 22시간17분1초* formatDateDuration(80221, { lang: 'zh-CN', delimiter: ',' }) // 22小时,17分钟,1秒* formatDateDuration(80221, { lang: 'zh-CN', format: ['hours', 'minutes'] }) // 22小时17分钟* formatDateDuration(80220, { lang: 'zh-CN' }) // 22小时17分钟* formatDateDuration(80220, { lang: 'zh-CN', zero: true }) // 22小时17分钟0秒* formatDateDuration(880220, { lang: 'zh-CN' }) // 244小时30分钟20秒* ```*/type OptionType = {locale?: Locale;zero?: boolean;delimiter?: string;format?: Array<string>;lang?: keyof typeof LANGUAGE_DATE_FNS_MAP;
};export const formatDateDuration = (second: number, options?: OptionType) => {const hours = Math.floor(second / 3600);const minutes = Math.floor((second % 3600) / 60);const seconds = Math.floor(second % 60);return formatDuration({hours,minutes,seconds,},{zero: options?.zero || false,locale: options?.locale || LANGUAGE_DATE_FNS_MAP[options?.lang || 'en'],...options,},).replace(/ /g, '');
};

最后

日期处理是前端必定会遇到的问题,本文也只是一部分场景的封装,后续工作中遇到其他utils,也会继续封装在github[1]

且遇到一个不好解决的问题是:不同国家的日期格式其实是不一样的,因为工作变动暂时不会关注这部分了,先记下来,后面有机会解决这个问题再写最终的解决方案

参考资料

[1]

github: https://github.com/zxyue25/date-fns-utils.git

Node 社群

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

 “分享、点赞、在看” 支持一波

抛弃 moment.js,基于 date-fns 封装日期相关 utils相关推荐

  1. 【Web技术】1518- 抛弃 moment.js,基于 date-fns 封装日期相关 utils

    作者:jjjona0215 https://juejin.cn/post/7151050708094189582 前言 本文将简要介绍前端常用日期处理库:官方停止维护的moment.js,无缝代替mo ...

  2. 原生JS基于window.scrollTo()封装垂直滚动动画工具函数

    概要: 原生JS基于window.scrollTo()封装垂直滚动动画工具函数,可应用与锚点定位.回到顶部等操作. ####封装原因: 在vue项目中,遇到需要实现垂直滚动效果的需求,初步想到的方法有 ...

  3. JS格式化 /Date(xxxxxx)/的日期类型

    //用来转换/Date(xxxxxx)/类型的JSON日期为要求的日期格式字符串 String.prototype._formatJsonDate = function (format) { var ...

  4. js本地存储函数封装基于localStorage本地存储

    函数封装 //基于localStorage本地存储 var store={set:function(name, value, day) { // 设置let d = new Date()let tim ...

  5. R语言基于日期范围筛选数据实战(Subset by a Date Range):日期范围之内的数据、日期范围之外的数据、日期之后的数据、日期之前的数据

    R语言基于日期范围筛选数据实战(Subset by a Date Range):日期范围之内的数据.日期范围之外的数据.日期之后的数据.日期之前的数据 目录 R语言基于日期范围筛选数据实战(Subse ...

  6. JS函数,数组,日期

    函数是JS的一个重点,函数就是代码复用的一种机制或是将代码封装成功能的代码段,函数一共有两种定义方式 函数声明的方式定义一个函数 function + 函数名:一般用单词的动词,例如getMax... ...

  7. layui table reload post请求_基于Layui组件封装的后台模版

    HG框架简介 HG-Layui-UI框架,是基于layui最新版UI搭建的一套通用后台管理框架,借鉴了市面上各大主流框架风格,采用iframe标签页实现,保留了传统开发模式的简单实用性. 为快速开发减 ...

  8. JS中常用方法的封装【转载】

    JS中常用方法的封装[转载] 转载理由:我想这么好的内容,可不能因为作者的博客倒闭了就没了,所以就Fork了一份,并复制了一份到自己的博客. 编写自己的代码库(javascript常用实例的实现与封装 ...

  9. 【JS】WTool.js 基于JQuery的工具集 (写着玩的)

    [JS]WTool.js  基于JQuery的工具集 (写着玩的) // console.info("加载WTool中...")/*** 威工具1.基于Jquery开发2.参考VU ...

最新文章

  1. 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
  2. 最小二乘法MSE 梯度下降法
  3. 企业——memcache对PHP页面的缓存加速优化
  4. 个盘子的汉诺塔需要移动几步_看漫画学C++039:递归解汉诺塔
  5. 前端学习(2039)vue之电商管理系统电商系统之优化运行server的Eslint警告
  6. mysql5.6.25密码_安装压缩版mysql5.6.25/ 5.7.14
  7. 无线路由器挖洞方法大比拼:白盒 or 黑盒?
  8. Spring MVC中的统一异常处理
  9. Linux配置网络与去除可视化界面
  10. 苹果手机透明桌面_微信界面全局透明壁纸设置教程 苹果iOS与安卓手机弄法步骤...
  11. 基于Android系统手机通讯录管理软件的设计与开发
  12. Modelsim与ISE联和仿真错误
  13. 房屋管理系统简单Damo
  14. 互联网日报 | 瑞幸咖啡同意支付1.8亿美元达成和解;国产游戏海外收入首破千亿;滴滴试水信用支付...
  15. 20P44 Premiere预设600个摄像机动画信号干扰调色视觉特效pr模板
  16. 如何解决王者荣耀排位赛中的系统制裁
  17. Table sink ‘default_catalog.default_database.t3‘ doesn‘t support consuming update and delete changes
  18. Firefox(火狐浏览器)常用插件
  19. 科技的成就(二十九)
  20. Vmware虚拟机突然连接不上网络【方案集合】

热门文章

  1. 两分钟打造淘宝抢单机器人
  2. Awstats 日志分析 安装
  3. java 调用ajax_JAVA AJAX调用
  4. c语言案例教学法的教学大纲,案例教学法在C语言教学中的应用.doc
  5. Unity填坑之粒子的ScaleMode
  6. 燃!阿里技术又破世界纪录:机器阅读理解力首次超过人类!
  7. 高标准农田信息化监测系统解决方案
  8. 蓝桥杯2023年真题 python B组
  9. interface与abstract类的区别
  10. java银行业务_java模拟银行存取款业务