怎么使用 day.js 简化 JavaScript 日期加减运算

作者:开源拾贝

时间:2026-11-03

浏览:144

本文详细讲解如何使用轻量级库 Day.js 简化 JavaScript 日期与时间的加减计算,对比原生 Date 对象的痛点,深入掌握 add 与 subtract 语法、链式操作、月末与闰年边界智能处理及常见避坑指南。

文章目录

在日常 JavaScript 开发中,日期加减是非常高频的业务需求,例如在数据看板中筛选“过去 7 天”、为订单设置“30 分钟支付倒计时”,或是为用户续期“1 年会员资格”。原生 JavaScript 提供的 Date 对象虽然内置了时间戳计算与取值方法,但在处理加减运算时非常容易引发隐蔽的业务 Bug:不仅原变量会被原地篡改(In-place Mutation),而且在跨月运算时极易因月份天数不同而发生“月末越界溢出”。

解决这一问题的最佳实践是引入仅约 2KB 的轻量日期库 Day.js。Day.js 采用了类似 Moment.js 的直观 API 设计,但全面拥抱了函数式编程的“不可变设计”(Immutable)。通过调用 .add() 和 .subtract() 方法,你可以安全、精确地按天、周、月、年或时分秒进行链式增减,同时借助其内置的月末智能对齐机制彻底规避跨月计算陷阱。本文将从原生痛点剖析、环境引入、核心加减语法、链式操作、月末边界处理到常见错误排查,完整讲解如何在项目中高效落地 Day.js 日期加减运算。

一、原生 Date 的计算痛点与 Day.js 的解决之道

为了深刻理解 Day.js 带来的便利,有必要先审视原生 Date 在做加减运算时的三大核心痛点:

  • 原地修改产生数据副作用:原生 Date 对象的 setDate()、setMonth() 等方法是破坏性的(Mutable)。一旦在某个函数中执行了加减,所有引用该日期变量的地方都会同步改变,这在 React 状态管理、Vue 响应式数据或公共工具函数中极易引发数据漂移。
  • 单位换算冗长且容易出错:如果通过毫秒戳 getTime() 来手动做加减,开发者必须手动编写 1000 * 60 * 60 * 24 这类乘法算式,代码不仅可读性差,而且在面对跨夏令时(DST)或自然月天数不固定(28至31天)时完全无能为力。
  • 月末越界自动溢出至下下月:原生 setMonth() 在遇到源月份是大月(31天)而目标月份天数较少(如 2 月)时,会自动把超出天数累加至下下个月。例如 1 月 31 日加 1 个月,原生 Date 会计算为 3 月 2 日或 3 日,严重违反业务直觉。

Day.js 正是为彻底解决这些问题而生。它保证所有加减运算均返回全新的 Day.js 实例,原对象始终保持只读安全;同时提供统一的语义化参数,支持任意时间维度的加减,并内置了边界自动修正机制。

怎么使用 day.js 简化 JavaScript 日期加减运算
图 1:原生 Date 的可变副作用机制与 Day.js 不可变运算机制对比

二、准备工作:引入 Day.js 与创建基准实例

Day.js 是一个零依赖的极简库,可以在浏览器端和 Node.js 环境中开箱即用。你可以根据当前项目的技术栈选择合适的安装方式。

1. 在工程化项目中安装与导入

在 npm、Yarn 或 pnpm 项目中,执行以下安装命令:

# 使用 npm 安装
npm install dayjs

# 或者使用 yarn 安装
yarn add dayjs

# 或者使用 pnpm 安装
pnpm add dayjs

在代码中支持 ES Module 规范或 CommonJS 规范导入:

// ES Module 方式(推荐用于 Vue、React、Vite 项目)
import dayjs from 'dayjs';

// CommonJS 方式(推荐用于传统 Node.js 服务端)
const dayjs = require('dayjs');

2. 在浏览器原生环境中通过 CDN 引入

如果不使用打包工具,可直接在 HTML 页面中使用 <script> 标签从公共 CDN 加载,加载后全局变量 window.dayjs 即可直接使用:

<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
<script>
  const now = dayjs();
  console.log(now.format());
</script>

3. 创建基准日期实例

进行加减运算前,首先需要创建一个 Day.js 包装对象作为运算基准。Day.js 支持多种初始输入源:

// 1. 以当前本地时间创建实例
const current = dayjs();

// 2. 从 ISO 日期字符串或标准日期字符串解析
const specificDate = dayjs('2026-11-01');

// 3. 从毫秒时间戳解析
const fromTimestamp = dayjs(1761955200000);

// 4. 包装既有的原生 JS Date 对象
const fromNative = dayjs(new Date());

三、核心语法:使用 add 与 subtract 执行日期加减

