32

私は、特定の文字列が有効な日付であるかどうかを確認するために多くのチェックを実行する、作業中の共通ライブラリをサポートしています。Java API、commons-langライブラリ、およびJodaTimeにはすべて、文字列を解析して日付に変換し、それが実際に有効な日付であるかどうかを通知できるメソッドがありますが、方法があることを期待していました。実際に日付オブジェクト(またはJodaTimeライブラリの場合のようにDateTime)を作成せずに検証を行う方法。たとえば、ここにサンプルコードの簡単な部分があります:

public boolean isValidDate(String dateString) {
    SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
    try {
        df.parse(dateString);
        return true;
    } catch (ParseException e) {
        return false;
    }
}

これは私には無駄に思えます。結果のオブジェクトを破棄しています。私のベンチマークによると、この共通ライブラリでの時間の約5%は、日付の検証に費やされています。明らかなAPIが欠落していることを願っています。どんな提案も素晴らしいでしょう!

アップデート

常に同じ日付形式(おそらくyyyyMMdd)を使用できると仮定します。正規表現の使用も考えましたが、毎月の日数やうるう年などに注意する必要があります...


結果

日付を1000万回解析しました

Using Java's SimpleDateFormat: ~32 seconds 
Using commons-lang DateUtils.parseDate: ~32 seconds
Using JodaTime's DateTimeFormatter: ~3.5 seconds 
Using the pure code/math solution by Slanec: ~0.8 seconds 
Using precomputed results by Slanec and dfb (minus filling cache): ~0.2 seconds

非常に創造的な答えがいくつかありました、私はそれを感謝します!私は今、コードをどのように見せたいかをどの程度の柔軟性が必要かを決定する必要があると思います。私の最初の質問であった純粋に最速だったので、dfbの答えは正しいと言うつもりです。ありがとう!

4

8 に答える 8

16

パフォーマンスが本当に心配で、日付形式が非常に単純な場合は、すべての有効な文字列を事前に計算して、メモリにハッシュするだけです。上記のフォーマットには、2050年までに有効な組み合わせが約800万個しかありません。


Slanecによる編集-リファレンス実装

この実装は、特定の日付形式によって異なります。それはそこにある特定の日付形式に適合させることができます(私の最初の答えと同じですが、少し良いです)。

dates1900年から2050年までのすべてのセット(文字列として保存されます-それらは54787個あります)を作成し、指定された日付を保存された日付と比較します。

セットが作成されると、それはdates地獄のように速いです。簡単なマイクロベンチマークは、私の最初のソリューションに比べて10倍の改善を示しました。

private static Set<String> dates = new HashSet<String>();
static {
    for (int year = 1900; year < 2050; year++) {
        for (int month = 1; month <= 12; month++) {
            for (int day = 1; day <= daysInMonth(year, month); day++) {
                StringBuilder date = new StringBuilder();
                date.append(String.format("%04d", year));
                date.append(String.format("%02d", month));
                date.append(String.format("%02d", day));
                dates.add(date.toString());
            }
        }
    }
}

public static boolean isValidDate2(String dateString) {
    return dates.contains(dateString);
}

PS使用するように変更することSet<Integer>も、Troveを使用するように変更することもできます。TIntHashSetこれにより、メモリ使用量が大幅に削減され(したがって、はるかに長い期間を使用できるようになります)、パフォーマンスは元のソリューションのすぐ下のレベルに低下します。

于 2012-07-14T02:44:15.170 に答える
13

あなたはあなたの考えを元に戻すことができます-文字列が間違いなく日付ではないときはできるだけ早く失敗するようにしてください:

  • これはnull
  • 8でlengthはありません(日付形式の例に基づいています!)
  • 数値以外のものが含まれています(日付形式が数値の日付のみの場合)

これらのいずれにも当てはまらない場合は、解析してみてください。できれば、事前に作成された静的Formatオブジェクトを使用して、メソッドの実行ごとに作成しないでください。


コメント後に編集

この巧妙なトリックに基づいて、私は高速検証メソッドを作成しました。見た目は醜いですが、特定の日付形式に依存しDate、オブジェクトを作成しないため、通常のライブラリメソッド(標準的な状況で使用する必要があります!)よりも大幅に高速です。日付をとして扱い、それ以降もint続きます。

daysInMonth()この方法を少しだけテストしたので(うるう年の条件はPeter Lawreyから取得)、明らかなバグがないことを願っています。

迅速な(推定!)マイクロベンチマークは、30倍のスピードアップを示しました。

public static boolean isValidDate(String dateString) {
    if (dateString == null || dateString.length() != "yyyyMMdd".length()) {
        return false;
    }

    int date;
    try {
        date = Integer.parseInt(dateString);
    } catch (NumberFormatException e) {
        return false;
    }

    int year = date / 10000;
    int month = (date % 10000) / 100;
    int day = date % 100;

    // leap years calculation not valid before 1581
    boolean yearOk = (year >= 1581) && (year <= 2500);
    boolean monthOk = (month >= 1) && (month <= 12);
    boolean dayOk = (day >= 1) && (day <= daysInMonth(year, month));

    return (yearOk && monthOk && dayOk);
}

