JavaScript日期减一月遇31日返回当月首日的问题排查
JavaScript日期减一个月时31号出错的原因及解决方法
这是个非常典型的JavaScript日期处理坑,我来给你唠清楚问题根源,以及怎么搞定它~
问题出在哪?
JavaScript里的setUTCMonth(包括本地时间的setMonth)有个容易被忽略的行为:当你设置的日期天数超过目标月份的最大天数时,它会自动向下溢出到下一个月。
举你的例子来说:
- 原日期是2018-05-31T09:54:23.850Z,调用
d.setUTCMonth(d.getUTCMonth() - 1)时,相当于要把月份从5月(索引4)改成4月(索引3)。 - 但4月只有30天,而原日期的天数是31,超过了4月的最大天数。这时JavaScript会自动计算:从4月1号开始往后数31天——4月有30天,所以31天就是4月30号加1天,也就是5月1号。
- 这就导致结果变成了2018-05-01T09:54:23.850Z,完全不符合预期。
再举个更极端的例子:如果是2023-03-31(3月有31天)调用减一个月,2月只有28天,结果会是2023-03-03T...,因为从2月1号数28天是2月28号,再数3天就是3月3号。
怎么解决?
核心思路是:先判断原日期的天数是否超过目标月份的最大天数,如果超过,就把日期设为目标月份的最后一天。
这里给你一个可靠的UTC版本实现:
function subtractOneMonth(date) { // 提取原日期的各个UTC组件 const year = date.getUTCFullYear(); const currentMonth = date.getUTCMonth(); const currentDay = date.getUTCDate(); const hours = date.getUTCHours(); const minutes = date.getUTCMinutes(); const seconds = date.getUTCSeconds(); const ms = date.getUTCMilliseconds(); // 计算目标月份(当前月-1) const targetMonth = currentMonth - 1; // 获取目标月份的最大天数:创建目标月份的下一个月第一天,再减一天 const lastDayOfTargetMonth = new Date(Date.UTC(year, targetMonth + 1, 0)).getUTCDate(); // 目标日期取原日期天数和目标月份最大天数的较小值 const targetDay = Math.min(currentDay, lastDayOfTargetMonth); // 返回处理后的新日期(避免修改原日期对象) return new Date(Date.UTC(year, targetMonth, targetDay, hours, minutes, seconds, ms)); } // 测试你的例子 const d1 = new Date('2018-05-31T09:54:23.850Z'); console.log(subtractOneMonth(d1)); // 输出:2018-04-30T09:54:23.850Z // 测试正常情况 const d2 = new Date('2018-05-30T09:50:05.345Z'); console.log(subtractOneMonth(d2)); // 输出:2018-04-30T09:50:05.345Z
如果需要处理本地时间(不用UTC),只需要把所有UTC相关的方法换成对应的本地方法即可(比如getFullYear代替getUTCFullYear,Date构造函数不用Date.UTC)。
这个方法的好处是:
- 不会修改原日期对象(返回新对象,避免副作用)
- 自动处理闰年、不同月份天数差异的问题
- 保留了原日期的时分秒毫秒信息
内容的提问来源于stack exchange,提问作者Nils
相关产品推荐
相关产品推荐

