一个线上问题让我发现了Calendar类中的秘密-周一真的是每周的第一天吗?

一、问题描述

假设我们当前有一张业务表,表上有两个字段start_date和end_date分别记录了该业务的开始和结束时间Ps:按自然周的逻辑记录,还有个字段achieved根据1和0标记该业务是否完成。

现在有个需求是:查出当前系统时间内未完成的业务。这个需求很简单:获取本周的周一并与end_date进行比较,再把achieved设置为0即可查出符合需求的业务。因此,开发完成后,我简单地自测下就提测了。

问题就出在清明节假收假上班后的第一天,那是一个下着小雨的周日 。这种天气配合周天以及三天假期的快乐后遗症让我慢悠悠地到了公司,把没做完的需求盘点下做完然后就可以早点下班了。就在这时,组长找上了我,告诉我节前的这个需求有问题:今天明明才周日,但是未完成的业务已经查询出来了,应该到了周一才能找出来才对。

本来我是十分自信的,毕竟这么简单的需求,我还不能把它拿捏了吗?但我看到结果后傻眼了,确实未完成的业务提前查出来了,于是开始了修复BUG之路。

二、问题排查

要排查问题当然要先从看代码开始:

java 复制代码
//1.获取当前周的周一  
//1-1.创建Calendar实例  
Calendar cal = Calendar.getInstance();  
//1-2.将日期设置为周一  
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);  
//1-3.使用Calendar的时间戳创建一个java.sql.Date实例  
java.sql.Date currentWeekMonday = new java.sql.Date(cal.getTimeInMillis());  
//2.设置查询条件
//2-1.声明查询条件构造对象
BusinessQueryCriteria businessQueryCriteria = new BusinessQueryCriteria();  
//2-2.设置查询参数
//2-2-1.设置状态
businessQueryCriteriasetStatus(1);
//2-2-2.设置用户id
businessQueryCriteria.setUserId(userId);
//2-2-3.设置未完成
businessQueryCriteria.setAchieved(0);
//2-2-4.设置结束时间小于等于当前周的周一
businessQueryCriteria.setEndDateLessThan(currentWeekMonday);

乍一看没什么问题,这里也没什么复杂逻辑,就是很基础的查询,那么自然就要开始debug了,于是在这个方法的一些关键位置打上断点。果然,debug发现问题:今天是2024-04-07 ,本周的周一应该是2024-04-01 ,但是通过debug发现1-3处的currentWeekMonday构建的日期却是2024-04-08,它本该生成的本周一却变成了下周一。

这就让人感觉很奇怪了,看起来获取日期那里也没什么问题,毕竟是很简单的逻辑。那么只能往更深层次看,于是点进 Calendar相关的源码发现了其中的秘密:

Calendar 类中,周日被默认为每周的第一天,周一为每周的第二天。那么回到我们上面的代码,问题的原因就水落石出了:今天是周日,在周日这天获取的本周一自然就是明天,在我们看来就是获取到了下周一,这里程序逻辑上没问题,但与我们实际场景使用逻辑相悖,因此针对周日需要特殊处理下。

三、问题修复

解决方案大致想到了以下三种,都可以解决这个问题,当然如果如果各位读者有别的方法,也欢迎在评论区多多交流!

1.加入判断机制

第一种方案思路自然是最简单的,既然只是周日这天会出现异常情况,那么我们就对周日多做一层判断,处理下就好了:

java 复制代码
//1.获取当前周的周一  
//1-1.创建Calendar实例  
Calendar cal = Calendar.getInstance();  
//1-2.判断今天是否是周日  
if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {  
//1-2-1.如果是周日,将日期回退7天以上获取上周的周一  
cal.add(Calendar.DATE, -7);  
}  
//1-3.将日期设置为周一  
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);

2.修改第一天

第二种方案就是利用Calendar提供的API重新将周一设置为第一天即可:

java 复制代码
//1.获取当前周的周一  
//1-1.创建Calendar实例  
Calendar cal = Calendar.getInstance();  
//1-2.设置周一为一周的第一天  
cal.setFirstDayOfWeek(Calendar.MONDAY);  
//1-3.将日期设置为周一  
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);  
//1-4.使用Calendar的时间戳创建一个java.sql.Date实例  
java.sql.Date currentWeekMonday = new java.sql.Date(cal.getTimeInMillis());

3.使用别的类库

相比于JDK自带的类库,现在越来越多优质的开源类库不断涌现,可以让我们轻松实现日期时间相关的操作,比如Hutool(Ps:不是广告)的星期操作默认就是以周一为第一天:

java 复制代码
  //1.获取当前周的周一
  //1-1.获取当前日期
  Date date = DateUtil.date();
  //1-2.获取本周的周一
  Date monday = DateUtil.beginOfWeek(date);

四、小结

也算是一次比较有意思的排错,但是也提醒我们日常开发中无论是使用JDK自带的类库还是开源的工具类,对于其实现还是要有所了解的,这样在遇到问题的时候才能从容应对。

相关推荐
無限進步D3 小时前
Java 运行原理
java·开发语言·入门
難釋懷3 小时前
安装Canal
java
是苏浙3 小时前
JDK17新增特性
java·开发语言
不光头强3 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp7 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多7 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood7 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员7 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai