# Prüfen, ob Datum gültig



## winter2 (28. Dez 2004)

Weiß jemand, wie man überprüfen kann, ob ein Datum, das in einem String vorliegt, einen gültigen Datumswert darstellt? Also insbesondere, daß Buchstaben als fehlerhaft erkannt werden, und auch nicht existente Daten wie "31.02.1998".
Auch solche wie "28.12.2003gg".
Sowas kann ich nicht in eine Datenbank schreiben....

Ich hatte das Thema vor Monaten hier schon, aber die gefundene Antwort taugt nicht, genausowenig wie alle anderen Lösungen im Archiv. Das folgende Beispiel:

```
try {
      java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd.MM.yyyy");
      java.util.Date d = sdf.parse(gebDat.getText());
} catch (java.text.ParseException pe) {
      // nicht gültiges Datum!
}
```

erkennt z.B. folgenden String als korrekt: "31.12.197p" als 31.12.197
oder "0.07.1997" als 30.06.1997


----------



## DP (28. Dez 2004)

was für ein feldformat hat dein feld in der db? wenn du müll reinschiebst, wirft deine datenbank eine exception...


----------



## abollm (28. Dez 2004)

Aus dem aktuellen Kalender-Thread und angepasst an dein Problem folgender, relativ simpler Code dazu:


```
...
   protected static SimpleDateFormat dateFormat = new SimpleDateFormat(
         "dd.MM.yyyy");

   ...
    // Datum-String
   String s = "01.02.2001";
      try {
         Date theDate = dateFormat.parse(s);
         System.out.println("Datumstring gewandelt: "
               + dateFormat.format(theDate));
      } catch (ParseException pe) {
         System.out.println("Eingegebenes Datum (" + s
               + ") entspricht nicht Format \"dd.MM.yyyy\"");
      }
   }
   ...
```

Man kann das natürlich noch nach Belieben verfeinern.


----------



## winter2 (28. Dez 2004)

...und wo ist der Unterschied zu meiner Methode, die schon nicht richtig funktioniert?
Die Datenbankverbindung bringt einen Fehler, genau, aber das will ich natürlich vermeiden und möglichst vorher schon abfangen, und dann eine entsprechende Meldung über das ungültige Datumsformat ausgeben.


----------



## abollm (28. Dez 2004)

winter2 hat gesagt.:
			
		

> ...und wo ist der Unterschied zu meiner Methode, die schon nicht richtig funktioniert?
> Die Datenbankverbindung bringt einen Fehler, genau, aber das will ich natürlich vermeiden und möglichst vorher schon abfangen, und dann eine entsprechende Meldung über das ungültige Datumsformat ausgeben.



Wird bei den von dir genannten (falschen) Beispielen denn keine Exception geworfen?


----------



## Bleiglanz (28. Dez 2004)

wie wärs mit einem PreparedStatement und der Methode setDate?

dann nimmt dir der JDBC Treiber wenigstens schon mal die Konvertierung java.util.Date ->  Datenbank ab?


----------



## abollm (28. Dez 2004)

EInmal unabhängig von Bleiglanz' Hinweis folgender Beispiel-Code, mit dem du vor einer Übergabe an die DB prüfen kannst, ob das Datum korrekt ist.

Zunächst die Klasse:

```
import java.util.*;

/**
 * Prüft auf gültiges Datum
 */
public class DateValidate {

	public static String stok = ".";
	/**
	 * Hier wird eine (deutsche) Datumsangabe (dd.MM.yyyy)
	 * auf Gültigkeit geprüft.
	 * Muss noch ergänzt werden, um z.B. die einzelnen
	 * Monatstage 30./31. zu überprüfen.
	 * 
	 * @param String date
	 * @return liefert "true" falls Datum gültig
	 */
	public boolean dateValidate(String date) throws Exception {

		boolean valid = true;
		int c = 1;
		int sd = 0, sm = 0, sy = 0;

		System.out.println("DV - Datum: " + date);
		StringTokenizer st = new StringTokenizer(date, stok);

		while (st.hasMoreTokens()) {
			if (c == 1) {
				sd = Integer.valueOf(st.nextToken()).intValue();
				//System.out.println("String Tag: " + sd);
			}
			if (c == 2) {
				sm = Integer.valueOf(st.nextToken()).intValue();
				//System.out.println("String Monat: " + sm);
			}
			if (c == 3) {
				sy = Integer.valueOf(st.nextToken()).intValue();
				//System.out.println("String Jahr: " + sy);
				String year = String.valueOf(sy);
				int length = year.length();
				//System.out.println("Länge: " + length);
				if (length != 4) {
					valid = false;
				}
			}
			if (c >= 4) {
				//System.out.println("Zähler c >= 4");
				valid = false;
				break;
			}
			c++;
		}
		if (valid == true) {
			try {
				//Schaltjahr
				if ((((sy % 4) == 0) && ((sy % 100) != 0) || ((sy % 400) == 0))) {
					//System.out.println("Schaltjahr");
					//Februar
					if (sm == 2) {
						//Kein gültiger Tag
						if (!(sd >= 1 && sd <= 29)) {
							valid = false;
						} else
							valid = true;
					}
				}
				//Kein Schaltjahr
				else {
					//System.out.println("kein Schaltjahr");
					//Februar
					if (sm == 2) {
                    //Kein gültiger Tag
						if (!(sd >= 1 && sd <= 28)) {
							valid = false;
						} else
							valid = true;
					}
				}
			    if (sd < 1 || sd > 31)
			    	valid = false;
			    if (sm < 1 || sm > 12)
			    	valid = false;
			} catch (Exception ee) {
				ee.printStackTrace();
			}

		}
		System.out.println("Gültigkeit: " + valid);
		return valid;
	}
}
```
Jetzt ein wenig Code zum Aufruf:

