首页/新闻资讯/正文详情

Luxon 日期时间数学运算完全指南:日历数学、时间数学与 Duration 换算精度

发布时间:2026/9/21 1:54:42 来源:云帆数科 栏目:资讯中心
Luxon 日期时间数学运算完全指南:日历数学、时间数学与 Duration 换算精度
Luxon 日期时间数学运算完全指南日历数学、时间数学与 Duration 换算精度【免费下载链接】luxon⏱ A library for working with dates and times in JS项目地址: https://gitcode.com/gh_mirrors/lu/luxon本指南系统讲解 Luxon 中与日期时间做数学相关的全部关键知识日历数学calendar math与时间数学time math的区别、多单位运算的先后顺序、DateTime 比较、Duration 的单位换算精度casual 与 longterm以及 Interval 锚定差分的正确姿势。读完本文你将能在跨月、跨年、跨 DST 的场景下写出不踩坑的日期运算代码并理解plus/diff/shiftTo等 API 底层的真实工作机制。为什么日期时间数学会让程序员困惑对许多程序员而言日期时间运算常常反直觉。以 2017 年 2 月 13 日为例说再过恰好一个月你自然会想到 3 月 13 日再过一个月是 4 月 13 日。但因为 2 月比 3 月短两次一个月实际加上的时间长度并不相同。反过来说从 2 月 13 日起 30 天你要去推算它落在 3 月的哪一天。Luxon 中这两类运算分别写为DateTime.local(2017, 2, 13).plus({ months: 1 }).toISODate() // 2017-03-13 DateTime.local(2017, 2, 13).plus({ days: 30 }).toISODate() // 2017-03-15从根本上说存在两种截然不同的运算模式见 docs/math.md日历数学Calendar math作用于高阶、可变长度的单位如年、月、日。时间数学Time math作用于低阶、恒定长度的单位如小时、分钟、秒。哪些单位属于哪种数学使用日历数学的单位年因为闰年而长度不同月因为各月天然长度不同日因为 DST 切换导致某些天是 23 或 25 小时季度始终是 3 个月但月有长短季度也随之变化周天数总是相同但天的长度会变周也跟着变。使用时间数学的单位小时恒为 60 分钟分钟恒为 60 秒秒恒为 1000 毫秒关于闰秒的说明与 JavaScript 整体行为一致ECMA-262 规范第 15.9.1.1 节Luxon不处理闰秒leap seconds。在绝大多数编程环境中闰秒都是作为底层系统时间的不可见变化发生的从 Luxon 的角度看极少数情况下同一个秒可能出现两次。闰秒的实际影响非常有限你无法表示闰秒本身——DateTime.utc(2016, 12, 31, 23, 59, 60).isValid返回false跨越闰秒的diff()计算结果不会精确等于外界真实流逝的秒数。这在你的应用需要精确知道最近 n 秒到底发生了什么的罕见场景下才会浮现如今这一误差也越来越多地被闰秒涂抹leap smear机制缓解。日历数学的正确思考方式不要把日历数学理解为对中间各段长度做繁琐检查而应理解为直接调整该单位本身、并保持更低阶的日期分量不变。回到2 月 13 日 1 个月的例子若没有 Luxon你需要手工操作原生Datevar d new Date(2017-02-13) d.setMonth(d.getMonth() 1) d.toLocaleString() // 3/13/2017, 12:00:00 AMLuxon 底层做的与此如出一辙它不会把运算摊平成毫秒差值——因为用户要的并不是那个。它直接摆弄自己认为的日期应该是什么再用内置的格里高利历Gregorian calendar算出新的时间戳。从源码看src/datetime.js 的adjustTime()正是这么实现的先把dur.years、dur.months、dur.quarters的整数部分直接加到日历年、月上天数部分用Math.min(inst.c.day, daysInMonth(year, month))做月末钳制所以 1 月 31 日加 1 个月得到 2 月 28/29 日再加dur.days与dur.weeks * 7只有把各单位的小数部分与hours、minutes、seconds、milliseconds汇总成一个 Duration 换算成毫秒后追加。这就是高阶单位走日历、低阶单位走时钟的代码级体现。测试 test/datetime/math.test.js 验证了月末场景DateTime.fromISO(2018-01-31T10:00).plus({ months: 1 })得到 2 月 28 日而闰年 2016 年则得到 2 月 29 日。DST 下的日历数学关于 DST 有专门章节见 时区文档这里给一个直观示例以作者所在时区 2017 年 3 月 12 日凌晨春令时拨快为例var start DateTime.local(2017, 3, 11, 10); start.hour // 10, 对照组 start.plus({days: 1}).hour // 10, 保持不变 start.plus({hours: 24}).hour // 11, DST 把钟拨快了一小时也就是说加一天保住了 10 点这个时刻尽管它实际上只过了 23 个小时。test/datetime/math.test.js 在America/Los_Angeles时区复现了同样的行为plus({ days: 1 })后hour仍为 10而plus({ hours: 24 })后hour变为 11。时间数学时间数学则不同它只是拨动时钟在纪元时间戳上做加减。加 63 小时本质上就是加上 63 小时对应的毫秒数。底层实现与日历数学恰好相反Luxon 把它摊平成毫秒、算出新的时间戳、再反推出日期。plus({ hours: 24 })与plus({ days: 1 })在 DST 切换期产生不同结果的根因正在于此。多单位数学的运算顺序一次可以同时对多个单位做数学运算DateTime.fromISO(2017-05-15).plus({months: 2, days: 6}).toISODate(); // 2017-07-21但事情没这么简单。下面这个表达式结果是什么DateTime.fromISO(2017-04-30).plus({months: 1, days: 1}).toISODate();若先加天中间值是 5 月 1 日再加一个月得到 6 月 1 日若先加月中间值是 5 月 30 日再加一天得到 5 月 31 日。顺序决定了结果。Luxon 的规则很简单数学运算按从高阶到低阶的顺序执行highest order to lowest order。因此上例结果是 5 月 31 日。这个规则并非逻辑上必然但它确实符合大多数人的直觉。当然如果分两步做Luxon 也无法强制该规则DateTime.fromISO(2017-04-30).plus({days: 1}).plus({months: 1}).toISODate() // 2017-06-01Luxon 的接口设计让你很难顺手做错这并非巧合。源码层面adjustTime()中orderedUnitsyears → quarters → months → weeks → days → hours → ...的顺序见 src/duration.js与先处理高阶单位的语义一脉相承。比较两个 DateTimeDateTime实现了#valueOf返回纪元时间戳因此可以直接用、、、比较d1 d2 // d1 是否在 d2 之前但要小心比较的是对象身份对不可变类型的库而言这不是有用的语义。请使用#equals同时比较时间与附加元数据如 locale 和时区。如果只关心时间戳是否相等可以d1.toMillis() d2.toMillis() // d1 和 d2 是否是同一时刻 d1 d2 // 同一测试利用对象隐式转换还可以用#hasSame做更精细的比较d1.hasSame(d2, year); // 两个 DateTime 是否处于同一公历年 d1.hasSame(d2, day); // 是否处于同一公历日隐含同年同月注意这些比较是针对日历的例如 d1 在 2017 年hasSame(d2, year)问的是 d2 是否也在 2017 年而不是两者是否相差不到一年——后者需要用diff。源码中hasSame的实现src/datetime.js是把对方时间戳与本方startOf(unit)到endOf(unit)的闭区间比对。若想按某个具体单位比较可以把#startOf与#valueOf组合使用var d1 DateTime.fromISO(2017-04-30); var d2 DateTime.fromISO(2017-04-01); d2 d1 // true d2.startOf(year) d1.startOf(year) // false d2.startOf(month) d1.startOf(month) // false d2.startOf(day) d1.startOf(day) // truestartOf支持year、quarter、month、week、day、hour、minute、second、millisecond等单位源码见 src/datetime.js 的逐级 fall-through 实现测试见 test/datetime/math.test.js。Duration 数学基础Duration是一段时间的量例如3 天零 6 小时。Luxon 并不知道这是哪3 天 6 小时——它只是用抽象、与时间线解耦的方式表达这些量。这既极其有用偶尔也让人困惑。基础用法var dur Duration.fromObject({ days: 3, hours: 6}) // 查看它 dur.toObject() // { days: 3, hours: 6 } // 用分钟表达 dur.as(minutes) // 4680 // 换算成分钟单位 dur.shiftTo(minutes).toObject() // { minutes: 4680 } // 加到一个 DateTime 上 DateTime.fromISO(2017-05-15).plus(dur).toISO() // 2017-05-18T06:00:00.000-04:00从类文档注释src/duration.js可以看到Duration 概念上就是单位 → 数量的映射外加若干配置与方法创建可用fromMillis/fromObject/fromISO变换可用plus/minus/normalize/set/reconfigure/shiftTo/negate输出可用as/toISO/toFormat/toJSON。求差diff用DateTime.diff求两个时刻之间的时间量结果是一个 Durationvar end DateTime.fromISO(2017-03-13); var start DateTime.fromISO(2017-02-13); var diffInMonths end.diff(start, months); diffInMonths.toObject(); // { months: 1 }注意必须指定用于记录差值的单位默认是毫秒var diff end.diff(start); diff.toObject() // { milliseconds: 2415600000 }也可以同时用多个单位var end DateTime.fromISO(2017-03-13); var start DateTime.fromISO(2017-02-11); end.diff(start, [months, days]).toObject() // { months: 1, days: 2 }diff的底层实现见 src/impl/diff.jshighOrderDiffs()按年 → 季度 → 月 → 周 → 天依次尝试用较大单位做差若超调则回退并改用更小单位源码注释明确说明了这一先大后小、超调回溯的游标推进算法剩余毫秒再交给Duration.fromMillis(...).shiftTo(...)处理低阶单位。Casual 与 longterm两种换算精度Duration 是带有特定单位的时间包但 Luxon 允许你在单位之间换算shiftTo返回以指定单位计量的新 Durationas把整个 Duration 换算成某单一单位并返回数值。var dur Duration.fromObject({ months: 4, weeks: 2, days: 6 }) dur.as(days) // 140 dur.shiftTo(days).toObject() // { days: 140 } dur.shiftTo(weeks, hours).toObject() // { weeks: 18, hours: 144 }换算依据是什么首先毫无争议的部分1 周 7 天1 天 24 小时1 小时 60 分钟1 分钟 60 秒1 秒 1000 毫秒这些恒等式可以上下滚动并保持一致例如 1 小时 60 × 60 × 1000 毫秒。但高阶单位并非如此即便不考虑 DST年有时 365 天、有时 366 天月有 28/29/30/31 天。默认情况下Luxon 使用所谓casual宽松换算月周天年1252365季度31391月430这些数字符合直觉大多数场景下够用。但它们不仅不精确甚至自相矛盾Duration.fromObject({ years:1 }).shiftTo(months).shiftTo(days).as(years) // 0.9863013698630136原因很简单12 × 30 ≠ 365。这类误差平时只是烦人一旦累积就可能造成大问题var dur Duration.fromObject({ years: 50000 }); DateTime.now().plus(dur.shiftTo(milliseconds)).year // 51984 DateTime.now().plus(dur).year // 52017两者相差 33 年因此 Luxon 提供了第二种换算方案longterm长期精确基于 400 年历法周期400 年含 146097 天月周天年1252.1775365.2425季度313.0443591.310625月4.34812530.436875这些小数显然不好用这正是它们不是默认方案的原因。源码中两套换算矩阵定义在 src/duration.jslowOrderMatrix周/天/时/分/秒/毫秒、casualMatrix年/季度/月使用 365/91/30 天并展开...lowOrderMatrix、以及由daysInYearAccurate 146097.0 / 400与daysInMonthAccurate 146097.0 / 4800推导的accurateMatrix。Duration构造函数src/duration.js按conversionAccuracy longterm选择矩阵并把它作为实例属性保存下来。哪些方法接受conversionAccuracy凡是从零创建 Duration 的方法都可以Duration.fromObject、Duration.fromISO以及end.diff(start, unit, opts)diff会把opts透传给Duration.fromObject见 src/impl/diff.js。取值casual默认或longterm。它是 Duration 自身的属性之后的任何换算都遵循所选规则由它派生的新 Duration 也会保留该属性Duration.fromObject({ years: 23 }, { conversionAccuracy: longterm }); Duration.fromISO(PY23, { conversionAccuracy: longterm }); end.diff(start, days, { conversionAccuracy: longterm })也可以把一个已存在的 Duration 改造成精确版本var pedanticDuration casualDuration.reconfigure({ conversionAccuracy: longterm });这些 Duration 之后会采用不同的换算方式。test/duration/units.test.js 中对longterm精度与reconfigure({ conversionAccuracy: longterm })均有覆盖性测试。换算会丢失信息在单位之间换算时要格外小心信息很容易丢失。假设我们把一个 diff 换算成了天数var end DateTime.fromISO(2017-03-13); var start DateTime.fromISO(2017-02-13); var diffInMonths end.diff(start, months); diffInMonths.as(days); // 30这只是月与天之间的换算也可以改用 longterm 精确换算但解决不了问题本身。而 2 月 13 日到 3 月 13 日之间的真实天数并不是 30var diffInDays end.diff(start, days); diffInDays.toObject(); // { days: 28 }关键在于diff 的结果是 Duration 对象而 Duration 只是运算吐出来的一堆时间单位不会记住输入的起止时刻Interval 才会。所以在换算单位时某些信息就丢了。这个错误在向上滚动时尤其常见var diff end.diff(start); // 默认单位是毫秒 // 呃这根本不是一个月 diff.as(months); // 0.9319444 // 甚至天数也不对提示我所在的时区有 DST diff.shiftTo(hours).as(days); // 27.958333333333332通常只要想清楚我要用 diff 做什么就不会踩这个坑请直接在你真正需要的单位上做 diff这样 Luxon 才能回答你真正想问的问题var monthsDiff end.diff(start, months); var daysDiff end.diff(start, days);但有时你确实需要一个代表减法本身的对象而不是结果。Interval 可以帮忙——它主要用于跟踪时间范围但也能充当锚定的 diff。例如var end DateTime.fromISO(2017-03-13); var start DateTime.fromISO(2017-02-13); var i Interval.fromDateTimes(start, end); i.length(days); // 28 i.length(months) // 1因为 Interval 保存了两个端点并在每次查询时实时计算length实现见 src/interval.js即this.toDuration(...[unit]).get(unit)所以它每次都能重新求差。当然正因为 Interval 不是抽象的时间包它不能用于 Duration 能用的地方——比如不能直接plus()给 DateTime因为 Luxon 不知道该按哪个单位做数学运算。但你可以在选定单位后把 Interval 转成 Durationi.toDuration(months).toObject(); // { months: 1 } i.toDuration(days).toObject(); // { days: 28 }甚至可以一次选多个单位end DateTime.fromISO(2018-05-25); i start.until(end); i.toDuration([years, months, days]).toObject(); // { years: 1, months: 3, days: 12 }当然一旦转成 Duration就又回到了 diff 时的那种处境——之后的进一步换算依然会丢信息。所以要点是想清楚在每个时点你手中握有的是什么信息。toDuration的实现见 src/interval.js。小结一套可复用的决策框架做加法/减法时先问单位是高阶还是低阶。years/months/days走日历数学保持低阶分量、处理月末钳制与 DSThours/minutes/seconds走时间数学纯毫秒累加多单位运算按高阶到低阶执行。比较时/走valueOf时间戳时间戳相等用toMillis()连元数据一起比用equals()按日历单位比用hasSame()按单位截断后比用startOf()。求差值时直接在目标单位上diff(start, unit)需要锚定且可反复查询的差用Interval.length()/toDuration()。换算 Duration 时认清 casual 与 longterm 两套矩阵及其自洽性差异12 × 30 ≠ 365需要跨世纪精度时用conversionAccuracy: longterm并时刻警惕换算导致的信息丢失。【免费下载链接】luxon⏱ A library for working with dates and times in JS项目地址: https://gitcode.com/gh_mirrors/lu/luxon创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

