0

I need the @Past to error when the field is set to now. I realize that the now value on the field, and the now value used when the validator is comparing would be slightly different, thus the need to set the tolerance in hibernate validator.

Problem is that i can not get this to work. Here is the junit:

@Test
public void testHibernateValidator_withPast_withTodayDate() {
    // populates with 'now'
    MyFormWithPast form = new MyFormWithPast();
    form.setDt(OffsetDateTime.now(Clock.systemUTC()));

    ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
            .configure()
            .clockProvider(() -> Clock.systemUTC())
            // adds tolerance so that when comparing, the form dt and 'now' is considered equal, 
            //   therefore dt is not a past datetime
            .temporalValidationTolerance(Duration.ofMinutes(1))
            .buildValidatorFactory();

    Validator validator = factory.getValidator();
    Set<ConstraintViolation<MyFormWithPast>> errors = validator.validate(form);

    // needs to fail, since 'now' shouldn't be considered 'past'
    assertFalse("now shoudnt be considered as Past", errors.isEmpty());
}

public static class MyFormWithPast {
    @Past
    private OffsetDateTime dt;

    public void setDt(OffsetDateTime dt) {
        this.dt = dt;
    }

    public OffsetDateTime getDt() {
        return dt;
    }
}

I expect the validation to fail when i put in 'now' in the field, as 'now' shouldnt be considered as 'past'. What did i miss ?

Bertie
  • 17,277
  • 45
  • 129
  • 182

2 Answers2

1

The temporal validation tolerance was designed to be more lenient, not stricter. You want it to be stricter.

I think you will need your own constraints to deal with what you want to do.

Guillaume Smet
  • 9,921
  • 22
  • 29
0

Just want to share my current solution, adding a default of 1 minute forward tolerance so that inputted 'now' is not considered a 'past'.

The annotation:

/**
 * Validates that the date is of the past, with forward tolerance of 1 minute, 
 *   to offset the time to create a 'now' instance to compare to.
 * The usage is when user selects 'today' in the UI, we dont want it to be considered as 'Past'
 * https://stackoverflow.com/questions/60341963/current-datetime-shouldnt-pass-the-validation-using-past-annotation
 * Annotation is applicable to {@link OffsetDateTime}.
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy=StrictPastValidator.class)
public @interface StrictPast {

    public static final String MESSAGE = "{constraints.StrictPast.message}";

    /**
     * @return The error message template.
     */
    String message() default MESSAGE;

    /**
     * @return The groups the constraint belongs to.
     */
    Class<?>[] groups() default { };

    /**
     * @return The payload associated to the constraint
     */
    Class<? extends Payload>[] payload() default {};

}

The validator:

public class StrictPastValidator implements ConstraintValidator<StrictPast, Object> {

    @Override
    public void initialize(StrictPast annotation) {
    }

    @Override
    public boolean isValid(Object input, ConstraintValidatorContext ignored) {
        if (input == null) {
             return true;
        } else if (input instanceof OffsetDateTime) {
            return isValidOffsetDateTime((OffsetDateTime) input);
        }
        throw new IllegalStateException("StrictPastValidator is not applicable to the field type " + input.getClass().getName());
    }

    private boolean isValidOffsetDateTime(OffsetDateTime input) {
        OffsetDateTime plusSecondsDt = input.plusSeconds(Duration.ofMinutes(1).getSeconds());
        return plusSecondsDt.isBefore(OffsetDateTime.now(Clock.systemUTC()));
    }

}
Bertie
  • 17,277
  • 45
  • 129
  • 182