Day.js 提供了两个核心方法来处理日期的增加与减少:.add(value, unit) 和 .subtract(value, unit)。它们的语法规则非常直观统一。

1. 方法签名与时间单位对照

两个方法的签名定义如下:

dayjsInstance.add(value: number, unit: string): Dayjs
dayjsInstance.subtract(value: number, unit: string): Dayjs

其中 value 为要加减的数值(整数),unit 为时间单位。Day.js 同时支持全称与简写,单位名称是不区分大小写的,但简写中的月份大写 'M' 与分钟小写 'm' 具有严格区分。下表列出了常用的时间单位映射:

时间维度 完整单位参数 (全称) 常用简写 (Shorthand) 应用场景举例
年 'year' / 'years' 'y' 计算会员年费到期、周年纪念日
月 'month' / 'months' 'M' (严格大写) 月度账单日推算、季度报表
周 'week' / 'weeks' 'w' 周计划、7天统计周期
日 / 天 'day' / 'days' 'd' 倒计时、履约延期、数据看板跨度
小时 'hour' / 'hours' 'h' 工单响应 SLA、时限超时检测
分钟 'minute' / 'minutes' 'm' (严格小写) 验证码有效时限、支付倒计时
秒 'second' / 'seconds' 's' 短定时器校准、精确日志标记
毫秒 'millisecond' / 'milliseconds' 'ms' 微秒级性能基准计算

2. 基础加减运算代码示例

以下示例展示了天数增加、月份扣减及时间顺延的完整调用:

import dayjs from 'dayjs';

const base = dayjs('2026-11-01 10:00:00');

// 1. 增加 7 天
const after7Days = base.add(7, 'day');
console.log(after7Days.format('YYYY-MM-DD HH:mm:ss'));

// 2. 减去 2 个月(使用简写 'M')
const before2Months = base.subtract(2, 'M');
console.log(before2Months.format('YYYY-MM-DD HH:mm:ss'));

// 3. 增加 45 分钟(使用简写 'm')
const after45Minutes = base.add(45, 'm');
console.log(after45Minutes.format('YYYY-MM-DD HH:mm:ss'));

// 4. 传负数参数:add(-3, 'd') 等价于 subtract(3, 'd')
const minusViaAdd = base.add(-3, 'day');
console.log(minusViaAdd.format('YYYY-MM-DD HH:mm:ss'));
控制台示例输出
2026-11-08 10:00:00
2026-09-01 10:00:00
2026-11-01 10:45:00
2026-10-29 10:00:00

3. 链式调用(Chaining)复合计算

由于每个运算方法返回的都是一个新的 Day.js 实例,你可以将多个不同维度的加减操作通过点语法直接连缀起来。例如计算“1 年后、再减去 1 个月、再顺延 15 天的时间点”:

const start = dayjs('2026-03-10');

// 连缀执行:加 1 年 → 减 1 个月 → 加 15 天
const complexResult = start
  .add(1, 'year')
  .subtract(1, 'month')
  .add(15, 'day');

console.log(complexResult.format('YYYY-MM-DD'));
控制台示例输出
2027-02-25
怎么使用 day.js 简化 JavaScript 日期加减运算
图 2:Day.js 日期加减运算的标准执行流程管道

四、实战场景与月末边界陷阱处理

理解了基础 API 后,我们来看看在真实业务中最为常见的实战场景,以及 Day.js 是如何优雅规避原生 Date 的边界缺陷的。

场景一:数据看板的时间范围筛选(结合 startOf / endOf)

在运营看板与报表统计中,常常需要获取“过去 7 天完整范围”或“本月至今”。为了剔除当前时间戳的时分秒偏差,通常将加减运算与 .startOf()(规整到周期起点)和 .endOf()(规整到周期终点)结合使用:

const now = dayjs();

// 获取“过去 7 天”的起止时间范围
// 起始时间:7 天前的 00:00:00
const startDate = now.subtract(7, 'day').startOf('day');
// 结束时间:昨天的 23:59:59
const endDate = now.subtract(1, 'day').endOf('day');

console.log('统计起始时间:', startDate.format('YYYY-MM-DD HH:mm:ss'));
console.log('统计结束时间:', endDate.format('YYYY-MM-DD HH:mm:ss'));
控制台示例输出(假设当前为 2026-11-01 16:30:20)
统计起始时间: 2026-10-25 00:00:00
统计结束时间: 2026-10-31 23:59:59

场景二:月末与闰年边界计算(自动吸附机制)

这是日期计算中最著名的“暗坑”。公历月份天数不固定(大月31天、小月30天、2月平年28天/闰年29天)。考虑一个经典业务:用户在 1 月 31 日开通了 1 个月的月卡,下个月该在何时到期?

