dayjs如何实现的日期格式化、多语言?

您所在的位置:网站首页 怎么把qq设置无性别显示 dayjs如何实现的日期格式化、多语言?

dayjs如何实现的日期格式化、多语言?

2023-06-05 05:47| 来源: 网络整理| 查看: 265

前言

在平时开发的过程中,肯定会遇到需要操作日期、比较日期、格式化日期的情况的。 为了满足以上的需求,出现了很多的日期JS库,moment、dayjs、date-fns等,这些都是比较流行的日期库。

但是moment已经停止维护, 而dayjs本身的api与moment基本相同, 因此可以比较方便的学习和迁移。

并且dayjs本身就具有轻量级、国际化、日期操作无副作用(即日期操作相关api都将返回新的实例)的特点,能够满足大部分的日期相关需求。

因此今天来看看dayjs是怎么实现的。

本文为了顾及不同水平的读者,因此写得可能会有点啰嗦,不过整体实现比较简单, 相信大部分读者都能很容易的理解。

dayjs 基本使用

在分析dayjs源码前,知道怎么使用dayjs能够更好的理解后续的内容。 因此为了方便没使用过dayjs的读者,这里列出了一些基本的使用例子。

1.解析ISO时间格式,然后格式化为YYYY/MM/DD形式

const dayjs = require('dayjs') // 输出: 2023/01/12 dayjs('2023-01-12T08:00:00.000Z').format('YYYY/MM/DD')

2.解析时间戳, 然后格式化为YYYY-MM-DD形式

const dayjs = require('dayjs') // 输出:2023-01-12 dayjs(1673514666940).format('YYYY-MM-DD')

3.解析当前时间, 然后格式化为YYYY-MM-DD形式

const dayjs = require('dayjs') dayjs().format('YYYY-MM-DD')

format方法传入格式化的占位符,format执行时,会将对应占位符替换为对应的数值。支持的占位符有:

标识示例描述YY23年,两位数YYYY2023年,四位数M1-12月,从1开始MM01-12月,两位数MMMJan-Dec月,英文缩写MMMMJanuary-December月,英文全称D1-31日DD01-31日,两位数d0-6一周中的一天,星期天是 0ddSu-Sa最简写的星期几dddSun-Sat简写的星期几ddddSunday-Saturday星期几,英文全称H0-23小时HH00-23小时,两位数h1-12小时, 12 小时制hh01-12小时, 12 小时制, 两位数m0-59分钟mm00-59分钟,两位数s0-59秒ss00-59秒,两位数S0-9毫秒(十),一位数SS00-99毫秒(百),两位数SSS000-999毫秒,三位数Z-05:00UTC 的偏移量,±HH:mmZZ-0500UTC 的偏移量,±HHmmAAM / PM上/下午,大写aam / pm上/下午,小写 dayjs 源码分析

从上面的示例中,可以看到使用dayjs分为两步: 1.通过dayjs函数创建dayjs实例。 2.调用实例的format方法进行日期的格式化。

我们先从第一步开始,分析实例的创建过程中,都做了什么事情。

