Browse Source

Fix date ranges for date and datetime fields (#10685)

Fixes #9765
tags/8.4.0.alpha1
Ilia Motornyi 6 years ago
parent
commit
ec314ee921

+ 1
- 0
all/src/main/templates/release-notes.html View File

@@ -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>

+ 11
- 0
client/src/main/java/com/vaadin/client/DateTimeService.java View File

@@ -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);

+ 2
- 4
client/src/main/java/com/vaadin/client/ui/CalendarEntry.java View File

@@ -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;
}

}

+ 45
- 36
client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java View File

@@ -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) {

+ 2
- 2
client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java View File

@@ -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);
}


+ 7
- 13
client/src/main/java/com/vaadin/client/ui/VDateTimeCalendarPanel.java View File

@@ -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.
*/

+ 2
- 9
client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java View File

@@ -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);

+ 63
- 9
server/src/main/java/com/vaadin/ui/AbstractDateField.java View File

@@ -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);
}

/**

+ 7
- 0
server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java View File

@@ -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);
}
}

+ 6
- 0
server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java View File

@@ -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);
}
}

+ 6
- 0
server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java View File

@@ -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

+ 9
- 5
shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java View File

@@ -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

+ 53
- 0
uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldTimeZones.java View File

@@ -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);
}

}

Loading…
Cancel
Save