4

ツリーの異なる形式を持つことができる日付文字列を解析しようとしています。String は 2 番目のパターンと一致するべきではありませんが、何らかの形で一致するため、間違った日付が返されます。

それが私のコードです:

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-2013、期待どおり31.01.2013になり31.01.2013ます。

次のようにパターンを設定すると、プログラムがまったく同じ出力を提供することを認識しました。

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

パターン内の文字数が無視されるのはなぜですか?

4

5 に答える 5

12

SimpleDateFormat には深刻な問題があります。デフォルトの寛大な設定では、ガベージな回答が生成される可能性があり、寛大な設定が役立つケースは考えられません。寛大な設定は、人間が入力した日付のバリエーションを合理的に解釈するための信頼できる方法ではありません。これがデフォルト設定であってはなりません。

可能であれば、代わりに DateTimeFormatter を使用してください。Ole VV の回答を参照してください。この新しいアプローチは優れており、スレッドセーフで不変のインスタンスを生成します。スレッド間で 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) {
        Date d = super.parse(text, pos);
        if (strict && d != null) {
           String format = this.format(d);
           if (pos.getIndex() + format.length() != text.length() ||
                 !text.endsWith(format)) {
              d = null; // Not exact match
           }
        }
        return d;
    }
}
于 2013-10-21T19:26:10.703 に答える
0

ありがとう@Teetoo。それは私の問題の解決策を見つけるのに役立ちました:

parse 関数をパターンに正確に一致させたい場合SimpleDateFormat.setLenientは、SimpleDateFormat の "lenient" () を次のように設定する必要があり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);

これは、セグメントごとにパターン文字を 1 つしか使用しない場合でも日付を解析しますが、2013 年はその日ではないため、2 番目のパターンと一致しないことが認識されます。長さのチェックと組み合わせて、私が欲しいものを正確に受け取ります.

于 2013-04-15T12:23:41.847 に答える