aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorAnna Koskinen <Ansku@users.noreply.github.com>2021-01-22 13:08:02 +0200
committerGitHub <noreply@github.com>2021-01-22 13:08:02 +0200
commitb4f011230fd5c9d56a0dd7ad7c00c584e25ee990 (patch)
treeec2f885c3cc2ea200b3405703b1ef9d3cbb8bb7c /server
parentf790ba970d49cfe5c50a3b28d099227af8fcc4a6 (diff)
downloadvaadin-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')
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractDateField.java62
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java20
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java7
-rw-r--r--server/src/test/java/com/vaadin/tests/data/converter/LocalDateTimeToDateConverterTest.java11
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/abstractdatefield/AbstractLocalDateTimeFieldDeclarativeTest.java16
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java32
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