OrcaFlex实战:海洋工程系泊与立管动态分析全流程指南
OrcaFlex实战:海洋工程系泊与立管动态分析全流程指南

简介:这是一份面向海洋工程、油气及风电领域工程师的OrcaFlex软件操作指南PDF,围绕钢悬链立管(SCR)的总体强度、运动疲劳与安装分析展开,适用于需要快速上手该有限元分析软件的初学者和项目人员。资源仅含1个PDF文档&a… · 2026/9/21 1:54:42

双均线策略进阶:用止损与仓位管理把回测曲线从过山车变缓坡
双均线策略进阶:用止损与仓位管理把回测曲线从过山车变缓坡

双均线策略可能是所有量化入门者写的第一个策略,但也是最容易被“回测骗”的策略。我在Supermind平台上折腾了几个月,把裸奔的双均线一步步加上止损和仓位管理,回测曲线从“过山车”变成了“缓坡上行”,这里把整个过程记录下来&am… · 2026/9/21 1:54:42

用VC++手写FTP服务器与客户端:从协议到断点续传的完整实践
用VC++手写FTP服务器与客户端:从协议到断点续传的完整实践

简介:面向VC/MFC网络通信开发者的FTP客户端与服务器完整源码,适合具有一定C基础、想深入理解FTP协议实现与Socket编程的读者。资源共26个文件,以7个cpp源文件和7个h头文件为核心,辅以dsw/dsp工程配置、txt说明文件及图标资源&… · 2026/9/21 1:54:42

