news 2026/9/18 10:08:29

Day.js 快速上手与 I18n 本地化实战:2kB 的 Moment.js 替代方案及西班牙语(es)Locale 的源码级解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day.js 快速上手与 I18n 本地化实战:2kB 的 Moment.js 替代方案及西班牙语(es)Locale 的源码级解析

Day.js 快速上手与 I18n 本地化实战:2kB 的 Moment.js 替代方案及西班牙语(es)Locale 的源码级解析

【免费下载链接】dayjs⏰ Day.js 2kB immutable date-time library alternative to Moment.js with the same modern API项目地址: https://gitcode.com/gh_mirrors/da/dayjs

本文基于 Day.js 官方西班牙语入门文档 docs/es-es/README-es-es.md 的核心骨架展开,覆盖安装、核心 API(解析/格式化/查询/变更)、I18n 按需加载与插件扩展四大实战主题,并结合 src/locale/es.js、src/index.js 等仓库源码,讲清「西班牙语本地化在 Day.js 内部究竟是如何注册、回退与生效的」。读完你可以独立完成:用 Day.js 替换 Moment.js 的存量代码、正确加载并使用eslocale、通过插件扩展格式化能力,并理解不可变链式调用的底层实现。

一、Day.js 是什么:定位与核心特性

官方文档对 Day.js 的定义(西班牙语原文转述):Day.js 是一个极简的 JavaScript 日期时间库,负责解析(parse)、校验(validate)、操作(manipulate)和显示(display)日期,面向现代浏览器,并保持了与 Moment.js 几乎完全兼容的 API。如果你用过 Moment.js,就已经会用了 Day.js

其核心特性清单如下(继承自 README 文档):

特性说明
熟悉的 API对 Moment.js 用户友好的 API 与格式 token
不可变(Inmutable)任何变更操作都返回新实例,原实例不变
可链式调用(Encadenable)方法自由串联,如startOf().add().set()
国际化(I18n)支持按语言按需加载 locale
2kB 迷你体积gzip 后约 2kB 的核心库
全浏览器兼容面向所有现代浏览器

「2kB 体积」并非口头宣称,仓库在 package.json 中用size-limit做了硬性约束:

"size-limit": [ { "limit": "2.99 KB", "path": "dayjs.min.js" } ]

即构建产物dayjs.min.js的 gzip 体积被强制限制在 2.99KB 以内,超出则构建失败。这也是「locale 与插件不进默认产物」这一设计在工程上的直接原因。

一个最典型的链式用法,完整体现了「解析 → 起点 → 增量 → 赋值 → 格式化」的工作流:

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

二、安装

按照 Installation 文档 指引(该文档现指向官方站点,仓库内保留说明占位),通过 npm 安装即可:

npm install dayjs --save

--save参数在 npm 5+ 中已为默认行为,命令可简写为npm install dayjs。仓库 package.json 声明了入口与类型文件:"main": "dayjs.min.js""types": "index.d.ts",因此直接import dayjs from 'dayjs'即可获得完整的 TypeScript 类型定义(见 types/index.d.ts)。

三、核心 API:解析、格式化、赋值与查询

文档给出的五类基础能力示例如下,覆盖了日常日期处理的绝大多数场景:

dayjs('2018-08-08') // 解析(parse) dayjs().format('{YYYY} MM-DDTHH:mm:ss SSS [Z] A') // 格式化(format) dayjs() .set('month', 3) .month() // 取值与赋值(get & set) dayjs().add(1, 'year') // 操作(manipulate) dayjs().isBefore(dayjs()) // 查询(query)

3.1 解析:parseDate 到底接受什么输入

在 src/index.js 中,parseDate是解析逻辑的入口,其行为可以概括为四条规则:

  1. null→ 直接构造new Date(NaN),即无效日期;
  2. 参数缺省 → 取当前时间new Date()
  3. Date实例 → 直接拷贝构造;
  4. 字符串 → 先尝试用REGEX_PARSE正则匹配,匹配成功时按「本地时区」拆字段构造,避免把"2018-08-08"误判为 UTC。

正则定义在 src/constant.js:

export const REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/

