线上问题记录:因闰年导致的数据查询错误
在今天的生产环境测试中,测试发现几个数据页面显示为空白。反馈给开发后,通过查看相关接口和后台日志,发现某个查询 SQL
出现了问题,错误信息如下:
此查询功能的前后端近期没有改动,排除是改动造成的。从日志上看,导致错误的原因是无效的时间查询参数 20230229。结合业务分析,我们需要查询最近1年的数据,因为今年是闰年,2月有29天,而去年2023年的2月份只有28天,没有29号,导致 20230229
为无效参数,从而查询失败。这个问题只在2月29日出现,过了这天就暂时不会出现。
闰年的2月有29天,而非闰年的2月只有28天。这是因为闰年的定义是1年有366天,相较于平年的365天,多出来的1天被添加到2月的29号。这额外的一天是为了与地球绕太阳运动周期相匹配,确保日历和季节的协调。闰年是按照一定的规则确定的,通常是能够被4整除的年份,但如果该年份能够被100整除却不能被400整除,那么该年份就不是闰年。
在代码中查找了下,发现在查最近1年、最近3年等查询场景,都是这样的处理方法,具体简化如下:
CTime now = CTime::CurrentTime();
start.Format("%04d%02d%02d", now.GetYear()-1, now.GetMonth(), now.GetDay());
这种做法,在当天为闰2月最后一天,且同时使用年、月和日时,会触发问题,其他时间则不会触发。找到了问题的根源,解决方法也就随之而出。为了提高解决方法的适用范围,在时间类中新增接口 ChangeYear(int yearOffset)
,表示将年份偏移yearOffset
年,支持向前/向后移动,考虑闰年影响。使用新接口来修改的实例代码如下:
CTime now = CTime::CurrentTime();
now.ChangeYear(-1); // 传-1表示往前推1年
start.Format("%04d%02d%02d", now.GetYear(), now.GetMonth(), now.GetDay());
同时,为了防止类似错误再次发生,在 GetYear()、GetMonth()
接口上,增加注释,表示对年份、月份进行加减时,要注意有效性,推荐使用 ChangeYear、ChangeMoth等接口。此外,如果仅仅使用年份,没用到月份和日期,减1的做法是可以的,一旦使用到月和日,就不能简单减1了。考虑到查询场景可能由不同人来维护,保证时间处理的一致性,统一使用 ChangeYear
等接口更好。
小结
查询不同时间范围的数据是常见功能,这个闰2月的问题给我们提了个醒,获取数据查询起止时间要用正确的方法,不能简单地靠加减固定数值得到新的查询时间。软件的严谨性就是在这一个个边边角角中完善建立起来的,希望各位读者有则改之无则加勉。