Skip to content

Commit

Permalink
DateField value should actively adjust to the set resolution.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ansku committed Jan 19, 2021
1 parent f790ba9 commit ec5c6ed
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 40 deletions.
62 changes: 41 additions & 21 deletions server/src/main/java/com/vaadin/ui/AbstractDateField.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
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;
Expand Down Expand Up @@ -179,7 +179,7 @@ public void blur() {
};

/**
* 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;
Expand Down Expand Up @@ -323,8 +323,9 @@ protected T reconstructDateFromFields(Map<String, Integer> resolutions,
* <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
Expand Down Expand Up @@ -377,8 +378,11 @@ public R getResolution() {
* 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();
}
}

/**
Expand All @@ -387,8 +391,8 @@ public void setResolution(R resolution) {
* 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
Expand Down Expand Up @@ -545,8 +549,8 @@ private void updateTimeZoneJSON(ZoneId zoneId, Locale locale, int startYear,
* 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
Expand Down Expand Up @@ -704,12 +708,13 @@ public void setDefaultValue(T defaultValue) {
* @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()) {
Expand All @@ -718,25 +723,40 @@ public void setValue(T value) {
} 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.
*
Expand Down Expand Up @@ -820,7 +840,7 @@ public void readDesign(Element design, DesignContext designContext) {
.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)
Expand Down
20 changes: 12 additions & 8 deletions server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ protected LocalDate buildDate(
@Override
protected RangeValidator<LocalDate> getRangeValidator() {
return new DateRangeValidator(getDateOutOfRangeMessage(),
getDate(getRangeStart(), getResolution()),
getDate(getRangeEnd(), getResolution()));
adjustToResolution(getRangeStart(), getResolution()),
adjustToResolution(getRangeEnd(), getResolution()));
}

@Override
Expand Down Expand Up @@ -134,7 +134,9 @@ protected Date convertToDate(LocalDate date) {
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;
}
Expand Down Expand Up @@ -171,19 +173,21 @@ protected LocalDate toType(TemporalAccessor temporalAccessor) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ protected int getDatePart(LocalDateTime date,
@Override
protected RangeValidator<LocalDateTime> getRangeValidator() {
return new DateTimeRangeValidator(getDateOutOfRangeMessage(),
getDate(getRangeStart(), getResolution()),
getDate(getRangeEnd(), getResolution()));
adjustToResolution(getRangeStart(), getResolution()),
adjustToResolution(getRangeEnd(), getResolution()));
}

@Override
Expand Down Expand Up @@ -143,7 +143,8 @@ protected Date convertToDate(LocalDateTime date) {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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 {
Expand Down Expand Up @@ -59,7 +60,15 @@ public void useWithBinder() throws ValidationException {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,19 @@
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);
Expand All @@ -57,8 +61,8 @@ public void abstractDateFieldAttributesDeserialization()
"<%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);

Expand All @@ -82,7 +86,7 @@ public void readOnlyValue()
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -39,7 +41,9 @@ protected LocalDateTime buildDate(

@Override
protected RangeValidator<LocalDateTime> getRangeValidator() {
return null;
return new DateTimeRangeValidator(getDateOutOfRangeMessage(),
adjustToResolution(getRangeStart(), getResolution()),
adjustToResolution(getRangeEnd(), getResolution()));
}

@Override
Expand All @@ -61,6 +65,32 @@ protected String formatDate(LocalDateTime value) {
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
Expand Down
Loading

0 comments on commit ec5c6ed

Please sign in to comment.