Skip to content

14. Beispiel 11 – Datumsumwandlung und -validierung

Die neue Anwendung verfügt über eine Datums-Eingabefunktion:

  • in [1], das Eingabeformular
  • in [2] die zurückgegebene Antwort

Die Anwendung funktioniert ähnlich wie bei der Eingabe von Ganzzahlen, daher werden wir nur auf die Punkte eingehen, die sich unterscheiden.

14.1. Das NetBeans-Projekt

Das NetBeans-Projekt sieht wie folgt aus:

  • in [1], die Anwendungsansichten
  • [Home.jsp]: die Startseite
  • [FormDate.jsp]: das Eingabeformular
  • [ConfirmationFormDate.jsp]: die Bestätigungsseite
  • in [2], die Nachrichtendatei [messages.properties] und die Hauptkonfigurationsdatei von Struts
  • in [3]:
  • [FormDate.java]: die Aktion, die das Formular anzeigt und verarbeitet
  • [FormDate-validation.xml]: die Validierungsregeln für die [FormDate]-Aktion. Diese Datei delegiert diese Validierungen an das Modell.
  • [FormDateModel]: das Modell für die [FormDate]-Aktion
  • [FormDateModel-validation.xml]: die Validierungsregeln für das Modell
  • [FormDateModel.properties]: die Meldungsdatei des Modells
  • [example.xml]: Sekundäre Struts-Konfigurationsdatei

14.2. Projektkonfiguration

Das Projekt wird in erster Linie über die folgende [example.xml]-Datei konfiguriert:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">
 
<struts>
  <package name="example" namespace="/example" extends="struts-default">
    <action name="Accueil">
      <result name="success">/example/Accueil.jsp</result>
    </action>
    <action name="FormDate" class="example.FormDate">
      <result name="input">/example/FormDate.jsp</result>
      <result name="cancel" type="redirect">/example/Accueil.jsp</result>
      <result name="success">/example/ConfirmationFormDate.jsp</result>
    </action>
  </package>
</struts>

Es ähnelt dem für die Eingabe von Ganzzahlen beschriebenen Verfahren.

14.3. Meldungsdateien

Die Datei [messages.properties] sieht wie folgt aus:


Accueil.titre=Accueil
Accueil.message=Struts 2 - Conversions et validations
Accueil.FormDate=Saisie de dates
Form.titre=Conversions et validations
FormDate.message=Struts 2 - Conversion et validation de dates
FormDate.conseil=Tapez les dates au format JJ/MM/AAAA comme dans 12/10/2008
Form.submitText=Valider
Form.cancelText=Annuler
Form.clearModel=Raz mod\u00e8le
Confirmation.titre=Confirmation
Confirmation.message=Confirmation des valeurs saisies
Confirmation.champ=champ
Confirmation.valeur=valeur
Confirmation.lien=Formulaire de test
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

Die Datei [FormDateModel.properties] sieht wie folgt aus:


date.format={0,date,dd/MM/yyyy}
date1.prompt=1-Tapez une date au format JJ/MM/AAAA
date1.error=Date erron\u00E9e
date2.prompt=2-Tapez une date au format JJ/MM/AAAA
date2.error=Date erron\u00E9e
date3.prompt=3-Tapez une date >=18/05/2000
date3.error=Date erron\u00E9e
date4.prompt=4-Tapez une date <=20/06/2001
date4.error=Date erron\u00E9e
date5.prompt=5-Tapez une date dans l''intervalle [18/05/2000,20/06/2001]
date5.error=Date errron\u00E9e
date6.prompt=6-Tapez une date dans l''intervalle [18/05/2000,20/06/2001]
date6.error=Date errron\u00E9e

Zeile 1 spielt eine wichtige Rolle. Wir werden darauf zurückkommen.

14.4. Das Eingabeformular

