日期范围验证

Posted

技术标签:

【中文标题】日期范围验证【英文标题】:Date range validation 【发布时间】:2013-10-06 06:08:07 【问题描述】:

我想比较两个日期(StartDate 和 EndDate)并检查一个是否在另一个之前。最简单的解决方案是在 backing bean 上执行此操作并“短路”该方法。

但是,此验证不会与其他表单验证同时发生。例如,如果我有另一个需要验证的字段(除了日期)并且输入无效,我只会收到该特定字段的消息。只有当其他字段有效时,我才会根据支持 bean 获得日期验证。

谁有解决办法?

【问题讨论】:

JSF validate on submit form的可能重复 【参考方案1】:

但是,此验证不会与其他表单验证同时发生。

支持 bean 操作方法确实不打算执行输入验证。


有人有解决办法吗?

为工作使用正确的工具;使用普通的Validator

@FacesValidator("dataRangeValidator")
public class DateRangeValidator implements Validator 
    // ...

反过来,使用单个验证器验证多个输入值确实是另一回事。基本上,您应该将其他组件或其值获取/传递到validate() 方法实现中。在最简单的形式中,您可以为此使用<f:attribute>。假设您使用<p:calendar> 来选择日期,下面是一个具体的启动示例:

<p:calendar id="startDate" binding="#startDateComponent" value="#bean.startDate" pattern="MM/dd/yyyy" required="true" />
<p:calendar id="endDate" value="#bean.endDate" pattern="MM/dd/yyyy" required="true">
    <f:validator validatorId="dateRangeValidator" />
    <f:attribute name="startDateComponent" value="#startDateComponent" />
</p:calendar>

(注意 binding 属性,它使组件在 EL 范围内完全可用给定的变量名;还请注意,此示例是原样的,您绝对不应该将它绑定到 bean财产!)

dateRangeValidator 如下所示:

@FacesValidator("dateRangeValidator")
public class DateRangeValidator implements Validator 

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException 
        if (value == null) 
            return; // Let required="true" handle.
        

        UIInput startDateComponent = (UIInput) component.getAttributes().get("startDateComponent");

        if (!startDateComponent.isValid()) 
            return; // Already invalidated. Don't care about it then.
        

        Date startDate = (Date) startDateComponent.getValue();

        if (startDate == null) 
            return; // Let required="true" handle.
        

        Date endDate = (Date) value;

        if (startDate.after(endDate)) 
            startDateComponent.setValid(false);
            throw new ValidatorException(new FacesMessage(
                FacesMessage.SEVERITY_ERROR, "Start date may not be after end date.", null));
        
    



如果你碰巧使用了JSF utility library OmniFaces,那么你也可以只使用它的&lt;o:validateOrder&gt; component。无需自定义验证器即可实现如下要求:

<p:calendar id="startDate" value="#bean.startDate" pattern="MM/dd/yyyy" required="true" />
<p:calendar id="endDate" value="#bean.endDate" pattern="MM/dd/yyyy" required="true" />
<o:validateOrder components="startDate endDate" />

另见:

JSF doesn't support cross-field validation, is there a workaround?

【讨论】:

Omnifaces 解决方案不允许在用户选择与结束日期相同的开始日期时配置行为(因为验证结束日期大于 (>) 开始日期,而不是大于或等于 (>= ))。 @MichałStochmal:它究竟是如何不允许你这样做的?根据链接的文档支持它。 对不起,我错过了。因此,对于任何询问 -> 使用接受以下值的type 属性:ltltegtgte 其中“e”允许重复。顺便说一句,感谢您的出色工作。【参考方案2】:

如果您使用可以限制最小和最大日期的 PrimeFaces。用户无法选择更大的范围这是一个示例:

<p:calendar id="startDate" value="#bean.startDate" maxdate="#bean.endDate">
     <p:ajax event="dateSelect" update="endDate"/>
</p:calendar>
<p:calendar id="endDate" value="#bean.endDate" mindate="#bean.startDate" disabled="#empty bean.startDate">
      <p:ajax event="dateSelect" update="startDate"/>
 </p:calendar>

【讨论】:

只有将readonlyInput="true" 添加到两个&lt;p:calendar&gt; 组件时才能正常工作。【参考方案3】:

像 Base 一样采用 Answer BalusC,并为我将来使用......

这可以设置间隔(年、月、周)、参考比较初始或最终。

