3

在进行数据查询时,控制台报了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<Resident> 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

image.png

我们发现此人的出生日期是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点。

解决问题

问题找到了,解决便是最简单的一环。

  1. 找到报错的历史数据,将0点改成8点。
  2. 找到历史的代码,将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点,只要不是凌晨的那几个小时,都没有什么问题。


潘杰
3.1k 声望238 粉丝