JS怎么获取当前月份的最后一天
在 JavaScript 中获取当前月份最后一天,最简便可靠的标准解法是 new Date(year, month + 1, 0)。本文通过图文与完整代码,深入解析下月第0天的借位回退机制、平闰年与跨年自适应、格式化字符串生成以及数据库查询边界等业务高频考点。
Date 构造函数的天数借位机制:执行 new Date(year, month + 1, 0)。这里的关键在于向天数参数传入 0,JavaScript 引擎会自动将其解析为“次月第 1 天的前一天”,也就是当前月份的自然最后一天,且能全自动处理平闰年二月(28天或29天)及大小月变化。
一、核心解决方案与一行代码写法
在日常开发中,如果我们只需要快速取得当前月份的最后一天的日期(例如本月是 31 号、30 号还是 28/29 号),可以直接使用以下标准单行实现:
// 快速获取当前月份最后一天的日期号数(28~31)
const now = new Date();
const lastDayNumber = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
console.log(lastDayNumber); // 示例输出:31
这段代码分为三个紧密衔接的调用:
now.getFullYear():获取当前年份(四位数,例如 2026)。now.getMonth():获取当前月份索引。在 JavaScript 规范中,月份索引从0开始计数(0 代表一月,11 代表十二月)。new Date(year, month + 1, 0):构造目标日期。我们将月份参数设置为month + 1(即下一个月),而天数参数设置为0。.getDate():从构造出的完整Date实例中,读取出当天对应的具体天数。
二、核心原理解析:“下月第 0 天”为什么有效?
初次接触此写法的读者常常会疑惑:自然日历中只有 1 号到 31 号,根本不存在“第 0 天”,为什么传入 0 不会抛出异常或返回 NaN?
这是由 ECMAScript 规范中对 Date(year, monthIndex, date, ...) 构造函数的溢出处理机制决定的。当我们在日期位置传入非标准范围的值时,JavaScript 引擎会执行自动算术进位或借位:
- 如果传入的天数大于当月最大天数(例如 1 月传入 32),引擎会自动向后进位为 2 月 1 日。
- 如果传入的天数等于
1,则代表该月的第 1 天。 - 如果传入的天数小于
1(例如传入0),引擎会将时间从“该月第 1 天”基准向前减去 1 天,从而自动借位回退到前一个月的最后一天。
巧合的是,因为 now.getMonth() 得到的就是基于 0 计数的当月索引,当我们要表示“当前月份的下一个月”时,参数直接就是 now.getMonth() + 1。紧接着向天数传入 0,引擎便精准回退到了当前月份的末尾。
三、操作步骤与常见输出格式实现
实际业务中,我们往往不仅仅需要数字 31,还常常需要完整的 Date 实例、形如 YYYY-MM-DD 的日期字符串,或者是用于后端 API 数据库区间检索的精确毫秒时间戳。
1. 获取完整的月末 Date 对象
如果后续需要进行复杂的日期比较或时间增减,可以直接保留构造出来的 Date 对象:
function getCurrentMonthLastDateObject() {
const now = new Date();
// 构造出的 Date 对象的时分秒默认是当地时间的 00:00:00
return new Date(now.getFullYear(), now.getMonth() + 1, 0);
}
2. 格式化为标准 YYYY-MM-DD 字符串
前端表单或接口请求中最常用的是标准日期字符串。在格式化时,需要注意月份从 0 开始必须先加 1,并使用 String.prototype.padStart() 补充前导零:
function getCurrentMonthLastDayString() {
const now = new Date();
const lastDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const year = lastDate.getFullYear();
// 注意:getMonth() 取出的值需要 + 1 才是人类习惯的 1~12 月
const month = String(lastDate.getMonth() + 1).padStart(2, '0');
const day = String(lastDate.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
console.log(getCurrentMonthLastDayString()); // 示例输出:"2026-03-31"
3. 生成月末闭区间查询时间戳(23:59:59.999)
当编写日志过滤、订单对账等范围检索功能时(如 createdAt >= startTime AND createdAt <= endTime),仅仅取 00:00:00 会遗漏月末白天产生的所有数据。此时可以通过传入时分秒参数构造精确的月末截止时间:
function getCurrentMonthEndTime() {
const now = new Date();
// new Date(year, month, date, hours, minutes, seconds, ms)
const endMoment = new Date(
now.getFullYear(),
now.getMonth() + 1,
0,
23, 59, 59, 999
);
return endMoment;
}
四、不同处理方案的对比与考量
除了 new Date(year, month + 1, 0) 之外,在社区或老旧项目中可能还会见到其他几种写法。我们将它们进行横向比对,帮助读者在选型时具备明确的判断依据:
| 实现方案 | 典型代码 | 优点 | 缺点 / 风险 |
|---|---|---|---|
| 构造参数借位法(推荐) | new Date(y, m + 1, 0) |
一行代码、无外部依赖、全自动适配闰年与大小月 | 需理解月份 0 索引与天数 0 的借位机制 |
| 实例方法 setDate(0) | d.setMonth(m + 1); d.setDate(0); |
基于现有实例操作 | 代码较啰嗦(3行),容易对原变量产生意外修改(副作用) |
| 静态天数表查表法 | [31, isLeap?29:28, ...][m] |
逻辑直观,直接查数组 | 必须额外手写整除 4、100、400 的闰年公式,代码冗长且易出错 |
| 现代 Temporal API(提案) | Temporal.Now.plainDateISO().daysInMonth |
语法现代清晰、不可变数据结构、防时区陷阱 | 尚未完全在所有旧版浏览器中原生普及,旧环境需 Polyfill |
五、业务落地必须留意的四大避坑要点
在真实业务工程中,处理时间日期往往最容易引发偶发性 Bug。以下列出获取月末日期时最关键的四个自检项:
1. 记住 getMonth() 的 0 索引规则
Date.prototype.getMonth() 返回的是 0~11,其中 0 代表一月,11 代表十二月。初学者常犯的错误是在构造下个月第 0 天时误以为要减 1,写成了 new Date(year, month, 0),这样算出来的实际上是上个月的最后一天。牢记:当月用 month,下个月就是 month + 1。
2. 12 月跨年无需额外编写 if 分支
当处于 12 月份时,now.getMonth() 返回的是 11。此时 month + 1 等于 12。在 JavaScript 规范中,月份传入 12 会自动向年份进位 1 年,同时变成次年 1 月;接着天数参数 0 又使其从次年 1 月 1 日倒退 1 天,恰好回到了当年的 12 月 31 日。因此,完全不需要专门写 if (month === 11) 这类冗余判断。
3. 注意字符串补零(padStart)
在拼接 YYYY-MM-DD 格式时,如果直接用数字拼接,遇到单数月份或单数天数(例如 3 月 5 日),会产生 2026-3-5,这不符合 ISO 8601 标准规范,在部分前后端解析框架中可能会解析失败。始终记得使用 .padStart(2, '0') 保证两位格式。
4. 区分“日期号数”与“查询区间”
如果是用来作为日历表格的渲染格子总数,使用 .getDate() 取得数值即可;如果是用于后端数据库接口传参,务必确认后端所需的时间格式(是只包含年月日的纯日期字符串,还是精确到 23:59:59.999 的 ISO 8601 UTC 字符串),避免因时区或未覆盖全天引发查询偏差。
JavaScript整除负数时Math.floor与Math.trunc的结果差异
2026-11-11 15:40:20
JS四舍五入出现精度丢失怎么解决
2026-11-11 14:28:16
怎么编写健壮的JavaScript四舍五入取整工具函数
2026-11-11 14:28:05
JS怎么获取当前月份的第一天
2026-11-11 14:28:04
JS数组some和every方法怎么用?布尔值判断场景解析
2026-11-11 14:27:52








