Samstag, Februar 22, 2020

Schnellste Möglichkeit, um festzustellen, ob eine Zeichenfolge ein gültiges Datum ist

Unterstütze ich eine gemeinsame Bibliothek bei der Arbeit, dass führt viele Kontrollen aus einer angegebenen Zeichenfolge, um zu sehen, ob es ein gültiges Datum ist. Die Java-API, commons-lang-library, und JodaTime haben alle Methoden, die Parsen einen string und ein Datum, damit Sie wissen, ob es sich tatsächlich um ein gültiges Datum ist oder nicht, aber ich hatte gehofft, dass es wäre eine Art zu tun, der Validierung, ohne tatsächlich die Schaffung eines date-Objekt (oder DateTime-wie ist der Fall mit der JodaTime-Bibliothek). Zum Beispiel hier ist ein einfaches Stück Beispiel-code:

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

Scheint es einfach verschwenderisch zu mir, wir werden wegwerfen die resultierende Objekt. Aus meinen benchmarks etwa 5% unserer Zeit in diesem gemeinsamen Bibliothek verbracht Validierung der Daten. Ich hoffe, ich bin nur fehlt eine offensichtliche API. Irgendwelche Vorschläge wäre toll!

UPDATE

Davon ausgehen, dass wir immer das gleiche Datumsformat zu allen Zeiten (wahrscheinlich JJJJMMTT). Ich dachte über die Verwendung von regex, aber dann würde es brauchen, um sich bewusst von der Anzahl der Tage in jedem Monat, Schaltjahre, usw…


Ergebnisse

Analysiert eine Datum 10 Millionen mal

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

Gab es einige sehr kreative Antworten, ich weiß es zu schätzen! Ich denke, jetzt brauche ich nur zu entscheiden, wie viel Flexibilität, die ich brauche, was ich will, der code Aussehen soll. Ich werde sagen, dass der dfb die Antwort ist richtig, denn es war rein der Schnellste, das war meine ursprüngliche Frage. Danke!

Kommentare dem Thema
Auslösen von Ausnahmen eher „heavy“. Haben Sie sich überlegt mit Hilfe von regulären Ausdrücken für die Validierung ? Kommentarautor: James P.
Gut, eine Sache, die Sie tun könnten, wenn Sie am Ende mit vielen der gleichen strings zu validieren, ist eine Art von memoization Technik. Kommentarautor: JRL
Europäische Datumsformat (TT/MM/JJJJ) oder das US-Datumsformat (MM/DD/YYYY)? Viel Glück. Kommentarautor: James
Ich glaube, du hast Recht. Aber überprüfen die Zeichenfolge, die Sie benötigen würde, einen regex. Dann ist diese regex muss sich bewusst sein, das format von Datum. Wenn Sie mehr als ein format des Datums in das system, müssen Sie 2 Methoden zum validieren von Daten oder übergeben Sie die regex für jede Methode zusammen mit dem string Datum. Trotzdem, es würde mich ein Chaos. Kommentarautor: Tiago Farias
Hinzugefügt ein update für einige dieser Fragen. Kommentarautor: jjathman

InformationsquelleAutor der Frage jjathman | 2012-07-14

