diff options
13 files changed, 214 insertions, 78 deletions
diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html index 6e1d221b31..20dd5d53c2 100644 --- a/all/src/main/templates/release-notes.html +++ b/all/src/main/templates/release-notes.html @@ -98,6 +98,7 @@ <h2 id="incompatible">Incompatible or Behavior-altering Changes in @version-minor@</h2> <li><tt>BindingBuilder</tt> will now automatically mark bound field <i>read-only</i> when bound to a read-only property or a <tt>null</tt> setter.</li> + <li>Date range limits in <tt>AbstractDateFieldState</tt> are now <tt>String</tt>s instead of <tt>Date</tt>s, some client-side method signatures were changed</li> <h2>For incompatible or behavior-altering changes in 8.3, please see <a href="https://vaadin.com/download/release/8.3/8.3.0/release-notes.html#incompatible">8.3 release notes</a></h2> diff --git a/client/src/main/java/com/vaadin/client/DateTimeService.java b/client/src/main/java/com/vaadin/client/DateTimeService.java index 4883d34caf..50d5f9ed2d 100644 --- a/client/src/main/java/com/vaadin/client/DateTimeService.java +++ b/client/src/main/java/com/vaadin/client/DateTimeService.java @@ -60,6 +60,17 @@ public class DateTimeService { setLocale(locale); } + /** + * Utility method to format positive int as zero-padded two-digits number + * + * @param i the value + * @return "00".."99" + * @since + */ + public static String asTwoDigits(int i) { + return (i < 10 ? "0" : "") + i; + } + public void setLocale(String locale) throws LocaleNotLoadedException { if (!LocaleService.getAvailableLocales().contains(locale)) { throw new LocaleNotLoadedException(locale); diff --git a/client/src/main/java/com/vaadin/client/ui/CalendarEntry.java b/client/src/main/java/com/vaadin/client/ui/CalendarEntry.java index d7bea84758..1efe0b6cf8 100644 --- a/client/src/main/java/com/vaadin/client/ui/CalendarEntry.java +++ b/client/src/main/java/com/vaadin/client/ui/CalendarEntry.java @@ -20,6 +20,8 @@ import java.util.Date; import com.vaadin.client.DateTimeService; +import static com.vaadin.client.DateTimeService.asTwoDigits; + public class CalendarEntry { private final String styleName; private Date start; @@ -137,8 +139,4 @@ public class CalendarEntry { return s; } - private static String asTwoDigits(int i) { - return (i < 10 ? "0" : "") + i; - } - } diff --git a/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java b/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java index 8810b26e30..c792d0a2cc 100644 --- a/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java @@ -60,6 +60,8 @@ import com.vaadin.client.WidgetUtil; import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.shared.util.SharedUtil; +import static com.vaadin.client.DateTimeService.asTwoDigits; + /** * Abstract calendar panel to show and select a date using a resolution. The * class is parameterized by the date resolution enumeration type. @@ -719,21 +721,23 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>> return true; } - Date valueDuplicate = (Date) date.clone(); - Date rangeStartDuplicate = (Date) rangeStart.clone(); + String dateStrResolution = dateStrResolution(date, minResolution); + return rangeStart.substring(0,dateStrResolution.length()) + .compareTo(dateStrResolution) <=0; + } - if (isYear(minResolution)) { - return valueDuplicate.getYear() >= rangeStartDuplicate.getYear(); + private String dateStrResolution(Date date, R minResolution) { + String dateStrResolution = (1900 + date.getYear()) + ""; + while (dateStrResolution.length() < 4) { + dateStrResolution = "0" + dateStrResolution; } - if (isMonth(minResolution)) { - valueDuplicate = clearDateBelowMonth(valueDuplicate); - rangeStartDuplicate = clearDateBelowMonth(rangeStartDuplicate); - } else { - valueDuplicate = clearDateBelowDay(valueDuplicate); - rangeStartDuplicate = clearDateBelowDay(rangeStartDuplicate); + if (!isYear(minResolution)) { + dateStrResolution += "-" + asTwoDigits(1 + date.getMonth()); + if (!isMonth(minResolution)) { + dateStrResolution += "-" + asTwoDigits(date.getDate()); + } } - - return !rangeStartDuplicate.after(valueDuplicate); + return dateStrResolution; } /** @@ -755,22 +759,9 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>> return true; } - Date valueDuplicate = (Date) date.clone(); - Date rangeEndDuplicate = (Date) rangeEnd.clone(); - - if (isYear(minResolution)) { - return valueDuplicate.getYear() <= rangeEndDuplicate.getYear(); - } - if (isMonth(minResolution)) { - valueDuplicate = clearDateBelowMonth(valueDuplicate); - rangeEndDuplicate = clearDateBelowMonth(rangeEndDuplicate); - } else { - valueDuplicate = clearDateBelowDay(valueDuplicate); - rangeEndDuplicate = clearDateBelowDay(rangeEndDuplicate); - } - - return !rangeEndDuplicate.before(valueDuplicate); - + String dateStrResolution = dateStrResolution(date, minResolution); + return rangeEnd.substring(0,dateStrResolution.length()) + .compareTo(dateStrResolution) >= 0; } private static Date clearDateBelowMonth(Date date) { @@ -1691,14 +1682,32 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>> * @param date */ private Date adjustDateToFitInsideRange(Date date) { - if (rangeStart != null && rangeStart.after(date)) { - date = (Date) rangeStart.clone(); - } else if (rangeEnd != null && rangeEnd.before(date)) { - date = (Date) rangeEnd.clone(); + if(!isAcceptedByRangeStart(date,resolution)) { + date = parseRangeString(rangeStart); + } else + if(!isAcceptedByRangeEnd(date,resolution)) { + date = parseRangeString(rangeEnd); } return date; } + private Date parseRangeString(String dateStr) { + if(dateStr == null || "".equals(dateStr)) return null; + int year = Integer.parseInt(dateStr.substring(0,4)) - 1900; + int month = parsePart(dateStr, 5, 2,1) - 1; + int day = parsePart(dateStr, 8, 2,1); + int hrs = parsePart(dateStr, 11, 2,0); + int min = parsePart(dateStr, 14, 2,0); + int sec = parsePart(dateStr, 17, 2,0); + + return new Date(year,month,day,hrs,min,sec); + } + + private int parsePart(String dateStr, int beginIndex, int length, int defValue) { + if(dateStr.length() < beginIndex + length) return defValue; + return Integer.parseInt(dateStr.substring(beginIndex, beginIndex + length)); + } + /** * Sets the data of the Panel. * @@ -1914,9 +1923,9 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>> private static final String SUBPART_DAY = "day"; private static final String SUBPART_MONTH_YEAR_HEADER = "header"; - private Date rangeStart; + private String rangeStart; - private Date rangeEnd; + private String rangeEnd; @Override public String getSubPartName( @@ -2070,7 +2079,7 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>> * @param newRangeStart * - the allowed range's start date */ - public void setRangeStart(Date newRangeStart) { + public void setRangeStart(String newRangeStart) { if (!SharedUtil.equals(rangeStart, newRangeStart)) { rangeStart = newRangeStart; if (initialRenderDone) { @@ -2088,7 +2097,7 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>> * @param newRangeEnd * - the allowed range's end date */ - public void setRangeEnd(Date newRangeEnd) { + public void setRangeEnd(String newRangeEnd) { if (!SharedUtil.equals(rangeEnd, newRangeEnd)) { rangeEnd = newRangeEnd; if (initialRenderDone) { diff --git a/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java b/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java index 4df4d0e325..bccf867d4e 100644 --- a/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java @@ -599,7 +599,7 @@ public abstract class VAbstractPopupCalendar<PANEL extends VAbstractCalendarPane * @param rangeStart * - the allowed range's start date */ - public void setRangeStart(Date rangeStart) { + public void setRangeStart(String rangeStart) { calendar.setRangeStart(rangeStart); } @@ -610,7 +610,7 @@ public abstract class VAbstractPopupCalendar<PANEL extends VAbstractCalendarPane * @param rangeEnd * - the allowed range's end date */ - public void setRangeEnd(Date rangeEnd) { + public void setRangeEnd(String rangeEnd) { calendar.setRangeEnd(rangeEnd); } diff --git a/client/src/main/java/com/vaadin/client/ui/VDateTimeCalendarPanel.java b/client/src/main/java/com/vaadin/client/ui/VDateTimeCalendarPanel.java index 5e9732e091..5dae92564a 100644 --- a/client/src/main/java/com/vaadin/client/ui/VDateTimeCalendarPanel.java +++ b/client/src/main/java/com/vaadin/client/ui/VDateTimeCalendarPanel.java @@ -79,8 +79,6 @@ public class VDateTimeCalendarPanel /** * Constructs the ListBoxes and updates their value * - * @param redraw - * Should new instances of the listboxes be created */ private void buildTime() { clear(); @@ -89,11 +87,11 @@ public class VDateTimeCalendarPanel if (getDateTimeService().isTwelveHourClock()) { hours.addItem("12"); for (int i = 1; i < 12; i++) { - hours.addItem(asTwoDigits(i)); + hours.addItem(DateTimeService.asTwoDigits(i)); } } else { for (int i = 0; i < 24; i++) { - hours.addItem(asTwoDigits(i)); + hours.addItem(DateTimeService.asTwoDigits(i)); } } @@ -109,14 +107,14 @@ public class VDateTimeCalendarPanel if (getResolution().compareTo(DateTimeResolution.MINUTE) <= 0) { mins = createListBox(); for (int i = 0; i < 60; i++) { - mins.addItem(asTwoDigits(i)); + mins.addItem(DateTimeService.asTwoDigits(i)); } mins.addChangeHandler(this); } if (getResolution().compareTo(DateTimeResolution.SECOND) <= 0) { sec = createListBox(); for (int i = 0; i < 60; i++) { - sec.addItem(asTwoDigits(i)); + sec.addItem(DateTimeService.asTwoDigits(i)); } sec.addChangeHandler(this); } @@ -130,7 +128,7 @@ public class VDateTimeCalendarPanel if (getDateTimeService().isTwelveHourClock()) { h -= h < 12 ? 0 : 12; } - add(new VLabel(asTwoDigits(h))); + add(new VLabel(DateTimeService.asTwoDigits(h))); } else { add(hours); } @@ -139,7 +137,7 @@ public class VDateTimeCalendarPanel add(new VLabel(delimiter)); if (isReadonly()) { final int m = mins.getSelectedIndex(); - add(new VLabel(asTwoDigits(m))); + add(new VLabel(DateTimeService.asTwoDigits(m))); } else { add(mins); } @@ -148,7 +146,7 @@ public class VDateTimeCalendarPanel add(new VLabel(delimiter)); if (isReadonly()) { final int s = sec.getSelectedIndex(); - add(new VLabel(asTwoDigits(s))); + add(new VLabel(DateTimeService.asTwoDigits(s))); } else { add(sec); } @@ -308,10 +306,6 @@ public class VDateTimeCalendarPanel } } - private static String asTwoDigits(int i) { - return (i < 10 ? "0" : "") + i; - } - /** * Dispatches an event when the panel when time is changed. */ diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java index 0702211ccb..08682ff13d 100644 --- a/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java @@ -123,8 +123,8 @@ public abstract class TextualDateConnector<PANEL extends VAbstractCalendarPanel< super.onStateChanged(stateChangeEvent); getWidget().setTextFieldEnabled(getState().textFieldEnabled); - getWidget().setRangeStart(nullSafeDateClone(getState().rangeStart)); - getWidget().setRangeEnd(nullSafeDateClone(getState().rangeEnd)); + getWidget().setRangeStart(getState().rangeStart); + getWidget().setRangeEnd(getState().rangeEnd); getWidget().calendar.setDateStyles(getState().dateStyles); getWidget().calendar @@ -167,13 +167,6 @@ public abstract class TextualDateConnector<PANEL extends VAbstractCalendarPanel< getWidget().setTextFieldTabIndex(); } - private Date nullSafeDateClone(Date date) { - if (date != null) { - return (Date) date.clone(); - } - return null; - } - @Override protected void setWidgetStyleName(String styleName, boolean add) { super.setWidgetStyleName(styleName, add); diff --git a/server/src/main/java/com/vaadin/ui/AbstractDateField.java b/server/src/main/java/com/vaadin/ui/AbstractDateField.java index 3998afbae6..13a6e50ce4 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractDateField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractDateField.java @@ -20,7 +20,9 @@ import java.lang.reflect.Type; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjuster; import java.util.Calendar; import java.util.Collections; @@ -77,6 +79,7 @@ import com.vaadin.util.TimeZoneUtil; public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & Serializable & Comparable<? super T>, R extends Enum<R>> extends AbstractField<T> implements FocusNotifier, BlurNotifier { + private static final DateTimeFormatter RANGE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd[ HH:mm:ss]", Locale.ENGLISH); private AbstractDateFieldServerRpc rpc = new AbstractDateFieldServerRpc() { @Override @@ -269,14 +272,12 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & * - the allowed range's start date */ public void setRangeStart(T startDate) { - Date date = convertToDate(startDate); - if (date != null && getState().rangeEnd != null - && date.after(getState().rangeEnd)) { + if (afterDate(startDate,convertFromDateString(getState().rangeEnd))) { throw new IllegalStateException( "startDate cannot be later than endDate"); } - getState().rangeStart = date; + getState().rangeStart = convertToDateString(startDate); } /** @@ -333,9 +334,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & * resolution) */ public void setRangeEnd(T endDate) { - Date date = convertToDate(endDate); - if (date != null && getState().rangeStart != null - && getState().rangeStart.after(date)) { + String date = convertToDateString(endDate); + if (afterDate(convertFromDateString(getState().rangeStart), endDate)) { throw new IllegalStateException( "endDate cannot be earlier than startDate"); } @@ -343,13 +343,67 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & getState().rangeEnd = date; } + /** * Returns the precise rangeStart used. * * @return the precise rangeStart used, may be {@code null}. */ public T getRangeStart() { - return convertFromDate(getState(false).rangeStart); + return convertFromDateString(getState(false).rangeStart); + } + + /** + * Parses string representaion of date range limit into date type + * + * @param temporalStr the string representation + * @return parsed value + * @see AbstractDateFieldState#rangeStart + * @see AbstractDateFieldState#rangeEnd + * @since + */ + protected T convertFromDateString(String temporalStr) { + if (temporalStr == null) { + return null; + } + return toType(RANGE_FORMATTER.parse(temporalStr)); + } + + /** + * Converts a temporal value into field-specific data type. + * @param temporalAccessor - source value + * @return conversion result. + * @since + */ + protected abstract T toType(TemporalAccessor temporalAccessor); + + /** + * Converts date range limit itno string representaion + * + * @param temporal the value + * @return textual representation + * @see AbstractDateFieldState#rangeStart + * @see AbstractDateFieldState#rangeEnd + * @since + */ + protected String convertToDateString(T temporal) { + if (temporal == null) { + return null; + } + return RANGE_FORMATTER.format(temporal); + } + + /** + * Checks if {@code value} is after {@code base} or not + * @param value temporal value + * @param base temporal value to compare to + * @return {@code true} if {@code value} is after {@code base}, {@code false} otherwise + */ + protected boolean afterDate(T value, T base) { + if (value == null || base == null) { + return false; + } + return value.compareTo(base) > 0; } /** @@ -358,7 +412,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & * @return the precise rangeEnd used, may be {@code null}. */ public T getRangeEnd() { - return convertFromDate(getState(false).rangeEnd); + return convertFromDateString(getState(false).rangeEnd); } /** diff --git a/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java b/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java index 84072288e5..be48ce758c 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java @@ -17,9 +17,11 @@ package com.vaadin.ui; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; +import java.time.temporal.TemporalAccessor; import java.util.Date; import java.util.Locale; import java.util.Map; @@ -155,4 +157,9 @@ public abstract class AbstractLocalDateField } return value.format(dateTimeFormatter); } + + @Override + protected LocalDate toType(TemporalAccessor temporalAccessor) { + return temporalAccessor == null? null : LocalDate.from(temporalAccessor); + } } diff --git a/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java b/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java index adf6e0bc53..ddb39b6a1e 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java @@ -21,6 +21,7 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; import java.util.Date; import java.util.Locale; import java.util.Map; @@ -179,4 +180,9 @@ public abstract class AbstractLocalDateTimeField } return value.format(dateTimeFormatter); } + + @Override + protected LocalDateTime toType(TemporalAccessor temporalAccessor) { + return temporalAccessor == null? null : LocalDateTime.from(temporalAccessor); + } } 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 eea1e33137..91d7384421 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,6 +1,7 @@ package com.vaadin.tests.server.component.datefield; import java.time.LocalDateTime; +import java.time.temporal.TemporalAccessor; import java.util.Date; import java.util.Map; @@ -55,6 +56,11 @@ public class DateFieldListenersTest extends AbstractListenerMethodsTestBase { protected String formatDate(LocalDateTime value) { return null; } + + @Override + protected LocalDateTime toType(TemporalAccessor temporalAccessor) { + return LocalDateTime.from(temporalAccessor); + } } @Test diff --git a/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java b/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java index 553e82a57e..30f8c71159 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java +++ b/shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java @@ -49,17 +49,21 @@ public class AbstractDateFieldState extends AbstractFieldState { /** * Start range that has been cleared, depending on the resolution of the - * date field. + * date field. The format is "2018-05-27" or "2018-05-27 14:38:39" + * + * @see com.vaadin.ui.AbstractDateField#RANGE_FORMATTER */ @NoLayout - public Date rangeStart; + public String rangeStart; /** - * End range that has been cleared, depending on the resolution of the date - * field. + * End range that has been cleared, depending on the resolution of the + * date field. The format is "2018-05-27" or "2018-05-27 14:38:39" + * + * @see com.vaadin.ui.AbstractDateField#RANGE_FORMATTER */ @NoLayout - public Date rangeEnd; + public String rangeEnd; /** * The JSON used to construct a TimeZone on the client side, can be diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldTimeZones.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldTimeZones.java new file mode 100644 index 0000000000..be7cd1456b --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldTimeZones.java @@ -0,0 +1,53 @@ +package com.vaadin.tests.components.datefield; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.ui.datefield.DateResolution; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.InlineDateField; +import com.vaadin.ui.InlineDateTimeField; + +@Widgetset("com.vaadin.DefaultWidgetSet") +public class DateFieldTimeZones extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + + InlineDateField d1 = new InlineDateField(); + InlineDateField d2 = new InlineDateField(); + InlineDateTimeField d3 = new InlineDateTimeField(); + InlineDateTimeField d4 = new InlineDateTimeField(); + InlineDateTimeField d5 = new InlineDateTimeField(); + + d1.setValue(LocalDate.of(2018, 1, 1)); + d2.setValue(LocalDate.of(2019, 12, 1)); + d3.setValue(LocalDateTime.of(2019, 12, 1,0,0,0)); + d4.setValue(LocalDateTime.of(2019, 12, 1,0,0,0)); + d4.setValue(LocalDateTime.of(2019, 12, 1,0,0,0)); + + d1.setResolution(DateResolution.DAY); + d2.setResolution(DateResolution.DAY); + + d2.setRangeStart(LocalDate.of(2018, 1, 1)); + d2.setRangeEnd(LocalDate.of(2019, 12, 1)); + + d3.setRangeStart(LocalDateTime.of(2018, 1, 1,0,0,0)); + d3.setRangeEnd(LocalDateTime.of(2019, 12, 1,0,0,0)); + + d5.setRangeStart(LocalDateTime.of(2018, 1, 1,0,0,0)); + d5.setRangeEnd(LocalDateTime.of(2019, 12, 1,0,0,0)); + + d5.setZoneId(ZoneId.of("-10")); + HorizontalLayout layout = new HorizontalLayout(); + layout.addComponents(d1, d2,d3,d4,d5); + + addComponent(layout); + } + +} |