@@ -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> | |||
@@ -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); |
@@ -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; | |||
} | |||
} |
@@ -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) { |
@@ -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); | |||
} | |||
@@ -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. | |||
*/ |
@@ -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); |
@@ -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); | |||
} | |||
/** |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 |
@@ -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 |
@@ -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); | |||
} | |||
} |