在進行資料查詢時,控制檯報了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
我們發現此人的出生日期是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點,只要不是凌晨的那幾個小時,都沒有什麼問題。