```
...
	protected static SimpleDateFormat dateFormat = new SimpleDateFormat(
			"dd.MM.yyyy");

	public static DateValidate dval = new DateValidate();

...
		// Datum-String 1 richtig!
		String s = "31.12.1998";
		try {
			Date theDate = dateFormat.parse(s);
			dateFormat.setLenient(false);

			System.out.println("Datumstring gewandelt: "
					+ dateFormat.format(theDate));
		} catch (ParseException pe) {
			System.out.println("Eingegebenes Datum (" + s
					+ ") entspricht nicht Format \"dd.MM.yyyy\"");
		}
		// Datum-String 2 falsch!
		s = "31.02.1998";
		try {
			if (dval.dateValidate(s) == false)
				System.out.println("Der Datumstring ist _nicht_ gültig!");
		} catch (Exception e) {
			System.out.println("Exception: " + e);
			//e.printStackTrace();
		}
		// Datum-String 3 falsch!
		s = "32.07.1997";
		try {
			if (dval.dateValidate(s) == false)
				System.out.println("Der Datumstring ist _nicht_ gültig!");
		} catch (Exception e) {
			System.out.println("Exception: " + e);
			//e.printStackTrace();
		}
		// Datum-String 4 falsch!
		s = "31.12.197";
		try {
			if (dval.dateValidate(s) == false)
				System.out.println("Der Datumstring ist _nicht_ gültig!");
		} catch (Exception e) {
			System.out.println("Exception: " + e);
			//e.printStackTrace();
		}
		

	}
...
```


----------



## schmeckzilla (28. Dez 2004)

Was spricht denn gegen Dateformat? Wenn man setLenient auf False setzt, dann validiert er auch ob es sich dabei um ein existierendes Datum handelt. So wird zum Beispiel der 30.02.2002 eine Exception werfen.

Ob es sich vorher, um genau die richtige Form handelt, würde ich mit einem regulären Ausdruck überprüfen.


```
public boolean checkID ( String id ) {
        DateFormat df = new SimpleDateFormat("dd.MM.yyyy");
        df.setLenient( false );
        try {
          Date d1 = df.parse(id);
          return true;
        } catch ( ParseException e ) {
            // nichts wenn falsch!
        }
        
        return false;
    }
```

@abollm
Wieso zerlegst Du den String den Selbst in einzelne Stücke und nutzt nicht nur das DateFormat.parse(String)? Was ist der Vorteil, wenn man selbst alles testet? Oder andersrum, worin liegt der Nachteil nur DateFormat zu verwenden?


----------



## schmeckzilla (28. Dez 2004)

Hab jetzt noch mal getestet, wie fehlertolerant Date und DateFormat sind.

Erst der Quelltext:

```
import java.util.*;
import java.text.*;

public class DateValidationTestDrive {
    
    public static void main (String[] args) {
        String[] testDates = { "31.02.1998", "28.12.2003gg", "0.07.1997", "31.12.197p",
        "21.02.2004", "12.22.2001", "29.02.04", "35.01.2001" };
        for (String testDate : testDates ) {
            DateFormat df = new SimpleDateFormat("dd.MM.yyyy");
            df.setLenient( false );
            System.out.print("Teste Datum: " + testDate + "...");
            try {
                // Fr Database soll gibt es java.sql.Date, dass
                // overrides java.util.Date und behandelt nur Datum
                // ohne Uhrzeiten, hab ich aber nicht getestet.
                Date d1 = df.parse(testDate);
                String okDate = df.format(d1);
                System.out.println("ok ==> Datumswert ist " + okDate);
            } catch ( ParseException e ) {
                System.out.println("nicht ok!!!");
            }    
        }    
    }
 }
```

Jetzt der Output:
Teste Datum: 31.02.1998...nicht ok!!!
Teste Datum: 28.12.2003gg...ok ==> Datumswert ist 28.12.2003
Teste Datum: 0.07.1997...nicht ok!!!
Teste Datum: 31.12.197p...ok ==> Datumswert ist 31.12.0197
Teste Datum: 21.02.2004...ok ==> Datumswert ist 21.02.2004
Teste Datum: 12.22.2001...nicht ok!!!
Teste Datum: 29.02.04...ok ==> Datumswert ist 29.02.0004
Teste Datum: 35.01.2001...nicht ok!!!