可以推断,该正则对分隔符做了宽松处理(-/均可,甚至可省略),同时兼容T与空格分隔的日期时间。此外,源码中对以Z结尾的字符串(!/Z$/i.test(date))跳过快速路径、直接交给原生Date解析——这类 ISO 字符串本身带时区信息,交给原生引擎更稳妥。

3.2 格式化:token 解析与 locale 的协作

format()的实现在 src/index.js,关键机制有三点:

  • 默认格式:不传参时使用 src/constant.js 中的FORMAT_DEFAULT = 'YYYY-MM-DDTHH:mm:ssZ'
  • 占位符识别:通过REGEX_FORMAT(src/constant.js)匹配 token,方括号内容[...]视为字面量原样输出,因此文档示例中的{YYYY}里的花括号、[Z]都是字面量;
  • 月份/星期名称来自 localeMMMMMMMddddddddd等 token 会读取当前实例的 locale 对象(this.$locale())中的monthsweekdays数组。这正是 I18n 生效的位置——同一串格式 token,在enes下输出不同的月份名。

源码中的取值优先级(可参考getShort函数,src/index.js)是:locale 提供的缩写/全称数组 → 若缺失则回退为英文全称截取(full[index].slice(0, length))。

3.3 赋值、变更与查询的不可变性保证

  • get & setset(string, int)在 src/index.js 中实现为return this.clone().$set(string, int)——先克隆再修改,这就是「不可变 + 可链式」两个特性在源码层的直接证据。值得注意的是月份/年份的 set 做了边界保护:先跳到当月 1 号再改月/年,最后用Math.min(this.$D, date.daysInMonth())收敛日期,避免「1 月 31 日改到 2 月」产生溢出(src/index.js)。
  • manipulateadd(number, units)(src/index.js)对月/年单位走set通道(处理了 2/31 日这类跨月边界),对天/小时/分钟/秒则按毫秒步长累加;subtract只是add(number * -1)的别名。
  • queryisBefore的实现是this.endOf(units) < dayjs(that)(src/index.js),isAfter对称使用startOfunits缺省时比较到毫秒级。

四、I18n:从「按需加载」到 es locale 的完整内部机制

这是文档中与es-es这一语言目录强相关的核心章节。官方示例:

import 'dayjs/locale/es' // 按需加载(carga bajo demanda) dayjs.locale('es') // 全局使用西班牙语区域设置 dayjs('2018-05-05') .locale('zh-cn') .format() // 在具体实例上改用简体中文

三点关键语义:locale 不会进入默认构建产物(配合 size-limit 约束实现 2kB);dayjs.locale('es')全局切换;实例级.locale(...)只影响该实例,且同样遵循不可变原则。

4.1 加载即注册:src/locale/es.js 做了什么

docs/es-es/README-es-es.md 中import 'dayjs/locale/es'对应仓库文件 src/locale/es.js。该文件定义了完整的西班牙语 locale 对象并在模块加载末尾执行注册:

const locale = { name: 'es', monthsShort: 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'), weekdays: 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'), weekdaysShort: 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'), weekdaysMin: 'do_lu_ma_mi_ju_vi_sá'.split('_'), months: 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'), weekStart: 1, formats: { LT: 'H:mm', LTS: 'H:mm:ss', L: 'DD/MM/YYYY', LL: 'D [de] MMMM [de] YYYY', LLL: 'D [de] MMMM [de] YYYY H:mm', LLLL: 'dddd, D [de] MMMM [de] YYYY H:mm' }, relativeTime: { future: 'en %s', past: 'hace %s', s: 'unos segundos', m: 'un minuto', mm: '%d minutos', h: 'una hora', hh: '%d horas', d: 'un día', dd: '%d días', M: 'un mes', MM: '%d meses', y: 'un año', yy: '%d años' }, ordinal: n => `${n}º` } dayjs.locale(locale, null, true) // 第三个参数 true:仅注册,不改变全局 locale export default locale

