背景
报表需求,需要传递每组数据中最小的日期,后台根据传递的最小日期,向前取参数传递的月份的上个月为结束时间的近五个月数据
例:参数传:2025/02,则需返回2025/01, 2024/12, 2024/11, 2024/10, 2024/09这五个年月数据。
实现方案
先将请求参数按照/进行分割,得到年与月份,然后使用日历类Calendar,进行设置。获取到2025年1月31号23点59分59秒999毫秒的结束时间与
2024年9月30号0点0分0秒000毫秒的开始时间,然后按照开始时间与结束时间查询出该时段的所有数据,并按照年/月进行分组统计。
示例代码
java
public static Date getEndTime(String date, int months) {
// 分割输入字符串为年和月
String[] parts = date.split("/");
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
// 创建 Calendar 实例(默认系统时区)
Calendar calendar = Calendar.getInstance();
// 设置年月(注意月份需减1)
calendar.set(Calendar.YEAR, year);
// 0-11 对应 1-12月
calendar.set(Calendar.MONTH, month - 1);
// 时间部分清零(时、分、秒、毫秒)
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
calendar.add(Calendar.MONTH, months);
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
return calendar.getTime();
}
上述代码单元测试结果,输出2025-01-21 23:59:59.999,符合预期

异常场景
当执行这段代码的时间如果在月底,且当前月份的天数大于请求参数所在月份时,则会报错,如下,如果执行方法的时间为5月30号,请求参数"2025/02"时,则获取不到预期值:2025-01-21 23:59:59.999,返回数据仍为:2025-02-28 23:59:59.999.

问题原因分析
原始代码的执行流程
-
Calendar.getInstance() sun.util.locale.provider.CalendarProviderImpl#getInstance 可以参考此方法
-
默认获取当前时间:
2025/05/30
(假设调用时的时间)。 -
此时
DAY_OF_MONTH=30
。
-
-
设置年月
javacalendar.set(Calendar.YEAR, 2025); // 2025年 calendar.set(Calendar.MONTH, 1); // 2月(Calendar.MONTH 是 0-11)
- 此时日期变为
2025/02/30
,但 2月没有30天 ,所以Calendar
会自动调整到 2025/03/02(因为 2025年2月只有28天,30 - 28 = 2,所以变成3月2日)。
- 此时日期变为
-
calendar.add(Calendar.MONTH, -1)
当前是
2025/03/02
,减去1个月变成2025/02/02
(不是1月,因为3月减1是2月)。 -
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH))
- 2月的最大天数是28(2025年不是闰年),所以最终日期是
2025/02/28 23:59:59.999
。
- 2月的最大天数是28(2025年不是闰年),所以最终日期是
为什么返回的是2月的最后一天?
-
关键问题 :
calendar.set(Calendar.MONTH, 1)
设置2月时,由于当前DAY_OF_MONTH=30
(来自Calendar.getInstance()
),而2月没有30天,所以Calendar
自动调整到了 3月2日。 -
然后
add(Calendar.MONTH, -1)
变成 2月2日,而不是预期的1月。 -
最后设置月末日期,得到
2025/02/28 23:59:59.999
。
修复方案
java
public static Date getEndTime(String date, int months) {
String[] parts = date.split("/");
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
Calendar calendar = Calendar.getInstance();
// 先清除时间部分,避免当前日期影响
calendar.clear(); // 或 calendar.set(Calendar.DAY_OF_MONTH, 1);
// 设置年月
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1); // 0-11
// 加减月份
calendar.add(Calendar.MONTH, months);
// 设置月末最后一天 + 23:59:59.999
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
return calendar.getTime();
}
总结
-
原始问题 :由于
Calendar
初始化时携带了当前日期(如30日),而2月没有30天,导致自动调整到3月2日,再-1
个月变成2月2日,最终返回2月末。 -
修复方法 :在设置年月前 清除
DAY_OF_MONTH
(如calendar.clear()
或set(DAY_OF_MONTH, 1)
),避免当前日期影响计算。
这样,传入 "2025/02"
和 -1
时,就能正确返回 2025年1月31日 23:59:59.999。