summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/DateTimeService.java93
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java11
-rw-r--r--client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java17
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractDateField.java51
-rw-r--r--server/src/main/java/com/vaadin/util/TimeZoneUtil.java150
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java20
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneId.java78
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/datefield/DateTimeFieldZoneIdTest.java204
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);
+ }
+}