14. 示例 11 – 日期转换与验证
新应用程序支持日期输入:
![]() |
- 在 [1] 中,输入表单
- 在 [2] 中,返回的响应
该应用程序的工作原理与整数输入类似,因此我们仅对不同之处进行说明。
14.1. NetBeans 项目
NetBeans 项目结构如下:
![]() |
- 在 [1] 中,应用程序视图
- [Home.jsp]:主页
- [FormDate.jsp]:输入表单
- [ConfirmationFormDate.jsp]:确认页面
- 在 [2] 中,消息文件 [messages.properties] 和 Struts 主配置文件
- 在 [3] 中:
- [FormDate.java]:用于显示和处理表单的操作
- [FormDate-validation.xml]:[FormDate] 操作的验证规则。该文件将这些验证委托给模型。
- [FormDateModel]:[FormDate] 操作的模型
- [FormDateModel-validation.xml]:模型的验证规则
- [FormDateModel.properties]:模型的消息文件
- [example.xml]:Struts 辅助配置文件
14.2. 项目配置
该项目主要通过以下 [example.xml] 文件进行配置:
<?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>
这与之前讨论的输入整数的情况类似。
14.3. 消息文件
[messages.properties] 文件内容如下:
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}".
[FormDateModel.properties] 文件内容如下:
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
第 1 行起着重要作用。我们稍后会再回到这一点。
14.4. 输入表单
[FormDate.jsp] 视图如下:
<%@ 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>
第 13 行至第 18 行是六个日期输入字段。date2 至 date5 字段具有 complex 值属性,这与实数输入的情况相同。由于遇到了同样的困难,因此采用了相同的方法来解决。
14.5. 确认视图
确认视图 [ConfirmationFormDate.jsp] 如下所示:

其代码如下:
<%@ 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>
[ConfirmationFormDate.jsp] 视图仅用于显示 [FormDateModel]。第 47–49 行展示了日期的显示方式。我们希望该显示遵循特定的日期格式。为此,我们使用 <s:text> 标签,该标签此前已用于应用程序的国际化处理。
此处使用的消息键为 *date.format*。该键位于 FormDateModel.properties 文件中:
date.format={0,date,dd/MM/yyyy}
此处与该键关联的值并非消息,而是一种显示格式:
- 0 是一个参数,表示要显示的值。在此处,它将对应模型的 date5 字段。
- date 代表日期。此前,我们曾使用 number 表示数字。
- dd/MM/yyyy 表示日期格式。此处,我们希望采用 dd/mm/yyyy 的形式。相应的 Java 格式为 dd/MM/yyyy(d:日,M:月,y:年)
该标签
<s:text name="date.format">
<s:param value="date5"/>
</s:text>
显示消息 {0, date, dd/MM/yyyy},其中第 2 行中的 date5 参数替换了格式中的 0 参数。因此,date5 模型将以 dd/mm/yyyy 格式显示。
14.6. [FormDateModel] 模板
来自 [FormDate.jsp] 表单的 date1 至 date6 字段被注入到以下 [FormDateModel] 模板中:
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
...
}
- date1 和 date6 字段的类型为 String
- 其余字段的类型为 Date
14.7. 模型验证
模型验证由两个文件控制:[FormDate-validation.xml] 和 [FormDateModel-validation.xml]。
[FormDate-validation.xml] 文件将验证任务委托给 [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>
我们已经遇到过这个文件。
[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-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>
- 第 11–20 行的规则用于验证 date1 输入字段是否符合正则表达式模式,该模式表示 dd/mm/yyyy 格式的日期。请注意,我们仅验证字符串是否具有日期的形式,但并不验证其是否为有效日期。因此,02/29/2011 虽然具有日期的形式,但并非有效日期。
- 第 22–29 行的规则检查 date2 输入字段是否为有效日期。
- 第 31–42 行的规则验证 date3 输入字段是否为 2000 年 5 月 18 日及之后的有效日期
- 第 44–55 行的规则验证 date4 输入字段是否为 2001 年 6 月 20 日或之前的有效日期。
- 第 57–69 行的规则验证 date5 输入字段是否为 2001 年 6 月 20 日之前且 2000 年 5 月 18 日之后的有效日期。
- 第 71–80 行中的规则验证 date6 字段的格式为 dd/mm/yyyy。
一旦 [FormDateModel-validation.xml] 文件被验证拦截器处理,后者将执行 [FormDate] 动作的 validate 方法(如果该方法存在)。我们将结合整个动作来介绍该方法。
14.8. [FormDate] 操作
[FormDate] 操作如下:
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;
}
}
}
}
[FormDate] 操作基于与 [FormInt] 操作相同的模型构建。我们仅对 validate 方法进行说明。请注意,validate 方法在处理完验证文件 [FormDateModel-validation.xml] 之后、执行 execute 方法之前执行。
- validate 方法负责完成 date1 和 date6 字段的验证。验证文件 [FormDateModel-validation.xml] 已确认输入的字符串符合日期格式。现在,我们将验证它们是否确实是有效的日期。另请注意,date1 和 date6 是模型中仅有的两个类型为 String 的字段。
- 第 50 行:我们创建日期格式 dd/mm/yyyy,以验证此类字符串是否确实为有效日期。
- 第 51 行:setLenient(false) 可防止将无效日期 32/01/2012 解释为有效日期 01/02/2012。
- 第 53 行:仅当之前的验证在此字段中未遇到任何错误时,才对 date1 进行验证。
- 第 56 行:我们验证 date1 是否为符合 dd/mm/yyyy 格式的有效日期。
- 第 58 行:如果不是这样,则在 date1 字段关联一条错误消息。
- 第 62 行:只有当该字段通过了之前的验证后,才会对 date6 进行验证。
- 第 66 行:我们验证 date6 是否为有效日期
- 第 68 行:如果 date6 < 2000/05/18 或 date6 > 2001/06/20,则 date6 不正确。
14.9. 最终细节
如以下示例所示,之前的表单存在缺陷:
![]() |
- 在[1]中,输入了一个无效的日期
- 而在[2]中,该日期被接受了。系统仅考虑了日期格式的首几个字符 dd/mm/yyyy。
与 Date 类型模型关联的 date2 至 date5 字段均存在此问题。再次说明,或许是我在文档中遗漏了某些内容。而与 String 类型模型关联的 date1 和 date6 字段则不存在此问题。