@FacesValidator("dateRangeValidator")
public class DateRangeValidator implements Validator 

   @Override
   public void validate(FacesContext facesContext, UIComponent component,
           Object value) throws ValidatorException 

       UIInput dateIniComponent = (UIInput) component.getAttributes().get("dateIniComponent");
       UIInput dateFinComponent = (UIInput) component.getAttributes().get("dateFinComponent");
       String range = ((String) component.getAttributes().get("range")).toLowerCase();
       String reference = ((String) component.getAttributes().get("reference")).toLowerCase();

       if (value == null) 
           return;
        else if (value instanceof Date) 
           Date dateIni = null;
           Date dateFin = null;
           if ((dateIniComponent == null) && (dateFinComponent != null)) 
               if (!dateFinComponent.isValid()) 
                   return; //No hay datos contra quien comparar
               
               dateFin = (Date) dateFinComponent.getValue();
               dateIni = (Date) value;
           

           if ((dateFinComponent == null) && (dateIniComponent != null)) 
               if (!dateIniComponent.isValid()) 
                   return; //No hay datos contra quien comparar
               
               dateIni = (Date) dateIniComponent.getValue();
               dateFin = (Date) value;
           

           if ((dateIni != null) && (dateFin != null)) 
               Calendar cal = Calendar.getInstance();

               cal.setTime(dateIni);
               Integer yearIni = cal.get(Calendar.YEAR);
               Integer monthIni = cal.get(Calendar.MONTH);
               Long daysMonthIni = (long) YearMonth.of(yearIni, monthIni + 1).lengthOfMonth();
               Long daysYearIni = (long) cal.getActualMaximum(Calendar.DAY_OF_YEAR);

               cal.setTime(dateFin);
               Integer yearFin = cal.get(Calendar.YEAR);
               Integer monthFin = cal.get(Calendar.MONTH);
               Long daysMonthFin = (long) YearMonth.of(yearFin, monthFin + 1).lengthOfMonth();
               Long daysYearFin = (long) cal.getActualMaximum(Calendar.DAY_OF_YEAR);

               Long daysAllowed =
                       ("year".equals(range) ? ("ini".equals(reference)?daysYearIni:("fin".equals(reference)?daysYearFin:null)) :
                       ("month".equals(range) ? ("ini".equals(reference)?daysMonthIni:("fin".equals(reference)?daysMonthFin:null)) : 
                       ("week".equals(range) ? 7 : null)));

               Long daysBetweenDates = TimeUnit.DAYS.convert(dateFin.getTime() - dateIni.getTime(), TimeUnit.MILLISECONDS);

               if (daysAllowed == null) 
                   FacesMessage facesMessage
                           = new FacesMessage(
                                   FacesMessage.SEVERITY_ERROR,
                                   "Rango de fechas mal expresado en el facelet (vista) ",
                                   "Rango de fechas mal expresado en el facelet (vista) ");
                   throw new ValidatorException(facesMessage);
               

               if (dateFin.before(dateIni)) 
                   FacesMessage facesMessage
                           = new FacesMessage(
                                   FacesMessage.SEVERITY_ERROR,
                                   "Fecha Final No es posterior a Fecha Inicial ",
                                   "La Fecha Final debe ser posterior a Fecha Inicial");
                   throw new ValidatorException(facesMessage);
               

               if (daysBetweenDates > daysAllowed) 
                   FacesMessage facesMessage
                           = new FacesMessage(
                                   FacesMessage.SEVERITY_ERROR,
                                   "Se ha excedido los dias permitidos " + daysAllowed + " entre fechas seleccionadas, entre las fechas hay " + daysBetweenDates + " dias",
                                   "entre las fechas hay " + daysBetweenDates + " dias");
                   throw new ValidatorException(facesMessage);
               
           

       
   

现在在视图中

    <p:outputLabel value="Date Initial:" for="itHeadDateInitial" />
    <p:calendar id="itHeadDateInitial"
                navigator="true" 
                binding="#bindingDateIniComponent" 
                value="#theBean.DateIni"
                pattern="dd-MM-yyyy" mask="true" >
        <f:validator validatorId="dateRangeValidator" />
        <f:attribute name="dateFinComponent" value="#bindingDateFinComponent" />
        <f:attribute name="range" value="year" />
        <f:attribute name="reference" value="ini" />
    </p:calendar>


    <p:outputLabel value="Date Final:" for="itHeadDateFinal" />
    <p:calendar id="itHeadDateFinal"
                navigator="true" 
                binding="#bindingDateFinComponent" 
                value="#theBean.DateFin"
                pattern="dd-MM-yyyy" mask="true" >
        <f:validator validatorId="dateRangeValidator" />
        <f:attribute name="dateIniComponent" value="#bindingDateIniComponent" />
        <f:attribute name="range" value="year" />
        <f:attribute name="reference" value="ini" />
    </p:calendar>

【讨论】:

确定属性不应该是验证器的子级吗? (我自己不确定,因为我自己从来没有使用过/需要这样的东西) 我理解并且我注意到您从验证器中的组件中读取了它们,但是将它们作为验证器的属性不是更合乎逻辑吗? 虽然我认为这会更合乎逻辑,但似乎(至少在前一段时间)在技术上是不可能的......***.com/questions/21937233/… 没想到,需要复习! 在这个答案中,他们创建了一个真正的自定义验证器标签。那绝对是“更漂亮”;-)【参考方案4】:

由于 BalusC 解决方案仅在您在表单上验证一个日期范围时才有效,因此这是一项允许多个日期范围验证的改进:

在 endDate 日历组件中添加另一个 &lt;f:attribute&gt;,您可以在其中指定 startDate 组件的绑定属性名称:

<f:attribute name="bindingAttributeName" value="startDateComponent" />

然后在验证器中:

String startDateBindingAttrName = (String) component.getAttributes().get("bindingAttributeName");
UIInput startDateComponent = (UIInput) component.getAttributes().get(startDateBindingAttrName);

【讨论】:

以上是关于日期范围验证的主要内容,如果未能解决你的问题,请参考以下文章

如何在流明中验证日期范围

验证日期是不是在 SqlDbType.DateTime 范围内

自定义验证规则,检查日期范围是不是已经被占用

验证范围中日期时间的扩展方法

需要有关日期格式和验证的伪代码的帮助

使用查询查找范围内的日期