【Developer Log】SimpleDateFormat的parse問題、ISO-8601格式
阿新 • • 發佈:2019-01-28
在併發處理時,SimpleDateFormat進行時間格式轉換會出現問題。本博將就問題情況以及如何進行時間轉換作為討論。
SimpleDateFormate併發parse()問題小實驗
下面是一個簡單的觀察小例子,同時提供ISO-8601時間格式的人工處理:
public class ParseTest { private final static SimpleDateFormat ISO8601_DATE = new SimpleDateFormat(ISO8601_DATEFORMAT); public static Date iso8601Parse(String iso8601string) throws ParseException{ String s = iso8601string.replace("Z", "+00:00"); int index = s.lastIndexOf(':'); s = s.substring(0,19)+ s.substring(index-3,index)+s.substring(index +1); return ISO8601_DATE.parse(s); } public void test(String timeStr){ for(int i = 0 ; i < 100; i ++){ new Thread(new Runnable() { @Override public void run() { try { Date date = iso8601Parse(timeStr); System.out.println("test1 : " + date); } catch (Exception e) { System.out.println("ERROR : test1 " + e.toString()); e.printStackTrace(); //這裡丟擲來的並不是預計的ParseException } } }).start(); } } public static void main(String[] args) throws InterruptedException { String timeStr = "2016-11-18T01:16:43.593203Z"; ParseTest t = new ParseTest(); t.test(timeStr); } }
例子很簡單,就是通過開啟執行緒,併發執行同一SimpleDateFormat物件的parse(String str)操作。執行時出現異常,如下:
報錯: java.lang.NumberFormatException: For input string: "2016E20164" at java.lang.NumberFormatException.forInputString(Unknown Source) at java.lang.Long.parseLong(Unknown Source) at java.lang.Long.parseLong(Unknown Source) at java.text.DigitList.getLong(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at cn.flowingflying.wei.test.ParseTest.iso8601Parse1(ParseTest.java:21) at cn.flowingflying.wei.test.ParseTest$1.run(ParseTest.java:38) at java.lang.Thread.run(Unknown Source) 或者 java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at cn.flowingflying.wei.test.ParseTest.iso8601Parse1(ParseTest.java:21) at cn.flowingflying.wei.test.ParseTest$1.run(ParseTest.java:38) at java.lang.Thread.run(Unknown Source) 更詭異的是看列印資訊:date翻譯出現問題 test1 : Fri Nov 18 09:16:43 CST 2016 test1 : Fri Nov 18 09:16:43 CST 2016 test1 : Sat Dec 18 09:16:43 CST 1 test1 : Fri Nov 18 09:16:43 CST 2016 test1 : Fri Nov 18 09:16:43 CST 2016 test1 : Fri Nov 18 09:16:43 CST 2016
根據情況,我們猜測SimpleDateFormat在處理parse()中很可能使用了方法之外的物件,例如是SimpleDateFormate的屬性,引發了併發執行的錯誤。當然具體的原因需要是查原始碼。不過,我們只是使用者,可以通過程式修訂來避免問題:
public class ParseTest { private final static String ISO8601_DATEFORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; //Warning : 如果採用靜態的共用SimpleDateFormat,在多執行緒的情況下,會出現不確定的解析錯誤。 //private final static SimpleDateFormat ISO8601_DATE = new SimpleDateFormat(ISO8601_DATEFORMAT); public static Date iso8601Parse(String iso8601string) throws ParseException{ String s = iso8601string.replace("Z", "+00:00"); int index = s.lastIndexOf(':'); s = s.substring(0,19)+ s.substring(index-3,index)+s.substring(index +1); return new SimpleDateFormat(ISO8601_DATEFORMAT).parse(s); } public void test(String timeStr){ for(int i = 0 ; i < 100; i ++){ new Thread(new Runnable() { @Override public void run() { try { Date date = iso8601Parse(timeStr); System.out.println("test1 : " + date); } catch (Exception e) { System.out.println("ERROR : test1 " + e.toString()); e.printStackTrace(); //這裡丟擲來的並不是預計的ParseException } } }).start(); } } public static void main(String[] args) throws InterruptedException { String timeStr = "2016-11-18T01:16:43.593203Z"; ParseTest t = new ParseTest(); t.test(timeStr); } }
問題解決。
ISO-8601的時間解析
實際上,Java1.8具有豐富的時間日期處理,可以使用OffsetDateTime和Instant來處理ISO-8601字串。
OffsetDateTime.parse(iso8601Str);
Instant.parse(iso8601Str);
時間日期格式轉換
對於格式轉換,建議使用DateTimeFormatter
private static final DateTimeFormatter TRIGGER_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime datetime = LocalDateTime.now();
datetime.format(TRIGGER_TIME_FORMATTER);