Calendar类日期设置进位问题

背景

报表需求,需要传递每组数据中最小的日期,后台根据传递的最小日期,向前取参数传递的月份的上个月为结束时间的近五个月数据

例:参数传: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.

问题原因分析

原始代码的执行流程

  1. Calendar.getInstance() sun.util.locale.provider.CalendarProviderImpl#getInstance 可以参考此方法

    • 默认获取当前时间:2025/05/30(假设调用时的时间)。

    • 此时 DAY_OF_MONTH=30

  2. 设置年月

    java 复制代码
    calendar.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日)。
  3. calendar.add(Calendar.MONTH, -1)

    当前是 2025/03/02,减去1个月变成 2025/02/02(不是1月,因为3月减1是2月)。

  4. calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH))

    • 2月的最大天数是28(2025年不是闰年),所以最终日期是 2025/02/28 23:59:59.999

为什么返回的是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

相关推荐
wjs20242 小时前
状态模式(State Pattern)
开发语言
我命由我123452 小时前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
liulilittle2 小时前
C++ TAP(基于任务的异步编程模式)
服务器·开发语言·网络·c++·分布式·任务·tap
励志要当大牛的小白菜4 小时前
ART配对软件使用
开发语言·c++·qt·算法
武子康4 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
爱装代码的小瓶子6 小时前
数据结构之队列(C语言)
c语言·开发语言·数据结构
YuTaoShao7 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw7 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
Maybe_ch7 小时前
.NET-键控服务依赖注入
开发语言·c#·.net
超浪的晨7 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发