Die Ansicht [FormDate.jsp] sieht wie folgt aus:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Form.titre"/></title>
    <s:head/>
  </head>
 
  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="FormDate.message"/></h2>
    <h4><s:text name="FormDate.conseil"/></h4>
    <s:form name="formulaire" action="FormDate">
      <s:textfield name="date1" key="date1.prompt"/>
      <s:textfield name="date2" key="date2.prompt" value="%{#parameters['date2']!=null ? #parameters['date2'] : date2==null ? '' :getText('date.format',{date2})}"/>
      <s:textfield name="date3" key="date3.prompt" value="%{#parameters['date3']!=null ? #parameters['date3'] : date3==null ? '' :getText('date.format',{date3})}"/>
      <s:textfield name="date4" key="date4.prompt" value="%{#parameters['date4']!=null ? #parameters['date4'] : date4==null ? '' :getText('date.format',{date4})}"/>
      <s:textfield name="date5" key="date5.prompt" value="%{#parameters['date5']!=null ? #parameters['date5'] : date5==null ? '' :getText('date.format',{date5})}"/>
      <s:textfield name="date6" key="date6.prompt"/>
      <s:submit key="Form.submitText" method="execute"/>
    </s:form>
    <br/>
    <s:url id="url" action="FormDate" method="cancel"/>
    <s:a href="%{url}"><s:text name="Form.cancelText"/></s:a>
      <br/>
    <s:url id="url" action="FormDate" method="clearModel"/>
    <s:a href="%{url}"><s:text name="Form.clearModel"/></s:a>
  </body>
</html>

Die Zeilen 13 bis 18 sind die sechs Eingabefelder für das Datum. Die Felder „date2“ bis „date5“ haben ein komplexes Wertattribut, genau wie bei der Eingabe von reellen Zahlen. Da dieselben Schwierigkeiten auftraten, wurden sie auf dieselbe Weise gelöst.

14.5. Die Bestätigungsansicht

Die Bestätigungsansicht [ConfirmationFormDate.jsp] sieht wie folgt aus:

Image

Der Code lautet wie folgt:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
  <head>
    <title><s:text name="Confirmation.titre"/></title>
    <s:head/>
  </head>
 
  <body background="<s:url value="/ressources/standard.jpg"/>">
    <h2><s:text name="Confirmation.message"/></h2>
    <table border="1">
      <tr>
        <th><s:text name="Confirmation.champ"/></th>
        <th><s:text name="Confirmation.valeur"/></th>
      </tr>
      <tr>
        <td><s:text name="date1.prompt"/></td>
        <td><s:text name="date1"/></td>
      </tr>
      <tr>
        <td><s:text name="date2.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date2"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date3.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date3"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date4.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date4"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date5.prompt"/></td>
        <td>
          <s:text name="date.format">
            <s:param value="date5"/>
          </s:text>
        </td>
      </tr>
      <tr>
        <td><s:text name="date6.prompt"/></td>
        <td><s:text name="date6"/></td>
      </tr>
    </table>
    <br/>
    <s:url id="url" action="FormDate!input"/>
    <s:a href="%{url}"><s:text name="Confirmation.lien"/></s:a>
  </body>
</html>

Die Ansicht [ConfirmationFormDate.jsp] zeigt lediglich das [FormDateModel] an. Die Zeilen 47–49 zeigen, wie ein Datum angezeigt wird. Wir möchten, dass diese Anzeige einem bestimmten Datumsformat folgt. Dazu verwenden wir das <s:text>-Tag, das wir zuvor bereits für die Internationalisierung der Anwendung genutzt haben.

Hier wird der Meldungsschlüssel *date.format* verwendet. Dieser Schlüssel befindet sich in der Datei FormDateModel.properties:


date.format={0,date,dd/MM/yyyy}

Der mit dem Schlüssel verknüpfte Wert ist hier keine Meldung, sondern ein Anzeigeformat:

  • 0 ist ein Parameter, der den anzuzeigenden Wert darstellt. Hier ist dies das Feld „date5“ des Modells.
  • date steht für ein Datum. Zuvor haben wir number für eine Zahl verwendet.
  • dd/MM/yyyy steht für das Datumsformat. Hier möchten wir es im Format dd/mm/yyyy haben. Das entsprechende Java-Format lautet dd/MM/yyyy (d: Tag, M: Monat, y: Jahr)

Das Tag


<s:text name="date.format">
            <s:param value="date5"/>
</s:text>

zeigt die Meldung {0, date, dd/MM/yyyy} an, wobei der Parameter date5 (Zeile 2) den Parameter 0 im Format ersetzt. Somit wird das Modell date5 im Format dd/mm/yyyy angezeigt.

14.6. Die Vorlage [FormDateModel]

Die Felder date1 bis date6 aus dem Formular [FormDate.jsp] werden in die folgende Vorlage [FormDateModel] eingefügt:


package example;
 
import java.util.Date;
 
public class FormDateModel {
 