dayjs 源码分析-创建对象实例 var dayjs = function dayjs(date, c) { // 省略部分非核心代码 /** * 初始化Dayjs类的构造函数中需要的配置参数cfg。 * 将date添加到cfg配置参数后, 传递给Dayjs类的构造函数创建Dayjs实例。 */ var cfg = typeof c === 'object' ? c : {}; cfg.date = date; cfg.args = arguments; return new Dayjs(cfg); };

以上代码中,主要就是通过Dayjs类创建dayjs实例, 然后直接返回。 Dayjs类的构造函数实现如下:

function Dayjs(cfg) { // 省略部分非核心代码实现 this.parse(cfg); } Dayjs.prototype.parse = function parse(cfg) { /** * 第一步:将传入的日期参数解析为js的Date对象。 * 因为传入的日期参数存在多种类型, 如时间戳、日期格式字符串、原生Date实例等不同格式, * 因此parseDate函数内部处理各种不同类型的参数,然后返回Date对象。 * 稍后再分析parseDate函数 */ this.$d = parseDate(cfg); /** * 第二步:通过parseDate获取到的Date实例, 将年月日时分秒等信息初始化到dayjs实例中。 */ this.init(); }; // 将年月日时分秒等信息初始化到dayjs实例中 Dayjs.prototype.init = function init() { var $d = this.$d; this.$y = $d.getFullYear(); this.$M = $d.getMonth(); this.$D = $d.getDate(); this.$W = $d.getDay(); this.$H = $d.getHours(); this.$m = $d.getMinutes(); this.$s = $d.getSeconds(); this.$ms = $d.getMilliseconds(); }

parseDate函数实现如下

// 处理执行dayjs函数时的日期参数。 function parseDate(cfg) { var date = cfg.date, utc = cfg.utc; /** * 传入null时, 返回无效日期。 * 如: dayjs(null) */ if (date === null) return new Date(NaN); /** * 传空时, 返回当前日期。 * 如: dayjs()。 * Utils.u函数功能:判断date是否为undefined */ if (Utils.u(date)) return new Date(); /** * 传递原生Date日期实例, 创建新的Date实例然后返回(避免直接操作原数据源可能导致的副作用影响)。 * 如: dayjs(new Date(1318781876406))。 * Utils.u函数功能:判断date是否为undefined */ if (date instanceof Date) return new Date(date); /** * 传递日期格式字符串时 * 如:dayjs('2023-01-12T08:00:00.000') 或 dayjs('2023/01/12T08:00:00.000') * */ if (typeof date === 'string' && !/Z$/i.test(date)) { /** * 匹配类似‘2023-01-12T08:00:00.000’和‘2023/01/12T08:00:00.000’的日期格式。 * 因为浏览器不支持2023/01/12T08:00:00.000斜杠形式的日期格式, * 因此dayjs中对斜杠形式的日期做了兼容处理。 * 不同的dayjs版本的正则不同。 * 旧版本可能不匹配类似‘2023/01/12T08:00:00.000’斜杠的日期格式 */ var d = date.match(/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/); if (d) { // 获取月份数值, Date中的月份从0开始, 因此需要减去1 var m = d[2] - 1 || 0; // 获取毫秒数值 var ms = (d[7] || '0').substring(0, 3); /** * 处理utc时间。 * 如: dayjs('2023-01-12T08:00:00.000', { utc: true })。 * utc时间为世界标准时间, 与我们的本地时间存在8个小时时差。 * 更多详情可自行查阅, 这里不懂的话可先跳过utc的处理。 */ if (utc) { return new Date(Date.UTC(d[1], m, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, ms)); } return new Date(d[1], m, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, ms); } } return new Date(date); }

因此执行dayjs函数时, 函数内部主要是通过Dayjs类创建dayjs实例返回。 而Dayjs类的构造函数主要只是做了以下两件事情。 1.执行parseDate,获取Date实例对象。由于执行dayjs时,传入的日期格式存在多种类型, 因此通过parseDate处理日期参数解析为Date实例返回, 并且进行了一些日期格式的兼容处理。 2.将年月日时分秒等信息初始化到dayjs实例中, 供后续使用。

dayjs 源码分析-format方法

format为格式化日期的方法,以下代码为了更容易理解进行了部分的简化和修改,但核心不变。

function format(formatStr) { // 获取当前语言的多语言资源, 稍后分析如何实现的多语言 var locale = this.$locale(); /** * 根据传入的hour小时, 返回当前时刻处于上午或下午 * hour小于12则返回上午, 大于12返回下午 * * AM: 表示上午 * PM: 表示下午 * * 可通过多语言中的locale.meridiem自定义逻辑(如中文时返回‘上午’、‘下午’, 而不是‘AM’、‘PM’) */ var meridiemFunc = locale.meridiem || function (hour, isLowercase) { var m = hour /** * 额外补充, 此处不感兴趣可跳过。 * 如执行:dayjs('2023-01-12T08:00:00.000').format('[YYYYescape] YYYY-MM-DD')。 * 中括号里的占位符不会被替换为对应数值。 * 因此输出的结果为: 'YYYYescape 2023-01-12' * * 而该特性的实现就是通过正则中的第一段'\[([^\]]+)]', 该段正则匹配中括号的内容。 * 而$1则是中括号的内容, 因此会直接返回, 不会替换为对应matches中的数值 */ return $1 || matches[match]; }); /** * 当string的长度不足length时, 在开头以pad作为填充字符填充不足的位置 * 如: padStart(1, 2, 0), * 字符1的字符数量不足2,因此用0补充 * 因此输出:01 */ function padStart(string, length, pad) { var s = String(string); if (!s || s.length >= length) return string; return "" + Array(length + 1 - s.length).join(pad) + string; }; /** * 获取arr中的index位置元素 * 如果arr不存在, 则获取full中的index位置元素, 并且截取该元素的length长度 */ function getShort(arr, index, full, length) { return (arr && arr[index]) || full[index].slice(0, length); }; }

以上代码主要分为两步: 1.获取不同日期占位符的数值到matches集合中。 2.通过正则匹配不同的日期占位符, 然后将占位符替换为matches中对应的数值。

dayjs 源码分析-多语言实现

在分析format方法的时候, 我们看到了format方法执行过程中,调用了$locale方法获取当前语言的多语言资源。那么现在,我们来看看$locale方法是怎么实现的。

// 默认加载英语的语言包 const en = require('./locale/en.js'); // 默认的语言语言环境。(创建dayjs实例的时候,如果未传递语言环境,则使用该默认的设置) var L = 'en'; // 语言环境的语言包集合 var Ls = {}; // 将英语语言包添加到语言包集合中 Ls[L] = en; function $locale() { return Ls[this.$L]; };

Ls为所有语言环境的语言包集合,Ls内默认只存在英语的语言包。 $L为当前实例的语言环境,默认en。 而$locale方法只是根据当前语言,从语言包集合中返回对应的语言包。

知道了Ls为语言包集合,$L为当前实例的语言环境。下一步我们还得知道如何扩展Ls中的语言包,以及$L语言环境是在什么时候设置、怎么改变的。

我们先看下$L是在什么时候设置的。在分析Dayjs类的构造函数的时候, 其实我省略了初始化多语言的处理。

更完整的Dayjs类的构造函数如下:

function Dayjs(cfg) { /** * 初始化当前的多语言环境。 * 如果外部传递了多语言环境,则使用外部传递的, 如dayjs('2023-01-12T08:00:00.000', { locale: 'zh' }) * 否则返回默认的‘en’ * 稍后详情分析parseLocale */ this.$L = parseLocale(cfg.locale, null, true); // 该函数在上面有分析过 this.parse(cfg); }

以上只是在创建dayjs实例时初始化的多语言环境, 但如果是执行过程中,想改变多语言环境,可以有两种方式: 1.全局改变, 即以后所有创建的dayjs实例都会使用改变后的多语言环境。

// 全局改变, 直接调用dayjs函数上的locale方法 dayjs.locale('zh')

然后我们看下全局的locale方法怎么实现, 其实全局的locale方法就是等于Dayjs构造函数时调用的parseLocale,我们等会再分析parseLocale。

dayjs.locale = parseLocale;

2.只改变某个dayjs实例的多语言环境。

// 调用dayjs获取dajys实例后, 再调用实例的locale方法 const dayjs1 = dayjs().locale('zh')

再看下实例上的locale方法怎么实现。

Dayjs.prototype.locale = function locale(preset, object) { // 省略部分非核心代码 /* * 第一步 * 复制新的dayjs实例(因此设置的多语言环境,影响的是复制出的新实例,而非当前实例) */ var that = this.clone(); /* * 第二步(重点) * 其实还是通过调用parseLocale进行多语言的处理, 只是第三个参数设置为true, 稍后分析parseLocale */ var nextLocaleName = parseLocale(preset, object, true); /* * 第三步 * 将parseLocale返回的多语言环境设置到当前实例上 */ if (nextLocaleName) that.$L = nextLocaleName; /* * 第四步 * 返回新实例 */ return that; };

看了全局设置和局部设置的实现后, 可以看到最核心的还是parseLocale。 那么现在,我们就来看看parseLocale做了什么事情。

// 处理语言环境或设置语言包资源 function parseLocale(preset, object, isLocal) { // 需要返回的语言环境结果。 var l; /* * 第一步 * 未传递多语言环境时, 则返回全局默认的多语言 */ if (!preset) return L; /* * 第二步 * 处理语言环境、添加语言包到语言包集合中 */ if (typeof preset === 'string') { var presetLower = preset.toLowerCase(); // 如果传递了多语言环境, 还需要在当前语言包集合中判断是否存在该语言环境的多语言, 存在才返回该语言环境 if (Ls[presetLower]) { l = presetLower; } /** * 存在object则表示为语言环境设置对应的语言包 * * 如 parseLocale('zh', { 多语言数据 }) */ if (object) { Ls[presetLower] = object; // 返回设置的语言环境 l = presetLower; } /** * 额外处理, 向下兼容语言包。 * * 如设置语言环境parseLocale('zh-cn')时, * 语言包中不存在'zh-cn', 那么将回退为使用‘zh’ */ var presetSplit = preset.split('-'); if (!l && presetSplit.length > 1) { return parseLocale(presetSplit[0]); } } else { /** * preset对象, 则对象中必须存在name属性. * name为语言 * preset则为该语言对应的语言包数据 */ var name = preset.name; Ls[name] = preset; l = name; } /* * 第三步 * 处理是否全局设置 * isLocal默认为false, 即默认是全局设置 * 如果是全局,则将语言环境设置到全局的L变量中 * 那么后续创建dayjs实例时,则会返回全局L变量的语言(第一步中未传自定义语言环境,直接返回全局L) */ if (!isLocal && l) L = l; // 将语言结果l返回, 或语言结果l不存在, 且是全局时,则返回全局L变量的语言 return l || !isLocal && L; }

通过上面的代码, 其实可以看到,parseLocale中主要做了以下几件事: 1.处理语言环境, 然后返回语言环境。 2.为语言环境在Ls语言包集合中添加语言包。 3.如果是全局设置,则将语言环境设置到全局的L变量中,为后续创建的dayjs实例直接使用全局的L语言。

因为不管是全局的locale还是局部的locale方法, 都是基于parseLocale实现, 也就是locale不仅可以切换多语言环境,还可以为多语言环境指定对应的语言包

有了切换语言环境和添加语言包这两步后, 后续只需要在需要用到多语言的地方调用$locale方法获取当前语言环境的语言包进行使用就可以。

总结

最后来个小总结。

format日期格式化: 1.获取不同的日期格式的占位符数据, 然后将数据添加到matches集合中。 2.通过正则匹配不同的日期占位符,然后将占位符替换为matches集合中的数据。

多语言: 1.创建dayjs实例时,通过parseLocale获取当前的语言环境,然后设置到实例的$L实例中。 2.如果需要拓展多语言资源包, 可通过locale方法为不同的语言设置语言包(locale内调用的也是parseLocale)。 3.有了语言环境、语言包集合, 后续就可以在需要用到多语言的地方调用$locale获取当前语言环境的语言包资源。

最后

除了日期格式化、多语言外,dayjs中比较常用到的还有日期的操作、日期比较的相关的功能,后面会再出一篇继续看看其他常见的功能实现,希望文章的内容对你有所帮助。

最后的最后,你可以从功能的实现、代码的组织、可读性等任何的角度思考下dayjs中做得比较好或者能够优化的地方吗?



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3