按照正常人类认知,2 月份没有 31 日,因此月卡应当在 2 月的最后一天(2月28日或29日)结束。然而原生 JavaScript 会得出荒谬的结果:

// 1. 原生 Date 的错误行为
const nativeDate = new Date('2026-01-31');
nativeDate.setMonth(nativeDate.getMonth() + 1);
// 2026 年 2 月只有 28 天,超出的 3 天溢出到了 3 月!
console.log('原生 Date 计算结果:', nativeDate.toISOString().slice(0, 10));
// 结果输出:2026-03-03(严重违背业务预期)

// 2. Day.js 的智能吸附处理
const dayjsDate = dayjs('2026-01-31');
const nextMonth = dayjsDate.add(1, 'month');
console.log('Day.js 计算结果:', nextMonth.format('YYYY-MM-DD'));
// 结果输出:2026-02-28(自动对齐目标月最后一天)

同样地,对于闰年的 2 月 29 日加 1 年,原生 Date 会溢出到下一年的 3 月 1 日,而 Day.js 会自动将其归整为平年的 2 月 28 日,确保年度周期处于同月份内。

怎么使用 day.js 简化 JavaScript 日期加减运算
图 3:月末跨月与闰年跨平年边界加减运算处理原理对比

五、进阶补充:时间差计算与扩展插件

在加减计算的基础上,经常需要反向验证两个日期之间的时间间隔,或结合插件处理复杂的时间段对象。

1. 使用 .diff() 计算时间差值

.diff() 方法用于计算两个日期之间的跨度,语法为 date1.diff(date2, unit, float):

const start = dayjs('2026-11-01');
const target = dayjs('2026-12-15');

// 计算相差的天数(整数)
const diffDays = target.diff(start, 'day');
console.log('相隔天数:', diffDays); // 输出:44

// 计算相差的月份(带浮点数,传第三个参数 true)
const diffMonthsExact = target.diff(start, 'month', true);
console.log('相隔月数:', diffMonthsExact.toFixed(1)); // 示例输出:1.5

2. 按需加载 Duration 插件处理时间段

如果业务中包含类似“将现有日期加上一个已定义的工时时长(如 2 天 4 小时 30 分钟)”,可以引入官方轻量插件 duration:

import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';

// 注册插件
dayjs.extend(duration);

const shiftDuration = dayjs.duration({
  days: 2,
  hours: 4,
  minutes: 30
});

const original = dayjs('2026-11-01 08:00:00');
// 直接将 duration 传递给 add 方法
const completed = original.add(shiftDuration);

console.log(completed.format('YYYY-MM-DD HH:mm:ss'));
控制台示例输出
2026-11-03 12:30:00

六、常见错误与避坑清单

初次从原生 Date 或其他库迁移至 Day.js 时,开发者常遇到以下三大问题,建议在开发中针对性检查:

避坑一:忘记接收加减运算的返回值
这是最常见的疏忽。很多习惯了原生 date.setDate(...) 的开发者会写出类似 const d = dayjs(); d.add(1, 'day'); 的代码,然后发现 d 的日期根本没有变化。请务必牢记:Day.js 是不可变对象,.add() 和 .subtract() 永远不会原地修改变量,必须赋值给新变量,例如 const nextDay = d.add(1, 'day');。
避坑二:单位简写字母大小写混淆
在简写模式下,'M'(大写)代表月份(Month),而 'm'(小写)代表分钟(Minute)。如果不小心将 .add(1, 'm') 误写成了加 1 个月,实际只会增加 1 分钟;反之若把 .add(30, 'M') 误当作 30 分钟,则会直接加 30 个月(2年半),引发灾难性的计算偏差。

避坑三:跨时区服务器上的自然日加减漂移
如果前端浏览器与后端服务器处于不同时区(例如中国 UTC+8 与伦敦 UTC+0),直接使用本地时间加减并转换为 ISO 字符串(toISOString())时,可能会因时差跨过零点而导致日期出现“提前一天或延后一天”的错觉。在涉及纯日期计算(如仅需年月日)时,推荐固定在本地时间下格式化为 YYYY-MM-DD 交付后端,或者引入 dayjs/plugin/utc 插件强制使用 UTC 模式执行加减。

怎么使用 day.js 简化 JavaScript 日期加减运算
图 4:Day.js 日期加减核心方法、单位对照及避坑指南速查图

总结

通过使用 Day.js,JavaScript 中的日期加减运算从以往容易踩坑的原地篡改和边界越界,转变为清晰、确定且安全的纯函数链式调用。在实际编码中,只要掌握 .add() 与 .subtract() 的基本调用格式,分清 'M' 与 'm' 的简写区分,并始终用变量接收运算产出的新实例,就能轻松应对各类复杂的时间运算场景,大幅提升代码的可读性与工程健壮度。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章