几个值得注意的细节:

  • weekStart: 1:西班牙语习惯以周一为一周起点。在startOf('week')的源码中(src/index.js),this.$locale().weekStart被直接用于计算周界,因此切到eslocale 后,「本周起始日」的行为会自动从周日变为周一;
  • formats六件套LDD/MM/YYYY)与英语 locale 的MM/DD/YYYY不同,说明西班牙语日期显示为「日/月/年」;LL等长格式通过[de]字面量占位符表达「D de MMMM de YYYY」的自然语言句式。这些预设格式供localizedFormat插件消费;
  • ordinal: n =>${n}º``:西班牙语序数词为数字后加º(如),供advancedFormat插件的Dotoken 调用;
  • relativeTime:完整的 13 个键(future/past/s/m/mm/h/hh/d/dd/M/MM/y/yy),供relativeTime插件生成「hace 3 días」「en 2 meses」这类自然时间。测试用例 test/locale/keys.test.js 强制校验每个 locale 的relativeTime必须恰好包含这 13 个键,保证跨语言一致性;
  • dayjs.locale(locale, null, true):第三个参数true表示「仅注册到全局 locale 表,不切换全局默认」,保证业务代码显式调用dayjs.locale('es')之前行为不被破坏。

locale 结构规范同样由测试守护:test/locale/keys.test.js 遍历src/locale目录全部 140+ 个 locale 文件,校验name与文件名一致且小写、weekdays为数组、formats必须含L/LL/LLL/LLLL/LT/LTS且不得含额外键、ordinal对 1–31 天均可输出等。

4.2 名称回退:es-es如何落到es

docs/es-es/ 目录本身以es-es(西班牙本土西班牙语)命名,而 locale 注册名是es。这背后是parseLocale的回退机制(src/index.js):