private static int daysInMonth(int year, int month) {
    int daysInMonth;
    switch (month) {
        case 1: // fall through
        case 3: // fall through
        case 5: // fall through
        case 7: // fall through
        case 8: // fall through
        case 10: // fall through
        case 12:
            daysInMonth = 31;
            break;
        case 2:
            if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
                daysInMonth = 29;
            } else {
                daysInMonth = 28;
            }
            break;
        default:
            // returns 30 even for nonexistant months 
            daysInMonth = 30;
    }
    return daysInMonth;
}

PS上記のサンプルメソッドtrueは「99999999」を返します。鉱山は既存の日付に対してのみtrueを返します:)。

于 2012-07-14T02:44:36.040 に答える
6

特定の日付が有効かどうかを知るためのより良い方法は、次のようなメソッドを定義することだと思います。

public static boolean isValidDate(String input, String format) {
    boolean valid = false;

    try {
        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
        String output = dateFormat.parse(input).format(format);
        valid = input.equals(output); 
    } catch (Exception ignore) {}

    return valid;
}

このメソッドは、日付が正しい形式であるかどうかをチェックし、もう一方では、日付が有効な日付に対応していることをチェックします。たとえば、日付「2015/02/29」は「2015/03/01」に解析されるため、入力と出力が異なり、メソッドはfalseを返します。

于 2015-06-01T16:21:01.557 に答える
2

これは、日付が正しい形式であり、実際に有効な日付であるかどうかを確認するための私の方法です。間違った日付を正しい日付に変換するためにSimpleDateFormatは必要ないと仮定しますが、代わりにメソッドはfalseを返します。コンソールへの出力は、メソッドが各ステップでどのように機能するかを確認するためにのみ使用されます。

public class DateFormat {

public static boolean validateDateFormat(String stringToValidate){
    String sdf = "yyyy-MM-dd HH:mm:ss";
    SimpleDateFormat format=new SimpleDateFormat(sdf);   
    String dateFormat = "[12]{1,1}[0-9]{3,3}-(([0]{0,1}[1-9]{1,1})|([1]{0,1}[0-2]{1,1}))-(([0-2]{0,1}[1-9]{1,1})|([3]{0,1}[01]{1,1}))[ ](([01]{0,1}[0-9]{1,1})|([2]{0,1}[0-3]{1,1}))((([:][0-5]{0,1}[0-9]{0,1})|([:][0-5]{0,1}[0-9]{0,1}))){0,2}";
    boolean isPassed = false;

    isPassed = (stringToValidate.matches(dateFormat)) ? true : false;


    if (isPassed){
        // digits are correct. Now, check that the date itself is correct
        // correct the date format to the full date format
        String correctDate = correctDateFormat(stringToValidate);
        try
        {
            Date d = format.parse(correctDate);
            isPassed = (correctDate.equals(new SimpleDateFormat(sdf).format(d))) ? true : false;
            System.out.println("In = " + correctDate + "; Out = " 
                    + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(d) + " equals = " 
                    + (correctDate.equals(new SimpleDateFormat(sdf).format(d))));
            // check that are date is less than current
            if (!isPassed || d.after(new Date())) {
                System.out.println(new SimpleDateFormat(sdf).format(d) + " is after current day " 
                        + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                isPassed = false;
            } else {
                isPassed = true;
            }
        } catch (ParseException e) {
            System.out.println(correctDate + " Exception! " + e.getMessage());
            isPassed = false;
        }
    } else {
        return false;
    }
    return isPassed;
}

/**
 *  method to fill up the values that are not full, like 2 hours -> 02 hours
 *  to avoid undesirable difference when we will compare original date with parsed date with SimpleDateFormat
 */
private static String correctDateFormat(String stringToValidate) {
    String correctDate = "";
    StringTokenizer stringTokens = new StringTokenizer(stringToValidate, "-" + " " + ":", false);
    List<String> tokens = new ArrayList<>();
    System.out.println("Inside of recognizer");
    while (stringTokens.hasMoreTokens()) {
        String token = stringTokens.nextToken();
        tokens.add(token);
        // for debug
        System.out.print(token + "|");
    }
    for (int i=0; i<tokens.size(); i++){
        if (tokens.get(i).length() % 2 != 0){
            String element = tokens.get(i);
            element = "0" + element;
            tokens.set(i, element);
        }
    }
    // build a correct final string
    // 6 elements in the date: yyyy-MM-dd hh:mm:ss
    // come through and add mandatory 2 elements
    for (int i=0; i<2; i++){
        correctDate = correctDate + tokens.get(i) + "-";
    }
    // add mandatory 3rd (dd) and 4th elements (hh)
    correctDate = correctDate + tokens.get(2) + " " + tokens.get(3);
    if (tokens.size() == 4){
        correctDate = correctDate + ":00:00";
    } else if (tokens.size() == 5){
        correctDate = correctDate + ":" + tokens.get(4) + ":00";
    } else if (tokens.size() == 6){
        correctDate = correctDate + ":" + tokens.get(4) + ":" + tokens.get(5);
    }  

    System.out.println("The full correct date format is " + correctDate);
    return correctDate;
}

}

そのためのJUnitテスト:

import static org.junit.Assert.*;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JUnitParamsRunner.class)
public class DateFormatTest {