Also ein paar Deiner Fälle würde er abfangen. Aber leider nicht alle. Da es bei Datenbanken aber meistens auf den Inhalt ankommt, würde ich es da schon genau versuchen. ;-)

Also erst Länge testen, 
  if ( testDate.length() != 10 ) 
dann mit einem regulären Ausdruck den String auf die Form ZZ.ZZ.ZZZZ überprüfen (Z steht hier für Ziffer),
  Pattern p = Pattern.compile("\\d{2}\\.\\d{2}\\.\\d{4}");
und danach das ganze dann mit Date und DateFormat. 

Schon fertig.


----------



## schmeckzilla (28. Dez 2004)

Komplett sieht das ganze dann so aus. Kann man sicherlich schöner schreiben, ist aber nur der erste schnelle Entwurf.


```
import java.util.*;
import java.util.regex.*;
import java.text.*;

public class DateValidationTestDrive {
    
    public static void main (String[] args) {
        String[] testDates = { "31.02.1998", "28.12.2003gg", "0.07.1997", "31.12.197p",
        "21.02.2004", "12.22.2001", "29.02.04", "35.01.2001", "WW.11.1234" };
        for (String testDate : testDates ) {
            System.out.print("Teste Datum: " + testDate + "...");
            
            // Länge überprfen
            if ( testDate.length() != 10 ) {
                System.out.println("Nicht 10 Zeichen lang!");
                continue;
            }
            
            // String überprfen, ob "ZZ.ZZ.ZZZZ"  (Z = Ziffer)
            Pattern p = Pattern.compile("\\d{2}\\.\\d{2}\\.\\d{4}");
            Matcher m = p.matcher(testDate);
            if ( m.find() ) {
                ; // empty Statement
            } else {
                System.out.println("Entspricht nicht dem RegEx");
                continue;
            }
            
            DateFormat df = new SimpleDateFormat("dd.MM.yyyy");
            df.setLenient( false );
            
            try {
                // Für Database gibt es java.sql.Date, dass
                // overrides java.util.Date und behandelt nur Datum
                // ohne Uhrzeiten, hab ich aber nicht getestet.
                Date d1 = df.parse(testDate);
                String okDate = df.format(d1);
                System.out.println("ok ==> Datumswert ist " + okDate);
            } catch ( ParseException e ) {
                System.out.println("nicht ok!!!");
            }    
        }    
    }
   
}
```

Am besten als eigene Methode anpassen und mit dem entsprechenden Rückgabewert. In einer ernsthaften Anwendung würde ich das Datum als Stringwert zurückgeben und selbst Exception werfen, falls das Datum fehlerhaft  ist. 
1. Exceptionart: String zu kurz.
2. Exceptionart: Formatfehler
3. Exceptionart: ungültiges Datum

So kannst Du das ganze dann sauber in Deiner App catchen.


----------



## abollm (29. Dez 2004)

> @abollm
> Wieso zerlegst Du den String den Selbst in einzelne Stücke und nutzt nicht nur das DateFormat.parse(String)? Was ist der Vorteil, wenn man selbst alles testet? Oder andersrum, worin liegt der Nachteil nur DateFormat zu verwenden?



Weil ich so auch rel. einfach i. Numberformat- als auch ii. (einfache) Format-Verletzungen feststellen kann. Alle aufgelisteten Fehleingaben habe ich mit dem Code abgefangen, gleichwohl der sicherlich noch verbesserungsfähig ist.


----------



## winter2 (29. Dez 2004)

Hallo,

mit Preparedstatements kenne ich mich nicht aus, aber ich habe mir eben mal kurz die Beschreibung der Methode durchgelesen. Man könnte mit einem try-catch-Kontrukt so vorab erkennen, ob es sich um ein zuläsiges Datum handelt. Allerdings würden Werte wie 2.5.199 ebenfalls nicht richtig erkannt.

Ich denke, die Methode von schmeckzilla hat sich erledigt, das ist immer noch nicht sicher genug. Die Klasse von abollm ist aufwendig und umständlich, aber damit läßt sich alles abfangen. Jahreszahl zwischen 1900 und 2100 usw. Die Idee, das Datum durch die Methode format der Klasse SimpleDateFormat formatieren zu lassen, ist klasse - dadurch verschwinden dann auch die Buchstaben am Ende. Warum bin ich da nicht gleich selbst draufgekommen, gleich diesen String weiterzuverwenden?

Ohne die Problematik mit den unsinnigen Jahreszahlen könnte man sich das alles sparen und gleich bei der ersten Methode (zweiter Beitrag von schmeckzilla) bleiben....
Sprich, man ermittelt das Jahr mit

```
GregorianCalendar cal = new GregorianCalendar();
         cal.setTime(theDate); 
         int jahr = cal.get(Calendar.YEAR);
```

... und prüft dann das Jahr auf den Bereich. In die DB reingeschrieben wird folgender String

```
String datum = dateFormat.format(theDate);
```


----------

