1. 程式人生 > >【Developer Log】SimpleDateFormat的parse問題、ISO-8601格式

【Developer Log】SimpleDateFormat的parse問題、ISO-8601格式

在併發處理時,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);