    @Parameters
    private static final Object[] getCorrectDate() {
        return new Object[] {
                new Object[]{"2014-12-13 12:12:12"},
                new Object[]{"2014-12-13 12:12:1"},
                new Object[]{"2014-12-13 12:12:01"},
                new Object[]{"2014-12-13 12:1"},
                new Object[]{"2014-12-13 12:01"},
                new Object[]{"2014-12-13 12"},
                new Object[]{"2014-12-13 1"},
                new Object[]{"2014-12-31 12:12:01"},
                new Object[]{"2014-12-30 23:59:59"},
        };
    }
    @Parameters
    private static final Object[] getWrongDate() {
        return new Object[] {
                new Object[]{"201-12-13 12:12:12"},
                new Object[]{"2014-12- 12:12:12"},
                new Object[]{"2014- 12:12:12"},
                new Object[]{"3014-12-12 12:12:12"},
                new Object[]{"2014-22-12 12:12:12"},
                new Object[]{"2014-12-42 12:12:12"},
                new Object[]{"2014-12-32 12:12:12"},
                new Object[]{"2014-13-31 12:12:12"},
                new Object[]{"2014-12-31 32:12:12"},
                new Object[]{"2014-12-31 24:12:12"},
                new Object[]{"2014-12-31 23:60:12"},
                new Object[]{"2014-12-31 23:59:60"},
                new Object[]{"2014-12-31 23:59:50."},
                new Object[]{"2014-12-31 "},
                new Object[]{"2014-12 23:59:50"},
                new Object[]{"2014 23:59:50"}
        };
    }

    @Test
    @Parameters(method="getCorrectDate")
    public void testMethodHasReturnTrueForCorrectDate(String dateToValidate) {
        assertTrue(DateFormat.validateDateFormatSimple(dateToValidate));
    }

    @Test
    @Parameters(method="getWrongDate")
    public void testMethodHasReturnFalseForWrongDate(String dateToValidate) {
        assertFalse(DateFormat.validateDateFormat(dateToValidate));
    }

}
于 2016-02-24T06:28:43.290 に答える
1
 public static int checkIfDateIsExists(String d, String m, String y) {
        Integer[] array30 = new Integer[]{4, 6, 9, 11};
        Integer[] array31 = new Integer[]{1, 3, 5, 7, 8, 10, 12};

        int i = 0;
        int day = Integer.parseInt(d);
        int month = Integer.parseInt(m);
        int year = Integer.parseInt(y);

        if (month == 2) {
            if (isLeapYear(year)) {
                if (day > 29) {
                    i = 2; // false
                } else {
                    i = 1; // true
                }
            } else {
                if (day > 28) {
                    i = 2;// false
                } else {
                    i = 1;// true
                }
            }
        } else if (month == 4 || month == 6 || month == 9 || month == 11) {
            if (day > 30) {
                i = 2;// false
            } else {
                i = 1;// true
            }
        } else {
            i = 1;// true
        }

        return i;
    }

i = 2を返す場合は日付が無効であることを意味し、日付が有効な場合は1を返します

于 2016-11-08T08:39:23.040 に答える
1

次の行が例外をスローする場合、それは無効な日付です。それ以外の場合、これは有効な日付を返します。次のステートメントで適切なDateTimeFormatterを使用していることを確認してください。

LocalDate.parse(uncheckedStringDate、DateTimeFormatter.BASIC_ISO_DATE)

于 2017-12-27T13:51:29.940 に答える
0

dfbによる回答に基づいて、2ステップのハッシュを実行できます。

  1. 日付を表す単純なオブジェクト(日、月、年)を作成します。次の50年間のすべての暦日を計算します。これは、2万未満の異なる日付である必要があります。
  2. 入力文字列がyyyyMMddと一致するかどうかを確認する正規表現を作成しますが、値が有効な日であるかどうかは確認しません(たとえば、99999999が通過します)
  3. チェック関数は最初に正規表現を実行し、それが成功した場合は、ハッシュ関数チェックに渡します。日付オブジェクトが8ビット+8ビット+8ビット(1900年以降の年)、次に24ビット* 20kであると仮定すると、ハッシュテーブル全体はかなり小さいはずです...確かに500Kb未満であり、シリアル化されている場合はディスクからのロードが非常に高速です。圧縮されています。
于 2012-07-14T04:31:44.870 に答える
0

正規表現と手動うるう年チェックを組み合わせて使用​​できます。したがって:

if (matches ^\d\d\d\d((01|03|05|07|08|10|12)(30|31|[012]\d)|(04|06|09|11)(30|[012]\d)|02[012]\d)$)
    if (endsWith "0229")
         return true or false depending on the year being a leap year
    return true
return false
于 2013-06-18T19:07:40.807 に答える