2013-04-15 52 views
4

我想解析一個日期字符串,可以有樹不同的格式。 儘管字符串不應該匹配第二個模式,但它會以某種方式執行,因此會返回錯誤的日期。SimpleDateFormat.parse()忽略模式中的字符數

這是我的代碼:

import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 

public class Start { 

    public static void main(String[] args) { 
     SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); 
     try{ 
      System.out.println(sdf.format(parseDate("2013-01-31"))); 
     } catch(ParseException ex){ 
      System.out.println("Unable to parse"); 
     } 
    } 

    public static Date parseDate(String dateString) throws ParseException{ 
     SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); 
     SimpleDateFormat sdf2 = new SimpleDateFormat("dd-MM-yyyy"); 
     SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd"); 

     Date parsedDate; 
     try { 
      parsedDate = sdf.parse(dateString); 
     } catch (ParseException ex) { 
      try{ 
       parsedDate = sdf2.parse(dateString); 
      } catch (ParseException ex2){ 
       parsedDate = sdf3.parse(dateString);  
      } 
     } 
     return parsedDate; 
    } 
} 

與輸入2013-01-31我得到的輸出05.07.0036

如果我試圖解析31-01-201331.01.2013我得到31.01.2013如預期。

我認識到,PROGRAMM會給我一模一樣的輸出,如果我這樣設置模式:

SimpleDateFormat sdf = new SimpleDateFormat("d.M.y"); 
SimpleDateFormat sdf2 = new SimpleDateFormat("d-M-y"); 
SimpleDateFormat sdf3 = new SimpleDateFormat("y-M-d"); 

爲什麼它忽略我的模式字符的數量?

回答

2

據記載在SimpleDateFormat的Javadoc:

對於格式化,模式字母的數目是最小位數,和更短的數字是零填充到這個量。 對於解析,模式字母的數量將被忽略,除非需要分隔兩個相鄰的字段。

+0

謝謝Teetoo。我沒有在oracle.com類文檔中看到這個。這是一個很好的隱藏陷阱,我們在測試過程中沒有發現。真的是一個糟糕的設計。另一個不好的功能是默認情況下它們是「寬鬆的」,所以你需要setLenient(false)。否則,他們會將各種垃圾解釋爲有效的日期/時間信息。 –

0

謝謝@Teetoo。 ,幫助我找到了解決我的問題:

如果我想解析功能,我的SimpleDateFormat的完全匹配模式我必須設置「網開一面」(SimpleDateFormat.setLenient)到false

SimpleDateFormat sdf = new SimpleDateFormat("d.M.y"); 
sdf.setLenient(false); 
SimpleDateFormat sdf2 = new SimpleDateFormat("d-M-y"); 
sdf2.setLenient(false); 
SimpleDateFormat sdf3 = new SimpleDateFormat("y-M-d"); 
sdf3.setLenient(false); 

如果我只爲每個細分受衆羣使用一個模式字母,這仍然會解析日期,但它會認識到2013年不能是一天,因此它不符合第二種模式。 結合長度檢查我準確地收集我想要的。

2

一種解決方法可能是用正則表達式來測試YYYY-MM-DD格式:

public static Date parseDate(String dateString) throws ParseException { 
    SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); 
    SimpleDateFormat sdf2 = new SimpleDateFormat("dd-MM-yyyy"); 
    SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd"); 

    Date parsedDate; 
    try { 
     if (dateString.matches("\\d{4}-\\d{2}-\\d{2}")) { 
      parsedDate = sdf3.parse(dateString); 
     } else { 
      throw new ParseException("", 0); 
     } 
    } catch (ParseException ex) { 
     try { 
      parsedDate = sdf2.parse(dateString); 
     } catch (ParseException ex2) { 
      parsedDate = sdf.parse(dateString); 
     } 
    } 
    return parsedDate; 
} 
8

沒有與SimpleDateFormat的幾個嚴重的問題。默認的寬鬆設置可以產生垃圾回答,我不能想到寬大處理有任何好處的情況。這絕不應該是默認設置。但是,放寬寬鬆只是解決方案的一部分。你仍然可以結束在測試中難以捕捉的垃圾結果。有關示例,請參閱下面的代碼中的註釋。

