http://git-wip-us.apache.org/repos/asf/wicket/blob/2bb684c1/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/TimeField.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/TimeField.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/TimeField.java new file mode 100644 index 0000000..68af251 --- /dev/null +++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/TimeField.java @@ -0,0 +1,520 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.extensions.markup.html.form.datetime; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.time.LocalTime; +import java.time.chrono.IsoChronology; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.FormatStyle; +import java.time.temporal.ChronoField; +import java.util.Arrays; +import java.util.Locale; + +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.AbstractTextComponent.ITextFormatProvider; +import org.apache.wicket.markup.html.form.DropDownChoice; +import org.apache.wicket.markup.html.form.FormComponentPanel; +import org.apache.wicket.markup.html.form.TextField; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.util.convert.IConverter; +import org.apache.wicket.util.convert.converter.IntegerConverter; +import org.apache.wicket.util.lang.Args; +import org.apache.wicket.validation.validator.RangeValidator; + +/** + * Works on a {@link java.util.Date} object. Displays a field for hours and a field for minutes, and + * an AM/PM field. The format (12h/24h) of the hours field depends on the time format of this + * {@link TimeField}'s {@link Locale}, as does the visibility of the AM/PM field (see + * {@link TimeField#use12HourFormat}). + * + * @author eelcohillenius + * @see TimeField for a variant with just the date field and date picker + */ +public class TimeField extends FormComponentPanel<LocalTime> implements ITextFormatProvider +{ + private static final long serialVersionUID = 1L; + + /** + * Enumerated type for different ways of handling the render part of requests. + */ + public enum AM_PM { + /** */ + AM("AM"), + + /** */ + PM("PM"); + + private final String value; + + AM_PM(final String name) + { + value = name; + } + + @Override + public String toString() + { + return value; + } + } + protected static final String HOURS = "hours"; + protected static final String MINUTES = "minutes"; + protected static final String AM_OR_PM_CHOICE = "amOrPmChoice"; + + private static final IConverter<Integer> MINUTES_CONVERTER = new IntegerConverter() { + private static final long serialVersionUID = 1L; + + protected NumberFormat newNumberFormat(Locale locale) { + return new DecimalFormat("00"); + } + }; + + // The TextField for "hours" and it's associated model object + private TextField<Integer> hoursField; + + // The TextField for "minutes" and it's associated model object + private TextField<Integer> minutesField; + + // The dropdown list for AM/PM and it's associated model object + private DropDownChoice<AM_PM> amOrPmChoice; + private LocalTime time = LocalTime.now(); + + /** + * Creates a new TimeField defaulting to using a short date pattern + * + * @param id + * The id of the text field + * @param model + * The model + * @param timePattern + * The pattern to use. Must be not null. See {@link SimpleDateFormat} for available + * patterns. + * @return TimeField + */ + public static TimeField forTimePattern(String id, IModel<LocalTime> model, String timePattern) + { + return new TimeField(id, model, new PatternTimeConverter(timePattern)); + } + + /** + * Creates a new TimeField defaulting to using a short date pattern + * + * @param id + * The id of the text field + * @param timePattern + * The pattern to use. Must be not null. See {@link SimpleDateFormat} for available + * patterns. + * @return TimeField + */ + public static TimeField forTimePattern(String id, String timePattern) + { + return forTimePattern(id, null, timePattern); + } + + /** + * Creates a new TimeField using the provided date style. + * + * @param id + * The id of the text field + * @param model + * The model + * @param timeStyle + * Date style to use. The first character is the date style, and the second character + * is the time style. Specify a character of 'S' for short style, 'M' for medium, 'L' + * for long, and 'F' for full. A date or time may be ommitted by specifying a style + * character '-'. See {@link org.joda.time.DateTimeFormat#forStyle(String)}. + * @return TimeField + */ + public static TimeField forTimeStyle(String id, IModel<LocalTime> model, String timeStyle) + { + return new TimeField(id, model, new StyleTimeConverter(timeStyle)); + } + + /** + * Creates a new TimeField using the provided date style. + * + * @param id + * The id of the text field + * @param timeStyle + * Date style to use. The first character is the date style, and the second character + * is the time style. Specify a character of 'S' for short style, 'M' for medium, 'L' + * for long, and 'F' for full. A date or time may be ommitted by specifying a style + * character '-'. See {@link org.joda.time.DateTimeFormat#forStyle(String)}. + * @return TimeField + */ + public static TimeField forTimeStyle(String id, String timeStyle) + { + return forTimeStyle(id, null, timeStyle); + } + + /** + * Creates a new TimeField defaulting to using a short date pattern + * + * @param id + * The id of the text field + * @return TimeField + */ + public static TimeField forShortStyle(String id) + { + return forShortStyle(id, null); + } + + /** + * Creates a new TimeField defaulting to using a short date pattern + * + * @param id + * The id of the text field + * @param model + * The model + * @return TimeField + */ + public static TimeField forShortStyle(String id, IModel<LocalTime> model) + { + return new TimeField(id, model, new StyleTimeConverter()); + } + + /** + * Creates a new TimeField using the provided converter. + * + * @param id + * The id of the text field + * @param converter + * the date converter + * @return TimeField + */ + public static TimeField withConverter(String id, LocalTimeConverter converter) + { + return withConverter(id, null, converter); + } + + /** + * Creates a new TimeField using the provided converter. + * + * @param id + * The id of the text field + * @param model + * The model + * @param converter + * the date converter + * @return TimeField + */ + public static TimeField withConverter(String id, IModel<LocalTime> model, LocalTimeConverter converter) + { + return new TimeField(id, model, converter); + } + + /** + * The converter for the TextField + */ + private final LocalTimeConverter converter; + + /** + * Construct. + * + * @param id + * the component id + */ + public TimeField(String id, LocalTimeConverter converter) + { + this(id, null, converter); + } + + /** + * Construct. + * + * @param id + * the component id + * @param model + * the component's model + */ + public TimeField(String id, IModel<LocalTime> model, LocalTimeConverter converter) + { + super(id, model); + + Args.notNull(converter, "converter"); + this.converter = converter; + + // Sets the type that will be used when updating the model for this component. + setType(LocalTime.class); + + + // Create and add the "hours" TextField + add(hoursField = newHoursTextField(HOURS, new HoursModel(), Integer.class)); + + // Create and add the "minutes" TextField + add(minutesField = newMinutesTextField(MINUTES, new MinutesModel(), Integer.class)); + + // Create and add the "AM/PM" Listbox + add(amOrPmChoice = new DropDownChoice<>(AM_OR_PM_CHOICE, new AmPmModel(), Arrays.asList(AM_PM.values()))); + + add(new WebMarkupContainer("hoursSeparator") + { + private static final long serialVersionUID = 1L; + + @Override + public boolean isVisible() + { + return minutesField.determineVisibility(); + } + }); + } + + /** + * create a new {@link TextField} instance for hours to be added to this panel. + * + * @param id + * the component id + * @param model + * model that should be used by the {@link TextField} + * @param type + * the type of the text field + * @return a new text field instance + */ + protected TextField<Integer> newHoursTextField(final String id, IModel<Integer> model, Class<Integer> type) { + TextField<Integer> hoursTextField = new TextField<Integer>(id, model, type) + { + private static final long serialVersionUID = 1L; + + @Override + protected String[] getInputTypes() + { + return new String[] {"number"}; + } + }; + hoursTextField.add(AttributeModifier.append("min", getMaximumHours() == 24 ? 0 : 1)); + hoursTextField.add(AttributeModifier.append("max", getMaximumHours() == 24 ? 23 : 12)); + hoursTextField.add(getMaximumHours() == 24 ? RangeValidator.range(0, 23) : RangeValidator.range(1, 12)); + hoursTextField.setLabel(new Model<>(HOURS)); + return hoursTextField; + } + + /** + * create a new {@link TextField} instance for minutes to be added to this panel. + * + * @param id + * the component id + * @param model + * model that should be used by the {@link TextField} + * @param type + * the type of the text field + * @return a new text field instance + */ + protected TextField<Integer> newMinutesTextField(final String id, IModel<Integer> model, + Class<Integer> type) + { + TextField<Integer> minutesField = new TextField<Integer>(id, model, type) + { + private static final long serialVersionUID = 1L; + + @Override + protected IConverter<?> createConverter(Class<?> type) + { + if (Integer.class.isAssignableFrom(type)) + { + return MINUTES_CONVERTER; + } + return null; + } + + @Override + protected String[] getInputTypes() + { + return new String[] {"number"}; + } + }; + minutesField.add(AttributeModifier.append("min", 0)); + minutesField.add(AttributeModifier.append("max", 59)); + minutesField.add(new RangeValidator<>(0, 59)); + minutesField.setLabel(new Model<>(MINUTES)); + return minutesField; + } + + @Override + public String getInput() + { + // since we override convertInput, we can let this method return a value + // that is just suitable for error reporting + return String.format("%s:%s", hoursField.getInput(), minutesField.getInput()); + } + + @Override + public void convertInput() + { + Integer hours = hoursField.getConvertedInput(); + Integer minutes = minutesField.getConvertedInput(); + AM_PM amOrPmInput = amOrPmChoice.getConvertedInput(); + + LocalTime localTime = null; + if (hours != null && minutes != null) + { + // Use the input to create a LocalTime object + localTime = LocalTime.of(hours, minutes); + + // Adjust for halfday if needed + if (use12HourFormat()) + { + int halfday = (amOrPmInput == AM_PM.PM ? 1 : 0); + localTime = localTime.with(ChronoField.AMPM_OF_DAY, halfday); + } + } + setConvertedInput(localTime); + } + + @Override + protected void onBeforeRender() { + hoursField.setRequired(isRequired()); + minutesField.setRequired(isRequired()); + + boolean use12HourFormat = use12HourFormat(); + amOrPmChoice.setVisible(use12HourFormat); + super.onBeforeRender(); + } + + /** + * Checks whether the current {@link Locale} uses the 12h or 24h time format. This method can be + * overridden to e.g. always use 24h format. + * + * @return true, if the current {@link Locale} uses the 12h format.<br/> + * false, otherwise + */ + protected boolean use12HourFormat() + { + String pattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(null, FormatStyle.SHORT, IsoChronology.INSTANCE, getLocale()); + return pattern.indexOf('a') != -1 || pattern.indexOf('h') != -1 || pattern.indexOf('K') != -1; + } + + /** + * @return either 12 or 24, depending on the hour format of the current {@link Locale} + */ + private int getMaximumHours() + { + return getMaximumHours(use12HourFormat()); + } + + /** + * Convenience method (mainly for optimization purposes), in case {@link #use12HourFormat()} has + * already been stored in a local variable and thus doesn't need to be computed again. + * + * @param use12HourFormat + * the hour format to use + * @return either 12 or 24, depending on the parameter <code>use12HourFormat</code> + */ + private int getMaximumHours(boolean use12HourFormat) + { + return use12HourFormat ? 12 : 24; + } + + protected class HoursModel implements IModel<Integer> + { + private static final long serialVersionUID = 1L; + + @Override + public Integer getObject() + { + LocalTime t = TimeField.this.getModelObject(); + if (t == null) + { + return null; + } + return getMaximumHours() == 24 ? t.getHour() : t.get(ChronoField.CLOCK_HOUR_OF_AMPM); + } + + @Override + public void setObject(Integer hour) + { + time = time.with(getMaximumHours() == 24 ? ChronoField.HOUR_OF_DAY : ChronoField.CLOCK_HOUR_OF_AMPM, hour); + } + } + + protected class MinutesModel implements IModel<Integer> + { + private static final long serialVersionUID = 1L; + + @Override + public Integer getObject() + { + LocalTime t = TimeField.this.getModelObject(); + return t == null ? null : t.getMinute(); + } + + @Override + public void setObject(Integer minute) + { + time = time.with(ChronoField.MINUTE_OF_HOUR, minute); + } + } + + protected class AmPmModel implements IModel<AM_PM> + { + private static final long serialVersionUID = 1L; + + @Override + public AM_PM getObject() + { + LocalTime t = TimeField.this.getModelObject(); + int i = t == null ? 0 : t.get(ChronoField.AMPM_OF_DAY); + return i == 0 ? AM_PM.AM : AM_PM.PM; + } + + @Override + public void setObject(AM_PM amPm) + { + int i = AM_PM.AM == amPm ? 0 : 1; + time = time.with(ChronoField.AMPM_OF_DAY, i); + } + } + + /** + * @return The specialized converter. + * @see org.apache.wicket.Component#createConverter(java.lang.Class) + */ + @Override + protected IConverter<?> createConverter(Class<?> clazz) + { + if (LocalTime.class.isAssignableFrom(clazz)) + { + return converter; + } + return null; + } + + /** + * @see org.apache.wicket.markup.html.form.AbstractTextComponent.ITextFormatProvider#getTextFormat() + */ + @Override + public final String getTextFormat() + { + return converter.getPattern(getLocale()); + } + + public static FormatStyle parseFormatStyle(char style) + { + switch (style) + { + case 'M': + return FormatStyle.MEDIUM; + case 'S': + default: + return FormatStyle.SHORT; + } + } +}
http://git-wip-us.apache.org/repos/asf/wicket/blob/2bb684c1/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeConverter.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeConverter.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeConverter.java new file mode 100644 index 0000000..9286f2e --- /dev/null +++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeConverter.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.extensions.markup.html.form.datetime; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.wicket.Session; +import org.apache.wicket.core.request.ClientInfo; +import org.apache.wicket.protocol.http.request.WebClientInfo; +import org.apache.wicket.util.convert.ConversionException; +import org.apache.wicket.util.convert.IConverter; +import org.apache.wicket.util.lang.Args; +import org.apache.wicket.util.string.Strings; + + +/** + * Base class for javax.time based date converters. It contains the logic to parse and format, + * optionally taking the time zone difference between clients and the server into account. + * <p> + * Converters of this class are best suited for per-component use. + * </p> + * + * @author eelcohillenius + */ +public abstract class ZonedDateTimeConverter implements IConverter<ZonedDateTime> +{ + private static final long serialVersionUID = 1L; + + /** + * Whether to apply the time zone difference when interpreting dates. + */ + private final boolean applyTimeZoneDifference; + + /** + * Construct. <p> When applyTimeZoneDifference is true, the current time is applied on the + * parsed date, and the date will be corrected for the time zone difference between the server + * and the client. For instance, if I'm in Seattle and the server I'm working on is in + * Amsterdam, the server is 9 hours ahead. So, if I'm inputting say 12/24 at a couple of hours + * before midnight, at the server it is already 12/25. If this boolean is true, it will be + * transformed to 12/25, while the client sees 12/24. </p> + * + * @param applyTimeZoneDifference + * whether to apply the difference in time zones between client and server + */ + public ZonedDateTimeConverter(boolean applyTimeZoneDifference) + { + this.applyTimeZoneDifference = applyTimeZoneDifference; + } + + public ZonedDateTime convertToObject(String value, DateTimeFormatter format, Locale locale) { + try + { + // parse date retaining the time of the submission + return ZonedDateTime.parse(value, format); + } + catch (RuntimeException e) + { + throw newConversionException(e, locale); + } + } + + @Override + public ZonedDateTime convertToObject(String value, Locale locale) + { + if (Strings.isEmpty(value)) + { + return null; + } + + DateTimeFormatter format = getFormat(locale); + Args.notNull(format, "format"); + + if (applyTimeZoneDifference) + { + ZoneId zoneId = getClientTimeZone(); + + // set time zone for client + format = format.withZone(getTimeZone()); + + ZonedDateTime dateTime = convertToObject(value, format, locale); + // apply the server time zone to the parsed value + if (zoneId != null) + { + dateTime = dateTime.withZoneSameInstant(zoneId); + } + + return dateTime; + } + else + { + return convertToObject(value, format, locale); + } + } + + /** + * Creates a ConversionException and sets additional context information to it. + * + * @param cause + * - {@link RuntimeException} cause + * @param locale + * - {@link Locale} used to set 'format' variable with localized pattern + * @return {@link ConversionException} + */ + ConversionException newConversionException(RuntimeException cause, Locale locale) + { + return new ConversionException(cause) + .setVariable("format", getPattern(locale)); + } + + @Override + public String convertToString(ZonedDateTime dateTime, Locale locale) + { + DateTimeFormatter format = getFormat(locale); + + if (applyTimeZoneDifference) + { + ZoneId zoneId = getClientTimeZone(); + if (zoneId != null) + { + // apply time zone to formatter + format = format.withZone(zoneId); + } + } + return format.format(dateTime); + } + + /** + * Gets whether to apply the time zone difference when interpreting dates. + * + * </p> When true, the current time is applied on the parsed date, and the date will be + * corrected for the time zone difference between the server and the client. For instance, if + * I'm in Seattle and the server I'm working on is in Amsterdam, the server is 9 hours ahead. + * So, if I'm inputting say 12/24 at a couple of hours before midnight, at the server it is + * already 12/25. If this boolean is true, it will be transformed to 12/25, while the client + * sees 12/24. </p> + * + * @return whether to apply the difference in time zones between client and server + */ + public final boolean getApplyTimeZoneDifference() + { + return applyTimeZoneDifference; + } + + /** + * @param locale + * The locale used to convert the value + * @return Gets the pattern that is used for printing and parsing + */ + public abstract String getPattern(Locale locale); + + /** + * Gets the client's time zone. + * + * @return The client's time zone or null + */ + protected ZoneId getClientTimeZone() + { + ClientInfo info = Session.get().getClientInfo(); + if (info instanceof WebClientInfo) + { + TimeZone timeZone = ((WebClientInfo) info).getProperties().getTimeZone(); + return timeZone.toZoneId(); + } + return null; + } + + /** + * @param locale + * The locale used to convert the value + * + * @return formatter The formatter for the current conversion + */ + public abstract DateTimeFormatter getFormat(Locale locale); + + /** + * Gets the server time zone. Override this method if you want to fix to a certain time zone, + * regardless of what actual time zone the server is in. + * + * @return The server time zone + */ + protected ZoneId getTimeZone() + { + return ZoneId.systemDefault(); + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/2bb684c1/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeField.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeField.java b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeField.java new file mode 100644 index 0000000..a6814f8 --- /dev/null +++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/form/datetime/ZonedDateTimeField.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.extensions.markup.html.form.datetime; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.wicket.Session; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.core.request.ClientInfo; +import org.apache.wicket.model.IModel; +import org.apache.wicket.protocol.http.request.WebClientInfo; + +/** + * Works on a {@link java.time.ZonedDateTimeTime} object. Displays a date field and a DatePicker, a field + * for hours and a field for minutes, and an AM/PM field. The format (12h/24h) of the hours field + * depends on the time format of this {@link ZonedDateTimeField}'s {@link Locale}, as does the visibility + * of the AM/PM field (see {@link ZonedDateTimeField#use12HourFormat}). + * <p> + * <strong>Ajaxifying the DateTimeField</strong>: If you want to update a DateTimeField with an + * {@link AjaxFormComponentUpdatingBehavior}, you have to attach it to the contained + * {@link DateField} by overriding {@link #newDateTextField(String, IModel)} and calling + * {@link #processInput()}: + * + * <pre>{@code + * DateTimeField dateTimeField = new DateTimeField(...) { + * protected DateTextField newDateTextField(String id, PropertyModel<Date> dateFieldModel) + * { + * DateTextField dateField = super.newDateTextField(id, dateFieldModel); + * dateField.add(new AjaxFormComponentUpdatingBehavior("change") { + * protected void onUpdate(AjaxRequestTarget target) { + * processInput(); // let DateTimeField process input too + * + * ... + * } + * }); + * return recorder; + * } + * } + * }</pre> + * + * @author eelcohillenius + * @see DateField for a variant with just the date field and date picker + */ +public class ZonedDateTimeField extends AbstractDateTimeField<ZonedDateTime> +{ + private static final long serialVersionUID = 1L; + + private ZonedDateTime dateTime = ZonedDateTime.now(); + + /** + * Construct. + * + * @param id + */ + public ZonedDateTimeField(final String id) + { + this(id, null); + } + + /** + * Construct. + * + * @param id + * @param model + */ + public ZonedDateTimeField(final String id, final IModel<ZonedDateTime> model) + { + super(id, model); + + // Sets the type that will be used when updating the model for this component. + setType(ZonedDateTime.class); + } + + /** + * Gets the client's time zone. + * + * @return The client's time zone or null + */ + protected ZoneId getClientTimeZone() + { + ClientInfo info = Session.get().getClientInfo(); + if (info instanceof WebClientInfo) + { + TimeZone timeZone = ((WebClientInfo) info).getProperties().getTimeZone(); + return timeZone != null ? timeZone.toZoneId() : null; + } + return null; + } + + ZonedDateTime performConvert(LocalDate date, LocalTime time) { + return ZonedDateTime.of(date, time, getClientTimeZone()); + } + + @Override + void prepareObject() { + ZonedDateTime modelObject = getModelObject(); + if (modelObject == null) + { + dateTime = null; + } + else + { + // convert date to the client's time zone if we have that info + ZoneId zone = getClientTimeZone(); + if (zone != null) + { + modelObject = modelObject.withZoneSameInstant(zone); + } + } + } + + LocalDate getLocalDate() + { + return dateTime.toLocalDate(); + } + + void setLocalDate(LocalDate date) + { + dateTime = dateTime.with(ChronoField.YEAR, date.getYear()) + .with(ChronoField.MONTH_OF_YEAR, date.getMonthValue()) + .with(ChronoField.DAY_OF_YEAR, date.getDayOfMonth()); + } + + LocalTime getLocalTime() + { + return dateTime.toLocalTime(); + } + + void setLocalTime(LocalTime time) + { + dateTime = dateTime.with(ChronoField.HOUR_OF_DAY, time.getHour()) + .with(ChronoField.MINUTE_OF_HOUR, time.getMinute()); + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/2bb684c1/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateConverterTest.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateConverterTest.java b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateConverterTest.java new file mode 100644 index 0000000..d590615 --- /dev/null +++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateConverterTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.extensions.markup.html.form.datetime; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +import org.apache.wicket.extensions.markup.html.form.datetime.PatternZonedDateTimeConverter; +import org.apache.wicket.extensions.markup.html.form.datetime.StyleZonedDateTimeConverter; +import org.apache.wicket.util.convert.ConversionException; +import org.apache.wicket.util.convert.IConverter; +import org.apache.wicket.util.convert.converter.CalendarConverter; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for {@link ZonedDateTimeConverter} and subclasses. + * + * @author akiraly + */ +public class DateConverterTest +{ + /** + * WICKET-3598 + */ + @Test + public void testLocaleUsed() + { + Locale locale = Locale.GERMAN; + + StyleZonedDateTimeConverter styleDateConverter = new StyleZonedDateTimeConverter("F-", false); + DateTimeFormatter styleFormatter = styleDateConverter.getFormat(locale); + + Assert.assertEquals(locale, styleFormatter.getLocale()); + + PatternZonedDateTimeConverter patternDateConverter = new PatternZonedDateTimeConverter( + styleDateConverter.getPattern(locale), false); + DateTimeFormatter patternFormatter = patternDateConverter.getFormat(locale); + + Assert.assertEquals(locale, patternFormatter.getLocale()); + + Calendar now = Calendar.getInstance(); + + ZonedDateTime zNow = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault()); + String actual = styleDateConverter.convertToString(zNow, locale); + String expected = patternDateConverter.convertToString(zNow, locale); + + Assert.assertEquals(expected, actual); + } + + /** + * WICKET-3658 + */ + @Test + public void testCalendarConverterWithDelegate() + { + Locale locale = Locale.GERMAN; + + Calendar input = Calendar.getInstance(locale); + input.clear(); + input.set(2011, Calendar.MAY, 7); + + final StyleZonedDateTimeConverter styleDateConverter = new StyleZonedDateTimeConverter("F-", false); + + CalendarConverter calendarConverter = new CalendarConverter(new IConverter<Date>() + { + private static final long serialVersionUID = 1L; + + @Override + public Date convertToObject(String value, Locale locale) throws ConversionException { + ZonedDateTime zd = styleDateConverter.convertToObject(value, locale); + return zd == null ? null : Date.from(zd.toInstant()); + } + + @Override + public String convertToString(Date value, Locale locale) { + return styleDateConverter.convertToString(ZonedDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()), locale); + } + + }); + + String expected = styleDateConverter.convertToString(ZonedDateTime.ofInstant(input.toInstant(), ZoneId.systemDefault()), locale); + String actual = calendarConverter.convertToString(input, locale); + + Assert.assertEquals(expected, actual); + + Calendar revert = calendarConverter.convertToObject(actual, locale); + + Assert.assertEquals(input, revert); + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/2bb684c1/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateTimeFieldTest.java ---------------------------------------------------------------------- diff --git a/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateTimeFieldTest.java b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateTimeFieldTest.java new file mode 100644 index 0000000..3b167e4 --- /dev/null +++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/form/datetime/DateTimeFieldTest.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.extensions.markup.html.form.datetime; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Locale; + +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.markup.IMarkupResourceStreamProvider; +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.util.resource.IResourceStream; +import org.apache.wicket.util.resource.StringResourceStream; +import org.apache.wicket.util.tester.FormTester; +import org.apache.wicket.util.tester.WicketTestCase; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class DateTimeFieldTest extends WicketTestCase { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void timeNullTest() { + TestTimePage page = new TestTimePage(null); + tester.startPage(page); + FormTester formTester = tester.newFormTester("form", false); + formTester.submit(); + assertNull(page.field.getModelObject()); + } + + @Test + public void timeNullHoursTest() { + TestTimePage page = new TestTimePage(null); + tester.startPage(page); + FormTester formTester = tester.newFormTester("form", false); + formTester.setValue("field:minutes", "8"); + formTester.submit(); + assertNull(page.field.getModelObject()); + } + + @Test + public void timeNullMinutesTest() { + TestTimePage page = new TestTimePage(null); + tester.startPage(page); + FormTester formTester = tester.newFormTester("form", false); + formTester.setValue("field:hours", "8"); + formTester.submit(); + assertNull(page.field.getModelObject()); + } + + @Test + public void timeNotNullTest() { + TestTimePage page = new TestTimePage(LocalTime.of(6, 15)); + tester.startPage(page); + FormTester formTester = tester.newFormTester("form", false); + formTester.setValue("field:hours", "8"); + formTester.submit(); + LocalTime t = page.field.getModelObject(); + assertNotNull(t); + assertEquals(8, t.getHour()); + assertEquals(15, t.getMinute()); + } + + @Test + public void dateNullTest() { + TestDatePage page = new TestDatePage(null); + tester.startPage(page); + FormTester formTester = tester.newFormTester("form", false); + formTester.submit(); + assertNull(page.field.getModelObject()); + } + + @Test + public void dateNotNullTest() { + LocalDate date = LocalDate.of(2017, 02, 13); + TestDatePage page = new TestDatePage(null); + tester.startPage(page); + FormTester formTester = tester.newFormTester("form", false); + formTester.setValue("field", new StyleDateConverter("F").convertToString(date, Locale.forLanguageTag("en-US"))); + formTester.submit(); + LocalDate d = page.field.getModelObject(); + assertNotNull(d); + assertEquals(date, d); + } + + public static class TestDatePage extends TestPage<LocalDate> + { + private static final long serialVersionUID = 1L; + + TestDatePage(LocalDate val) + { + super(val); + tag = "input type=\"text\""; + } + + @Override + FormComponent<LocalDate> newComponent() + { + return DateField.forDateStyle("field", model, "F"); + } + } + + public static class TestTimePage extends TestPage<LocalTime> + { + private static final long serialVersionUID = 1L; + + TestTimePage(LocalTime val) + { + super(val); + } + + @Override + FormComponent<LocalTime> newComponent() + { + return TimeField.forTimeStyle("field", model, "F"); + } + } + + public abstract static class TestPage<T extends Serializable> extends WebPage implements IMarkupResourceStreamProvider + { + private static final long serialVersionUID = 1L; + Form<Void> form; + FormComponent<T> field; + IModel<T> model = new Model<T>(); + String tag = "span"; + + /** */ + public TestPage(T val) + { + add(form = new Form<>("form")); + model.setObject(val); + form.add(field = newComponent()); + } + + abstract FormComponent<T> newComponent(); + + @Override + public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) + { + return new StringResourceStream(String.format("<html><body>" + + "<form wicket:id=\"form\"><%s wicket:id=\"field\"/></form></body></html>", tag)); + } + + @Override + protected void onDetach() + { + super.onDetach(); + model.detach(); + } + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/2bb684c1/wicket-user-guide/src/main/asciidoc/helloWorld/helloWorld_1.adoc ---------------------------------------------------------------------- diff --git a/wicket-user-guide/src/main/asciidoc/helloWorld/helloWorld_1.adoc b/wicket-user-guide/src/main/asciidoc/helloWorld/helloWorld_1.adoc index c24e4dd..5dac973 100644 --- a/wicket-user-guide/src/main/asciidoc/helloWorld/helloWorld_1.adoc +++ b/wicket-user-guide/src/main/asciidoc/helloWorld/helloWorld_1.adoc @@ -6,7 +6,6 @@ Wicket is available as a binary package on the main site http://wicket.apache.o | wicket-core | Contains the main classes of the framework, like class _Component_ and _Application_. | wicket-request, wicket-util | wicket-request | This module contains the classes involved into web request processing. | wicket-util | wicket-util | Contains general-purpose utility classes for functional areas such as I/O, lang, string manipulation, security, etc... | None -| wicket-datetime | Contains special purpose components designed to work with date and time. | wicket-core | wicket-bean-validation | Provides support for JSR 303 standard validation. | wicket-core | wicket-devutils | Contains utility classes and components to help developers with tasks such as debugging, class inspection and so on. | wicket-core, wicket-extensions |wicket-extensions | Contains a vast set of built-in components to build a rich UI for our web application (Ajax support is part of this module). | wicket-core
