From 7be8ef7e6cd9724a552dd5ae35912e7eb3cf73dd Mon Sep 17 00:00:00 2001
From: stroomdev66
+ * CSS Style Rules
+ *
+ *
+ *
+ *
+ *
+ * Example
+ * {@example com.google.gwt.examples.DatePickerExample}
+ *
+ * A datepicker may show days not in the current month. It + * must show all days in the current month. + *
+ * + * @return the current month + */ + public JsDate getCurrentMonth() { + return getModel().getCurrentMonth(); + } + + /** + * Returns the first shown date. + * + * @return the first date. + */ + // Final because the view should always control the value of the first date. + public final JsDate getFirstDate() { + return view.getFirstDate(); + } + + /** + * Gets the highlighted date (the one the mouse is hovering over), if any. + * + * @return the highlighted date + */ + public final JsDate getHighlightedDate() { + return CalendarUtil.copyDate(highlighted); + } + + /** + * Returns the last shown date. + * + * @return the last date. + */ + // Final because the view should always control the value of the last date. + public final JsDate getLastDate() { + return view.getLastDate(); + } + + /** + * Returns the number of year to display in the years selection dropdown. + */ + public int getVisibleYearCount() { + return visibleYearCount; + } + + /** + * Gets the style associated with a date (does not include styles set via + * {@link #addTransientStyleToDates}). + * + * @param date the date + * @return the styles associated with this date + */ + public String getStyleOfDate(JsDate date) { + return styler.getStyleName(date); + } + + /** + * Returns the selected date, or null if none is selected. + * + * @return the selected date, or null + */ + public final JsDate getValue() { + return CalendarUtil.copyDate(value); + } + + /** + * Is the visible date enabled? + * + * @param date the date, which must be visible + * @return is the date enabled? + */ + public boolean isDateEnabled(JsDate date) { + assert isDateVisible(date) : date + " is not visible"; + return getView().isDateEnabled(date); + } + + /** + * Is the date currently shown in the date picker? + * + * @param date + * @return is the date currently shown + */ + public boolean isDateVisible(JsDate date) { + CalendarView r = getView(); + JsDate first = r.getFirstDate(); + JsDate last = r.getLastDate(); + return (date != null && (CalendarUtil.isSameDate(first, date) + || CalendarUtil.isSameDate(last, date) || + (first.getTime() < date.getTime()) && last.getTime() > date.getTime())); + } + + /** + * Can the user navigate through the years? + * + * @return is the year navigation is enabled + */ + public boolean isYearArrowsVisible() { + return yearArrowsVisible; + } + + /** + * Is the year and month selectable via a dropdown? + */ + public boolean isYearAndMonthDropdownVisible() { + return yearAndMonthDropdownVisible; + } + + @Override + public void onLoad() { + ShowRangeEvent.fire(this, getFirstDate(), getLastDate()); + } + + /** + * Removes the styleName from the given dates (even if it is transient). + */ + public void removeStyleFromDates(String styleName, JsDate date) { + styler.setStyleName(date, styleName, false); + if (isDateVisible(date)) { + getView().removeStyleFromDate(styleName, date); + } + } + + /** + * Removes the styleName from the given dates (even if it is transient). + */ + public void removeStyleFromDates(String styleName, JsDate date, + JsDate... moreDates) { + removeStyleFromDates(styleName, date); + for (JsDate d : moreDates) { + removeStyleFromDates(styleName, d); + } + } + + /** + * Removes the styleName from the given dates (even if it is transient). + */ + public void removeStyleFromDates(String styleName, Iterable+ * A datepicker may show days not in the current month. It + * must show all days in the current month. + *
+ * + * @param month the month to show + */ + public void setCurrentMonth(JsDate month) { + getModel().setCurrentMonth(month); + refreshAll(); + } + + /** + * Set the number of years to display in the years selection dropdown. The range of years will be + * centered on the selected date. + */ + public void setVisibleYearCount(int numberOfYears) { + if (numberOfYears <= 0) { + throw new IllegalArgumentException("The number of years to display must be positive"); + } + + visibleYearCount = numberOfYears; + getMonthSelector().refresh(); + } + + /** + * Set if the user can navigate through the years via a set of backward and forward buttons. + */ + public void setYearArrowsVisible(boolean yearArrowsVisible) { + this.yearArrowsVisible = yearArrowsVisible; + getMonthSelector().refresh(); + } + + /** + * If thedropdownVisible
is equal to true, the user will be able to change the current month and
+ * the current year of the date picker via two dropdown lists.
+ */
+ public void setYearAndMonthDropdownVisible(boolean dropdownVisible) {
+ this.yearAndMonthDropdownVisible = dropdownVisible;
+ getMonthSelector().refresh();
+ }
+
+ /**
+ * Sets the date picker style name.
+ */
+ @Override
+ public void setStyleName(String styleName) {
+ css = new StandardCss(styleName, "customDatePicker");
+ super.setStyleName(styleName);
+ }
+
+ /**
+ * Sets a visible date to be enabled or disabled. This is only set until the
+ * next time the DatePicker is refreshed.
+ */
+ public final void setTransientEnabledOnDates(boolean enabled, JsDate date) {
+ assert isDateVisible(date) : date + " must be visible";
+ getView().setEnabledOnDate(enabled, date);
+ }
+
+ /**
+ * Sets a visible date to be enabled or disabled. This is only set until the
+ * next time the DatePicker is refreshed.
+ */
+ public final void setTransientEnabledOnDates(boolean enabled, JsDate date,
+ JsDate... moreDates) {
+ setTransientEnabledOnDates(enabled, date);
+ for (JsDate d : moreDates) {
+ setTransientEnabledOnDates(enabled, d);
+ }
+ }
+
+ /**
+ * Sets a group of visible dates to be enabled or disabled. This is only set
+ * until the next time the DatePicker is refreshed.
+ */
+ public final void setTransientEnabledOnDates(boolean enabled,
+ Iterable+ * DateRecord class exposes almost the same set of interface as Date class with + * only a few exceptions. The main purpose is the record all the information + * during parsing phase and resolve them in a later time when all information + * can be processed together. + */ +public class DateRecord { + + public static final int AM = 0; + public static final int PM = 1; + + private static final int JS_START_YEAR = 1900; + + private int era; + private int year; + private int month; + private int dayOfMonth; + private int ampm; + private boolean midnightIs24; + private int hours; + private int minutes; + private int seconds; + private int milliseconds; + + private int tzOffset; + private int dayOfWeek; + private boolean ambiguousYear; + + /** + * Initialize DateExt object with default value. Here we use -1 for most of + * the field to indicate that field is not set. + */ + public DateRecord() { + era = -1; + ambiguousYear = false; + year = Integer.MIN_VALUE; + month = -1; + dayOfMonth = -1; + ampm = -1; + midnightIs24 = false; + hours = -1; + minutes = -1; + seconds = -1; + milliseconds = -1; + dayOfWeek = -1; + tzOffset = Integer.MIN_VALUE; + } + + /** + * calcDate uses all the field available so far to fill a Date object. For + * those information that is not provided, the existing value in 'date' will + * be kept. Ambiguous year will be resolved after the date/time values are + * resolved. + *
+ * If the strict option is set to true, calcDate will calculate certain + * invalid dates by wrapping around as needed. For example, February 30 will + * wrap to March 2. + * + * @param date The Date object being filled. Its value should be set to an + * acceptable default before pass in to this method + * @param strict true to be strict when parsing + * @return true if successful, otherwise false. + */ + public boolean calcDate(JsDate date, boolean strict) { + // Year 0 is 1 BC, and so on. + if (this.era == 0 && this.year > 0) { + this.year = -(this.year - 1); + } + + if (this.year > Integer.MIN_VALUE) { + date.setFullYear(this.year - JS_START_YEAR); + } + + // "setMonth" and "setDate" is a little bit tricky. Suppose content in + // date is 11/30, switch month to 02 will lead to 03/02 since 02/30 does + // not exist. And you certain won't like 02/12 turn out to be 03/12. So + // here to set date to a smaller number before month, and later setMonth. + // Real date is set after, and that might cause month switch. However, + // that's desired. + int orgDayOfMonth = date.getDate(); + date.setDate(1); + + if (this.month >= 0) { + date.setMonth(this.month); + } + + if (this.dayOfMonth >= 0) { + date.setDate(this.dayOfMonth); + } else if (this.month >= 0) { + // If the month was parsed but dayOfMonth was not, then the current day of + // the month shouldn't affect the parsed month. For example, if "Feb2006" + // is parse on January 31, the resulting date should be in February, not + // March. So, we limit the day of the month to the maximum day within the + // parsed month. + JsDate tmp = JsDate.create(date.getFullYear(), date.getMonth(), 35); + int daysInCurrentMonth = 35 - tmp.getDate(); + date.setDate(Math.min(daysInCurrentMonth, orgDayOfMonth)); + } else { + date.setDate(orgDayOfMonth); + } + + // adjust ampm + if (this.hours < 0) { + this.hours = date.getHours(); + } + + if (this.ampm > 0) { + if (this.hours < 12) { + this.hours += 12; + } + } + date.setHours(this.hours == 24 && this.midnightIs24 + ? 0 + : this.hours); + + if (this.minutes >= 0) { + date.setMinutes(this.minutes); + } + + if (this.seconds >= 0) { + date.setSeconds(this.seconds); + } + + if (this.milliseconds >= 0) { + date.setTime(date.getTime() / 1000 * 1000 + this.milliseconds); + } + + // If strict, verify that the original date fields match the calculated date + // fields. We do this before we set the timezone offset, which will skew all + // of the dates. + // + // We don't need to check the day of week as it is guaranteed to be correct + // or return false below. + if (strict) { + if ((this.year > Integer.MIN_VALUE) + && ((this.year - JS_START_YEAR) != date.getFullYear())) { + return false; + } + if ((this.month >= 0) && (this.month != date.getMonth())) { + return false; + } + if ((this.dayOfMonth >= 0) && (this.dayOfMonth != date.getDate())) { + return false; + } + // Times have well defined maximums + if (this.hours == 24 && this.midnightIs24) { + if (this.ampm > 0) { + return false; + } + } else if (this.hours >= 24) { + return false; + } else if (this.hours == 0 && this.midnightIs24) { + return false; + } + if (this.minutes >= 60) { + return false; + } + if (this.seconds >= 60) { + return false; + } + if (this.milliseconds >= 1000) { + return false; + } + } + + // Resolve ambiguous year if needed. + if (this.ambiguousYear) { // the two-digit year == the default start year + JsDate defaultCenturyStart = JsDate.create(); + defaultCenturyStart.setFullYear(defaultCenturyStart.getFullYear() - 80); + if (date.getTime() < defaultCenturyStart.getTime()) { + date.setFullYear(defaultCenturyStart.getFullYear() + 100); + } + } + + // Date is resolved to the nearest dayOfWeek if date is not explicitly + // specified. There is one exception, if the nearest dayOfWeek falls + // into a different month, the 2nd nearest dayOfWeek, which is on the + // other direction, will be used. + if (this.dayOfWeek >= 0) { + if (this.dayOfMonth == -1) { + // Adjust to the nearest day of the week. + int adjustment = (7 + this.dayOfWeek - date.getDay()) % 7; + if (adjustment > 3) { + adjustment -= 7; + } + int orgMonth = date.getMonth(); + date.setDate(date.getDate() + adjustment); + + // If the nearest weekday fall into a different month, we will use the + // 2nd nearest weekday, which will be on the other direction, and is + // sure fall into the same month. + if (date.getMonth() != orgMonth) { + date.setDate(date.getDate() + (adjustment > 0 + ? -7 + : 7)); + } + } else { + if (date.getDay() != this.dayOfWeek) { + return false; + } + } + } + + // Adjust time zone. + if (this.tzOffset > Integer.MIN_VALUE) { + int offset = date.getTimezoneOffset(); + date.setTime(date.getTime() + (this.tzOffset - offset) * 60 * 1000); + // HBJ date.setTime(date.getTime() + this.tzOffset * 60 * 1000); + } + + return true; + } + + /** + * Set ambiguous year field. This flag indicates that a 2 digit years's + * century need to be determined by its date/time value. This can only be + * resolved after its date/time is known. + * + * @param ambiguousYear true if it is ambiguous year. + */ + public void setAmbiguousYear(boolean ambiguousYear) { + this.ambiguousYear = ambiguousYear; + } + + /** + * Set morning/afternoon field. + * + * @param ampm ampm value. + */ + public void setAmpm(int ampm) { + this.ampm = ampm; + } + + /** + * Set dayOfMonth field. + * + * @param day dayOfMonth value + */ + public void setDayOfMonth(int day) { + this.dayOfMonth = day; + } + + /** + * Set dayOfWeek field. + * + * @param dayOfWeek day of the week. + */ + public void setDayOfWeek(int dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + /** + * Set Era field. + * + * @param era era value being set. + */ + public void setEra(int era) { + this.era = era; + } + + /** + * Set hour field. + * + * @param hours hour value. + */ + public void setHours(int hours) { + this.hours = hours; + } + + /** + * Set midnightIs24 field. + * + * @param midnightIs24 whether an hour value of 24 signifies midnight. + */ + public void setMidnightIs24(boolean midnightIs24) { + this.midnightIs24 = midnightIs24; + } + + /** + * Set milliseconds field. + * + * @param milliseconds milliseconds value. + */ + public void setMilliseconds(int milliseconds) { + this.milliseconds = milliseconds; + } + + /** + * Set minute field. + * + * @param minutes minute value. + */ + public void setMinutes(int minutes) { + this.minutes = minutes; + } + + /** + * Set month field. + * + * @param month month value. + */ + public void setMonth(int month) { + this.month = month; + } + + /** + * Set seconds field. + * + * @param seconds second value. + */ + public void setSeconds(int seconds) { + this.seconds = seconds; + } + + /** + * Set timezone offset, in minutes. + * + * @param tzOffset timezone offset. + */ + public void setTzOffset(int tzOffset) { + this.tzOffset = tzOffset; + } + + /** + * Set year field. + * + * @param value year value. + */ + public void setFullYear(int value) { + this.year = value; + } +} diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeConstants.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeConstants.java new file mode 100644 index 00000000000..c76474d5692 --- /dev/null +++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeConstants.java @@ -0,0 +1,70 @@ +package stroom.widget.datepicker.client; + +import stroom.widget.datepicker.client.IntlDateTimeFormat.FormatOptions; +import stroom.widget.datepicker.client.IntlDateTimeFormat.FormatOptions.Day; +import stroom.widget.datepicker.client.IntlDateTimeFormat.FormatOptions.Month; +import stroom.widget.datepicker.client.IntlDateTimeFormat.FormatOptions.Weekday; + +import com.google.gwt.core.client.GWT; + +public class DateTimeConstants { + + public static final int DAYS_IN_WEEK = 7; + + public static final int MONTHS_IN_YEAR = 12; + + private static final int MAX_DAYS_IN_MONTH = 31; + + private final String[] languages; + private final String[] dayOfWeekNames; + private final String[] dayOfMonthNames; + private final String[] monthNames; + + public DateTimeConstants() { + languages = new String[IntlDateTimeFormat.getLanguageCount()]; + for (int i = 0; i < languages.length; i++) { + languages[i] = IntlDateTimeFormat.getLanguage(i); + GWT.log(languages[i]); + } + + dayOfWeekNames = new String[DAYS_IN_WEEK]; + for (int i = 0; i < dayOfWeekNames.length; i++) { + final JsDate date = JsDate.create(2000, 0, i + 1); + final String dayOfWeekName = IntlDateTimeFormat.format(date, languages, + FormatOptions.builder().weekday(Weekday.SHORT).build()); + dayOfWeekNames[date.getDay()] = dayOfWeekName; + } + + dayOfMonthNames = new String[MAX_DAYS_IN_MONTH + 1]; + for (int i = 1; i < dayOfMonthNames.length; i++) { + final JsDate date = JsDate.create(2000, 0, i); + final String dayOfMonthName = IntlDateTimeFormat.format(date, languages, + FormatOptions.builder().day(Day.NUMERIC).build()); + dayOfMonthNames[i] = dayOfMonthName; + } + + monthNames = new String[MONTHS_IN_YEAR]; + for (int i = 0; i < monthNames.length; i++) { + final JsDate date = JsDate.create(2000, i, 1); + final String monthName = IntlDateTimeFormat.format(date, languages, + FormatOptions.builder().month(Month.LONG).build()); + monthNames[i] = monthName; + } + } + + public String[] getLanguages() { + return languages; + } + + public String[] getDayOfWeekNames() { + return dayOfWeekNames; + } + + public String[] getDayOfMonthNames() { + return dayOfMonthNames; + } + + public String[] getMonthNames() { + return monthNames; + } +} diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeFormat.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeFormat.java new file mode 100644 index 00000000000..731d5146d19 --- /dev/null +++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeFormat.java @@ -0,0 +1,19 @@ +package stroom.widget.datepicker.client; + +public interface DateTimeFormat { + + /** + * Format a date object. + * + * @param date the date object being formatted + * @return string representation for this date in desired format + */ + String format(JsDate date); + + /** + * Retrieve the pattern used in this DateTimeFormat object. + * + * @return pattern string + */ + String getPattern(); +} diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeFormatImpl.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeFormatImpl.java new file mode 100644 index 00000000000..aded2d0a7db --- /dev/null +++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DateTimeFormatImpl.java @@ -0,0 +1,2176 @@ +/* + * Copyright 2008 Google Inc. + * + * 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 stroom.widget.datepicker.client; + +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.i18n.shared.DateTimeFormatInfo; +import com.google.gwt.i18n.shared.DefaultDateTimeFormatInfo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Formats and parses dates and times using locale-sensitive patterns. + * + *
Symbol | + *Meaning | + *Presentation | + *Example | + *
---|---|---|---|
G |
+ * era designator | + *Text | + *AD |
+ *
y |
+ * year | + *Number | + *1996 |
+ *
L |
+ * standalone month in year | + *Text or Number | + *July (or) 07 |
+ *
M |
+ * month in year | + *Text or Number | + *July (or) 07 |
+ *
d |
+ * day in month | + *Number | + *10 |
+ *
h |
+ * hour in am/pm (1-12) | + *Number | + *12 |
+ *
H |
+ * hour in day (0-23) | + *Number | + *0 |
+ *
m |
+ * minute in hour | + *Number | + *30 |
+ *
s |
+ * second in minute | + *Number | + *55 |
+ *
S |
+ * fractional second | + *Number | + *978 |
+ *
E |
+ * day of week | + *Text | + *Tuesday |
+ *
c |
+ * standalone day of week | + *Text | + *Tuesday |
+ *
a |
+ * am/pm marker | + *Text | + *PM |
+ *
k |
+ * hour in day (1-24) | + *Number | + *24 |
+ *
K |
+ * hour in am/pm (0-11) | + *Number | + *0 |
+ *
z |
+ * time zone | + *Text | + *Pacific Standard Time(see comment) |
+ *
Z |
+ * time zone (RFC 822) | + *Text | + *-0800(See comment) |
+ *
v |
+ * time zone id | + *Text | + *America/Los_Angeles(See comment) |
+ *
' |
+ * escape for text | + *Delimiter | + *'Date=' |
+ *
'' |
+ * single quote | + *Literal | + *'o''clock' |
+ *
+ * The number of pattern letters influences the format, as follows: + *
+ * + *"EEEE"
produces
+ * "Monday"
, "EEE"
produces "Mon"
)"m"
produces "6"
, "mm"
+ * produces "06"
). Year is handled specially; that is, if the count
+ * of 'y' is 2, the Year will be truncated to 2 digits. (e.g., if
+ * "yyyy"
produces "1997"
, "yy"
produces
+ * "97"
.) Unlike other fields, fractional seconds are padded on the
+ * right with zero."M"
+ * produces "1"
, "MM"
produces "01"
,
+ * "MMM"
produces "Jan"
, and "MMMM"
+ * produces "January"
. Some pattern letters also treat a count
+ * of 5 specially, meaning a single-letter abbreviation: L
,
+ * M
, E
, and c
.
+ * Any characters in the pattern that are not in the ranges of ['a
+ * '..'z
'] and ['A
'..'Z
'] will be treated
+ * as quoted text. For instance, characters like ':
', '
+ * .
', '
' (space), '#
' and '
+ * @
' will appear in the resulting time text even they are not
+ * embraced within single quotes.
+ *
+ * [Time Zone Handling] Web browsers don't provide all the information we need + * for proper time zone formating -- so GWT has a copy of the required data, for + * your convenience. For simpler cases, one can also use a fallback + * implementation that only keeps track of the current timezone offset. These + * two approaches are called, respectively, Common TimeZones and Simple + * TimeZones, although both are implemented with the same TimeZone class. + *
+ * "TimeZone createTimeZone(String timezoneData)" returns a Common TimeZone + * object, and "TimeZone createTimeZone(int timeZoneOffsetInMinutes)" returns a + * Simple TimeZone object. The one provided by OS fall into to Simple TimeZone + * category. For formatting purpose, following table shows the behavior of GWT + * DateTimeFormat. + *
+ *Pattern | + *Common TimeZone | + *Simple TimeZone | + *
---|---|---|
z, zz, zzz | + *PDT | + *UTC-7 | + *
zzzz | + *Pacific Daylight Time | + *UTC-7 | + *
Z, ZZ | + *-0700 | + *-0700 | + *
ZZZ | + *-07:00 | + *-07:00 | + *
ZZZZ | + *GMT-07:00 | + *GMT-07:00 | + *
v, vv, vvv, vvvv | + *America/Los_Angeles | + *Etc/GMT+7 | + *
+ * The pattern does not need to specify every field. If the year, month, or + * day is missing from the pattern, the corresponding value will be taken from + * the current date. If the month is specified but the day is not, the day will + * be constrained to the last day within the specified month. If the hour, + * minute, or second is missing, the value defaults to zero. + *
+ * + *+ * As with formatting (described above), the count of pattern letters determines + * the parsing behavior. + *
+ * + *+ * Although the current pattern specification doesn't not specify behavior for + * all letters, it may in the future. It is strongly discouraged to use + * unspecified letters as literal text without quoting them. + *
+ *+ * [Note on TimeZone] The time zone support for parsing is limited. Only + * standard GMT and RFC format are supported. Time zone specification using time + * zone id (like America/Los_Angeles), time zone names (like PST, Pacific + * Standard Time) are not supported. Normally, it is too much a burden for a + * client application to load all the time zone symbols. And in almost all those + * cases, it is a better choice to do such parsing on server side through + * certain RPC mechanism. This decision is based on particular use cases we have + * studied; in principle, it could be changed in future versions. + *
+ * + *Pattern | + *Formatted Text | + *
---|---|
"yyyy.MM.dd G 'at' HH:mm:ss vvvv" |
+ * 1996.07.10 AD at 15:08:56 America/Los_Angeles |
+ *
"EEE, MMM d, ''yy" |
+ * Wed, July 10, '96 |
+ *
"h:mm a" |
+ * 12:08 PM |
+ *
"hh 'o''clock' a, zzzz" |
+ * 12 o'clock PM, Pacific Daylight Time |
+ *
"K:mm a, vvvv" |
+ * 0:00 PM, America/Los_Angeles |
+ *
"yyyyy.MMMMM.dd GGG hh:mm aaa" |
+ * 01996.July.10 AD 12:08 PM |
+ *
+ * When parsing a date string using the abbreviated year pattern (
+ * "yy"
), the parser must interpret the abbreviated year relative
+ * to some century. It does this by adjusting dates to be within 80 years before
+ * and 20 years after the time the parser instance is created. For example,
+ * using a pattern of "MM/dd/yy"
and a DateTimeFormat
+ * object created on Jan 1, 1997, the string "01/11/12"
would be
+ * interpreted as Jan 11, 2012 while the string "05/04/64"
would be
+ * interpreted as May 4, 1964. During parsing, only strings consisting of
+ * exactly two digits, as defined by {@link Character#isDigit(char)},
+ * will be parsed into the default century. If the year pattern does not have
+ * exactly two 'y' characters, the year is interpreted literally, regardless of
+ * the number of digits. For example, using the pattern
+ * "MM/dd/yyyy"
, "01/11/12" parses to Jan 11, 12 A.D.
+ *
+ * When numeric fields abut one another directly, with no intervening delimiter + * characters, they constitute a run of abutting numeric fields. Such runs are + * parsed specially. For example, the format "HHmmss" parses the input text + * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to + * parse "1234". In other words, the leftmost field of the run is flexible, + * while the others keep a fixed width. If the parse fails anywhere in the run, + * then the leftmost field is shortened by one character, and the entire run is + * parsed again. This is repeated until either the parse succeeds or the + * leftmost field is one character in length. If the parse still fails at that + * point, the parse of the run fails. + *
+ * + *
+ * In the current implementation, timezone parsing only supports
+ * GMT:hhmm
, GMT:+hhmm
, and GMT:-hhmm
.
+ *
Example: {@code 2008-10-03T10:29:40.046-04:00} + *
http://code.google.com/p/google-web-toolkit/issues/detail?id=3068 + *
http://www.iso.org/iso/support/faqs/faqs_widely_used_standards/widely_used_standards_other/date_and_time_format.htm + */ + ISO_8601, + + /** + * RFC 2822 date format, fixed across all locales. + *
Example: {@code Thu, 20 May 2010 17:54:50 -0700} + *
http://tools.ietf.org/html/rfc2822#section-3.3
+ */
+ RFC_2822,
+
+ DATE_FULL,
+ DATE_LONG,
+ DATE_MEDIUM,
+ DATE_SHORT,
+
+ TIME_FULL,
+ TIME_LONG,
+ TIME_MEDIUM,
+ TIME_SHORT,
+
+ DATE_TIME_FULL,
+ DATE_TIME_LONG,
+ DATE_TIME_MEDIUM,
+ DATE_TIME_SHORT,
+
+ DAY,
+ HOUR_MINUTE,
+ HOUR_MINUTE_SECOND,
+ HOUR24_MINUTE,
+ HOUR24_MINUTE_SECOND,
+ MINUTE_SECOND,
+ MONTH,
+ MONTH_ABBR,
+ MONTH_ABBR_DAY,
+ MONTH_DAY,
+ MONTH_NUM_DAY,
+ MONTH_WEEKDAY_DAY,
+ YEAR,
+ YEAR_MONTH,
+ YEAR_MONTH_ABBR,
+ YEAR_MONTH_ABBR_DAY,
+ YEAR_MONTH_DAY,
+ YEAR_MONTH_NUM,
+ YEAR_MONTH_NUM_DAY,
+ YEAR_MONTH_WEEKDAY_DAY,
+ YEAR_QUARTER,
+ YEAR_QUARTER_ABBR,
+ }
+
+ /**
+ * Class PatternPart holds a "compiled" pattern part.
+ */
+ private static class PatternPart {
+
+ public String text;
+ public int count; // 0 has a special meaning, it stands for literal
+ public boolean abutStart;
+
+ public PatternPart(String txt, int cnt) {
+ text = txt;
+ count = cnt;
+ abutStart = false;
+ }
+ }
+
+ protected static final String RFC2822_PATTERN = "EEE, d MMM yyyy HH:mm:ss Z";
+ protected static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ";
+
+ private static final int NUMBER_BASE = 10;
+ private static final int JS_START_YEAR = 1900;
+
+ private static final Map See {@link CustomDateTimeFormat} if you need a localized format that is
+ * not supported here.
+ *
+ * @param predef {@link PredefinedFormat} describing desired format
+ * @return a DateTimeFormat instance for the specified format
+ */
+ public static DateTimeFormatImpl getFormat(PredefinedFormat predef) {
+ if (usesFixedEnglishStrings(predef)) {
+ String pattern;
+ switch (predef) {
+ case RFC_2822:
+ pattern = RFC2822_PATTERN;
+ break;
+ case ISO_8601:
+ pattern = ISO8601_PATTERN;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected predef type " + predef);
+ }
+ return getFormat(pattern, new DefaultDateTimeFormatInfo());
+ }
+ DateTimeFormatInfo dtfi = getDefaultDateTimeFormatInfo();
+ String pattern;
+ switch (predef) {
+ case DATE_FULL:
+ pattern = dtfi.dateFormatFull();
+ break;
+ case DATE_LONG:
+ pattern = dtfi.dateFormatLong();
+ break;
+ case DATE_MEDIUM:
+ pattern = dtfi.dateFormatMedium();
+ break;
+ case DATE_SHORT:
+ pattern = dtfi.dateFormatShort();
+ break;
+ case DATE_TIME_FULL:
+ pattern = dtfi.dateTimeFull(dtfi.timeFormatFull(),
+ dtfi.dateFormatFull());
+ break;
+ case DATE_TIME_LONG:
+ pattern = dtfi.dateTimeLong(dtfi.timeFormatLong(),
+ dtfi.dateFormatLong());
+ break;
+ case DATE_TIME_MEDIUM:
+ pattern = dtfi.dateTimeMedium(dtfi.timeFormatMedium(),
+ dtfi.dateFormatMedium());
+ break;
+ case DATE_TIME_SHORT:
+ pattern = dtfi.dateTimeShort(dtfi.timeFormatShort(),
+ dtfi.dateFormatShort());
+ break;
+ case DAY:
+ pattern = dtfi.formatDay();
+ break;
+ case HOUR24_MINUTE:
+ pattern = dtfi.formatHour24Minute();
+ break;
+ case HOUR24_MINUTE_SECOND:
+ pattern = dtfi.formatHour24MinuteSecond();
+ break;
+ case HOUR_MINUTE:
+ pattern = dtfi.formatHour12Minute();
+ break;
+ case HOUR_MINUTE_SECOND:
+ pattern = dtfi.formatHour12MinuteSecond();
+ break;
+ case MINUTE_SECOND:
+ pattern = dtfi.formatMinuteSecond();
+ break;
+ case MONTH:
+ pattern = dtfi.formatMonthFull();
+ break;
+ case MONTH_ABBR:
+ pattern = dtfi.formatMonthAbbrev();
+ break;
+ case MONTH_ABBR_DAY:
+ pattern = dtfi.formatMonthAbbrevDay();
+ break;
+ case MONTH_DAY:
+ pattern = dtfi.formatMonthFullDay();
+ break;
+ case MONTH_NUM_DAY:
+ pattern = dtfi.formatMonthNumDay();
+ break;
+ case MONTH_WEEKDAY_DAY:
+ pattern = dtfi.formatMonthFullWeekdayDay();
+ break;
+ case TIME_FULL:
+ pattern = dtfi.timeFormatFull();
+ break;
+ case TIME_LONG:
+ pattern = dtfi.timeFormatLong();
+ break;
+ case TIME_MEDIUM:
+ pattern = dtfi.timeFormatMedium();
+ break;
+ case TIME_SHORT:
+ pattern = dtfi.timeFormatShort();
+ break;
+ case YEAR:
+ pattern = dtfi.formatYear();
+ break;
+ case YEAR_MONTH:
+ pattern = dtfi.formatYearMonthFull();
+ break;
+ case YEAR_MONTH_ABBR:
+ pattern = dtfi.formatYearMonthAbbrev();
+ break;
+ case YEAR_MONTH_ABBR_DAY:
+ pattern = dtfi.formatYearMonthAbbrevDay();
+ break;
+ case YEAR_MONTH_DAY:
+ pattern = dtfi.formatYearMonthFullDay();
+ break;
+ case YEAR_MONTH_NUM:
+ pattern = dtfi.formatYearMonthNum();
+ break;
+ case YEAR_MONTH_NUM_DAY:
+ pattern = dtfi.formatYearMonthNumDay();
+ break;
+ case YEAR_MONTH_WEEKDAY_DAY:
+ pattern = dtfi.formatYearMonthWeekdayDay();
+ break;
+ case YEAR_QUARTER:
+ pattern = dtfi.formatYearQuarterFull();
+ break;
+ case YEAR_QUARTER_ABBR:
+ pattern = dtfi.formatYearQuarterShort();
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected predefined format "
+ + predef);
+ }
+ return getFormat(pattern, dtfi);
+ }
+
+ /**
+ * Returns a DateTimeFormat object using the specified pattern. If you need to
+ * format or parse repeatedly using the same pattern, it is highly recommended
+ * that you cache the returned Note that the pattern supplied is used as-is -- for example, if you
+ * supply "MM/dd/yyyy" as the pattern, that is the order you will get the
+ * fields, even in locales where the order is different. It is recommended to
+ * use {@link #getFormat(PredefinedFormat)} instead -- if you use this method,
+ * you are taking responsibility for localizing the patterns yourself.
+ *
+ * @param pattern string to specify how the date should be formatted
+ * @return a This should be a method on PredefinedFormat, but that would defeat the
+ * enum optimizations GWT is currently capable of.
+ *
+ * @param predef
+ * @return true if the specified format requires English names/separators
+ */
+ private static boolean usesFixedEnglishStrings(PredefinedFormat predef) {
+ switch (predef) {
+ case RFC_2822:
+ return true;
+ case ISO_8601:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private final ArrayList
+ * Dates are parsed leniently, so invalid dates will be wrapped around as
+ * needed. For example, February 30 will wrap to March 2.
+ *
+ * @param text the string being parsed
+ * @return a parsed date/time value
+ * @throws IllegalArgumentException if the entire text could not be converted
+ * into a number
+ */
+ public JsDate parse(String text) throws IllegalArgumentException {
+ return parse(text, false);
+ }
+
+ /**
+ * This method modifies a {@link Date} object to reflect the date that is
+ * parsed from an input string.
+ *
+ * Dates are parsed leniently, so invalid dates will be wrapped around as
+ * needed. For example, February 30 will wrap to March 2.
+ *
+ * @param text the string that need to be parsed
+ * @param start the character position in "text" where parsing should start
+ * @param date the date object that will hold parsed value
+ * @return 0 if parsing failed, otherwise the number of characters advanced
+ */
+ public int parse(String text, int start, JsDate date) {
+ return parse(text, start, date, false);
+ }
+
+ /**
+ * Parses text to produce a {@link Date} value. An
+ * {@link IllegalArgumentException} is thrown if either the text is empty or
+ * if the parse does not consume all characters of the text.
+ *
+ * Dates are parsed strictly, so invalid dates will result in an
+ * {@link IllegalArgumentException}.
+ *
+ * @param text the string being parsed
+ * @return a parsed date/time value
+ * @throws IllegalArgumentException if the entire text could not be converted
+ * into a number
+ */
+ public JsDate parseStrict(String text) throws IllegalArgumentException {
+ return parse(text, true);
+ }
+
+ /**
+ * This method modifies a {@link Date} object to reflect the date that is
+ * parsed from an input string.
+ *
+ * Dates are parsed strictly, so invalid dates will return 0. For example,
+ * February 30 will return 0 because February only has 28 days.
+ *
+ * @param text the string that need to be parsed
+ * @param start the character position in "text" where parsing should start
+ * @param date the date object that will hold parsed value
+ * @return 0 if parsing failed, otherwise the number of characters advanced
+ */
+ public int parseStrict(String text, int start, JsDate date) {
+ return parse(text, start, date, true);
+ }
+
+ /**
+ * @param timezoneOffset
+ * @return {@link TimeZone} instance
+ */
+ protected TimeZone createTimeZone(int timezoneOffset) {
+ // MUSTFIX(jat): implement
+ return TimeZone.createTimeZone(timezoneOffset);
+ }
+
+ /**
+ * Method append current content in buf as pattern part if there is any, and
+ * clear buf for next part.
+ *
+ * @param buf pattern part text specification
+ * @param count pattern part repeat count
+ */
+ private void addPart(StringBuilder buf, int count) {
+ if (buf.length() > 0) {
+ patternParts.add((new PatternPart(buf.toString(), count)));
+ buf.setLength(0);
+ }
+ }
+
+ /**
+ * Formats (0..11) Hours field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void format0To11Hours(StringBuilder buf, int count, JsDate date) {
+ int value = date.getHours() % 12;
+ zeroPaddingNumber(buf, value, count);
+ }
+
+ /**
+ * Formats (0..23) Hours field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void format0To23Hours(StringBuilder buf, int count, JsDate date) {
+ int value = date.getHours();
+ zeroPaddingNumber(buf, value, count);
+ }
+
+ /**
+ * Formats (1..12) Hours field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void format1To12Hours(StringBuilder buf, int count, JsDate date) {
+ int value = date.getHours() % 12;
+ if (value == 0) {
+ zeroPaddingNumber(buf, 12, count);
+ } else {
+ zeroPaddingNumber(buf, value, count);
+ }
+ }
+
+ /**
+ * Formats (1..24) Hours field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void format24Hours(StringBuilder buf, int count, JsDate date) {
+ int value = date.getHours();
+ if (value == 0) {
+ zeroPaddingNumber(buf, 24, count);
+ } else {
+ zeroPaddingNumber(buf, value, count);
+ }
+ }
+
+ /**
+ * Formats AM/PM field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param date hold the date object to be formatted
+ */
+ private void formatAmPm(StringBuilder buf, JsDate date) {
+ if (date.getHours() >= 12 && date.getHours() < 24) {
+ buf.append(dateTimeFormatInfo.ampms()[1]);
+ } else {
+ buf.append(dateTimeFormatInfo.ampms()[0]);
+ }
+ }
+
+ /**
+ * Formats Date field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatDate(StringBuilder buf, int count, JsDate date) {
+ int value = date.getDate();
+ zeroPaddingNumber(buf, value, count);
+ }
+
+ /**
+ * Formats Day of week field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatDayOfWeek(StringBuilder buf, int count, JsDate date) {
+ int value = date.getDay();
+ if (count == 5) {
+ buf.append(dateTimeFormatInfo.weekdaysNarrow()[value]);
+ } else if (count == 4) {
+ buf.append(dateTimeFormatInfo.weekdaysFull()[value]);
+ } else {
+ buf.append(dateTimeFormatInfo.weekdaysShort()[value]);
+ }
+ }
+
+ /**
+ * Formats Era field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatEra(StringBuilder buf, int count, JsDate date) {
+ int value = date.getFullYear() >= -JS_START_YEAR
+ ? 1
+ : 0;
+ if (count >= 4) {
+ buf.append(dateTimeFormatInfo.erasFull()[value]);
+ } else {
+ buf.append(dateTimeFormatInfo.erasShort()[value]);
+ }
+ }
+
+ /**
+ * Formats Fractional seconds field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatFractionalSeconds(StringBuilder buf, int count, JsDate date) {
+ /*
+ * Fractional seconds should be left-justified, ie. zero must be padded from
+ * left. For example, if the value in milliseconds is 5, and the count is 3,
+ * the output will be "005".
+ *
+ * Values with less than three digits are rounded to the desired number of
+ * places, but the rounded values are truncated at 9 or 99 in order to avoid
+ * changing the values of seconds.
+ */
+ double time = date.getTime();
+ int value;
+ if (time < 0) {
+ value = 1000 - (int) (-time % 1000);
+ if (value == 1000) {
+ value = 0;
+ }
+ } else {
+ value = (int) (time % 1000);
+ }
+ if (count == 1) {
+ value = Math.min((value + 50) / 100, 9); // Round to 100ms, clamp to 9
+ buf.append((char) ('0' + value));
+ } else if (count == 2) {
+ value = Math.min((value + 5) / 10, 99); // Round to 10ms, clamp to 99
+ zeroPaddingNumber(buf, value, 2);
+ } else {
+ zeroPaddingNumber(buf, value, 3);
+
+ if (count > 3) {
+ zeroPaddingNumber(buf, 0, count - 3);
+ }
+ }
+ }
+
+ /**
+ * Formats Minutes field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatMinutes(StringBuilder buf, int count, JsDate date) {
+ int value = date.getMinutes();
+ zeroPaddingNumber(buf, value, count);
+ }
+
+ /**
+ * Formats Month field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatMonth(StringBuilder buf, int count, JsDate date) {
+ int value = date.getMonth();
+ switch (count) {
+ case 5:
+ buf.append(dateTimeFormatInfo.monthsNarrow()[value]);
+ break;
+ case 4:
+ buf.append(dateTimeFormatInfo.monthsFull()[value]);
+ break;
+ case 3:
+ buf.append(dateTimeFormatInfo.monthsShort()[value]);
+ break;
+ default:
+ zeroPaddingNumber(buf, value + 1, count);
+ }
+ }
+
+ /**
+ * Formats Quarter field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatQuarter(StringBuilder buf, int count, JsDate date) {
+ int value = date.getMonth() / 3;
+ if (count < 4) {
+ buf.append(dateTimeFormatInfo.quartersShort()[value]);
+ } else {
+ buf.append(dateTimeFormatInfo.quartersFull()[value]);
+ }
+ }
+
+ /**
+ * Formats Seconds field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatSeconds(StringBuilder buf, int count, JsDate date) {
+ int value = date.getSeconds();
+ zeroPaddingNumber(buf, value, count);
+ }
+
+ /**
+ * Formats Standalone weekday field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatStandaloneDay(StringBuilder buf, int count, JsDate date) {
+ int value = date.getDay();
+ if (count == 5) {
+ buf.append(dateTimeFormatInfo.weekdaysNarrowStandalone()[value]);
+ } else if (count == 4) {
+ buf.append(dateTimeFormatInfo.weekdaysFullStandalone()[value]);
+ } else if (count == 3) {
+ buf.append(dateTimeFormatInfo.weekdaysShortStandalone()[value]);
+ } else {
+ zeroPaddingNumber(buf, value, 1);
+ }
+ }
+
+ /**
+ * Formats Standalone Month field according to pattern specified.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatStandaloneMonth(StringBuilder buf, int count, JsDate date) {
+ int value = date.getMonth();
+ if (count == 5) {
+ buf.append(dateTimeFormatInfo.monthsNarrowStandalone()[value]);
+ } else if (count == 4) {
+ buf.append(dateTimeFormatInfo.monthsFullStandalone()[value]);
+ } else if (count == 3) {
+ buf.append(dateTimeFormatInfo.monthsShortStandalone()[value]);
+ } else {
+ zeroPaddingNumber(buf, value + 1, count);
+ }
+ }
+
+ /**
+ * Formats Timezone field.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatTimeZone(StringBuilder buf, int count, JsDate date,
+ TimeZone timeZone) {
+ if (count < 4) {
+ buf.append(timeZone.getShortName(date));
+ } else {
+ buf.append(timeZone.getLongName(date));
+ }
+ }
+
+ /**
+ * Formats Timezone field following RFC.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date hold the date object to be formatted
+ */
+ private void formatTimeZoneRFC(StringBuilder buf, int count, JsDate date,
+ TimeZone timeZone) {
+ if (count < 3) {
+ buf.append(timeZone.getRFCTimeZoneString(date));
+ } else if (count == 3) {
+ buf.append(timeZone.getISOTimeZoneString(date));
+ } else {
+ buf.append(timeZone.getGMTString(date));
+ }
+ }
+
+ /**
+ * Formats Year field according to pattern specified. Javascript Date object
+ * seems incapable handling 1BC and year before. It can show you year 0 which
+ * does not exists. following we just keep consistent with javascript's
+ * toString method. But keep in mind those things should be unsupported.
+ *
+ * @param buf where formatted string will be appended to
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted; 2 is treated specially with the last two digits of
+ * the year, while more than 2 digits are zero-padded
+ * @param date hold the date object to be formatted
+ */
+ private void formatYear(StringBuilder buf, int count, JsDate date) {
+ int value = date.getFullYear();
+ if (value < 0) {
+ value = -value;
+ }
+ switch (count) {
+ case 1: // no padding
+ buf.append(value);
+ break;
+ case 2: // last 2 digits of year, zero-padded
+ zeroPaddingNumber(buf, value % 100, 2);
+ break;
+ default: // anything else is zero-padded
+ zeroPaddingNumber(buf, value, count);
+ break;
+ }
+ }
+
+ /**
+ * Method getNextCharCountInPattern calculate character repeat count in
+ * pattern.
+ *
+ * @param pattern describe the format of date string that need to be parsed
+ * @param start the position of pattern character
+ * @return repeat count
+ */
+ private int getNextCharCountInPattern(String pattern, int start) {
+ char ch = pattern.charAt(start);
+ int next = start + 1;
+ while (next < pattern.length() && pattern.charAt(next) == ch) {
+ ++next;
+ }
+ return next - start;
+ }
+
+ /**
+ * Method identifies the start of a run of abutting numeric fields. Take the
+ * pattern "HHmmss" as an example. We will try to parse 2/2/2 characters of
+ * the input text, then if that fails, 1/2/2. We only adjust the width of the
+ * leftmost field; the others remain fixed. This allows "123456" => 12:34:56,
+ * but "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we try 4/2/2,
+ * 3/2/2, 2/2/2, and finally 1/2/2. The first field of connected numeric
+ * fields will be marked as abutStart, its width can be reduced to accommodate
+ * others.
+ */
+ private void identifyAbutStart() {
+ // 'abut' parts are continuous numeric parts. abutStart is the switch
+ // point from non-abut to abut.
+ boolean abut = false;
+
+ int len = patternParts.size();
+ for (int i = 0; i < len; i++) {
+ if (isNumeric(patternParts.get(i))) {
+ // If next part is not following abut sequence, and isNumeric.
+ if (!abut && i + 1 < len && isNumeric(patternParts.get(i + 1))) {
+ abut = true;
+ patternParts.get(i).abutStart = true;
+ }
+ } else {
+ abut = false;
+ }
+ }
+ }
+
+ /**
+ * Method checks if the pattern part is a numeric field.
+ *
+ * @param part pattern part to be examined
+ * @return
+ * If using lenient parsing, certain invalid dates and times will be parsed.
+ * For example, February 32nd would be parsed as March 4th in lenient mode,
+ * but would throw an exception in non-lenient mode.
+ *
+ * @param text the string being parsed
+ * @param strict true to be strict when parsing, false to be lenient
+ * @return a parsed date/time value
+ * @throws IllegalArgumentException if the entire text could not be converted
+ * into a number
+ */
+ private JsDate parse(String text, boolean strict) {
+ JsDate curDate = JsDate.create();
+ JsDate date = JsDate.create(curDate.getFullYear(), curDate.getMonth(),
+ curDate.getDate());
+ int charsConsumed = parse(text, 0, date, strict);
+ if (charsConsumed == 0 || charsConsumed < text.length()) {
+ throw new IllegalArgumentException(text);
+ }
+ return date;
+ }
+
+ /**
+ * This method parses the input string and fills its value into a {@link JsDate}
+ * .
+ *
+ * If using lenient parsing, certain invalid dates and times will be parsed.
+ * For example, February 32nd would be parsed as March 4th in lenient mode,
+ * but would return 0 in non-lenient mode.
+ *
+ * @param text the string that need to be parsed
+ * @param start the character position in "text" where parsing should start
+ * @param date the date object that will hold parsed value
+ * @param strict true to be strict when parsing, false to be lenient
+ * @return 0 if parsing failed, otherwise the number of characters advanced
+ */
+ private int parse(String text, int start, JsDate date, boolean strict) {
+ DateRecord cal = new DateRecord();
+ int[] parsePos = {start};
+
+ // For parsing abutting numeric fields. 'abutPat' is the
+ // offset into 'pattern' of the first of 2 or more abutting
+ // numeric fields. 'abutStart' is the offset into 'text'
+ // where parsing the fields begins. 'abutPass' starts off as 0
+ // and increments each time we try to parse the fields.
+ int abutPat = -1; // If >=0, we are in a run of abutting numeric fields.
+ int abutStart = 0;
+ int abutPass = 0;
+
+ for (int i = 0; i < patternParts.size(); ++i) {
+ PatternPart part = patternParts.get(i);
+
+ if (part.count > 0) {
+ if (abutPat < 0 && part.abutStart) {
+ abutPat = i;
+ abutStart = parsePos[0];
+ abutPass = 0;
+ }
+
+ // Handle fields within a run of abutting numeric fields. Take
+ // the pattern "HHmmss" as an example. We will try to parse
+ // 2/2/2 characters of the input text, then if that fails,
+ // 1/2/2. We only adjust the width of the leftmost field; the
+ // others remain fixed. This allows "123456" => 12:34:56, but
+ // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
+ // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
+ if (abutPat >= 0) {
+ // If we are at the start of a run of abutting fields, then
+ // shorten this field in each pass. If we can't shorten
+ // this field any more, then the parse of this set of
+ // abutting numeric fields has failed.
+ int count = part.count;
+ if (i == abutPat) {
+ count -= abutPass++;
+ if (count == 0) {
+ return 0;
+ }
+ }
+
+ if (!subParse(text, parsePos, part, count, cal)) {
+ // If the parse fails anywhere in the run, back up to the
+ // start of the run and retry.
+ i = abutPat - 1;
+ parsePos[0] = abutStart;
+ continue;
+ }
+ } else {
+ // Handle non-numeric fields and non-abutting numeric fields.
+ abutPat = -1;
+ if (!subParse(text, parsePos, part, 0, cal)) {
+ return 0;
+ }
+ }
+ } else {
+ // Handle literal pattern characters. These are any
+ // quoted characters and non-alphabetic unquoted characters.
+ abutPat = -1;
+ // A run of white space in the pattern matches a run
+ // of white space in the input text.
+ if (part.text.charAt(0) == ' ') {
+ // Advance over run in input text.
+ int s = parsePos[0];
+ skipSpace(text, parsePos);
+
+ // Must see at least one white space char in input.
+ if (parsePos[0] > s) {
+ continue;
+ }
+ } else if (text.startsWith(part.text, parsePos[0])) {
+ parsePos[0] += part.text.length();
+ continue;
+ }
+
+ // We fall through to this point if the match fails.
+ return 0;
+ }
+ }
+
+ // Calculate the date from the parts
+ if (!cal.calcDate(date, strict)) {
+ return 0;
+ }
+
+ // Return progress.
+ return parsePos[0] - start;
+ }
+
+ /**
+ * Method parses a integer string and return integer value.
+ *
+ * @param text string being parsed
+ * @param pos parse position
+ * @return integer value
+ */
+ private int parseInt(String text, int[] pos) {
+ int ret = 0;
+ int ind = pos[0];
+ if (ind >= text.length()) {
+ return -1;
+ }
+ char ch = text.charAt(ind);
+ while (ch >= '0' && ch <= '9') {
+ ret = ret * 10 + (ch - '0');
+ ind++;
+ if (ind >= text.length()) {
+ break;
+ }
+ ch = text.charAt(ind);
+ }
+ if (ind > pos[0]) {
+ pos[0] = ind;
+ } else {
+ ret = -1;
+ }
+ return ret;
+ }
+
+ /**
+ * Method parses the input pattern string a generate a vector of pattern
+ * parts.
+ *
+ * @param pattern describe the format of date string that need to be parsed
+ */
+ private void parsePattern(String pattern) {
+ StringBuilder buf = new StringBuilder(32);
+ boolean inQuote = false;
+
+ for (int i = 0; i < pattern.length(); i++) {
+ char ch = pattern.charAt(i);
+
+ // Handle space, add literal part (if exist), and add space part.
+ if (ch == ' ') {
+ addPart(buf, 0);
+ buf.append(' ');
+ addPart(buf, 0);
+ while (i + 1 < pattern.length() && pattern.charAt(i + 1) == ' ') {
+ i++;
+ }
+ continue;
+ }
+
+ // If inside quote, except two quote connected, just copy or exit.
+ if (inQuote) {
+ if (ch == '\'') {
+ if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') {
+ // Quote appeared twice continuously, interpret as one quote.
+ buf.append(ch);
+ ++i;
+ } else {
+ inQuote = false;
+ }
+ } else {
+ // Literal.
+ buf.append(ch);
+ }
+ continue;
+ }
+
+ // Outside quote now.
+ if (PATTERN_CHARS.indexOf(ch) > 0) {
+ addPart(buf, 0);
+ buf.append(ch);
+ int count = getNextCharCountInPattern(pattern, i);
+ addPart(buf, count);
+ i += count - 1;
+ continue;
+ }
+
+ // Two consecutive quotes is a quote literal, inside or outside of quotes.
+ if (ch == '\'') {
+ if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') {
+ buf.append('\'');
+ i++;
+ } else {
+ inQuote = true;
+ }
+ } else {
+ buf.append(ch);
+ }
+ }
+
+ addPart(buf, 0);
+
+ identifyAbutStart();
+ }
+
+ /**
+ * Method parses time zone offset.
+ *
+ * @param text the time text to be parsed
+ * @param pos Parse position
+ * @param cal DateRecord object that holds parsed value
+ * @return DateTimeFormat
object and reuse it
+ * rather than calling this method repeatedly.
+ *
+ * DateTimeFormat
object that can be used for format or
+ * parse date/time values matching the specified pattern
+ * @throws IllegalArgumentException if the specified pattern could not be
+ * parsed
+ */
+ public static DateTimeFormatImpl getFormat(String pattern) {
+ return getFormat(pattern, getDefaultDateTimeFormatInfo());
+ }
+
+ /**
+ * Internal factory method that provides caching.
+ *
+ * @param pattern
+ * @param dtfi
+ * @return DateTimeFormat instance
+ */
+ protected static DateTimeFormatImpl getFormat(String pattern,
+ DateTimeFormatInfo dtfi) {
+ DateTimeFormatInfo defaultDtfi = getDefaultDateTimeFormatInfo();
+ DateTimeFormatImpl dtf = null;
+ if (dtfi == defaultDtfi) {
+ dtf = cache.get(pattern);
+ }
+ if (dtf == null) {
+ dtf = new DateTimeFormatImpl(pattern, dtfi);
+ if (dtfi == defaultDtfi) {
+ cache.put(pattern, dtf);
+ }
+ }
+ return dtf;
+ }
+
+ private static DateTimeFormatInfo getDefaultDateTimeFormatInfo() {
+ // MUSTFIX(jat): implement
+ return LocaleInfo.getCurrentLocale().getDateTimeFormatInfo();
+ }
+
+ /**
+ * Returns true if the predefined format is one that specifies always using
+ * English names/separators.
+ * true
if the pattern part is numberic field
+ */
+ private boolean isNumeric(PatternPart part) {
+ if (part.count <= 0) {
+ return false;
+ }
+ int i = NUMERIC_FORMAT_CHARS.indexOf(part.text.charAt(0));
+ // M & L (index 0 and 1) are only numeric if there are less than 3 chars
+ return (i > 1 || (i >= 0 && part.count < 3));
+ }
+
+ /**
+ * Method attempts to match the text at a given position against an array of
+ * strings. Since multiple strings in the array may match (for example, if the
+ * array contains "a", "ab", and "abc", all will match the input string
+ * "abcd") the longest match is returned.
+ *
+ * @param text the time text being parsed
+ * @param start where to start parsing
+ * @param data the string array to parsed
+ * @param pos to receive where the match stopped
+ * @return the new start position if matching succeeded; a negative number
+ * indicating matching failure
+ */
+ private int matchString(String text, int start, String[] data, int[] pos) {
+ int count = data.length;
+
+ // There may be multiple strings in the data[] array which begin with
+ // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
+ // We keep track of the longest match, and return that. Note that this
+ // unfortunately requires us to test all array elements.
+ int bestMatchLength = 0, bestMatch = -1;
+ String textInLowerCase = text.substring(start).toLowerCase(Locale.ROOT);
+ for (int i = 0; i < count; ++i) {
+ int length = data[i].length();
+ // Always compare if we have no match yet; otherwise only compare
+ // against potentially better matches (longer strings).
+ if (length > bestMatchLength
+ && textInLowerCase.startsWith(data[i].toLowerCase(Locale.ROOT))) {
+ bestMatch = i;
+ bestMatchLength = length;
+ }
+ }
+ if (bestMatch >= 0) {
+ pos[0] = start + bestMatchLength;
+ }
+ return bestMatch;
+ }
+
+ /**
+ * Parses text to produce a {@link JsDate} value. An
+ * {@link IllegalArgumentException} is thrown if either the text is empty or
+ * if the parse does not consume all characters of the text.
+ * true
if parsing successful, otherwise
+ * false
+ */
+ private boolean parseTimeZoneOffset(String text, int[] pos, DateRecord cal) {
+ if (pos[0] >= text.length()) {
+ cal.setTzOffset(0);
+ return true;
+ }
+
+ int sign;
+ switch (text.charAt(pos[0])) {
+ case '+':
+ sign = 1;
+ break;
+ case '-':
+ sign = -1;
+ break;
+ default:
+ cal.setTzOffset(0);
+ return true;
+ }
+ ++(pos[0]);
+
+ // Look for hours:minutes or hhmm.
+ int st = pos[0];
+ int value = parseInt(text, pos);
+ if (value == 0 && pos[0] == st) {
+ return false;
+ }
+
+ int offset;
+ if (pos[0] < text.length() && text.charAt(pos[0]) == ':') {
+ // This is the hours:minutes case.
+ offset = value * MINUTES_PER_HOUR;
+ ++(pos[0]);
+ st = pos[0];
+ value = parseInt(text, pos);
+ if (value == 0 && pos[0] == st) {
+ return false;
+ }
+ offset += value;
+ } else {
+ // This is the hhmm case.
+ offset = value;
+ // Assume "-23".."+23" refers to hours.
+ if (offset < 24 && (pos[0] - st) <= 2) {
+ offset *= MINUTES_PER_HOUR;
+ } else {
+ offset = offset % 100 + offset / 100 * MINUTES_PER_HOUR;
+ }
+ }
+
+ offset *= sign;
+ cal.setTzOffset(-offset);
+ return true;
+ }
+
+ /**
+ * Method skips space in the string as pointed by pos.
+ *
+ * @param text input string
+ * @param pos where skip start, and return back where skip stop
+ */
+ private void skipSpace(String text, int[] pos) {
+ while (pos[0] < text.length()
+ && WHITE_SPACE.indexOf(text.charAt(pos[0])) >= 0) {
+ ++(pos[0]);
+ }
+ }
+
+ /**
+ * Formats a single field according to pattern specified.
+ *
+ * @param ch pattern character for this field
+ * @param count number of time pattern char repeats; this controls how a field
+ * should be formatted
+ * @param date the date object to be formatted
+ * @param adjustedDate holds the time zone adjusted date fields
+ * @param adjustedTime holds the time zone adjusted time fields
+ * @return true
if pattern valid, otherwise false
+ */
+ private boolean subFormat(StringBuilder buf, char ch, int count, JsDate date,
+ JsDate adjustedDate, JsDate adjustedTime, TimeZone timezone) {
+ switch (ch) {
+ case 'G':
+ formatEra(buf, count, adjustedDate);
+ break;
+ case 'y':
+ formatYear(buf, count, adjustedDate);
+ break;
+ case 'M':
+ formatMonth(buf, count, adjustedDate);
+ break;
+ case 'k':
+ format24Hours(buf, count, adjustedTime);
+ break;
+ case 'S':
+ formatFractionalSeconds(buf, count, adjustedTime);
+ break;
+ case 'E':
+ formatDayOfWeek(buf, count, adjustedDate);
+ break;
+ case 'a':
+ formatAmPm(buf, adjustedTime);
+ break;
+ case 'h':
+ format1To12Hours(buf, count, adjustedTime);
+ break;
+ case 'K':
+ format0To11Hours(buf, count, adjustedTime);
+ break;
+ case 'H':
+ format0To23Hours(buf, count, adjustedTime);
+ break;
+ case 'c':
+ formatStandaloneDay(buf, count, adjustedDate);
+ break;
+ case 'L':
+ formatStandaloneMonth(buf, count, adjustedDate);
+ break;
+ case 'Q':
+ formatQuarter(buf, count, adjustedDate);
+ break;
+ case 'd':
+ formatDate(buf, count, adjustedDate);
+ break;
+ case 'm':
+ formatMinutes(buf, count, adjustedTime);
+ break;
+ case 's':
+ formatSeconds(buf, count, adjustedTime);
+ break;
+ case 'z':
+ formatTimeZone(buf, count, date, timezone);
+ break;
+ case 'v':
+ buf.append(timezone.getID());
+ break;
+ case 'Z':
+ formatTimeZoneRFC(buf, count, date, timezone);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Converts one field of the input string into a numeric field value. Returns
+ * false
if failed.
+ *
+ * @param text the time text to be parsed
+ * @param pos Parse position
+ * @param part the pattern part for this field
+ * @param digitCount when greater than 0, numeric parsing must obey the count
+ * @param cal DateRecord object that will hold parsed value
+ * @return true
if parsing successful
+ */
+ @SuppressWarnings("fallthrough")
+ private boolean subParse(String text, int[] pos, PatternPart part,
+ int digitCount, DateRecord cal) {
+
+ skipSpace(text, pos);
+
+ int start = pos[0];
+ char ch = part.text.charAt(0);
+
+ // Parse integer value if it is a numeric field.
+ int value = -1; // initialize value to be -1,
+ if (isNumeric(part)) {
+ if (digitCount > 0) {
+ if ((start + digitCount) > text.length()) {
+ return false;
+ }
+ value = parseInt(text.substring(0, start + digitCount), pos);
+ } else {
+ value = parseInt(text, pos);
+ }
+ }
+
+ switch (ch) {
+ case 'G': // era
+ value = matchString(text, start, dateTimeFormatInfo.erasFull(), pos);
+ cal.setEra(value);
+ return true;
+ case 'M': // month
+ return subParseMonth(text, pos, cal, value, start);
+ case 'L': // standalone month
+ return subParseStandaloneMonth(text, pos, cal, value, start);
+ case 'E': // day of week
+ return subParseDayOfWeek(text, pos, start, cal);
+ case 'c': // standalone day of week
+ return subParseStandaloneDay(text, pos, start, cal);
+ case 'a': // AM/PM
+ value = matchString(text, start, dateTimeFormatInfo.ampms(), pos);
+ cal.setAmpm(value);
+ return true;
+ case 'y': // year
+ return subParseYear(text, pos, start, value, part, cal);
+ case 'd': // day of month
+ if (value <= 0) {
+ return false;
+ }
+ cal.setDayOfMonth(value);
+ return true;
+ case 'S': // fractional seconds
+ if (value < 0) {
+ return false;
+ }
+ return subParseFractionalSeconds(value, start, pos[0], cal);
+ case 'h': // hour (1..12)
+ if (value == 12) {
+ value = 0;
+ }
+ // fall through
+ case 'K': // hour (0..11)
+ case 'H': // hour (0..23)
+ if (value < 0) {
+ return false;
+ }
+ cal.setHours(value);
+ cal.setMidnightIs24(false);
+ return true;
+ case 'k': // hour (1..24)
+ if (value < 0) {
+ return false;
+ }
+ cal.setHours(value);
+ cal.setMidnightIs24(true);
+ return true;
+ case 'm': // minute
+ if (value < 0) {
+ return false;
+ }
+ cal.setMinutes(value);
+ return true;
+ case 's': // second
+ if (value < 0) {
+ return false;
+ }
+ cal.setSeconds(value);
+ return true;
+
+ case 'Z': // time zone RFC
+ // ISO-8601 times can have a literal Z to indicate GMT+0
+ if (start < text.length() && text.charAt(start) == 'Z') {
+ pos[0]++;
+ cal.setTzOffset(0);
+ return true;
+ }
+ // $FALL-THROUGH$
+ case 'z': // time zone offset
+ case 'v': // time zone generic
+ return subParseTimeZoneInGMT(text, start, pos, cal);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Method subParseDayOfWeek parses day of the week field.
+ *
+ * @param text the time text to be parsed
+ * @param pos Parse position
+ * @param start from where parse start
+ * @param cal DateRecord object that holds parsed value
+ * @return true
if parsing successful, otherwise
+ * false
+ */
+ private boolean subParseDayOfWeek(String text, int[] pos, int start,
+ DateRecord cal) {
+ int value;
+ // 'E' - DAY_OF_WEEK
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 (DDDD) first:
+ value = matchString(text, start, dateTimeFormatInfo.weekdaysFull(), pos);
+ if (value < 0) {
+ value = matchString(text, start, dateTimeFormatInfo.weekdaysShort(), pos);
+ }
+ if (value < 0) {
+ return false;
+ }
+ cal.setDayOfWeek(value);
+ return true;
+ }
+
+ /**
+ * Method subParseFractionalSeconds parses fractional seconds field.
+ *
+ * @param value parsed numberic value
+ * @param start
+ * @param end parse position
+ * @param cal DateRecord object that holds parsed value
+ * @return true
if parsing successful, otherwise
+ * false
+ */
+ private boolean subParseFractionalSeconds(int value, int start, int end,
+ DateRecord cal) {
+ // Fractional seconds left-justify.
+ int i = end - start;
+ if (i < 3) {
+ while (i < 3) {
+ value *= 10;
+ i++;
+ }
+ } else {
+ int a = 1;
+ while (i > 3) {
+ a *= 10;
+ i--;
+ }
+ value = (value + (a >> 1)) / a;
+ }
+ cal.setMilliseconds(value);
+ return true;
+ }
+
+ /**
+ * Parses Month field.
+ *
+ * @param text the time text to be parsed
+ * @param pos Parse position
+ * @param cal DateRecord object that will hold parsed value
+ * @param value numeric value if this field is expressed using numberic
+ * pattern
+ * @param start from where parse start
+ * @return true
if parsing successful
+ */
+ private boolean subParseMonth(String text, int[] pos, DateRecord cal,
+ int value, int start) {
+ // When month is symbols, i.e., MMM or MMMM, value will be -1.
+ if (value < 0) {
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 first:
+ value = matchString(text, start, dateTimeFormatInfo.monthsFull(), pos);
+ if (value < 0) { // count == 4 failed, now try count == 3.
+ value = matchString(text, start, dateTimeFormatInfo.monthsShort(), pos);
+ }
+ if (value < 0) {
+ return false;
+ }
+ cal.setMonth(value);
+ return true;
+ } else if (value > 0) {
+ cal.setMonth(value - 1);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parses standalone day of the week field.
+ *
+ * @param text the time text to be parsed
+ * @param pos Parse position
+ * @param start from where parse start
+ * @param cal DateRecord object that holds parsed value
+ * @return true
if parsing successful, otherwise
+ * false
+ */
+ private boolean subParseStandaloneDay(String text, int[] pos, int start,
+ DateRecord cal) {
+ int value;
+ // 'c' - DAY_OF_WEEK
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 (cccc) first:
+ value = matchString(text, start, dateTimeFormatInfo.weekdaysFullStandalone(),
+ pos);
+ if (value < 0) {
+ value = matchString(text, start,
+ dateTimeFormatInfo.weekdaysShortStandalone(), pos);
+ }
+ if (value < 0) {
+ return false;
+ }
+ cal.setDayOfWeek(value);
+ return true;
+ }
+
+ /**
+ * Parses a standalone month field.
+ *
+ * @param text the time text to be parsed
+ * @param pos Parse position
+ * @param cal DateRecord object that will hold parsed value
+ * @param value numeric value if this field is expressed using numberic
+ * pattern
+ * @param start from where parse start
+ * @return true
if parsing successful
+ */
+ private boolean subParseStandaloneMonth(String text, int[] pos,
+ DateRecord cal, int value, int start) {
+ // When month is symbols, i.e., LLL or LLLL, value will be -1.
+ if (value < 0) {
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 first:
+ value = matchString(text, start,
+ dateTimeFormatInfo.monthsFullStandalone(), pos);
+ if (value < 0) { // count == 4 failed, now try count == 3.
+ value = matchString(text, start,
+ dateTimeFormatInfo.monthsShortStandalone(), pos);
+ }
+ if (value < 0) {
+ return false;
+ }
+ cal.setMonth(value);
+ return true;
+ } else if (value > 0) {
+ cal.setMonth(value - 1);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method parses GMT type timezone.
+ *
+ * @param text the time text to be parsed
+ * @param start from where parse start
+ * @param pos Parse position
+ * @param cal DateRecord object that holds parsed value
+ * @return true
if parsing successful, otherwise
+ * false
+ */
+ private boolean subParseTimeZoneInGMT(String text, int start, int[] pos,
+ DateRecord cal) {
+ // First try to parse generic forms such as GMT-07:00. Do this first
+ // in case localized DateFormatZoneData contains the string "GMT"
+ // for a zone; in that case, we don't want to match the first three
+ // characters of GMT+/-HH:MM etc.
+
+ // For time zones that have no known names, look for strings
+ // of the form:
+ // GMT[+-]hours:minutes or
+ // GMT[+-]hhmm or
+ // GMT.
+ if (text.startsWith(GMT, start)) {
+ pos[0] = start + GMT.length();
+ return parseTimeZoneOffset(text, pos, cal);
+ }
+ // Likewise for UTC.
+ if (text.startsWith(UTC, start)) {
+ pos[0] = start + UTC.length();
+ return parseTimeZoneOffset(text, pos, cal);
+ }
+
+ // At this point, check for named time zones by looking through
+ // the locale data from the DateFormatZoneData strings.
+ // Want to be able to parse both short and long forms.
+ /*
+ * i = subParseZoneString(text, start, cal); if (i != 0) return i;
+ */
+
+ // As a last resort, look for numeric timezones of the form
+ // [+-]hhmm as specified by RFC 822. This code is actually
+ // a little more permissive than RFC 822. It will try to do
+ // its best with numbers that aren't strictly 4 digits long.
+ return parseTimeZoneOffset(text, pos, cal);
+ }
+
+ /**
+ * Method subParseYear parse year field. Year field is special because 1, two
+ * digit year need to be resolved. 2, we allow year to take a sign. 3, year
+ * field participate in abut processing. In my testing, negative year does not
+ * seem working due to JDK (or GWT implementation) limitation. It is not a
+ * big deal so we don't worry about it. But keep the logic here so that we
+ * might want to replace DateRecord with our a calendar class.
+ *
+ * @param text the time text to be parsed
+ * @param pos parse position
+ * @param start where this field starts
+ * @param value integer value of year
+ * @param part the pattern part for this field
+ * @param cal DateRecord object that will hold parsed value
+ * @return true
if successful
+ */
+ private boolean subParseYear(String text, int[] pos, int start, int value,
+ PatternPart part, DateRecord cal) {
+ char ch = ' ';
+ if (value < 0) {
+ if (pos[0] >= text.length()) {
+ return false;
+ }
+ ch = text.charAt(pos[0]);
+ // Check if it is a sign.
+ if (ch != '+' && ch != '-') {
+ return false;
+ }
+ ++(pos[0]);
+ value = parseInt(text, pos);
+ if (value < 0) {
+ return false;
+ }
+ if (ch == '-') {
+ value = -value;
+ }
+ }
+
+ // no sign, only 2 digit was actually parsed, pattern say it has 2 digit.
+ if (ch == ' ' && (pos[0] - start) == 2 && part.count == 2) {
+ // Assume for example that the defaultCenturyStart is 6/18/1903.
+ // This means that two-digit years will be forced into the range
+ // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
+ // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
+ // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
+ // other fields specify a date before 6/18, or 1903 if they specify a
+ // date afterwards. As a result, 03 is an ambiguous year. All other
+ // two-digit years are unambiguous.
+ JsDate date = JsDate.create();
+ int defaultCenturyStartYear = date.getFullYear() + 1900 - 80;
+ int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
+ cal.setAmbiguousYear(value == ambiguousTwoDigitYear);
+ value += (defaultCenturyStartYear / 100) * 100
+ + (value < ambiguousTwoDigitYear
+ ? 100
+ : 0);
+ }
+ cal.setFullYear(value);
+ return true;
+ }
+
+ /**
+ * Formats a number with the specified minimum number of digits, using zero to
+ * fill the gap.
+ *
+ * @param buf where zero padded string will be written to
+ * @param value the number value being formatted
+ * @param minWidth minimum width of the formatted string; zero will be padded
+ * to reach this width
+ */
+ private void zeroPaddingNumber(StringBuilder buf, int value, int minWidth) {
+ int b = NUMBER_BASE;
+ for (int i = 0; i < minWidth - 1; i++) {
+ if (value < b) {
+ buf.append('0');
+ }
+ b *= NUMBER_BASE;
+ }
+ buf.append(value);
+ }
+}
diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DayLabel.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DayLabel.java
new file mode 100644
index 00000000000..4240535eaac
--- /dev/null
+++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DayLabel.java
@@ -0,0 +1,8 @@
+package stroom.widget.datepicker.client;
+
+public class DayLabel extends AbstractCell {
+
+ public DayLabel(final String text) {
+ setText(text);
+ }
+}
diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DefaultCalendarView.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DefaultCalendarView.java
new file mode 100644
index 00000000000..bc7550f4090
--- /dev/null
+++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DefaultCalendarView.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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 stroom.widget.datepicker.client;
+
+import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+
+/**
+ * Simple calendar view. Not extensible as we wish to evolve it freely over
+ * time.
+ */
+public final class DefaultCalendarView extends CalendarView {
+
+ private final DateGrid grid = new DateGrid();
+
+ private JsDate firstDisplayed;
+
+ private final JsDate lastDisplayed = JsDate.today();
+
+ private DateCell ariaSelectedCell;
+
+ /**
+ * Constructor.
+ */
+ public DefaultCalendarView() {
+ }
+
+ @Override
+ public void addStyleToDate(String styleName, JsDate date) {
+ assert getDatePicker().isDateVisible(date) : "You tried to add style " + styleName + " to "
+ + date + ". The calendar is currently showing " + getFirstDate()
+ + " to " + getLastDate();
+ final DateCell dateCell = getCell(date);
+ if (dateCell != null) {
+ dateCell.addStyleName(styleName);
+ }
+ }
+
+ @Override
+ public JsDate getFirstDate() {
+ return firstDisplayed;
+ }
+
+ @Override
+ public JsDate getLastDate() {
+ return lastDisplayed;
+ }
+
+ @Override
+ public boolean isDateEnabled(JsDate date) {
+ final DateCell dateCell = getCell(date);
+ if (dateCell != null) {
+ return dateCell.isEnabled();
+ }
+ return false;
+ }
+
+ @Override
+ public void refresh() {
+ firstDisplayed = getModel().getCurrentFirstDayOfFirstWeek();
+
+ if (firstDisplayed.getDate() == 1) {
+ // show one empty week if date is Monday is the first in month.
+ addDays(firstDisplayed, -7);
+ }
+
+ lastDisplayed.setTime(firstDisplayed.getTime());
+
+ for (int i = 0; i < grid.getNumCells(); i++) {
+ if (i != 0) {
+ addDays(lastDisplayed, 1);
+ }
+ final DateCell cell = grid.getCell(i);
+ if (cell != null) {
+ cell.update(lastDisplayed);
+ }
+ }
+ setAriaSelectedCell(null);
+ }
+
+ private static void addDays(JsDate date, int days) {
+ CalendarUtil.addDaysToDate(date, days);
+ // We might hit DST transition. Try resetting back so follow up days continue showing midnight.
+ CalendarUtil.resetTime(date);
+ }
+
+ @Override
+ public void removeStyleFromDate(String styleName, JsDate date) {
+ final DateCell dateCell = getCell(date);
+ if (dateCell != null) {
+ dateCell.removeStyleName(styleName);
+ }
+ }
+
+ @Override
+ public void setAriaSelectedCell(JsDate date) {
+ if (ariaSelectedCell != null) {
+ ariaSelectedCell.setAriaSelected(false);
+ }
+ DateCell newSelectedCell = date != null
+ ? getCell(date)
+ : null;
+ if (newSelectedCell != null) {
+ newSelectedCell.setAriaSelected(true);
+ }
+ ariaSelectedCell = newSelectedCell;
+ }
+
+ @Override
+ public void setEnabledOnDate(boolean enabled, JsDate date) {
+ final DateCell dateCell = getCell(date);
+ if (dateCell != null) {
+ dateCell.setEnabled(enabled);
+ }
+ }
+
+ @Override
+ public void setup() {
+ // Preparation
+ CellFormatter formatter = grid.getCellFormatter();
+ int weekendStartColumn = -1;
+ int weekendEndColumn = -1;
+
+ // Set up the day labels.
+ for (int i = 0; i < CalendarModel.DAYS_IN_WEEK; i++) {
+ int shift = CalendarUtil.getStartingDayOfWeek();
+ int dayIdx = i + shift < CalendarModel.DAYS_IN_WEEK
+ ? i + shift
+ : i + shift - CalendarModel.DAYS_IN_WEEK;
+ final DayLabel cell = new DayLabel(getModel().formatDayOfWeek(dayIdx));
+ grid.addElement(cell);
+ grid.setWidget(0, i, cell);
+
+ if (CalendarUtil.isWeekend(dayIdx)) {
+ formatter.setStyleName(0, i, css().weekendLabel());
+ if (weekendStartColumn == -1) {
+ weekendStartColumn = i;
+ } else {
+ weekendEndColumn = i;
+ }
+ } else {
+ formatter.setStyleName(0, i, css().weekdayLabel());
+ }
+ }
+
+ // Set up the calendar grid.
+ for (int row = 1; row <= CalendarModel.WEEKS_IN_MONTH; row++) {
+ for (int column = 0; column < CalendarModel.DAYS_IN_WEEK; column++) {
+ final int index = grid.getNumCells();
+ final DateCell cell = new DateCell(
+ this,
+ css(),
+ column == weekendStartColumn || column == weekendEndColumn,
+ index);
+ grid.addElement(cell);
+ grid.addCell(cell);
+ grid.setWidget(row, column, cell);
+ }
+ }
+ initWidget(grid);
+ grid.setStyleName(css().days());
+ }
+
+ private DateCell getCell(JsDate date) {
+ int index = CalendarUtil.getDaysBetween(firstDisplayed, date);
+ if (index < 0 || grid.getNumCells() <= index) {
+ return null;
+ }
+
+ final DateCell cell = grid.getCell(index);
+ if (cell instanceof DateCell) {
+ final DateCell dateCell = (DateCell) cell;
+// final double cellTime = dateCell.value.getTime();
+// final double time = date.getTime();
+// if (cellTime != time) {
+// throw new IllegalStateException(date + " cannot be associated with cell "
+// + cell + " as it has date " + dateCell.value);
+// }
+ return dateCell;
+ }
+
+ return null;
+ }
+}
diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DefaultMonthSelector.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DefaultMonthSelector.java
new file mode 100644
index 00000000000..9f4263ba98b
--- /dev/null
+++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/DefaultMonthSelector.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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 stroom.widget.datepicker.client;
+
+import stroom.docref.HasDisplayValue;
+import stroom.item.client.SelectionBox;
+import stroom.svg.shared.SvgImage;
+import stroom.widget.button.client.InlineSvgButton;
+import stroom.widget.datepicker.client.IntlDateTimeFormat.FormatOptions;
+import stroom.widget.datepicker.client.IntlDateTimeFormat.FormatOptions.Year;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.ui.FlowPanel;
+
+import java.util.Objects;
+
+/**
+ * A simple {@link MonthSelector} used for the default date picker. It allows to select months and
+ * years but the name is not changed for backward compatibility. Also not extensible as we wish to
+ * evolve it freely over time.
+ */
+
+public final class DefaultMonthSelector extends MonthSelector {
+
+ private InlineSvgButton monthBackwards;
+ private InlineSvgButton monthForwards;
+ private FlowPanel layout;
+ private InlineSvgButton yearBackwards;
+ private InlineSvgButton yearForwards;
+ private SelectionBoxNaN
. Use {@link Double#isNaN(double)} to check
+ * the result.
+ */
+ public static native double parse(String dateString) /*-{
+ return Date.parse(dateString);
+ }-*/;
+
+ // CHECKSTYLE_OFF: Matching the spec.
+
+ /**
+ * Returns the internal millisecond representation of the specified UTC date
+ * and time.
+ */
+ public static native double UTC(int year,
+ int month,
+ int dayOfMonth,
+ int hours,
+ int minutes,
+ int seconds,
+ int millis) /*-{
+ return Date.UTC(year, month, dayOfMonth, hours, minutes, seconds, millis);
+ }-*/;
+
+ // CHECKSTYLE_ON
+
+ /**
+ * Non directly instantiable, use one of the {@link #create()} methods.
+ */
+ protected JsDate() {
+ }
+
+ /**
+ * Returns the day of the month.
+ */
+ public final native int getDate() /*-{
+ return this.getDate();
+ }-*/;
+
+ /**
+ * Returns the day of the week, from 0
(Sunday) to 6
+ * Saturday.
+ */
+ public final native int getDay() /*-{
+ return this.getDay();
+ }-*/;
+
+ /**
+ * Returns the four-digit year.
+ */
+ public final native int getFullYear() /*-{
+ return this.getFullYear();
+ }-*/;
+
+ /**
+ * Returns the hour, between 0
(midnight) and 23
.
+ */
+ public final native int getHours() /*-{
+ return this.getHours();
+ }-*/;
+
+ /**
+ * Returns the minutes, between 0
and 59
.
+ */
+ public final native int getMinutes() /*-{
+ return this.getMinutes();
+ }-*/;
+
+ /**
+ * Returns the month, from 0
(January) to 11
+ * December.
+ */
+ public final native int getMonth() /*-{
+ return this.getMonth();
+ }-*/;
+
+ /**
+ * Returns the seconds, between 0
and 59
.
+ */
+ public final native int getSeconds() /*-{
+ return this.getSeconds();
+ }-*/;
+
+ /**
+ * Returns the milliseconds, between 0
and 999
.
+ */
+ public final native int getMilliseconds() /*-{
+ return this.getMilliseconds();
+ }-*/;
+
+ /**
+ * Returns the internal millisecond representation of the date, the number of
+ * milliseconds since midnight on January 1st, 1970.
+ */
+ public final native double getTime() /*-{
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Returns the difference, in minutes, between the local and UTC
+ * representations of this date. The value returned is affected by whether or
+ * not daylight savings time would be in effect on specified date.
+ */
+ public final native int getTimezoneOffset() /*-{
+ return this.getTimezoneOffset();
+ }-*/;
+
+ /**
+ * Returns the day of the month, in UTC.
+ */
+ public final native int getUTCDate() /*-{
+ return this.getUTCDate();
+ }-*/;
+
+ /**
+ * Returns the day of the week, from 0
(Sunday) to 6
+ * Saturday, in UTC.
+ */
+ public final native int getUTCDay() /*-{
+ return this.getUTCDay();
+ }-*/;
+
+ /**
+ * Returns the four-digit year, in UTC.
+ */
+ public final native int getUTCFullYear() /*-{
+ return this.getUTCFullYear();
+ }-*/;
+
+ /**
+ * Returns the hour, between 0
(midnight) and 23
, in
+ * UTC.
+ */
+ public final native int getUTCHours() /*-{
+ return this.getUTCHours();
+ }-*/;
+
+ /**
+ * Returns the minutes, between 0
and 59
, in UTC.
+ */
+ public final native int getUTCMinutes() /*-{
+ return this.getUTCMinutes();
+ }-*/;
+
+ /**
+ * Returns the month, from 0
(January) to 11
+ * December, in UTC.
+ */
+ public final native int getUTCMonth() /*-{
+ return this.getUTCMonth();
+ }-*/;
+
+ /**
+ * Returns the seconds, between 0
and 59
, in UTC.
+ */
+ public final native int getUTCSeconds() /*-{
+ return this.getUTCSeconds();
+ }-*/;
+
+ /**
+ * Returns the milliseconds, between 0
and 999
, in
+ * UTC.
+ */
+ public final native int getUTCMilliseconds() /*-{
+ return this.getUTCMilliseconds();
+ }-*/;
+
+ /**
+ * Sets the day of the month. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setDate(int dayOfMonth) /*-{
+ this.setDate(dayOfMonth);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the year. Returns the millisecond representation of the adjusted date.
+ */
+ public final native double setFullYear(int year) /*-{
+ this.setFullYear(year);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the year and month. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setFullYear(int year,
+ int month) /*-{
+ this.setFullYear(year, month);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the year, month, and day. Returns the millisecond representation of
+ * the adjusted date.
+ */
+ public final native double setFullYear(int year,
+ int month,
+ int day) /*-{
+ this.setFullYear(year, month, day);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour. Returns the millisecond representation of the adjusted date.
+ */
+ public final native double setHours(int hours) /*-{
+ this.setHours(hours);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour and minutes. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setHours(int hours,
+ int minutes) /*-{
+ this.setHours(hours, minutes);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour, minutes, and seconds. Returns the millisecond representation
+ * of the adjusted date.
+ */
+ public final native double setHours(int hours,
+ int minutes,
+ int seconds) /*-{
+ this.setHours(hours, minutes, seconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour, minutes, seconds, and milliseconds. Returns the millisecond
+ * representation of the adjusted date.
+ */
+ public final native double setHours(int hours,
+ int minutes,
+ int seconds,
+ int milliseconds) /*-{
+ this.setHours(hours, minutes, seconds, milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the minutes. Returns the millisecond representation of the adjusted
+ * date.
+ */
+ public final native double setMinutes(int minutes) /*-{
+ this.setMinutes(minutes);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the minutes and seconds. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setMinutes(int minutes,
+ int seconds) /*-{
+ this.setMinutes(minutes, seconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the minutes, seconds, and milliseconds. Returns the millisecond
+ * representation of the adjusted date.
+ */
+ public final native double setMinutes(int minutes,
+ int seconds,
+ int milliseconds) /*-{
+ this.setMinutes(minutes, seconds, milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the month. Returns the millisecond representation of the adjusted
+ * date.
+ */
+ public final native double setMonth(int month) /*-{
+ this.setMonth(month);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the month and day. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setMonth(int month,
+ int dayOfMonth) /*-{
+ this.setMonth(month, dayOfMonth);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the seconds. Returns the millisecond representation of the adjusted
+ * date.
+ */
+ public final native double setSeconds(int seconds) /*-{
+ this.setSeconds(seconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the seconds and milliseconds. Returns the millisecond representation
+ * of the adjusted date.
+ */
+ public final native double setSeconds(int seconds,
+ int milliseconds) /*-{
+ this.setSeconds(seconds, milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the milliseconds. Returns the millisecond representation
+ * of the adjusted date.
+ */
+ public final native double setMilliseconds(int milliseconds) /*-{
+ this.setMilliseconds(milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the internal date representation. Returns the
+ * milliseconds
argument.
+ */
+ public final native double setTime(double milliseconds) /*-{
+ this.setTime(milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the day of the month, in UTC. Returns the millisecond representation
+ * of the adjusted date.
+ */
+ public final native double setUTCDate(int dayOfMonth) /*-{
+ this.setUTCDate(dayOfMonth);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the year, in UTC. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setUTCFullYear(int year) /*-{
+ this.setUTCFullYear(year);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the year and month, in UTC. Returns the millisecond representation of
+ * the adjusted date.
+ */
+ public final native double setUTCFullYear(int year,
+ int month) /*-{
+ this.setUTCFullYear(year, month);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the year, month, and day, in UTC. Returns the millisecond
+ * representation of the adjusted date.
+ */
+ public final native double setUTCFullYear(int year,
+ int month,
+ int day) /*-{
+ this.setUTCFullYear(year, month, day);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour, in UTC. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setUTCHours(int hours) /*-{
+ this.setUTCHours(hours);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour and minutes, in UTC. Returns the millisecond representation
+ * of the adjusted date.
+ */
+ public final native double setUTCHours(int hours,
+ int minutes) /*-{
+ this.setUTCHours(hours, minutes);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour, minutes, and seconds, in UTC. Returns the millisecond
+ * representation of the adjusted date.
+ */
+ public final native double setUTCHours(int hours,
+ int minutes,
+ int seconds) /*-{
+ this.setUTCHours(hours, minutes, seconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the hour, minutes, seconds, and milliseconds, in UTC. Returns the
+ * millisecond representation of the adjusted date.
+ */
+ public final native double setUTCHours(int hours,
+ int minutes,
+ int seconds,
+ int milliseconds) /*-{
+ this.setUTCHours(hours, minutes, seconds, milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the minutes, in UTC. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setUTCMinutes(int minutes) /*-{
+ this.setUTCMinutes(minutes);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the minutes and seconds, in UTC. Returns the millisecond
+ * representation of the adjusted date.
+ */
+ public final native double setUTCMinutes(int minutes,
+ int seconds) /*-{
+ this.setUTCMinutes(minutes, seconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the minutes, seconds, and milliseconds, in UTC. Returns the
+ * millisecond representation of the adjusted date.
+ */
+ public final native double setUTCMinutes(int minutes,
+ int seconds,
+ int milliseconds) /*-{
+ this.setUTCMinutes(minutes, seconds, milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the month, in UTC. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setUTCMonth(int month) /*-{
+ this.setUTCMonth(month);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the month and day, in UTC. Returns the millisecond representation of
+ * the adjusted date.
+ */
+ public final native double setUTCMonth(int month,
+ int dayOfMonth) /*-{
+ this.setUTCMonth(month, dayOfMonth);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the seconds, in UTC. Returns the millisecond representation of the
+ * adjusted date.
+ */
+ public final native double setUTCSeconds(int seconds) /*-{
+ this.setUTCSeconds(seconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the seconds and milliseconds, in UTC. Returns the millisecond
+ * representation of the adjusted date.
+ */
+ public final native double setUTCSeconds(int seconds,
+ int milliseconds) /*-{
+ this.setUTCSeconds(seconds, milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Sets the milliseconds, in UTC. Returns the millisecond
+ * representation of the adjusted date.
+ */
+ public final native double setUTCMilliseconds(int milliseconds) /*-{
+ this.setUTCMilliseconds(milliseconds);
+ return this.getTime();
+ }-*/;
+
+ /**
+ * Returns a date string in the local time zone.
+ */
+ public final native String toDateString() /*-{
+ return this.toDateString();
+ }-*/;
+
+ /**
+ * The toISOString() method of Date instances returns a string representing this date in the date time string
+ * format, a simplified format based on ISO 8601, which is always 24 or 27 characters long
+ * (YYYY-MM-DDTHH:mm:ss.sssZ or ±YYYYYY-MM-DDTHH:mm:ss.sssZ, respectively).
+ * The timezone is always UTC, as denoted by the suffix Z.
+ */
+ public final native String toISOString() /*-{
+ return this.toISOString();
+ }-*/;
+
+ /**
+ * The toJSON() method of Date instances returns a string representing this date in the same ISO format as
+ * toISOString().
+ */
+ public final native String toJSON() /*-{
+ return this.toJSON();
+ }-*/;
+
+ /**
+ * Returns a date string in the local time zone according to local formatting
+ * conventions.
+ */
+ public final native String toLocaleDateString() /*-{
+ return this.toLocaleDateString();
+ }-*/;
+
+ /**
+ * Returns a date and time string in the local time zone according to local
+ * formatting conventions.
+ */
+ public final native String toLocaleString() /*-{
+ return this.toLocaleString();
+ }-*/;
+
+ /**
+ * Returns a time string in the local time zone according to local formatting
+ * conventions.
+ */
+ public final native String toLocaleTimeString() /*-{
+ return this.toLocaleTimeString();
+ }-*/;
+
+ /**
+ * Returns a time string in the local time zone.
+ */
+ public final native String toTimeString() /*-{
+ return this.toTimeString();
+ }-*/;
+
+ /**
+ * Returns a date and time string in UTC.
+ */
+ public final native String toUTCString() /*-{
+ return this.toUTCString();
+ }-*/;
+
+ /**
+ * Returns the millisecond representation, as {@link #getTime()}.
+ */
+ public final native double valueOf() /*-{
+ return this.valueOf();
+ }-*/;
+}
diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/MonthSelector.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/MonthSelector.java
new file mode 100644
index 00000000000..87f75aa4be6
--- /dev/null
+++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/MonthSelector.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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 stroom.widget.datepicker.client;
+
+/**
+ * Abstract month selector widget.
+ */
+public abstract class MonthSelector extends DatePickerComponent {
+
+}
\ No newline at end of file
diff --git a/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/TimeZone.java b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/TimeZone.java
new file mode 100644
index 00000000000..2f45c581ac0
--- /dev/null
+++ b/stroom-core-client-widget/src/main/java/stroom/widget/datepicker/client/TimeZone.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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 stroom.widget.datepicker.client;
+
+import com.google.gwt.core.client.JsArrayInteger;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.i18n.client.TimeZoneInfo;
+
+/**
+ * The TimeZone class implements a time zone information source for client
+ * applications. The time zone object is instantiated from a TimeZoneData object,
+ * which is made from a JSON string that contains all the data needed for
+ * the specified time zone. Applications can instantiate a time zone statically,
+ * in which case the data could be retrieved from
+ * the {@link com.google.gwt.i18n.client.constants.TimeZoneConstants TimeZoneConstants}
+ * class. Applications can also choose to instantiate from a string obtained
+ * from a server. The time zone string contains locale specific data. If the
+ * application only uses a short representation, the English data will usually
+ * satisfy the user's need. In the case that only the time zone offset is known,
+ * there is a decent fallback that only uses the time zone offset to create a
+ * TimeZone object.
+ */
+public class TimeZone {
+ // constants to reference time zone names in the time zone names array
+ private static final int STD_SHORT_NAME = 0;
+ private static final int STD_LONG_NAME = 1;
+ private static final int DLT_SHORT_NAME = 2;
+ private static final int DLT_LONG_NAME = 3;
+
+ /**
+ * This factory method provides a decent fallback to create a time zone object
+ * just based on a given time zone offset.
+ *
+ * @param timeZoneOffsetInMinutes time zone offset in minutes
+ * @return a new time zone object
+ */
+ public static TimeZone createTimeZone(int timeZoneOffsetInMinutes) {
+ TimeZone tz = new TimeZone();
+ tz.standardOffset = timeZoneOffsetInMinutes;
+ tz.timezoneID = composePOSIXTimeZoneID(timeZoneOffsetInMinutes);
+ tz.tzNames = new String[2];
+ tz.tzNames[0] = composeUTCString(timeZoneOffsetInMinutes);
+ tz.tzNames[1] = composeUTCString(timeZoneOffsetInMinutes);
+ tz.transitionPoints = null;
+ tz.adjustments = null;
+ return tz;
+ }
+
+ /**
+ * This factory method creates a time zone instance from a JSON string that
+ * contains the time zone information for desired time zone. Applications can
+ * get such a string from the TimeZoneConstants class, or it can request the
+ * string from the server. Either way, the application obtains the original
+ * string from the data provided in the TimeZoneConstant.properties file,
+ * which was carefully prepared from CLDR and Olson time zone database.
+ *
+ * @param tzJSON JSON string that contains time zone data
+ * @return a new time zone object
+ */
+ public static TimeZone createTimeZone(String tzJSON) {
+ TimeZoneInfo tzData = TimeZoneInfo.buildTimeZoneData(tzJSON);
+
+ return createTimeZone(tzData);
+ }
+
+ public static TimeZone createTimeZone(TimeZoneInfo timezoneData) {
+ TimeZone tz = new TimeZone();
+
+ tz.timezoneID = timezoneData.getID();
+ tz.standardOffset = -timezoneData.getStandardOffset();
+
+ JsArrayString jsTimezoneNames = timezoneData.getNames();
+
+ tz.tzNames = new String[jsTimezoneNames.length()];
+
+ for (int i = 0; i < jsTimezoneNames.length(); i++) {
+ tz.tzNames[i] = jsTimezoneNames.get(i);
+ }
+
+ JsArrayInteger transitions = timezoneData.getTransitions();
+
+ if (transitions == null || transitions.length() == 0) {
+ tz.transitionPoints = null;
+ tz.adjustments = null;
+ } else {
+ int transitionNum = transitions.length() / 2;
+ tz.transitionPoints = new int[transitionNum];
+ tz.adjustments = new int[transitionNum];
+
+ for (int i = 0; i < transitionNum; ++i) {
+ tz.transitionPoints[i] = transitions.get(i * 2);
+ tz.adjustments[i] = transitions.get(i * 2 + 1);
+ }
+ }
+ return tz;
+ }
+
+ /**
+ * In GMT representation, +/- has reverse sign of time zone offset.
+ * when offset == 480, it should output GMT-08:00.
+ */
+ private static String composeGMTString(int offset) {
+ char data[] = {'G', 'M', 'T', '-', '0', '0', ':', '0', '0'};
+ if (offset <= 0) {
+ data[3] = '+';
+ offset = -offset; // suppress the '-' sign for text display.
+ }
+ data[4] = (char) (data[4] + (offset / 60) / 10);
+ data[5] = (char) (data[5] + (offset / 60) % 10);
+ data[7] = (char) (data[7] + (offset % 60) / 10);
+ data[8] = (char) (data[8] + offset % 10);
+ return new String(data);
+ }
+
+ /**
+ * POSIX time zone ID as fallback.
+ */
+ private static String composePOSIXTimeZoneID(int offset) {
+ if (offset == 0) {
+ return "Etc/GMT";
+ }
+ String str;
+ if (offset < 0) {
+ offset = -offset;
+ str = "Etc/GMT-";
+ } else {
+ str = "Etc/GMT+";
+ }
+ return str + offsetDisplay(offset);
+ }
+
+ private static String composeUTCString(int offset) {
+ if (offset == 0) {
+ return "UTC";
+ }
+ String str;
+ if (offset < 0) {
+ offset = -offset;
+ str = "UTC+";
+ } else {
+ str = "UTC-";
+ }
+ return str + offsetDisplay(offset);
+ }
+
+ private static String offsetDisplay(int offset) {
+ int hour = offset / 60;
+ int mins = offset % 60;
+ if (mins == 0) {
+ return Integer.toString(hour);
+ }
+ return Integer.toString(hour) + ":" + Integer.toString(mins);
+ }
+
+ private String timezoneID;
+ private int standardOffset;
+ private String[] tzNames;
+ private int[] transitionPoints;
+ private int[] adjustments;
+
+ private TimeZone() {
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getDaylightAdjustment(JsDate)
+ */
+ public int getDaylightAdjustment(JsDate date) {
+ if (transitionPoints == null) {
+ return 0;
+ }
+ double timeInHours = date.getTime() / 1000 / 3600;
+ int index = 0;
+ while (index < transitionPoints.length &&
+ timeInHours >= transitionPoints[index]) {
+ ++index;
+ }
+ return (index == 0) ? 0 : adjustments[index - 1];
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getGMTString(JsDate)
+ */
+ public String getGMTString(JsDate date) {
+ return composeGMTString(getOffset(date));
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getID()
+ */
+ public String getID() {
+ return timezoneID;
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getISOTimeZoneString(JsDate)
+ */
+ public String getISOTimeZoneString(JsDate date) {
+ int offset = -getOffset(date);
+ char data[] = {'+', '0', '0', ':', '0', '0'};
+ if (offset < 0) {
+ data[0] = '-';
+ offset = -offset; // suppress the '-' sign for text display.
+ }
+ data[1] = (char) (data[1] + (offset / 60) / 10);
+ data[2] = (char) (data[2] + (offset / 60) % 10);
+ data[4] = (char) (data[4] + (offset % 60) / 10);
+ data[5] = (char) (data[5] + offset % 10);
+ return new String(data);
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getLongName(JsDate)
+ */
+ public String getLongName(JsDate date) {
+ return tzNames[isDaylightTime(date) ? DLT_LONG_NAME : STD_LONG_NAME];
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getOffset(JsDate)
+ */
+ public int getOffset(JsDate date) {
+ return standardOffset - getDaylightAdjustment(date);
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getRFCTimeZoneString(JsDate)
+ */
+ public String getRFCTimeZoneString(JsDate date) {
+ int offset = -getOffset(date);
+ char data[] = {'+', '0', '0', '0', '0'};
+ if (offset < 0) {
+ data[0] = '-';
+ offset = -offset; // suppress the '-' sign for text display.
+ }
+ data[1] = (char) (data[1] + (offset / 60) / 10);
+ data[2] = (char) (data[2] + (offset / 60) % 10);
+ data[3] = (char) (data[3] + (offset % 60) / 10);
+ data[4] = (char) (data[4] + offset % 10);
+ return new String(data);
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getShortName(JsDate)
+ */
+ public String getShortName(JsDate date) {
+ return tzNames[isDaylightTime(date) ? DLT_SHORT_NAME : STD_SHORT_NAME];
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#getStandardOffset()
+ */
+ public int getStandardOffset() {
+ return standardOffset;
+ }
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.i18n.client.TimeZoneIntf#isDaylightTime(JsDate)
+ */
+ public boolean isDaylightTime(JsDate date) {
+ return getDaylightAdjustment(date) > 0;
+ }
+}
diff --git a/stroom-core-client-widget/src/main/resources/stroom/widget/datepicker/DatePicker.gwt.xml b/stroom-core-client-widget/src/main/resources/stroom/widget/datepicker/DatePicker.gwt.xml
new file mode 100644
index 00000000000..dfeb77f7adb
--- /dev/null
+++ b/stroom-core-client-widget/src/main/resources/stroom/widget/datepicker/DatePicker.gwt.xml
@@ -0,0 +1,7 @@
+
+
+