i网站建设踩坑实录:被黑后选哪家更靠谱
i网站建设踩坑实录:被黑后选哪家更靠谱

i网站建设踩坑实录:被黑后选哪家更靠谱 上周凌晨三点,我的手机疯狂震动。客户在群里@我,说官网突然弹出一堆博彩广告,百度一搜全是挂马链接。那一刻,冷汗直接下来了。… · 2026/9/21 6:04:19

php做网站页面在哪做一文搞懂避坑指南
php做网站页面在哪做一文搞懂避坑指南

php做网站页面在哪做一文搞懂避坑指南 找建站公司报价三万八,回来一看还是套模板?很多甲方朋友在这一步就栽了跟头,怕被坑高价,又怕自己不懂技术被忽悠。别慌,今天咱们不聊虚的,直接拆解 php做网站页面在哪做 的底层逻辑, 一文搞懂… · 2026/9/21 5:48:20

Simulink与FlightGear联合仿真:飞行器控制算法三维可视化验证平台搭建
Simulink与FlightGear联合仿真:飞行器控制算法三维可视化验证平台搭建

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

测序数据可视化:从BAM到bigWig的UCSC工具链实战指南
测序数据可视化:从BAM到bigWig的UCSC工具链实战指南

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

反激电源TL431补偿器设计与波特图调试实战
反激电源TL431补偿器设计与波特图调试实战

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

MATLAB配置MinGW编译器全指南:从安装到排错一次搞定
MATLAB配置MinGW编译器全指南:从安装到排错一次搞定

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

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化
Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡… · 2026/9/21 0:02:39

Word表格编号全攻略:从列表编号到题注交叉引用
Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技… · 2026/9/21 0:02:39

从第一个站到第二个站:独立开发者的静态网站选型与落地实践
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&… · 2026/9/20 0:00:41

Claude Code 按智谱AI指南装完,ANTHROPIC_BASE_URL 改走 TaoToken 兼容通道行不行
Claude Code 按智谱AI指南装完,ANTHROPIC_BASE_URL 改走 TaoToken 兼容通道行不行

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

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程
agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and … · 2026/9/21 0:00:18

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,… · 2026/9/21 0:00:18

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码