  // constructor without parameters
  public FormDateModel() {
  }
  // fields
  private String date1;
  private Date date2 = new Date();
  private Date date3 = new Date();
  private Date date4;
  private Date date5;
  private String date6;
 
  // raz model
  public void clearModel() {
    date1 = null;
    date2 = null;
    date3 = null;
    date4 = null;
    date5 = null;
    date6 = null;
  }
 
  // getters and setters
...
}
  • Die Felder „date1“ und „date6“ sind vom Typ String
  • die anderen Felder sind vom Typ Date

14.7. Modellvalidierung

Die Modellvalidierung wird durch zwei Dateien gesteuert: [FormDate-validation.xml] und [FormDateModel-validation.xml].

Die Datei [FormDate-validation.xml] delegiert Validierungen an [FormDateModel-validation.xml]:


<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/exemple-10/example/xwork-validator-1.0.2.dtd">
 
<validators>
  <field name="model" >
    <field-validator type="visitor">
      <param name="appendPrefix">false</param>
      <message/>
    </field-validator>
  </field>
</validators>

Wir sind dieser Datei bereits begegnet.

Die Datei [FormDateModel-validation.xml] enthält die folgenden Validierungsregeln:


<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->
 
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/exemple-11/example/xwork-validator-1.0.2.dtd">
 
<validators>
 
  <field name="date1" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="date1.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^\d{2}/\d{2}/\d{4}$</param>
      <param name="trim">true</param>
      <message key="date1.error"/>
    </field-validator>
  </field>
 
  <field name="date2" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
   </field>
 
  <field name="date3" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date3.error"/>
    </field-validator>
    <field-validator type="date" short-circuit="true">
      <param name="min">18/05/2000</param>
      <message key="date3.error"/>
    </field-validator>
  </field>
 
  <field name="date4" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date4.error"/>
    </field-validator>
    <field-validator type="date" short-circuit="true">
      <param name="max">20/06/2001</param>
      <message key="date4.error"/>
    </field-validator>
  </field>
 
 <field name="date5" >
    <field-validator type="required" short-circuit="true">
      <message key="date2.error"/>
    </field-validator>
    <field-validator type="conversion" short-circuit="true">
      <message key="date5.error"/>
    </field-validator>
    <field-validator type="date" short-circuit="true">
      <param name="min">18/05/2000</param>
      <param name="max">20/06/2001</param>
      <message key="date5.error"/>
    </field-validator>
  </field>
 
  <field name="date6" >
    <field-validator type="requiredstring" short-circuit="true">
      <message key="date6.error"/>
    </field-validator>
    <field-validator type="regex" short-circuit="true">
      <param name="expression">^\d{2}/\d{2}/\d{4}$</param>
      <param name="trim">true</param>
      <message key="date6.error"/>
    </field-validator>
  </field>
 
</validators>

  • Die Regel in den Zeilen 11–20 überprüft, ob das Eingabefeld „date1“ dem Muster eines regulären Ausdrucks entspricht, der ein Datum im Format TT/MM/JJJJ darstellt. Beachten Sie, dass wir überprüfen, ob die Zeichenfolge die Form eines Datums hat, aber nicht, ob es sich um ein gültiges Datum handelt. Somit hat „29.02.2011“ zwar die Form eines Datums, ist aber kein gültiges Datum.
  • Die Regel in den Zeilen 22–29 prüft, ob das Eingabefeld „date2“ ein gültiges Datum ist.
  • Die Regel in den Zeilen 31–42 überprüft, ob das Eingabefeld „date3“ ein gültiges Datum >= 18.05.2000 ist
  • Die Regel in den Zeilen 44–55 überprüft, ob das Eingabefeld „date4“ ein gültiges Datum am oder vor dem 20.06.2001 ist.
  • Die Regel in den Zeilen 57–69 überprüft, ob das Eingabefeld „date5“ ein gültiges Datum <= 20. Juni 2001 und >= 18. Mai 2000 ist.
  • Die Regel in den Zeilen 71–80 überprüft, ob das Feld „date6“ im Format TT/MM/JJJJ vorliegt.

Sobald die Datei [FormDateModel-validation.xml] vom Validierungs-Interceptor verarbeitet wurde, führt dieser die validate-Methode der [FormDate]-Aktion aus, sofern diese vorhanden ist. Wir werden sie zusammen mit der gesamten Aktion vorstellen.

14.8. Die Aktion [FormDate]