const parseLocale = (preset, object, isLocal) => { // ... if (typeof preset === 'string') { const presetLower = preset.toLowerCase() if (Ls[presetLower]) { l = presetLower } if (object) { Ls[presetLower] = object; l = presetLower } const presetSplit = preset.split('-') if (!l && presetSplit.length > 1) { return parseLocale(presetSplit[0]) // 'es-es' 未注册 → 递归尝试 'es' } } // ... }

逻辑是:先查精确名称(统一转小写);若未命中且名称带连字符,则递归尝试前缀(es-eses)。因此dayjs.locale('ES-ES')dayjs.locale('es-es')最终都会命中eslocale。而dayjs('...').locale('zh-cn')这种实例级调用,走Dayjs.locale()方法(src/index.js),它克隆实例后设置$L字段,原实例不受影响。

全局层面,dayjs.locale直接就是parseLocale本身(src/index.js),全局默认 locale 初始为en,且enlocale 在核心源码中内置(src/index.js),无需任何 import。

五、插件(Complementos):advancedFormat 示例与扩展机制

文档对插件的定义是:「插件是一个独立模块,可以添加到 Day.js 上以扩展功能或增加新特性」。官方示例:

import advancedFormat from 'dayjs/plugin/advancedFormat' // 按需加载 dayjs.extend(advancedFormat) // 使用插件 dayjs().format('Q Do k kk X x') // 现在可以使用更多格式

5.1 extend 的「只安装一次」语义

dayjs.extend的实现在 src/index.js:

dayjs.extend = (plugin, option) => { if (!plugin.$i) { // 每个插件只安装一次 plugin(option, Dayjs, dayjs) plugin.$i = true } return dayjs }

约定插件是一个函数,接收(option, Dayjs, dayjs)三个参数;$i标记保证重复extend是幂等的。注意示例中没有import dayjs from 'dayjs'一行也能工作的前提——示例是接在完整上下文之后展示的;独立使用时需先引入 dayjs 本体。

5.2 advancedFormat 插件:Q、Do、k、X 从哪来

docs/es-es/README-es-es.md 示例中的'Q Do k kk X x',其 token 支持在 src/plugin/advancedFormat/index.js 中全部可以找到。该插件通过重写proto.format的方式工作:先用自己的正则/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g(src/plugin/advancedFormat/index.js)替换扩展 token,再调用被保存下来的原format处理剩余的标准 token:

Token含义源码实现
Q季度(1–4)Math.ceil((this.$M + 1) / 3)
Do序数词日期,依赖 locale 的 ordinallocale.ordinal(this.$D),即西班牙语下输出
w / ww本地周数(受 weekStart 影响)this.week()
W / WWISO 周数this.isoWeek()
k / kk24 制小时(0 点表示为 24)this.$H === 0 ? 24 : this.$H
X/x秒级 / 毫秒级 Unix 时间戳Math.floor(time / 1000)/getTime()
gggg / GGGG本地周年起 / ISO 周年起this.weekYear()/this.isoWeekYear()
z / zzz时区缩写 / 全称this.offsetName()(需 timezone 插件支持)

这里可以看到 I18n 与插件的协同:Do直接读取locale.ordinal,在eslocale 下得到31º,在enlocale 下得到1st31st

仓库 src/plugin/ 目录共提供 37 个官方插件(advancedFormatrelativeTimeutcdurationtimezonecustomParseFormat等),每个插件均配有独立类型定义(types/plugin/)与测试(test/plugin/)。其中relativeTime插件正是 src/locale/es.js 中relativeTime字段的消费者。

六、工程质量与适用边界

结合仓库配置,可以确认几条适用于本文所述功能的工程事实:

  • 测试策略:package.json 的test脚本在Pacific/AucklandEurope/LondonAmerica/Whitehorse三个不同时区环境下分别运行,且 jest 配置要求src目录100% 行覆盖率--coverageThreshold),locale 结构由 test/locale/keys.test.js 全量守护;
  • 体积约束size-limit硬上限 2.99KB(gzip),配合「locale/插件按需引入」的设计达成 README 宣称的 2kB 级别体积;
  • 许可协议:MIT(LICENSE),文档中亦明确「Day.js 以 MIT License 授权」。

适用边界方面需要注意:本文所有行为描述以当前仓库源码为准。基础format不支持Q/Do/k/X等扩展 token(需 advancedFormat 插件);相对时间、LL系列格式的实际消费方分别是relativeTimelocalizedFormat插件;z/zzztoken 依赖 timezone 插件提供的offsetName。此外核心解析基于原生Date与宽松正则,复杂的任意格式解析属于customParseFormat插件的能力范围。

七、小结

以 docs/es-es/README-es-es.md 为主线,本文完成了从「文档级用法」到「源码级机制」的闭环:

  1. 安装:npm install dayjs,默认产物内置enlocale,体积受 2.99KB 硬约束;
  2. 五大 API(解析/格式化/get&set/操作/查询)全部以「克隆后再修改」实现不可变性,set对月/年做了跨月边界收敛;
  3. I18n:import 'dayjs/locale/es'触发dayjs.locale(locale, null, true)的静默注册,parseLocale提供es-es → es的名称回退,eslocale 携带weekStart: 1DD/MM/YYYY日期格式、º序数词与 13 键相对时间模板;
  4. 插件:dayjs.extend幂等安装,advancedFormat以「先替换扩展 token、再委托原 format」的装饰器模式扩展格式能力,且与 locale 的ordinal深度协作。

对于从 Moment.js 迁移的团队,这套「核心 API 同名同形 + 扩展能力按需插件化」的设计,正是其 API 兼容性承诺在实现层面的来源。

【免费下载链接】dayjs⏰ Day.js 2kB immutable date-time library alternative to Moment.js with the same modern API项目地址: https://gitcode.com/gh_mirrors/da/dayjs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 10:08:15

蓝屏代码全解读:从0xc000021a到unexpected store exception的排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 10:07:05

Reasonix 能力诊断快速清单:6 大能力的加载顺序与一分钟排障

Reasonix 能力诊断快速清单&#xff1a;6 大能力的加载顺序与一分钟排障 【免费下载链接】DeepSeek-Reasonix DeepSeek-native AI coding agent for your terminal. Engineered around prefix-cache stability — leave it running. 项目地址: https://gitcode.com/GitHub_Tr…

作者头像 李华
网站建设 2026/9/18 10:06:03

Gyroflow 视频防抖:3 步让运动镜头丝滑稳定

Gyroflow 视频防抖&#xff1a;3 步让运动镜头丝滑稳定 【免费下载链接】gyroflow Video stabilization using gyroscope data 项目地址: https://gitcode.com/GitHub_Trending/gy/gyroflow 拍 Vlog 时画面抖得像坐过山车&#xff1f;Gyroflow 是一款开源免费的视频防抖…

作者头像 李华