最近博主遇到一個日期轉換不符合預期的問題。
具體現象就是一個“17JUN38”的生日想轉換為“yyyy-MM-dd”格式的日期轉成了“2038-06-17”。生日比當前時間還大,明顯出錯了。
//當時出錯的方法
private String dateSwitch(String date){ SimpleDateFormat inSdf = new SimpleDateFormat("ddMMMyy",Locale.ENGLISH); SimpleDateFormat outSdf = new SimpleDateFormat("yyyy-MM-dd"); Date paramDate = null; try { paramDate = inSdf.parse(date); } catch (ParseException e) { LOGGER.warn("",e); } return outSdf.format(paramDate); }
明明原始時間只告訴了兩位年份,能給你轉出四位年份都不錯了!還要什么自行車!摔!
博主很生氣,想跟產品理論,但是想想產品經理那40米的長刀,哎,算了我還是自己想想辦法吧。
到這里肯定有小伙伴肯定想到了,既然是生日,那肯定比當前時間小,先轉一下,如果時間比當前大,就減去100年唄。
這么簡單的解決辦法博主作為一個資深的開發能想不到么?(手動狗頭)
開個玩笑,博主還是個菜鳥,主要是為什么要減去100年呢?寫代碼這種事情很嚴肅的,不能說你看到一個現象轉換出錯的“2038-06-17”比預期的“1938-06-17”差一百年你就得出結論。不明白的代碼不能亂用,不然到時候坑人坑己(嚴肅臉)。所以博主去偷偷研究了一下parse的源代碼。
在SimpleDateFormat的構造函數最后面,會調用initialize()方法,然后initialize()方法會調用下面這么一段代碼:
/* Initialize the fields we use to disambiguate ambiguous years. Separate * so we can call it from readObject(). */ private void initializeDefaultCentury() { calendar.setTimeInMillis(System.currentTimeMillis()); calendar.add( Calendar.YEAR, -80 ); parseAmbiguousDatesAsAfter(calendar.getTime()); } /* Define one-century window into which to disambiguate dates using * two-digit years. */ private void parseAmbiguousDatesAsAfter(Date startDate) { defaultCenturyStart = startDate; calendar.setTime(startDate); defaultCenturyStartYear = calendar.get(Calendar.YEAR); }
這里會初始化一個叫做defaultCenturyStart以及defaultCenturyStartYear的屬性,請大家記住這倆字段。
public Date parse(String text, ParsePosition pos) { …… …… …… //其他代碼就略去了,有興趣的同學可以自己看看源碼// At this point the fields of Calendar have been set. Calendar // will fill in default values for missing fields when the time // is computed. pos.index = start; Date parsedDate; try { parsedDate = calb.establish(calendar).getTime(); // If the year value is ambiguous, // then the two-digit year == the default start year if (ambiguousYear[0]) { if (parsedDate.before(defaultCenturyStart)) {//------① parsedDate = calb.addYear(100).establish(calendar).getTime(); } } } // An IllegalArgumentException will be thrown by Calendar.getTime() // if any fields are out of range, e.g., MONTH == 17. catch (IllegalArgumentException e) { pos.errorIndex = start; pos.index = oldStart; return null; } return parsedDate; }
這里只粘了部分跟這個問題有關的代碼,因為篇幅太長就不一一往這里貼了,有興趣的小伙伴可以自己去看看全部的源碼哦!
這里可以看到:
If the year value is ambiguous,then the two-digit year == the default start year
要是傳入的年份描述不清楚,那就使用默認的起始年去補全
那么用來補全不完整的年份的默認起始年是個什么東東呢?這時候前面讓大家記住的defaultCenturyStartYear字段就有用了。這個字段里面存的是當前時間的年份減去80年的年份,比如說我當前2018-09-13,減80年是1938年,然后程序會用這個19去補全傳入的年份,用前面的例子,就是補成了1938-06-17。
緊接着下面有一個判斷語句①。defaultCenturyStart字段也是就是當前時間減去80年,就是1938-09-13,1938-06-17在1938-09-13之前,於是程序便進入判斷體,將年份加上100,得到了2038-06-17。
好了到了這里就破案了,所以之前的猜測有了證據支撐,變成了結論。
這個時候,如果有跟我一樣好奇的小伙伴可能會問了,為什么要用這個80作為標准呢?
其實博主猜測是編寫這個大佬經過科學計算或者什么調查統計得到的一個數字吧,當然沒有博主沒有對此做過專門的調查,並沒有發言權。我們來看看這個標准能不能修改:
/** * Sets the 100-year period 2-digit years will be interpreted as being in * to begin on the date the user specifies. * * @param startDate During parsing, two digit years will be placed in the range * <code>startDate</code> to <code>startDate + 100 years</code>. * @see #get2DigitYearStart * @since 1.2 */ public void set2DigitYearStart(Date startDate) { parseAmbiguousDatesAsAfter(new Date(startDate.getTime())); }
這里提供了一個public的方法,可自己手動設置前面黑體的兩個字段,可以根據自己的需求來做出改變。
所以最終代碼改為
private String dateSwitch(String date){ SimpleDateFormat inSdf = new SimpleDateFormat("ddMMMyy",Locale.ENGLISH); SimpleDateFormat outSdf = new SimpleDateFormat("yyyy-MM-dd"); Date paramDate = null; try { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.YEAR, -100); inSdf.set2DigitYearStart(calendar.getTime()); paramDate = inSdf.parse(date); } catch (ParseException e) { LOGGER.warn("",e); } return outSdf.format(paramDate); }
好了總結一下:
當傳入一個年份不完整的日期兩位(或者三位,一位不行),程序會用當前時間減去80年的前兩位去補全日期,得到時間A,然后跟當前時間減去80年,得到時間B。如果A在B之前,那么便將時間A做+100年運算,得到最終時間C。80年這個標准可以通過set2DigitYearStart()方法來自定義,來滿足不同的需求。
