From b4f011230fd5c9d56a0dd7ad7c00c584e25ee990 Mon Sep 17 00:00:00 2001 From: Anna Koskinen Date: Fri, 22 Jan 2021 13:08:02 +0200 Subject: DateField value should actively adjust to the set resolution. (#12183) --- .../main/java/com/vaadin/ui/AbstractDateField.java | 62 ++++++++++++++-------- .../java/com/vaadin/ui/AbstractLocalDateField.java | 20 ++++--- .../com/vaadin/ui/AbstractLocalDateTimeField.java | 7 +-- .../LocalDateTimeToDateConverterTest.java | 11 +++- .../AbstractLocalDateTimeFieldDeclarativeTest.java | 16 +++--- .../datefield/DateFieldListenersTest.java | 32 ++++++++++- 6 files changed, 108 insertions(+), 40 deletions(-) (limited to 'server') diff --git a/server/src/main/java/com/vaadin/ui/AbstractDateField.java b/server/src/main/java/com/vaadin/ui/AbstractDateField.java index 84a26436c0..dbbc19b673 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractDateField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractDateField.java @@ -38,9 +38,9 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.googlecode.gentyref.GenericTypeReflector; import org.jsoup.nodes.Element; +import com.googlecode.gentyref.GenericTypeReflector; import com.vaadin.data.Result; import com.vaadin.data.ValidationResult; import com.vaadin.data.Validator; @@ -179,7 +179,7 @@ public abstract class AbstractDateField * Note: Negative, i.e. BC dates are not supported. *

- * Note: It's usually recommended to use only one of the following at the same - * time: Range validator with Binder or DateField's setRangeStart check. + * Note: It's usually recommended to use only one of the following at the + * same time: Range validator with Binder or DateField's setRangeStart + * check. * * @param startDate * - the allowed range's start date @@ -377,8 +378,11 @@ public abstract class AbstractDateField - * Note: It's usually recommended to use only one of the following at the same - * time: Range validator with Binder or DateField's setRangeEnd check. + * Note: It's usually recommended to use only one of the following at the + * same time: Range validator with Binder or DateField's setRangeEnd check. * * @param endDate * the allowed range's end date (inclusive, based on the current @@ -545,8 +549,8 @@ public abstract class AbstractDateField validator = getRangeValidator(); - ValidationResult result = validator.apply(value, + ValidationResult result = validator.apply(adjusted, new ValueContext(this, this)); if (result.isError()) { @@ -718,25 +723,40 @@ public abstract class AbstractDateField getRangeValidator() { return new DateRangeValidator(getDateOutOfRangeMessage(), - getDate(getRangeStart(), getResolution()), - getDate(getRangeEnd(), getResolution())); + adjustToResolution(getRangeStart(), getResolution()), + adjustToResolution(getRangeEnd(), getResolution())); } @Override @@ -134,7 +134,9 @@ public abstract class AbstractLocalDateField return Date.from(date.atStartOfDay(ZoneOffset.UTC).toInstant()); } - private LocalDate getDate(LocalDate date, DateResolution forResolution) { + @Override + protected LocalDate adjustToResolution(LocalDate date, + DateResolution forResolution) { if (date == null) { return null; } @@ -171,19 +173,21 @@ public abstract class AbstractLocalDateField protected Result handleUnparsableDateString(String dateString) { // Handle possible week number, which cannot be parsed client side due // limitations in GWT - if (this.getDateFormat() != null && this.getDateFormat().contains("w")) { + if (getDateFormat() != null && getDateFormat().contains("w")) { Date parsedDate; - SimpleDateFormat df = new SimpleDateFormat(this.getDateFormat(),this.getLocale()); + SimpleDateFormat df = new SimpleDateFormat(getDateFormat(), + getLocale()); try { parsedDate = df.parse(dateString); } catch (ParseException e) { return super.handleUnparsableDateString(dateString); } - ZoneId zi = this.getZoneId(); - if (zi == null) { + ZoneId zi = getZoneId(); + if (zi == null) { zi = ZoneId.systemDefault(); } - LocalDate date = Instant.ofEpochMilli(parsedDate.getTime()).atZone(zi).toLocalDate(); + LocalDate date = Instant.ofEpochMilli(parsedDate.getTime()) + .atZone(zi).toLocalDate(); return Result.ok(date); } else { return super.handleUnparsableDateString(dateString); diff --git a/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java b/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java index 6f7037d732..39f16735a9 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java @@ -111,8 +111,8 @@ public abstract class AbstractLocalDateTimeField @Override protected RangeValidator getRangeValidator() { return new DateTimeRangeValidator(getDateOutOfRangeMessage(), - getDate(getRangeStart(), getResolution()), - getDate(getRangeEnd(), getResolution())); + adjustToResolution(getRangeStart(), getResolution()), + adjustToResolution(getRangeEnd(), getResolution())); } @Override @@ -143,7 +143,8 @@ public abstract class AbstractLocalDateTimeField return Date.from(date.toInstant(ZoneOffset.UTC)); } - private LocalDateTime getDate(LocalDateTime date, + @Override + protected LocalDateTime adjustToResolution(LocalDateTime date, DateTimeResolution forResolution) { if (date == null) { return null; diff --git a/server/src/test/java/com/vaadin/tests/data/converter/LocalDateTimeToDateConverterTest.java b/server/src/test/java/com/vaadin/tests/data/converter/LocalDateTimeToDateConverterTest.java index 6851761cfc..a9dca56c15 100644 --- a/server/src/test/java/com/vaadin/tests/data/converter/LocalDateTimeToDateConverterTest.java +++ b/server/src/test/java/com/vaadin/tests/data/converter/LocalDateTimeToDateConverterTest.java @@ -14,6 +14,7 @@ import com.vaadin.data.Binder; import com.vaadin.data.ValidationException; import com.vaadin.data.ValueContext; import com.vaadin.data.converter.LocalDateTimeToDateConverter; +import com.vaadin.shared.ui.datefield.DateTimeResolution; import com.vaadin.ui.DateTimeField; public class LocalDateTimeToDateConverterTest extends AbstractConverterTest { @@ -59,7 +60,15 @@ public class LocalDateTimeToDateConverterTest extends AbstractConverterTest { BeanWithDate bean = new BeanWithDate(); binder.writeBean(bean); - assertEquals(DATE, bean.getDate()); + assertEquals(DateTimeResolution.MINUTE, dateField.getResolution()); + + // create a comparison date that matches the resolution + Calendar calendar = Calendar + .getInstance(TimeZone.getTimeZone(ZoneOffset.UTC)); + calendar.clear(); + calendar.set(2017, Calendar.JANUARY, 1, 1, 1, 0); + Date date = calendar.getTime(); + assertEquals(date, bean.getDate()); } public static class BeanWithDate { diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstractdatefield/AbstractLocalDateTimeFieldDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/abstractdatefield/AbstractLocalDateTimeFieldDeclarativeTest.java index d4d2c397b7..de6dc322ad 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/abstractdatefield/AbstractLocalDateTimeFieldDeclarativeTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/abstractdatefield/AbstractLocalDateTimeFieldDeclarativeTest.java @@ -25,15 +25,19 @@ import com.vaadin.ui.AbstractLocalDateTimeField; public abstract class AbstractLocalDateTimeFieldDeclarativeTest extends AbstractFieldDeclarativeTest { - protected DateTimeFormatter DATE_FORMATTER = DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + // field initialised with DateTimeResolution.MINUTE, seconds get truncated + protected DateTimeFormatter VALUE_DATE_FORMATTER = DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:00", Locale.ROOT); + // only field value conforms to resolution, range keeps the initial values + protected DateTimeFormatter RANGE_DATE_FORMATTER = DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ROOT); @Override public void valueDeserialization() throws InstantiationException, IllegalAccessException { LocalDateTime value = LocalDateTime.of(2003, 02, 27, 10, 37, 43); String design = String.format("<%s value='%s'/>", getComponentTag(), - DATE_FORMATTER.format(value)); + VALUE_DATE_FORMATTER.format(value)); T component = getComponentClass().newInstance(); component.setValue(value); @@ -57,8 +61,8 @@ public abstract class AbstractLocalDateTimeFieldDeclarativeTest", - getComponentTag(), DATE_FORMATTER.format(end), - DATE_FORMATTER.format(start), dateOutOfRange, + getComponentTag(), RANGE_DATE_FORMATTER.format(end), + RANGE_DATE_FORMATTER.format(start), dateOutOfRange, resolution.name().toLowerCase(Locale.ROOT), dateFormat, parseErrorMsg); @@ -82,7 +86,7 @@ public abstract class AbstractLocalDateTimeFieldDeclarativeTest", - getComponentTag(), DATE_FORMATTER.format(value)); + getComponentTag(), VALUE_DATE_FORMATTER.format(value)); T component = getComponentClass().newInstance(); component.setValue(value); diff --git a/server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java b/server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java index 91d7384421..da7306e025 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java @@ -1,12 +1,14 @@ package com.vaadin.tests.server.component.datefield; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAccessor; import java.util.Date; import java.util.Map; import org.junit.Test; +import com.vaadin.data.validator.DateTimeRangeValidator; import com.vaadin.data.validator.RangeValidator; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; @@ -39,7 +41,9 @@ public class DateFieldListenersTest extends AbstractListenerMethodsTestBase { @Override protected RangeValidator getRangeValidator() { - return null; + return new DateTimeRangeValidator(getDateOutOfRangeMessage(), + adjustToResolution(getRangeStart(), getResolution()), + adjustToResolution(getRangeEnd(), getResolution())); } @Override @@ -61,6 +65,32 @@ public class DateFieldListenersTest extends AbstractListenerMethodsTestBase { protected LocalDateTime toType(TemporalAccessor temporalAccessor) { return LocalDateTime.from(temporalAccessor); } + + @Override + protected LocalDateTime adjustToResolution(LocalDateTime date, + DateTimeResolution forResolution) { + if (date == null) { + return null; + } + switch (forResolution) { + case YEAR: + return date.withDayOfYear(1).toLocalDate().atStartOfDay(); + case MONTH: + return date.withDayOfMonth(1).toLocalDate().atStartOfDay(); + case DAY: + return date.toLocalDate().atStartOfDay(); + case HOUR: + return date.truncatedTo(ChronoUnit.HOURS); + case MINUTE: + return date.truncatedTo(ChronoUnit.MINUTES); + case SECOND: + return date.truncatedTo(ChronoUnit.SECONDS); + default: + assert false : "Unexpected resolution argument " + + forResolution; + return null; + } + } } @Test -- cgit v1.2.3