這是SimpleDateFormat的一個擴展,強制嚴格模式匹配。這應該是該類的默認行爲。

import java.text.DateFormatSymbols; 
import java.text.ParseException; 
import java.text.ParsePosition; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.Locale; 

/** 
* Extension of SimpleDateFormat that implements strict matching. 
* parse(text) will only return a Date if text exactly matches the 
* pattern. 
* 
* This is needed because SimpleDateFormat does not enforce strict 
* matching. First there is the lenient setting, which is true 
* by default. This allows text that does not match the pattern and 
* garbage to be interpreted as valid date/time information. For example, 
* parsing "2010-09-01" using the format "yyyyMMdd" yields the date 
* 2009/12/09! Is this bizarre interpretation the ninth day of the 
* zeroth month of 2010? If you are dealing with inputs that are not 
* strictly formatted, you WILL get bad results. You can override lenient 
* with setLenient(false), but this strangeness should not be the default. 
* 
* Second, setLenient(false) still does not strictly interpret the pattern. 
* For example "2010/01/5" will match "yyyy/MM/dd". And data disagreement like 
* "1999/2011" for the pattern "yyyy/yyyy" is tolerated (yielding 2011). 
* 
* Third, setLenient(false) still allows garbage after the pattern match. 
* For example: "20100901" and "20100901andGarbage" will both match "yyyyMMdd". 
* 
* This class restricts this undesirable behavior, and makes parse() and 
* format() functional inverses, which is what you would expect. Thus 
* text.equals(format(parse(text))) when parse returns a non-null result. 
* 
* @author zobell 
* 
*/ 
public class StrictSimpleDateFormat extends SimpleDateFormat { 

    protected boolean strict = true; 

    public StrictSimpleDateFormat() { 
     super(); 
     setStrict(true); 
    } 

    public StrictSimpleDateFormat(String pattern) { 
     super(pattern); 
     setStrict(true); 
    } 

    public StrictSimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) { 
     super(pattern, formatSymbols); 
     setStrict(true); 
    } 

    public StrictSimpleDateFormat(String pattern, Locale locale) { 
     super(pattern, locale); 
     setStrict(true); 
    } 

    /** 
    * Set the strict setting. If strict == true (the default) 
    * then parsing requires an exact match to the pattern. Setting 
    * strict = false will tolerate text after the pattern match. 
    * @param strict 
    */ 
    public void setStrict(boolean strict) { 
     this.strict = strict; 
     // strict with lenient does not make sense. Really lenient does 
     // not make sense in any case. 
     if (strict) 
      setLenient(false); 
    } 

    public boolean getStrict() { 
     return strict; 
    } 

    /** 
    * Parse text to a Date. Exact match of the pattern is required. 
    * Parse and format are now inverse functions, so this is 
    * required to be true for valid text date information: 
    * text.equals(format(parse(text)) 
    * @param text 
    * @param pos 
    * @return 
    */ 
    @Override 
    public Date parse(String text, ParsePosition pos) { 
     int posIndex = pos.getIndex(); 
     Date d = super.parse(text, pos); 
     if (strict && d != null) { 
      String format = this.format(d); 
      if (posIndex + format.length() != text.length() || 
       !text.endsWith(format)) { 
       d = null; // Not exact match 
      } 
     } 
     return d; 
    } 
} 
+1

很好的實施。但是我不會用'setStrict'來對付'setLenient',因爲這會造成冗餘。更好的方法是堅持'setLenient'超級方法,讓構造函數調用'setLenient(false)',解析方法應該調用'!isLenient()'而不是'strict'。有了冗餘,就有可能出現不一致。想象一下:'setStrict(true); setLenient(true);'*現在是嚴格還是寬鬆?* – ADTC

+0

setStrict(true)覆蓋並隱藏寬鬆設置。設置setLenient(false)不會改變外部行爲,但它可以允許內部解析()在我的parse()將拒絕的奇怪日期上成功。 setStrict(false)將返回標準的SimpleDateFormat行爲,其中包含您選擇的任何寬鬆變體。我的目標是對SimpleDateFormat進行真正改變的候選人,它允許用戶恢復舊的不良行爲。我希望strict = true會是默認的,因爲它符合人們的理解。 –