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

简介: 在开发一个查询未完成业务的需求时,遇到了一个问题:清明节后周日提前查出了应于周一才显示的未完成业务。原因是代码中使用了`Calendar`类,默认将周日视为一周的第一天,导致当天获取的“本周一”实际上是下周一。通过调试发现,`Calendar`类的默认设置与实际需求不符。为解决此问题,提出了三种方案:1. 加入判断机制,对周日特殊处理;2. 修改`Calendar`类的设置,将周一设为一周的第一天;3. 使用其他日期处理库如Hutool,默认以周一为一周的开始。此次排错提醒我们在日常开发中需深入了解所用类库的实现细节,以便更好地应对潜在问题。

一、问题描述

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

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

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

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

二、问题排查

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

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

三、问题修复

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

1.加入判断机制

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

//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重新将周一设置为第一天即可:

//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:不是广告)的星期操作默认就是以周一为第一天:

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

四、小结

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

目录
相关文章
|
9月前
|
缓存 NoSQL Java
【📕分布式锁通关指南 11】源码剖析redisson之读写锁的实现
Redisson 的 `RedissonReadWriteLock` 提供了高效的分布式读写锁实现,适用于读多写少的场景。通过 Redis 与 Lua 脚本结合,确保读锁并行、写锁互斥,以及读写之间的互斥,保障了分布式环境下的数据一致性。它支持可重入、自动过期和锁释放机制,提升了系统并发性能与资源控制能力。
219 0
|
9月前
|
消息中间件 负载均衡 中间件
⚡ 构建真正的高性能即时通讯服务:基于 Netty 集群的架构设计与实现
本文介绍了如何基于 Netty 构建分布式即时通讯集群。随着用户量增长,单体架构面临性能瓶颈,文章对比了三种集群方案:Nginx 负载均衡、注册中心服务发现与基于 ZooKeeper 的消息路由架构。最终选择第三种方案,通过 ZooKeeper 实现服务注册发现与消息路由,并结合 RabbitMQ 支持跨服务器消息广播。文中还详细讲解了 ZooKeeper 搭建、Netty 集群改造、动态端口分配、服务注册、负载均衡及消息广播的实现,构建了一个高可用、可水平扩展的即时通讯系统。
961 0
|
8月前
|
人工智能 API 语音技术
免费版的配音软件,支持童声男声女声不同声音选项,语音转文字软件推荐支持多种声音
免费版的配音软件,支持童声男声女声不同声音选项,语音转文字软件推荐支持多种声音
2400 2
|
IDE Java 开发工具
@Transactional 你真的用对了吗?
在日常开发中,`@Transactional`注解常用于声明式事务管理,但其原理和使用不当可能引发问题。本文通过一个实际场景探讨了自调用方法时事务不生效的问题,并分析了潜在风险:数据不一致。为解决此问题,提供了三种方案:1) 将方法移动到其他服务类;2) 使用`AopContext.currentProxy()`获取代理对象;3) 通过`ApplicationContext`获取Bean。最终建议尽量避免自调用事务操作,确保数据一致性。
586 6
|
人工智能 自然语言处理 Java
对话即服务:Spring Boot整合MCP让你的CRUD系统秒变AI助手
本文介绍了如何通过Model Context Protocol (MCP) 协议将传统Spring Boot服务改造为支持AI交互的智能系统。MCP作为“万能适配器”,让AI以统一方式与多种服务和数据源交互,降低开发复杂度。文章以图书管理服务为例,详细说明了引入依赖、配置MCP服务器、改造服务方法(注解方式或函数Bean方式)及接口测试的全流程。最终实现用户通过自然语言查询数据库的功能,展示了MCP在简化AI集成、提升系统易用性方面的价值。未来,“对话即服务”有望成为主流开发范式。
8882 7
|
前端开发 Java API
SpringBoot整合Flowable【07】- 驳回节点任务
本文通过绩效流程的业务场景,详细介绍了如何在Flowable工作流引擎中实现任务驳回功能。具体步骤包括:获取目标任务节点和当前任务节点信息,进行必要的判空和逻辑校验,调用API完成节点回退,并清理相关脏数据(如历史任务和变量)。最后通过测试验证了驳回功能的正确性,确保流程能够成功回退到指定节点并清除中间产生的冗余数据。此功能在实际业务中非常有用,能够满足上级驳回自评等需求。
2513 0
SpringBoot整合Flowable【07】- 驳回节点任务
|
前端开发
`Promise.allSettled()`方法与`Promise.all()`方法有何不同?
`Promise.allSettled()` 提供了一种更灵活和全面的方式来处理多个 `Promise`,使得我们能够更好地应对各种异步操作的情况,尤其是需要详细了解每个 `Promise` 结果的场景。
|
Java Go 开发者
2023年终总结-一名23届毕业生的风雨秋招路
人生如巧克力,充满未知。23届大学生经历网课、封校后迎来秋招寒冬,笔者投递三百多家公司,最终收到三个Offer。签约中厂后,享受短暂的轻松时光。热爱编程,参加字节青训营,获技术提升与人脉积累。毕业旅行至云南时突遇毁约,但家人支持下继续前行。重新求职后选择深圳工作,入职半年收获良多。展望2024,立下多个目标,愿新的一年实现愿望。
339 4
|
机器学习/深度学习 监控
在进行多任务学习时,确保模型不会过度拟合单一任务而忽视其他任务
多任务学习(MTL)中,为避免模型过度拟合单一任务,可采取任务权重平衡、损失函数设计、正则化、早停法、交叉验证、任务无关特征学习、模型架构选择、数据增强、任务特定组件、梯度归一化、模型集成、任务选择性训练、性能监控、超参数调整、多任务学习策略、领域适应性和模型解释性分析等策略,以提高模型泛化能力和整体表现。
|
机器学习/深度学习 算法