【总结】1571- 抛弃 moment.js ,基于 date-fns 封装日期相关utils

news/2024/11/2 14:26:18/

2a65900927f5c2d9f390c80a6146452e.jpeg

转自: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均在 https://github.com/zxyue25/date-fns-utils.git 实现

大纲

  • 常用日期处理库

    • 官方停止维护的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.jsabbb2789ef4095766fa63420da5c42ad.jpeg

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 (https://github.com/zxyue25/date-fns-utils.gi)

往期回顾

#

如何使用 TypeScript 开发 React 函数式组件?

#

11 个需要避免的 React 错误用法

#

6 个 Vue3 开发必备的 VSCode 插件

#

3 款非常实用的 Node.js 版本管理工具

#

6 个你必须明白 Vue3 的 ref 和 reactive 问题

#

6 个意想不到的 JavaScript 问题

#

试着换个角度理解低代码平台设计的本质

808c862212314386d7cea7dbff57c7cf.gif


http://www.ppmy.cn/news/686201.html

相关文章

CVE-2020-15778漏洞修复

漏洞描述 https://access.redhat.com/security/cve/cve-2020-15778 简单来说&#xff0c;就是scp命令是可以注入特殊字段&#xff0c;在目标主机上执行指令的。 原理是因为scp是通过ssh实现的&#xff0c;一样的需要用户名和密码才能登陆。也就是说&#xff0c;这个漏洞其实针对…

梁宁:VisionPro、GPT、Web3三件套齐备,元宇宙开启

本文内容整理自图灵社区对谈栏目直播&#xff0c;主题为 ChatGPT 真需求&#xff0c;从产品的第一性原理解析。 上篇内容回顾&#xff1a;梁宁&#xff1a;为什么中国没有像 ChatGPT 和 Vision Pro 这样的创新产品&#xff1f; 梁宁&#xff0c;产品战略专家&#xff0c;曾任湖…

算法设计与分析 第五次编程作业 1571. 最大流

题目描述 给出一个网络图&#xff0c;及其源点汇点&#xff0c;求出其网络最大流。 输入格式 第一行包含四个正整数 n , m , s , t n,m,s,t n,m,s,t, 分别表示点的个数&#xff0c;有向边的数量&#xff0c;源点序号&#xff0c;汇点序号。 接下来 m m m行每行三个正整数&a…

东北大学OJ-1571: 实验5-13:分段函数(多分支)

东北大学OJ-1571: 实验5-13:分段函数(多分支) 大家好,我叫亓官劼(q guān ji ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客,B站昵称为:亓官劼,地址为亓官劼的B站 本文原创为亓官劼,请大家支持原创,部分平台一直在盗取博主的文…

有哪些工具或者软件堪称神器?

著作权归作者所有。 商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 作者&#xff1a;居里安同学 链接&#xff1a;http://www.zhihu.com/question/36546814/answer/70322446 来源&#xff1a;知乎 直接搬运我在另一个问题的答案了&#xff1a;有哪些工具或者软…

markdown基本语法 【未修改完成!】

目录 Markdown 简介Markdown 编辑器推荐 在线版 1. dillinger2. StackEdit3. MaHua4. 简书5. 马克飞象windows 1. MarkdownPad2. MarkPad3. Smark4. MiuOSX 1. Mou2. MacDown3. Ulysses4. iA Writer5. MWeb跨平台 1. Cmd Markdown2. 小书匠编辑器3. FarBox4. Sublime Text 25. …

【Linux入门学习之】Ubuntu常用软件

【Linux入门学习之】Ubuntu常用软件 速配指南之软件参考 本文定位&#xff1a;作为速配指南的补充&#xff0c;列出国内用户比较常用的软件。请将论坛软件推荐版块的内容逐步转移至wiki&#xff0c;而非本文。 本文作用&#xff1a;为新手指明软件的方向&#xff0c;也可供已…

Android手机开发总结

导读&#xff1a;对于Android开发者来说&#xff0c;成系列的技术文章对他们的技术成长帮助最大。如下是我们向您强烈推荐的主题为Android开发的第一个系列文章。 《Android核心分析》整理如下&#xff1a; 1. 方法论探讨之设计意图 为什么要研究Android&#xff0c;是因为它…