7 Kommentare

  1. 12

    Wenn Sie wirklich besorgt über die Leistung und Ihre Datum-format ist wirklich so einfach, genau vorherberechnet werden alle gültigen strings und hash in den Speicher. Das format, das Sie haben oben nur ~ 8 Millionen gültige Kombinationen bis 2050


    BEARBEITEN von Slanec – Referenz-Implementierung

    Dieser Umsetzung hängt von Ihren spezifischen dateformat. Es könnten angepasst werden, um jede spezifische dateformat gibt (genau wie meine erste Antwort, aber ein bisschen besser).

    Macht es eine Menge aller dates von 1900 bis 2050 (als Zeichenfolgen gespeichert werden – es gibt 54787) und vergleicht dann den angegebenen Terminen mit diesen gespeichert.

    Einmal die dates aufgebaut wird, es ist schnell wie die Hölle. Eine schnelle microbenchmark zeigte eine Verbesserung um einen Faktor 10 gegenüber meiner ersten Lösung.

    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);
    }

    P. S. Es kann geändert werden, um Set<Integer> oder sogar Fundgrube’s TIntHashSet das reduziert den Speicherverbrauch viel (und daher ermöglicht die Verwendung einer viel größeren Zeitspanne), die Leistung fällt dann auf ein Niveau knapp unterhalb meine ursprüngliche Lösung.

    InformationsquelleAutor der Antwort dfb

  2. 10

    Sie zurückgreifen können, Ihr denken – versuchen zu scheitern, so schnell wie möglich, wenn die Zeichenfolge definitiv ist kein Datum:

    Wenn keiner von denen zutreffen, dann versuchen Sie zu analysieren – vorzugsweise mit einer vorgefertigten statischen Format Objekt, nicht auf jede Methode, die ausgeführt wird.


    BEARBEITEN nach Kommentaren

    Basierend auf dieser nette trick, schrieb ich eine schnelle Validierung der Methode. Es sieht häßlich aus, aber ist deutlich schneller als die üblichen Bibliothek Methoden (die sollten in jeder standard-situation!), da es stützt sich auf Ihre bestimmten Datum format und keine Date Objekt. Es behandelt das Datum als int und geht aus.

    Getestet habe ich die daysInMonth() Methode, die nur ein bisschen (das Schaltjahr Zustand von Peter Lawrey), so dass ich hoffe, dass es keine offensichtlichen Fehler.

    Einen schnellen (geschätzt!) microbenchmark ergab sich eine Beschleunigung um einen Faktor von 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;
    }

    P. S. Dein Beispiel oben genannte Methode zurück true für „99999999“. Mir wird nur true zurückgegeben, für vorhandene Daten :).

    InformationsquelleAutor der Antwort Petr Janeček

  3. 3

    Ich denke, dass der bessere Weg zu wissen, ob zu einem bestimmten Datum gültig ist, ist die Definition einer Methode wie:

    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;
    }

    Einerseits die Methode überprüft das Datum mit dem richtigen format , und auf der anderen Seite prüft das Datum entspricht ein gültiges Datum ist . Zum Beispiel das Datum „2015/02/29“ analysiert werden, um „2015/03/01“, so die input-und output anders sein wird, und die Methode false zurückgeben.

    InformationsquelleAutor der Antwort victor.hernandez

  4. 0

    Aufbauend auf die Antwort von dfb-konnten Sie eine zwei-Schritt-hash.

    1. Erstellen Sie ein einfaches Objekt (Tag,Monat,Jahr), der ein Datum repräsentiert. Berechnen Sie jeden Kalender Tag für die nächsten 50 Jahre, die weniger als 20k verschiedene Termine.
    2. Eine regex, die bestätigt, wenn Sie Ihre Eingabe-Zeichenfolge in der Form JJJJMMTT, aber nicht überprüfen, ob der Wert ein Gültiger Tag (z.B. 99999999 passieren)
    3. Der check-Funktion wird zuerst eine regex, und wenn das gelingt — übergeben es an die hash-Funktion überprüfen. Vorausgesetzt, dass Ihr date-Objekt ist ein 8bit + 8bit + 8bit (für die Jahre nach 1900), dann 24 bit * 20k, dann wird die ganze Hashtabelle sollte ziemlich klein… sicherlich unter 500Kb, und sehr schnell zu laden von der Festplatte, wenn Sie serialisiert und komprimiert werden.

    InformationsquelleAutor der Antwort Arcymag

  5. 0

    Könnte man eine Kombination von-regex-und-Handbuch-ein Schaltjahr-überprüfung. Also:

    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

    InformationsquelleAutor der Antwort Ingo

  6. 0
     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;
        }

    wenn es gibt i = 2 bedeutet, dass das Datum ungültig ist und gibt 1 zurück, wenn das Datum gültig ist

    InformationsquelleAutor der Antwort Sudeep Singh

  7. 0

    Wenn folgende Zeile wirft exception, dann ist es ungültig Datum, sonst wird das Ergebnis gültiges Datum ist. Bitte stellen Sie sicher, dass Sie geeignete DateTimeFormatter in der folgenden Anweisung.

    LocalDate.parse(uncheckedStringDate, DateTimeFormatter.BASIC_ISO_DATE)

    InformationsquelleAutor der Antwort Rakesh

Kostenlose Online-Tests