Die Aktion [FormDate] funktioniert wie folgt:


package example;
 
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.interceptor.validation.SkipValidation;
 
public class FormDate extends ActionSupport implements ModelDriven, SessionAware {
 
  // constructor without parameters
  public FormDate() {
  }
 
  // action model
  public Object getModel() {
    if (session.get("model") == null) {
      session.put("model", new FormDateModel());
    }
    return session.get("model");
  }
 
  @SkipValidation
  public String clearModel() {
    // close to the model
    ((FormDateModel) getModel()).clearModel();
    // result
    return INPUT;
  }
 
  public String cancel() {
    // cleaning the model
    ((FormDateModel) getModel()).clearModel();
    // result
    return "cancel";
  }
  // SessionAware
  Map<String, Object> session;
 
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
 
  // validation
  @Override
  public void validate() {
    // date formatting
    SimpleDateFormat formateurDate = new SimpleDateFormat("dd/MM/yyyy");
    formateurDate.setLenient(false);
    // enter valid date1 ?
    if (getFieldErrors().get("date1") == null) {
      // check validity date
      try {
        formateurDate.parse(((FormDateModel) getModel()).getDate1());
      } catch (Exception e) {
        addFieldError("date1", getText("date1.error"));
      }
    }
    // valid date6 entry?
    if (getFieldErrors().get("date6") == null) {
      Date d = null;
      try {
        // check validity date
        d = formateurDate.parse(((FormDateModel) getModel()).getDate6());
        // terminal check
        if (d.after(formateurDate.parse("20/06/2001")) || d.before(formateurDate.parse("18/05/2000"))) {
          addFieldError("date6", getText("date6.error"));
          return;
        }
      } catch (Exception e) {
        addFieldError("date6", getText("date6.error"));
        return;
      }
    }
  }
}

Die Aktion [FormDate] basiert auf demselben Modell wie die Aktion [FormInt]. Wir werden nur auf die Methode „validate“ eingehen. Zur Erinnerung: Die Methode „validate“ wird ausgeführt, nachdem die Validierungsdatei [FormDateModel-validation.xml] verarbeitet wurde und bevor die Methode „execute“ ausgeführt wird.

  • Die validate-Methode schließt die Validierung der Felder date1 und date6 ab. Die Validierungsdatei [FormDateModel-validation.xml] hatte bereits überprüft, ob die eingegebenen Zeichenfolgen im Datumsformat vorliegen. Wir überprüfen nun, ob es sich tatsächlich um gültige Datumsangaben handelt. Beachten Sie auch, dass date1 und date6 die einzigen beiden Felder im Modell vom Typ String waren.
  • Zeile 50: Wir erstellen ein Datumsformat dd/mm/yyyy, um zu überprüfen, ob Zeichenfolgen dieses Typs tatsächlich gültige Datumsangaben sind.
  • Zeile 51: setLenient(false) verhindert, dass das ungültige Datum 32/01/2012 als gültiges Datum 01/02/2012 interpretiert wird.
  • Zeile 53: Wir validieren date1 nur, wenn bei den vorherigen Validierungen keine Fehler in diesem Feld aufgetreten sind.
  • Zeile 56: Wir überprüfen, ob „date1“ ein gültiges Datum im Format TT/MM/JJJJ ist.
  • Zeile 58: Ist dies nicht der Fall, wird dem Feld „date1“ eine Fehlermeldung zugeordnet.
  • Zeile 62: date6 wird nur validiert, wenn dieses Feld die vorherigen Validierungen bestanden hat.
  • Zeile 66: Wir überprüfen, ob date6 ein gültiges Datum ist
  • Zeile 68: Wenn date6 < 18.05.2000 oder date6 > 20.06.2001 ist, ist date6 falsch.

14.9. Abschließende Details

Das vorherige Formular weist Mängel auf, wie das folgende Beispiel zeigt:

  • In [1] wurde ein ungültiges Datum eingegeben
  • in [2] wurde es akzeptiert. Es wurden nur die ersten Zeichen tt/mm/jjjj berücksichtigt.

Die Felder date2 bis date5, die mit Modellen vom Typ „Date“ verknüpft sind, weisen alle dieses Problem auf. Vielleicht habe ich auch hier etwas in der Dokumentation übersehen. Die Felder date1 und date6, die mit Modellen vom Typ „String“ verknüpft sind, weisen dieses Problem nicht auf.