diff options
author | Anna Koskinen <Ansku@users.noreply.github.com> | 2021-01-22 13:08:02 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-22 13:08:02 +0200 |
commit | b4f011230fd5c9d56a0dd7ad7c00c584e25ee990 (patch) | |
tree | ec2f885c3cc2ea200b3405703b1ef9d3cbb8bb7c /server | |
parent | f790ba970d49cfe5c50a3b28d099227af8fcc4a6 (diff) | |
download | vaadin-framework-b4f011230fd5c9d56a0dd7ad7c00c584e25ee990.tar.gz vaadin-framework-b4f011230fd5c9d56a0dd7ad7c00c584e25ee990.zip |
DateField value should actively adjust to the set resolution. (#12183)7.7.23
Diffstat (limited to 'server')
6 files changed, 108 insertions, 40 deletions
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<T extends Temporal & TemporalAdjuster & }; /** - * The default start year (inclusive) from which to calculate the + * The default start year (inclusive) from which to calculate the * daylight-saving time zone transition dates. */ private static final int DEFAULT_START_YEAR = 1980; @@ -323,8 +323,9 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & * <p> * Note: Negative, i.e. BC dates are not supported. * <p> - * 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<T extends Temporal & TemporalAdjuster & * the resolution to set, not {@code null} */ public void setResolution(R resolution) { - this.resolution = resolution; - updateResolutions(); + if (!resolution.equals(this.resolution)) { + this.resolution = resolution; + setValue(adjustToResolution(getValue(), resolution)); + updateResolutions(); + } } /** @@ -387,8 +391,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & * validate. If {@code endDate} is set to {@code null}, any value after * {@code startDate} will be accepted by the range. * <p> - * 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<T extends Temporal & TemporalAdjuster & * inclusive) between which to calculate the daylight-saving time zone * transition dates. Both parameters are used when '{@code z}' is included * inside the {@link #setDateFormat(String)}, they would have no effect - * otherwise. Specifically, these parameters determine the range of years in - * which zone names are are adjusted to show the daylight saving names. + * otherwise. Specifically, these parameters determine the range of years in + * which zone names are are adjusted to show the daylight saving names. * * If no values are provided, by default {@link startYear} is set to * {@value #DEFAULT_START_YEAR}, and {@link endYear} is set to @@ -704,12 +708,13 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & * @param value * the new value, may be {@code null} * @throws IllegalArgumentException - * if the value is not within range bounds + * if the value is not within range bounds */ @Override public void setValue(T value) { + T adjusted = adjustToResolution(value, getResolution()); RangeValidator<T> validator = getRangeValidator(); - ValidationResult result = validator.apply(value, + ValidationResult result = validator.apply(adjusted, new ValueContext(this, this)); if (result.isError()) { @@ -718,26 +723,41 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & } else { currentErrorMessage = null; /* - * First handle special case when the client side component has a date - * string but value is null (e.g. unparsable date string typed in by the - * user). No value changes should happen, but we need to do some - * internal housekeeping. + * First handle special case when the client side component has a + * date string but value is null (e.g. unparsable date string typed + * in by the user). No value changes should happen, but we need to + * do some internal housekeeping. */ - if (value == null && !getState(false).parsable) { + if (adjusted == null && !getState(false).parsable) { /* - * Side-effects of doSetValue clears possible previous strings and - * flags about invalid input. + * Side-effects of doSetValue clears possible previous strings + * and flags about invalid input. */ doSetValue(null); markAsDirty(); return; } - super.setValue(value); + super.setValue(adjusted); } } /** + * Adjusts the given date to the given resolution. Any values that are more + * specific than the given resolution are truncated to their default values. + * + * @param date + * the date to adjust, can be {@code null} + * @param resolution + * the resolution to be used in the adjustment, can be + * {@code null} + * @return an adjusted date that matches the given resolution, or + * {@code null} if the given date, resolution, or both were + * {@code null} + */ + protected abstract T adjustToResolution(T date, R resolution); + + /** * Checks whether ISO 8601 week numbers are shown in the date selector. * * @return true if week numbers are shown, false otherwise. @@ -820,7 +840,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & .info("cannot parse " + design.attr("value") + " as date"); } - doSetValue(date); + doSetValue(adjustToResolution(date, getResolution())); } else { throw new RuntimeException("Cannot detect resoluton type " + Optional.ofNullable(dateType).map(Type::getTypeName) diff --git a/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java b/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java index 9314f5d9b9..a2d4b0f3aa 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java @@ -103,8 +103,8 @@ public abstract class AbstractLocalDateField @Override protected RangeValidator<LocalDate> 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<LocalDate> 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<LocalDateTime> 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<T extends AbstractLocalDateTimeField> extends AbstractFieldDeclarativeTest<T, LocalDateTime> { - 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<T extends Abstra "<%s show-iso-week-numbers range-end='%s' range-start='%s' " + "date-out-of-range-message='%s' resolution='%s' " + "date-format='%s' lenient parse-error-message='%s'/>", - 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<T extends Abstra throws InstantiationException, IllegalAccessException { LocalDateTime value = LocalDateTime.of(2003, 02, 27, 23, 12, 34); String design = String.format("<%s value='%s' readonly/>", - 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<LocalDateTime> 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 |