在进行数据查询时,控制台报了Caused by: com.mysql.cj.exceptions.WrongArgumentException: HOUR_OF_DAY: 0 -> 1
异常,查询得知:这是由于查mysql库,转换类型为datetime
类型的字段引起的。
网上的解决方案有多种,大多数都是通过设置时区来解决的,但遗憾的是通过测试我发现即使在将数据跑在时区正确的数据库上,在执行起来我这仍然出错。
最后发现我的问题出现在夏令时上。
夏令时
记得小时候过过几个夏令时,大概的意思就是在某一天把表调快1个小时,然后再到某一天把表再调慢1个小时。这直接造成的问题的是:xxxx年xx月xx日会对应上两个时间戳。
比如我们假设把表调慢的那一天是2021年10月4日的12点。具体的操作是当时钟第一次经过2021年10月4日12点时,我们把表调到2021年10月4日11点。所以在2021年10月4日11点至12点,我们会重新过一次。
关于时间这块,曾经回达到一个时间戳为负的问题,也有那么点意思:https://segmentfault.com/q/1010000038248983,赶兴趣的可以看看。
那么问题来了,比我们记录用户的出生时间,精确到分钟。如果这个人录入的是2021年10月4日11点20分,那我们的系统没有办法来准确的判断这个时间是第一个11点20分,还是过1小时后的第二个11点20分。
夏令时,还给我们带来的另一个问题。有些时间是对应不上时间戳的。
再比如我们设置在2021年5月1日0时,将表调快1时,则在历史上不会出现2021年5月1日0时至1时的时间,所以如果我们统计出生时间点,用户写的是:2021年5月1日0时30分,则该数据必须是个假数据。
排查
夏令时讲完后,我们讲下排查过程。其实并不是所有的数据在查询时,都会报这种异常,所以要把那个特殊的点找出来,这里给一种最笨的展示方法:
boolean last = false;
int page = 0;
Pageable pageable = PageRequest.of(page, 1);
while (!last) {
try {
Page residents = this.residentRepository.findAll(specification, pageable);
page++;
pageable = PageRequest.of(page, 1);
last = residents.isLast();
} catch (Exception e) {
last = true;
e.printStackTrace();
this.logger.info("当前页" + pageable.getPageNumber());
}
}
最终控制台打印信息:2021-11-04 13:25:38.562 INFO 4226 --- [nio-8081-exec-7] c.y.s.service.ResidentServiceImpl : 当前页1089
然后我们去数据表中把这条记录查出来:
select * FROM resident limit 1089, 1
我们发现此人的出生日期是1947年4月15日0时0分0分。其实这个日期用户仅仅是输入了1947-4-15,只是我们存的时候自动添加了0时0分0秒。但恰巧,这个数字对应的时间戳,它恰恰就是一个无效数字。
测试代码如下:
@Test
void time() {
Calendar calendar = Calendar.getInstance();
// 启用严格检查模式
calendar.setLenient(false);
calendar.set(1947, 3, 15, 0, 0, 0);
System.out.println(calendar.getTime());
}
异常内容:java.lang.IllegalArgumentException: HOUR_OF_DAY: 0 -> 1
它是在说:你说自己是0点出生的,但是本JAVA大牛查了一下,1947年4月15日就没有0点,当天的最小值是1点。
解决问题
问题找到了,解决便是最简单的一环。
- 找到报错的历史数据,将0点改成8点。
- 找到历史的代码,将0点改成8点。
public static Timestamp getTimeStampFormIdNumber(String idNumber) {
// 进行出生日期赋值
int year = Integer.valueOf(idNumber.substring(6, 10));
int month = Integer.valueOf(idNumber.substring(10, 12));
int day = Integer.valueOf(idNumber.substring(12, 14));
Calendar calendar = Calendar.getInstance();
- calendar.set(year, month - 1, day, 0, 0, 0);
+ calendar.set(year, month - 1, day, 12, 0, 0);
return new Timestamp(calendar.getTimeInMillis());
}
至于为什么改成12点,是由于我发现夏令时的修改(调快或调慢)都规避了12点,所以时间为12点,则可以有效避免无效时间戳的问题.当然了,你还可以改成13点,只要不是凌晨的那几个小时,都没有什么问题。