diff options
8 files changed, 615 insertions, 9 deletions
diff --git a/client/src/main/java/com/vaadin/client/DateTimeService.java b/client/src/main/java/com/vaadin/client/DateTimeService.java index 2e9627a5a5..577b079ce2 100644 --- a/client/src/main/java/com/vaadin/client/DateTimeService.java +++ b/client/src/main/java/com/vaadin/client/DateTimeService.java @@ -21,6 +21,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.i18n.client.TimeZone; import com.google.gwt.i18n.shared.DateTimeFormat; import com.vaadin.shared.ui.datefield.DateResolution; @@ -50,7 +51,7 @@ public class DateTimeService { * Creates a new date time service with a given locale. * * @param locale - * e.g. fi, en etc. + * e.g. {@code fi}, {@code en}, etc. * @throws LocaleNotLoadedException */ public DateTimeService(String locale) throws LocaleNotLoadedException { @@ -305,10 +306,40 @@ public class DateTimeService { * @return */ public String formatDate(Date date, String formatStr) { + return formatDate(date, formatStr, null); + } + + /** + * Check if format contains the month name. If it does we manually convert + * it to the month name since DateTimeFormat.format always uses the current + * locale and will replace the month name wrong if current locale is + * different from the locale set for the DateField. + * + * MMMM is converted into long month name, MMM is converted into short month + * name. '' are added around the name to avoid that DateTimeFormat parses + * the month name as a pattern. + * + * z is converted into the time zone name, using the specified + * {@code timeZoneJSON} + * + * @param date + * The date to convert + * @param formatStr + * The format string that might contain {@code MMM} or + * {@code MMMM} + * @param timeZone + * The {@link TimeZone} used to replace {@code z}, can be + * {@code null} + * + * @return the formatted date string + * @since 8.2 + */ + public String formatDate(Date date, String formatStr, TimeZone timeZone) { /* * Format month and day names separately when locale for the * DateTimeService is not the same as the browser locale */ + formatStr = formatTimeZone(date, formatStr, timeZone); formatStr = formatMonthNames(date, formatStr); formatStr = formatDayNames(date, formatStr); @@ -406,6 +437,66 @@ public class DateTimeService { return formatStr; } + private String formatTimeZone(Date date, String formatStr, + TimeZone timeZone) { + // if 'z' is found outside quotes and timeZone is used + if (getIndexOf(formatStr, 'z') != -1 && timeZone != null) { + return replaceTimeZone(formatStr, timeZone.getShortName(date)); + } + return formatStr; + } + + /** + * Replaces the {@code z} characters of the specified {@code formatStr} with + * the given {@code timeZoneName}. + * + * @param formatStr + * The format string, which is the pattern describing the date + * and time format + * @param timeZoneName + * the time zone name + * @return the format string, with {@code z} replaced (if found) + */ + private static String replaceTimeZone(String formatStr, + String timeZoneName) { + + // search for 'z' outside the quotes (inside quotes is escaped) + int start = getIndexOf(formatStr, 'z'); + if (start == -1) { + return formatStr; + } + + // if there are multiple consecutive 'z', treat them as one + int end = start; + while (end + 1 < formatStr.length() + && formatStr.charAt(end + 1) == 'z') { + end++; + } + return formatStr.substring(0, start) + "'" + timeZoneName + "'" + + formatStr.substring(end + 1); + } + + /** + * Returns the first index of the specified {@code ch}, which is outside the + * quotes. + */ + private static int getIndexOf(String str, char ch) { + boolean inQuote = false; + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '\'') { + if (i + 1 < str.length() && str.charAt(i + 1) == '\'') { + i++; + } else { + inQuote ^= true; + } + } else if (c == ch && !inQuote) { + return i; + } + } + return -1; + } + /** * Replaces month names in the entered date with the name in the current * browser locale. diff --git a/client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java b/client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java index c9c6df01f4..206709c86d 100644 --- a/client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java @@ -27,6 +27,7 @@ import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.i18n.client.TimeZone; import com.google.gwt.user.client.ui.TextBox; import com.vaadin.client.BrowserInfo; import com.vaadin.client.Focusable; @@ -70,6 +71,9 @@ public abstract class VAbstractTextualDate<R extends Enum<R>> /** For internal use only. May be removed or replaced in the future. */ private String formatStr; + /** For internal use only. May be removed or replaced in the future. */ + private TimeZone timeZone; + public VAbstractTextualDate(R resoluton) { super(resoluton); text = new TextBox(); @@ -177,7 +181,7 @@ public abstract class VAbstractTextualDate<R extends Enum<R>> String formatString = getFormatString(); if (currentDate != null) { dateText = getDateTimeService().formatDate(currentDate, - formatString); + formatString, timeZone); } else { dateText = ""; } @@ -195,7 +199,10 @@ public abstract class VAbstractTextualDate<R extends Enum<R>> Roles.getTextboxRole() .removeAriaReadonlyProperty(text.getElement()); } + } + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; } @Override @@ -220,7 +227,7 @@ public abstract class VAbstractTextualDate<R extends Enum<R>> // FIXME: Add a description/example here of when this is // needed text.setValue(getDateTimeService().formatDate(getDate(), - getFormatString()), false); + getFormatString(), timeZone), false); } // remove possibly added invalid value indication diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java index fb3351ea4f..81eea0e71e 100644 --- a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java @@ -16,8 +16,11 @@ package com.vaadin.client.ui.datefield; +import com.google.gwt.i18n.client.TimeZone; +import com.google.gwt.i18n.client.TimeZoneInfo; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.UIDL; +import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.ui.VAbstractTextualDate; import com.vaadin.shared.ui.datefield.AbstractTextualDateFieldState; @@ -71,4 +74,18 @@ public abstract class AbstractTextualDateConnector<R extends Enum<R>> return (AbstractTextualDateFieldState) super.getState(); } + @OnStateChange("timeZoneJSON") + private void onTimeZoneJSONChange() { + TimeZone timeZone; + String timeZoneJSON = getState().timeZoneJSON; + if (timeZoneJSON != null) { + TimeZoneInfo timeZoneInfo = TimeZoneInfo + .buildTimeZoneData(timeZoneJSON); + timeZone = TimeZone.createTimeZone(timeZoneInfo); + } else { + timeZone = null; + } + getWidget().setTimeZone(timeZone); + } + } diff --git a/server/src/main/java/com/vaadin/ui/AbstractDateField.java b/server/src/main/java/com/vaadin/ui/AbstractDateField.java index bc6b5f49a8..b5f11f7122 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractDateField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractDateField.java @@ -19,6 +19,7 @@ import java.io.Serializable; import java.lang.reflect.Type; import java.text.SimpleDateFormat; import java.time.LocalDate; +import java.time.ZoneId; import java.time.temporal.Temporal; import java.time.temporal.TemporalAdjuster; import java.util.Calendar; @@ -56,6 +57,7 @@ import com.vaadin.shared.ui.datefield.DateFieldConstants; import com.vaadin.shared.ui.datefield.DateResolution; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.util.TimeZoneUtil; /** * A date editor component with {@link LocalDate} as an input value. @@ -95,6 +97,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & */ private String dateFormat; + private ZoneId zoneId; + private boolean lenient = false; private String dateString = ""; @@ -445,6 +449,53 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & } /** + * Sets the {@link ZoneId}, which is used when {@code z} is included inside + * the {@link #setDateFormat(String)}. + * + * @param zoneId + * the zone id + * @since 8.2 + */ + public void setZoneId(ZoneId zoneId) { + if (zoneId != this.zoneId + || (zoneId != null && !zoneId.equals(this.zoneId))) { + updateTimeZoneJSON(zoneId, getLocale()); + } + this.zoneId = zoneId; + } + + private void updateTimeZoneJSON(ZoneId zoneId, Locale locale) { + String timeZoneJSON; + if (zoneId != null && locale != null) { + timeZoneJSON = TimeZoneUtil.toJSON(zoneId, locale); + } else { + timeZoneJSON = null; + } + getState().timeZoneJSON = timeZoneJSON; + } + + @Override + public void setLocale(Locale locale) { + Locale oldLocale = getLocale(); + if (locale != oldLocale + || (locale != null && !locale.equals(oldLocale))) { + updateTimeZoneJSON(getZoneId(), locale); + } + super.setLocale(locale); + } + + /** + * Returns the {@link ZoneId}, which is used when {@code z} is included + * inside the {@link #setDateFormat(String)}. + * + * @return the zoneId + * @since 8.2 + */ + public ZoneId getZoneId() { + return zoneId; + } + + /** * Specifies whether or not date/time interpretation in component is to be * lenient. * diff --git a/server/src/main/java/com/vaadin/util/TimeZoneUtil.java b/server/src/main/java/com/vaadin/util/TimeZoneUtil.java new file mode 100644 index 0000000000..ec3d28e730 --- /dev/null +++ b/server/src/main/java/com/vaadin/util/TimeZoneUtil.java @@ -0,0 +1,150 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed 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 com.vaadin.util; + +import java.io.Serializable; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + +import elemental.json.JsonArray; +import elemental.json.JsonObject; +import elemental.json.impl.JreJsonFactory; +import elemental.json.impl.JsonUtil; + +/** + * Utilities related to {@link com.google.gwt.i18n.client.TimeZone}. + * + * @author Vaadin Ltd + * @since 8.2 + */ +public final class TimeZoneUtil implements Serializable { + + /** + * The start year used to send the time zone transition dates. + */ + private static final int STARTING_YEAR = 1980; + + /** + * Till how many years from now, should we send the time zone transition + * dates. + */ + private static final int YEARS_FROM_NOW = 20; + + private TimeZoneUtil() { + // Static utils only + } + + /** + * Returns a JSON string of the specified {@code zoneId} and {@link Locale}, + * which is used in + * {@link com.google.gwt.i18n.client.TimeZone#createTimeZone(String)}. + * + * @param zoneId + * the {@link ZoneId} to get the daylight transitions from + * @param locale + * the locale used to determine the short name of the time zone + * + * @return the encoded string + */ + public static String toJSON(ZoneId zoneId, Locale locale) { + if (zoneId == null || locale == null) { + return null; + } + ZoneRules rules = zoneId.getRules(); + TimeZone timeZone = TimeZone.getTimeZone(zoneId); + List<Long> transitionsList = new ArrayList<>(); + + TimeZoneInfo info = new TimeZoneInfo(); + + int endYear = LocalDate.now().getYear() + YEARS_FROM_NOW; + if (timeZone.useDaylightTime()) { + for (int year = STARTING_YEAR; year <= endYear; year++) { + ZonedDateTime i = LocalDateTime.of(year, 1, 1, 0, 0) + .atZone(zoneId); + while (true) { + ZoneOffsetTransition t = rules + .nextTransition(i.toInstant()); + if (t == null) { + break; + } + i = t.getInstant().atZone(zoneId); + if (i.toLocalDate().getYear() != year) { + break; + } + long epocHours = Duration + .ofSeconds(t.getInstant().getEpochSecond()) + .toHours(); + long duration = Math.max(t.getDuration().toMinutes(), 0); + transitionsList.add(epocHours); + transitionsList.add(duration); + } + } + } + info.id = zoneId.getId(); + info.transitions = transitionsList.stream().mapToLong(l -> l).toArray(); + info.std_offset = (int) Duration.ofMillis(timeZone.getRawOffset()) + .toMinutes(); + info.names = new String[] { + timeZone.getDisplayName(false, TimeZone.SHORT, locale), + timeZone.getDisplayName(false, TimeZone.LONG, locale), + timeZone.getDisplayName(true, TimeZone.SHORT, locale), + timeZone.getDisplayName(true, TimeZone.LONG, locale) }; + + return stringify(info); + } + + private static String stringify(TimeZoneInfo info) { + JreJsonFactory factory = new JreJsonFactory(); + JsonObject object = factory.createObject(); + object.put("id", info.id); + object.put("std_offset", info.std_offset); + object.put("names", getArray(factory, info.names)); + object.put("transitions", getArray(factory, info.transitions)); + return JsonUtil.stringify(object); + } + + private static JsonArray getArray(JreJsonFactory factory, long[] array) { + JsonArray jsonArray = factory.createArray(); + for (int i = 0; i < array.length; i++) { + jsonArray.set(i, array[i]); + } + return jsonArray; + } + + private static JsonArray getArray(JreJsonFactory factory, String[] array) { + JsonArray jsonArray = factory.createArray(); + for (int i = 0; i < array.length; i++) { + jsonArray.set(i, array[i]); + } + return jsonArray; + } + + private static class TimeZoneInfo implements Serializable { + String id; + int std_offset; + String[] names; + long[] transitions; + } +} 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 80cd6cbd53..5bd920389e 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 @@ -33,17 +33,25 @@ public class AbstractDateFieldState extends AbstractFieldState { primaryStyleName = "v-datefield"; } - /* + /** * Start range that has been cleared, depending on the resolution of the - * date field + * date field. */ @NoLayout - public Date rangeStart = null; + public Date rangeStart; - /* + /** * End range that has been cleared, depending on the resolution of the date - * field + * field. */ @NoLayout - public Date rangeEnd = null; + public Date rangeEnd; + + /** + * The JSON used to construct a TimeZone on the client side, can be + * {@code null}. + * + * @since 8.2 + */ + public String timeZoneJSON; } diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneId.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneId.java new file mode 100644 index 0000000000..cbdbc23ec1 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneId.java @@ -0,0 +1,78 @@ +package com.vaadin.tests.components.datefield; + +import java.time.LocalDateTime; +import java.time.Month; +import java.time.ZoneId; +import java.util.Locale; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Stream; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.DateTimeField; +import com.vaadin.ui.TextField; + +public class DateTimeFieldZoneId extends AbstractTestUI { + + static final String ZONE_ID = "zoneId"; + static final String LOCALE_ID = "localeId"; + static final String PATTERN_ID = "patternId"; + + static final LocalDateTime INITIAL_DATE_TIME = LocalDateTime.of(2017, + Month.JANUARY, 1, 0, 0); + private static final String FORMAT_PATTERN = "dd MMM yyyy - hh:mm:ss a z"; + + @Override + protected String getTestDescription() { + return "DateTimeField to correctly show time zone name"; + } + + @Override + protected Integer getTicketNumber() { + return 8844; + } + + @Override + protected void setup(VaadinRequest request) { + final ComboBox<String> zoneIdComboBox = new ComboBox<>(); + zoneIdComboBox.setId(ZONE_ID); + Set<String> zoneIdSet = new TreeSet<>(ZoneId.getAvailableZoneIds()); + zoneIdComboBox.setItems(zoneIdSet); + addComponent(zoneIdComboBox); + + final ComboBox<Locale> localeIdComboBox = new ComboBox<>(); + localeIdComboBox.setId(LOCALE_ID); + Stream<Locale> localeStream = Stream.of(Locale.getAvailableLocales()) + .sorted((l1, l2) -> l1.toString().compareTo(l2.toString())); + localeIdComboBox.setItems(localeStream); + addComponent(localeIdComboBox); + + final TextField patternTextField = new TextField(); + patternTextField.setId(PATTERN_ID); + patternTextField.setValue(FORMAT_PATTERN); + addComponent(patternTextField); + + final DateTimeField dateTimeField = new DateTimeField(); + dateTimeField.setValue(INITIAL_DATE_TIME); + dateTimeField.setDateFormat(FORMAT_PATTERN); + addComponent(dateTimeField); + + zoneIdComboBox.addValueChangeListener(event -> { + String value = event.getValue(); + if (value == null) { + dateTimeField.setZoneId(null); + } else { + dateTimeField.setZoneId(ZoneId.of(value)); + } + }); + + localeIdComboBox.addValueChangeListener( + event -> dateTimeField.setLocale(event.getValue())); + + patternTextField.addValueChangeListener( + event -> dateTimeField.setDateFormat(event.getValue())); + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneIdTest.java b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneIdTest.java new file mode 100644 index 0000000000..d86307868d --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneIdTest.java @@ -0,0 +1,204 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed 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 com.vaadin.tests.components.datefield; + +import static com.vaadin.tests.components.datefield.DateTimeFieldZoneId.INITIAL_DATE_TIME; +import static com.vaadin.tests.components.datefield.DateTimeFieldZoneId.LOCALE_ID; +import static com.vaadin.tests.components.datefield.DateTimeFieldZoneId.PATTERN_ID; +import static com.vaadin.tests.components.datefield.DateTimeFieldZoneId.ZONE_ID; +import static java.time.temporal.ChronoUnit.MONTHS; +import static org.junit.Assert.assertTrue; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.TimeZone; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.vaadin.testbench.elements.ComboBoxElement; +import com.vaadin.testbench.elements.DateTimeFieldElement; +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class DateTimeFieldZoneIdTest extends MultiBrowserTest { + + private static TimeZone defaultTimeZone; + private static LocalDateTime THIRTY_OF_JULY = INITIAL_DATE_TIME + .plus(6, MONTHS).withDayOfMonth(30); + + @BeforeClass + public static void init() { + defaultTimeZone = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("Brazil/Acre")); + } + + @AfterClass + public static void cleanup() { + TimeZone.setDefault(defaultTimeZone); + } + + @Test + public void defaultDisplayName() { + openTestURL(); + + DateTimeFieldElement dateField = $(DateTimeFieldElement.class).first(); + dateField.openPopup(); + + LocalDate initialDate = INITIAL_DATE_TIME.toLocalDate(); + assertEndsWith(dateField, getUTCString(initialDate)); + + dateField.setDateTime(THIRTY_OF_JULY); + + assertEndsWith(dateField, getUTCString(THIRTY_OF_JULY.toLocalDate())); + } + + @Test + public void zoneIdTokyo() { + openTestURL(); + + DateTimeFieldElement dateField = $(DateTimeFieldElement.class).first(); + + setZoneId("Asia/Tokyo"); + + dateField.openPopup(); + + assertEndsWith(dateField, "JST"); + + dateField.setDateTime(THIRTY_OF_JULY); + + assertEndsWith(dateField, "JST"); + } + + @Test + public void zoneIdBerlin() { + openTestURL(); + + DateTimeFieldElement dateField = $(DateTimeFieldElement.class).first(); + + setZoneId("Europe/Berlin"); + + dateField.openPopup(); + + assertEndsWith(dateField, "CET"); + + dateField.setDateTime(THIRTY_OF_JULY); + + assertEndsWith(dateField, "CEST"); + } + + @Test + public void defaultDisplayNameLocaleGerman() { + openTestURL(); + + setLocale("de"); + + DateTimeFieldElement dateField = $(DateTimeFieldElement.class).first(); + dateField.openPopup(); + + assertEndsWith(dateField, + getUTCString(INITIAL_DATE_TIME.toLocalDate())); + + dateField.setDateTime(THIRTY_OF_JULY); + + assertEndsWith(dateField, getUTCString(THIRTY_OF_JULY.toLocalDate())); + } + + @Test + public void zoneIdBeirutLocaleGerman() { + openTestURL(); + + DateTimeFieldElement dateField = $(DateTimeFieldElement.class).first(); + + setZoneId("Asia/Beirut"); + setLocale("de"); + + dateField.openPopup(); + + assertEndsWith(dateField, "OEZ"); + + dateField.setDateTime(THIRTY_OF_JULY); + + assertEndsWith(dateField, "OESZ"); + } + + @Test + public void zInQuotes() { + openTestURL(); + + DateTimeFieldElement dateField = $(DateTimeFieldElement.class).first(); + + setZoneId("Asia/Tokyo"); + + TextFieldElement patternField = $(TextFieldElement.class) + .id(PATTERN_ID); + patternField.setValue("dd MMM yyyy - hh:mm:ss a 'z' z"); + + dateField.openPopup(); + + assertEndsWith(dateField, "z JST"); + + dateField.setDateTime(THIRTY_OF_JULY); + + assertEndsWith(dateField, "z JST"); + } + + private void assertEndsWith(DateTimeFieldElement element, String suffix) { + String text = element.getValue(); + assertTrue(text + " should end with " + suffix, text.endsWith(suffix)); + } + + /** + * Returns the timezone name formatted as returned by + * {@link com.google.gwt.i18n.client.DateTimeFormat}, which supports only + * standard GMT and RFC format. + * + * The {@link ZoneId} used is the operating system default + */ + private static String getUTCString(LocalDate localDate) { + Instant instant = localDate.atStartOfDay() + .atZone(defaultTimeZone.toZoneId()).toInstant(); + Duration duration = Duration + .ofMillis(defaultTimeZone.getOffset(instant.toEpochMilli())); + + String suffix; + if (duration.toMinutes() == 0) { + suffix = ""; + } else { + long minutes = duration.toMinutes() + % Duration.ofHours(1).toMinutes(); + long hours = duration.toHours(); + suffix = (hours >= 0 ? "+" : "") + hours + + (minutes != 0 ? ":" + minutes : ""); + } + + return "UTC" + suffix; + } + + private void setZoneId(String zoneId) { + ComboBoxElement zoneIdComboBox = $(ComboBoxElement.class).id(ZONE_ID); + zoneIdComboBox.selectByText(zoneId); + } + + private void setLocale(String locale) { + ComboBoxElement zoneIdComboBox = $(ComboBoxElement.class).id(LOCALE_ID); + zoneIdComboBox.selectByText(locale); + } +} |