From b518e9b575bd6baf0e466582d31a40503c7e886b Mon Sep 17 00:00:00 2001 From: Jouni Koivuviita Date: Tue, 10 Jul 2007 12:38:34 +0000 Subject: [PATCH] DateField text-style implemented. svn changeset:1828/svn branch:trunk --- .../toolkit/terminal/gwt/Client.gwt.xml | 14 +- .../terminal/gwt/client/DateLocale.java | 57 +++++ .../terminal/gwt/client/DateTimeService.java | 10 + .../gwt/client/ui/IPopupCalendar.java | 55 ++++- .../terminal/gwt/client/ui/ITextualDate.java | 191 ++++++++------- .../gwt/gwtwidgets/util/DateLocale.java | 85 +++++++ .../gwt/gwtwidgets/util/SimpleDateFormat.java | 221 ++++++++++++++++++ .../gwt/gwtwidgets/util/SimpleDateParser.java | 157 +++++++++++++ .../gwt/gwtwidgets/util/regex/Pattern.java | 176 ++++++++++++++ .../textfield/css/textfield.css | 4 + 10 files changed, 880 insertions(+), 90 deletions(-) create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/DateLocale.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/DateLocale.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateFormat.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateParser.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/regex/Pattern.java diff --git a/src/com/itmill/toolkit/terminal/gwt/Client.gwt.xml b/src/com/itmill/toolkit/terminal/gwt/Client.gwt.xml index 2ecad6ba07..19fed850ce 100644 --- a/src/com/itmill/toolkit/terminal/gwt/Client.gwt.xml +++ b/src/com/itmill/toolkit/terminal/gwt/Client.gwt.xml @@ -1,18 +1,22 @@ - + - + - + - + + + + + - + diff --git a/src/com/itmill/toolkit/terminal/gwt/client/DateLocale.java b/src/com/itmill/toolkit/terminal/gwt/client/DateLocale.java new file mode 100644 index 0000000000..f0b1ddc9ed --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/DateLocale.java @@ -0,0 +1,57 @@ +package com.itmill.toolkit.terminal.gwt.client; + +public class DateLocale extends com.itmill.toolkit.terminal.gwt.gwtwidgets.util.DateLocale { + + private static String locale; + + public DateLocale() { + locale = LocaleService.getDefaultLocale(); + } + + public static void setLocale(String l) { + if(LocaleService.getAvailableLocales().contains(locale)) + locale = l; + else // TODO redirect to console + System.out.println("Tried to use an unloaded locale \""+locale+"\". Using default in stead ("+locale+")"); + } + + public static String getAM() { + try { + return LocaleService.getAmPmStrings(locale)[0]; + } catch (LocaleNotLoadedException e) { + // TODO redirect to console + System.out.println("Tried to use an unloaded locale \""+locale+"\"."); + return "AM"; + } + } + + public static String getPM() { + try { + return LocaleService.getAmPmStrings(locale)[1]; + } catch (LocaleNotLoadedException e) { + // TODO redirect to console + System.out.println("Tried to use an unloaded locale \""+locale+"\"."); + return "PM"; + } + } + + public String[] getWEEKDAY_LONG() { + try { + return LocaleService.getDayNames(locale); + } catch (LocaleNotLoadedException e) { + // TODO redirect to console + System.out.println("Tried to use an unloaded locale \""+locale+"\"."); + return null; + } + } + + public String[] getWEEKDAY_SHORT() { + try { + return LocaleService.getShortDayNames(locale); + } catch (LocaleNotLoadedException e) { + // TODO redirect to console + System.out.println("Tried to use an unloaded locale \""+locale+"\"."); + return null; + } + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/DateTimeService.java b/src/com/itmill/toolkit/terminal/gwt/client/DateTimeService.java index 7e92ff80a3..77429b0a04 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/DateTimeService.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/DateTimeService.java @@ -140,6 +140,16 @@ public class DateTimeService { return start; } + public String getDateFormat() { + try { + return LocaleService.getDateFormat(currentLocale); + } catch (LocaleNotLoadedException e) { + // TODO redirect to console + System.out.println(e + ":" + e.getMessage()); + } + return "M/d/yy"; + } + public static int getNumberOfDaysInMonth(Date date){ int month = date.getMonth(); if(month == 1 && true == isLeapYear(date)) diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPopupCalendar.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPopupCalendar.java index 4d5933dce7..b2a70ed9fc 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPopupCalendar.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPopupCalendar.java @@ -1,5 +1,58 @@ package com.itmill.toolkit.terminal.gwt.client.ui; -public class IPopupCalendar extends IDateField { +import com.google.gwt.user.client.ui.ChangeListener; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.PopupListener; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.Client; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IPopupCalendar extends IDateField implements Paintable, ChangeListener, ClickListener { + + private ITextField text; + + private IButton calendarToggle; + + private ICalendarPanel calendar; + + private PopupPanel popup; + + public IPopupCalendar() { + super(); + text = new ITextField(); + text.addChangeListener(this); + calendarToggle = new IButton(); + calendarToggle.setText("..."); + calendarToggle.addClickListener(this); + calendar = new ICalendarPanel(this); + popup = new PopupPanel(true); + popup.setStyleName(IDateField.CLASSNAME+"-calendar"); + popup.setWidget(calendar); + add(text); + add(calendarToggle); + } + + public void updateFromUIDL(UIDL uidl, Client client) { + super.updateFromUIDL(uidl, client); + + + + calendar.updateCalendar(); + } + + public void onChange(Widget sender) { + + } + + public void onClick(Widget sender) { + if(sender == calendarToggle) { + popup.setPopupPosition(calendarToggle.getAbsoluteLeft(), calendarToggle.getAbsoluteTop() + calendarToggle.getOffsetHeight() + 2); + popup.show(); + popup.setWidth(calendar.getOffsetWidth() + "px"); + popup.setHeight(calendar.getOffsetHeight() + "px"); + } + } } diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITextualDate.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITextualDate.java index 73993bbd70..61d79ec6fd 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITextualDate.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITextualDate.java @@ -1,113 +1,136 @@ package com.itmill.toolkit.terminal.gwt.client.ui; -import java.util.Date; +import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.ChangeListener; -import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.Widget; import com.itmill.toolkit.terminal.gwt.client.Client; +import com.itmill.toolkit.terminal.gwt.client.DateLocale; import com.itmill.toolkit.terminal.gwt.client.Paintable; import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.gwtwidgets.util.SimpleDateFormat; public class ITextualDate extends IDateField implements Paintable, ChangeListener { - private ListBox year; + private ITextField text; - private ListBox month; + private SimpleDateFormat format; - private ListBox day; + private DateLocale dl; public ITextualDate() { - + super(); + text = new ITextField(); + text.addChangeListener(this); + add(text); } public void updateFromUIDL(UIDL uidl, Client client) { super.updateFromUIDL(uidl, client); + buildTime(); + } + + private void buildTime() { + dl = new DateLocale(); + DateLocale.setLocale(currentLocale); - if(uidl.hasVariable("year")) { - int selectedYear = uidl.getIntVariable("year"); - int y = getWidgetIndex(year); - if(y > -1 || !currentLocale.equals(uidl.getStringAttribute("locale"))) { - year = (ListBox) getWidget(y); - // Deselect old value - year.setItemSelected(year.getSelectedIndex(), false); - // and select new - for(int i=0; i < year.getItemCount(); i++) - if(year.getValue(i).equals(""+selectedYear)) { - year.setSelectedIndex(i); - break; - } - } else { - year = new ListBox(); - year.setStyleName(ISelect.CLASSNAME); - int today = 1900 + (new Date()).getYear(); - for(int i=1970; i= IDateField.RESOLUTION_DAY) + DateLocale.SUPPORTED_DF_TOKENS = DateLocale.TOKENS_RESOLUTION_DAY; + + format = new SimpleDateFormat(dts.getDateFormat()); + format.setLocale(dl); + + String dateText = format.format(date); + + if(currentResolution >= IDateField.RESOLUTION_HOUR) { + DateLocale.SUPPORTED_DF_TOKENS = DateLocale.TOKENS_RESOLUTION_ALL; + int h = date.getHours(); + if(h > 11 && dts.isTwelveHourClock()) + h -= 12; + int m = currentResolution > IDateField.RESOLUTION_HOUR? date.getMinutes() : 0; + dateText += " " + (h<10?"0"+h:""+h) + dts.getClockDelimeter() + (m<10?"0"+m:""+m); } - if(uidl.hasVariable("month")) { - int selectedMonth = uidl.getIntVariable("month"); - int y = getWidgetIndex(month); - if(y > -1) { - month = (ListBox) getWidget(y); - // Deselect old value - month.setItemSelected(month.getSelectedIndex(), false); - // and select new - for(int i=0; i < month.getItemCount(); i++) - if(month.getValue(i).equals(""+selectedMonth)) { - month.setSelectedIndex(i); - break; - } - } else { - month = new ListBox(); - month.setStyleName(ISelect.CLASSNAME); - int today = (new Date()).getMonth(); - for(int i=0; i<12; i++) { - month.addItem(""+(i+1), ""+i); - if(i == selectedMonth) - month.setSelectedIndex(month.getItemCount()-1); - } - month.addChangeListener(this); - add(month); - } + if(currentResolution >= IDateField.RESOLUTION_SEC) { + int s = date.getSeconds(); + dateText += dts.getClockDelimeter() + (s<10?"0"+s:""+s); } - if(uidl.hasVariable("day")) { - int selectedMonth = uidl.getIntVariable("day"); - int y = getWidgetIndex(day); - if(y > -1) { - day = (ListBox) getWidget(y); - // Deselect old value - day.setItemSelected(day.getSelectedIndex(), false); - // and select new - for(int i=0; i = IDateField.RESOLUTION_HOUR) + f += " " + (dts.isTwelveHourClock()? + DateLocale.TOKEN_HOUR_12 + DateLocale.TOKEN_HOUR_12 + : DateLocale.TOKEN_HOUR_24 + DateLocale.TOKEN_HOUR_24) + + dts.getClockDelimeter() + DateLocale.TOKEN_MINUTE + DateLocale.TOKEN_MINUTE; + if(currentResolution >= IDateField.RESOLUTION_SEC) + f += dts.getClockDelimeter() + DateLocale.TOKEN_SECOND + DateLocale.TOKEN_SECOND; + if(currentResolution == IDateField.RESOLUTION_MSEC) + f += "." + DateLocale.TOKEN_MILLISECOND + DateLocale.TOKEN_MILLISECOND + DateLocale.TOKEN_MILLISECOND; + if(currentResolution >= IDateField.RESOLUTION_HOUR && dts.isTwelveHourClock()) + f += " " + DateLocale.TOKEN_AM_PM; + + format = new SimpleDateFormat(f); + DateLocale.setLocale(currentLocale); + format.setLocale(dl); + + try { + date = format.parse(text.getText()); + } catch (Exception e) { + // TODO redirect to console + System.out.println(e); + text.addStyleName(ITextField.CLASSNAME+"-error"); + Timer t = new Timer() { + public void run() { + text.removeStyleName(ITextField.CLASSNAME+"-error"); + } + }; + t.schedule(2000); + return; + } + + // Update variables + // (only the smallest defining resolution needs to be immediate) + client.updateVariable(id, "year", date.getYear()+1900, currentResolution==IDateField.RESOLUTION_YEAR); + if(currentResolution >= IDateField.RESOLUTION_MONTH) + client.updateVariable(id, "month", date.getMonth()+1, currentResolution==IDateField.RESOLUTION_MONTH&&immediate); + if(currentResolution >= IDateField.RESOLUTION_DAY) + client.updateVariable(id, "day", date.getDate(), currentResolution==IDateField.RESOLUTION_DAY&&immediate); + if(currentResolution >= IDateField.RESOLUTION_HOUR) + client.updateVariable(id, "hour", date.getHours(), currentResolution==IDateField.RESOLUTION_HOUR&&immediate); + if(currentResolution >= IDateField.RESOLUTION_MIN) + client.updateVariable(id, "min", date.getMinutes(), currentResolution==IDateField.RESOLUTION_MIN&&immediate); + if(currentResolution >= IDateField.RESOLUTION_SEC) + client.updateVariable(id, "sec", date.getSeconds(), currentResolution==IDateField.RESOLUTION_SEC&&immediate); + if(currentResolution == IDateField.RESOLUTION_MSEC) + client.updateVariable(id, "msec", getMilliseconds(), immediate); + + buildTime(); + } } } diff --git a/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/DateLocale.java b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/DateLocale.java new file mode 100644 index 0000000000..bb4cb01e9d --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/DateLocale.java @@ -0,0 +1,85 @@ +package com.itmill.toolkit.terminal.gwt.gwtwidgets.util; + +import java.util.Arrays; +import java.util.List; + +/** + * Date locale support for the {@link SimpleDateParser}. You are encouraged to + * extend this class and provide implementations for other locales. + * @author George Georgovassilis + * + */ +public class DateLocale { + public final static String TOKEN_DAY_OF_WEEK = "E"; + + public final static String TOKEN_DAY_OF_MONTH = "d"; + + public final static String TOKEN_MONTH = "M"; + + public final static String TOKEN_YEAR = "y"; + + public final static String TOKEN_HOUR_12 = "h"; + + public final static String TOKEN_HOUR_24 = "H"; + + public final static String TOKEN_MINUTE = "m"; + + public final static String TOKEN_SECOND = "s"; + + public final static String TOKEN_MILLISECOND = "S"; + + public final static String TOKEN_AM_PM = "a"; + + public final static String AM = "AM"; + + public final static String PM = "PM"; + + public static List SUPPORTED_DF_TOKENS = Arrays.asList(new String[] { + TOKEN_DAY_OF_WEEK, TOKEN_DAY_OF_MONTH, TOKEN_MONTH, TOKEN_YEAR, + TOKEN_HOUR_12, TOKEN_HOUR_24, TOKEN_MINUTE, TOKEN_SECOND, + TOKEN_AM_PM }); + + public static List TOKENS_RESOLUTION_ALL = Arrays.asList(new String[] { + TOKEN_DAY_OF_WEEK, TOKEN_DAY_OF_MONTH, TOKEN_MONTH, TOKEN_YEAR, + TOKEN_HOUR_12, TOKEN_HOUR_24, TOKEN_MINUTE, TOKEN_SECOND, + TOKEN_AM_PM }); + + public static List TOKENS_RESOLUTION_YEAR = Arrays.asList(new String[] { + TOKEN_YEAR}); + + public static List TOKENS_RESOLUTION_MONTH = Arrays.asList(new String[] { + TOKEN_YEAR, TOKEN_MONTH}); + + public static List TOKENS_RESOLUTION_DAY = Arrays.asList(new String[] { + TOKEN_YEAR, TOKEN_MONTH, TOKEN_DAY_OF_MONTH}); + + public String[] MONTH_LONG = { "January", "February", "March", "April", + "May", "June", "July", "August", "September", "October", + "November", "December" }; + + public String[] MONTH_SHORT = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sept", "Oct", "Nov", "Dec" }; + + public String[] WEEKDAY_LONG = { "Sunday", "Monday", "Tuesday", + "Wednesday", "Thursday", "Friday", "Saturday" }; + + public String[] WEEKDAY_SHORT = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", + "Sat" }; + + public static String getAM() { + return AM; + } + + public static String getPM() { + return PM; + } + + public String[] getWEEKDAY_LONG() { + return WEEKDAY_LONG; + } + + public String[] getWEEKDAY_SHORT() { + return WEEKDAY_SHORT; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateFormat.java b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateFormat.java new file mode 100644 index 0000000000..79ce6518fc --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateFormat.java @@ -0,0 +1,221 @@ +/* + * Copyright 2006 Robert Hanson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.itmill.toolkit.terminal.gwt.gwtwidgets.util; + +import java.util.Date; + +/** + *
+ *
Title: + *
SimpleDateFormat
+ *

+ *

Description: + *
GWT does not implement any of the java.text package, so this class tries + * to fill the void of the missing java.text.SimpleDateFormat class. This + * version however only supports a subset of the date and time patterns + * supported by its java.text counterpart. The pattern symbols supported by this + * class are: + *
+ *
E
+ *
Day in a week
+ *
d
+ *
Day of the month
+ *
y
+ *
Year
+ *
M
+ *
Month January, Jan, 01, 1
+ *
H
+ *
Hour in 24 hour format (0-23)
+ *
h
+ *
Hour in 12 hour format (1-12)
+ *
m
+ *
Minute of the hour
+ *
s
+ *
Seconds of the minute
+ *
a
+ *
am/pm
+ *
+ * All characters that are not recognised as a date format character are + * translated literally into the output string.
+ *

+ *

+ *

+ * A simple date parsing facility has also been implemented resembling the java + * prototype. You can currently parse most numeric patterns but no temporal + * literals (such as day or month names). + *

+ * + * @author Jason Essington + * @author George Georgovassilis + * @version $Revision: 0.0 $ + */ +public class SimpleDateFormat { + private String format; + private DateLocale locale = new DateLocale(); + + /** + * Gets the support locale for formatting and parsing dates + * @return + */ + public DateLocale getLocale() { + return locale; + } + + public void setLocale(DateLocale locale) { + this.locale = locale; + } + + public SimpleDateFormat(String pattern) { + format = pattern; + } + + public String format(Date date) { + String f = ""; + if (format != null && format.length() > 0) { + String lastTokenType = null; + String currentToken = ""; + for (int i = 0; i < format.length(); i++) { + String thisChar = format.substring(i, i + 1); + String currentTokenType = DateLocale.SUPPORTED_DF_TOKENS + .contains(thisChar) ? thisChar : ""; + if (currentTokenType.equals(lastTokenType) || i == 0) { + currentToken += thisChar; + lastTokenType = currentTokenType; + } else { + if ("".equals(lastTokenType)) + f += currentToken; + else + f += handleToken(currentToken, date); + currentToken = thisChar; + lastTokenType = currentTokenType; + } + } + if ("".equals(lastTokenType)) + f += currentToken; + else + f += handleToken(currentToken, date); + } + return f; + } + + /** + * takes a date format string and returns the formatted portion of the date. + * For instance if the token is MMMM then the full month name is returned. + * + * @param token + * date format token + * @param date + * date to format + * @return formatted portion of the date + */ + private String handleToken(String token, Date date) { + String response = token; + String tc = token.substring(0, 1); + if (DateLocale.TOKEN_DAY_OF_WEEK.equals(tc)) { + if (token.length() > 3) + response = locale.getWEEKDAY_LONG()[date.getDay()]; + else + response = locale.getWEEKDAY_SHORT()[date.getDay()]; + } else if (DateLocale.TOKEN_DAY_OF_MONTH.equals(tc)) { + if (token.length() == 1) + response = Integer.toString(date.getDate()); + else + response = twoCharDateField(date.getDate()); + } else if (DateLocale.TOKEN_MONTH.equals(tc)) { + switch (token.length()) { + case 1: + response = Integer.toString(date.getMonth() + 1); + break; + case 2: + response = twoCharDateField(date.getMonth() + 1); + break; + case 3: + response = locale.MONTH_SHORT[date.getMonth()]; + break; + default: + response = locale.MONTH_LONG[date.getMonth()]; + break; + } + } else if (DateLocale.TOKEN_YEAR.equals(tc)) { + if (token.length() >= 2) + response = Integer.toString(date.getYear() + 1900); + else + response = twoCharDateField(date.getYear()); + } else if (DateLocale.TOKEN_HOUR_12.equals(tc)) { + int h = date.getHours(); + if (h == 0) + h = 12; + else if (h > 12) + h -= 12; + //if (token.length() > 1) + response = twoCharDateField(h); + //else + //response = Integer.toString(h); + } else if (DateLocale.TOKEN_HOUR_24.equals(tc)) { + //if (token.length() > 1) + response = twoCharDateField(date.getHours()); + //else + //response = Integer.toString(date.getHours()); + } else if (DateLocale.TOKEN_MINUTE.equals(tc)) { + //if (token.length() > 1) + response = twoCharDateField(date.getMinutes()); + //else + //response = Integer.toString(date.getMinutes()); + } else if (DateLocale.TOKEN_SECOND.equals(tc)) { + //if (token.length() > 1) + response = twoCharDateField(date.getSeconds()); + //else + //response = Integer.toString(date.getSeconds()); + } else if (DateLocale.TOKEN_AM_PM.equals(tc)) { + int hour = date.getHours(); + if (hour > 11) + response = DateLocale.getPM(); + else + response = DateLocale.getAM(); + } + return response; + } + + /** + * This is basically just a sneaky way to guarantee that our 1 or 2 digit + * numbers come out as a 2 character string. we add an arbitrary number + * larger than 100, convert this new number to a string, then take the right + * most 2 characters. + * + * @param num + * @return + */ + private String twoCharDateField(int num) { + String res = Integer.toString(num + 1900); + res = res.substring(res.length() - 2); + return res; + } + + private static Date newDate(long time) { + return new Date(time); + } + + /** + * Parses text and returns the corresponding date object. + * + * @param source + * @return java.util.Date + */ + public Date parse(String source){ + return SimpleDateParser.parse(source, format); + }; + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateParser.java b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateParser.java new file mode 100644 index 0000000000..90fdb22644 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/SimpleDateParser.java @@ -0,0 +1,157 @@ +package com.itmill.toolkit.terminal.gwt.gwtwidgets.util; + +import java.util.Date; + +import com.itmill.toolkit.terminal.gwt.client.DateTimeService; +import com.itmill.toolkit.terminal.gwt.gwtwidgets.util.regex.Pattern; + +/** + * This is a simple regular expression based parser for date notations. + * While our aim is to fully support in the future the JDK date parser, currently + * only numeric notations and literals are supported such as dd/MM/yyyy HH:mm:ss.SSSS. + * Each entity is parsed with the same number of digits, i.e. for dd two digits will be + * parsed while for d only one will be parsed. + * @author George Georgovassilis + * + */ + +public class SimpleDateParser { + + + private final static String DAY_IN_MONTH = "d"; + + private final static String MONTH = "M"; + + private final static String YEAR = "y"; + + private final static String LITERAL = "\\"; + + private final static int DATE_PATTERN = 0; + + private final static int REGEX_PATTERN = 1; + + private final static int COMPONENT = 2; + + private final static int REGEX = 0; + + private final static int INSTRUCTION = 1; + + private final static String[] TOKENS[] = { + { "SSSS", "(\\d\\d\\d\\d)",DateLocale.TOKEN_MILLISECOND }, + { "SSS", "(\\d\\d\\d)", DateLocale.TOKEN_MILLISECOND }, + { "SS", "(\\d\\d)", DateLocale.TOKEN_MILLISECOND }, + { "S", "(\\d)", DateLocale.TOKEN_MILLISECOND }, + { "ss", "(\\d\\d)", DateLocale.TOKEN_SECOND }, + { "s", "(\\d\\d)", DateLocale.TOKEN_SECOND }, + { "mm", "(\\d\\d)", DateLocale.TOKEN_MINUTE }, + { "m", "(\\d\\d)", DateLocale.TOKEN_MINUTE}, + { "HH", "(\\d\\d)", DateLocale.TOKEN_HOUR_24}, + { "H", "(\\d{1,2})", DateLocale.TOKEN_HOUR_24 }, + { "hh", "(\\d\\d)", DateLocale.TOKEN_HOUR_12}, + { "h", "(\\d{1,2})", DateLocale.TOKEN_HOUR_12 }, + { "dd", "(\\d\\d)", DateLocale.TOKEN_DAY_OF_MONTH }, + { "d", "(\\d{1,2})", DateLocale.TOKEN_DAY_OF_MONTH }, + { "MM", "(\\d\\d)", DateLocale.TOKEN_MONTH }, + { "M", "(\\d{1,2})", DateLocale.TOKEN_MONTH }, + { "yyyy", "(\\d\\d\\d\\d)", DateLocale.TOKEN_YEAR }, + { "yyy", "(\\d\\d\\d\\d)", DateLocale.TOKEN_YEAR }, + { "yy", "(\\d\\d\\d\\d)", DateLocale.TOKEN_YEAR }, + { "y", "(\\d{1,2})", DateLocale.TOKEN_YEAR }, + { "a", "(\\S{1,4})", DateLocale.TOKEN_AM_PM } + }; + + private Pattern regularExpression; + + private String instructions = ""; + + private static void _parse(String format, String[] args) { + if (format.length() == 0) + return; + if (format.startsWith("'")){ + format = format.substring(1); + int end = format.indexOf("'"); + if (end == -1) + throw new IllegalArgumentException("Unmatched single quotes."); + args[REGEX]+=Pattern.quote(format.substring(0,end)); + format = format.substring(end+1); + } + for (int i = 0; i < TOKENS.length; i++) { + String[] row = TOKENS[i]; + String datePattern = row[DATE_PATTERN]; + if (!format.startsWith(datePattern)) + continue; + format = format.substring(datePattern.length()); + args[REGEX] += row[REGEX_PATTERN]; + args[INSTRUCTION] += row[COMPONENT]; + _parse(format, args); + return; + } + args[REGEX] += Pattern.quote(""+format.charAt(0)); + format = format.substring(1); + _parse(format, args); + } + + private static void load(Date date, String text, String component, String input, Pattern regex) { + if (component.equals(DateLocale.TOKEN_MILLISECOND)) { + // TODO implement setMilliseconds to date object + } + + if (component.equals(DateLocale.TOKEN_SECOND)) { + date.setSeconds(Integer.parseInt(text)); + } + + if (component.equals(DateLocale.TOKEN_MINUTE)) { + date.setMinutes(Integer.parseInt(text)); + } + + if (component.equals(DateLocale.TOKEN_HOUR_24)) { + date.setHours(Integer.parseInt(text)); + } + + if (component.equals(DateLocale.TOKEN_HOUR_12)) { + int h = Integer.parseInt(text); + String token = com.itmill.toolkit.terminal.gwt.client.DateLocale.getPM(); + String which = input.substring(input.length() - token.length()); // Assumes both AM and PM tokens have same length + if(which.equals(token)) + h += 12; + date.setHours(h); + } + + if (component.equals(DateLocale.TOKEN_DAY_OF_MONTH)) { + date.setDate(Integer.parseInt(text)); + } + if (component.equals(DateLocale.TOKEN_MONTH)) { + date.setMonth(Integer.parseInt(text)-1); + } + if (component.equals(DateLocale.TOKEN_YEAR)) { + //TODO: fix for short patterns + date.setYear(Integer.parseInt(text)-1900); + } + + } + + public SimpleDateParser(String format) { + String[] args = new String[] { "", "" }; + _parse(format, args); + regularExpression = new Pattern(args[REGEX]); + instructions = args[INSTRUCTION]; + } + + public Date parse(String input) { + Date date = new Date(0, 0, 0, 0, 0, 0); + String matches[] = regularExpression.match(input); + if (matches == null) + throw new IllegalArgumentException(input+" does not match "+regularExpression.pattern()); + if (matches.length-1!=instructions.length()) + throw new IllegalArgumentException("Different group count - "+input+" does not match "+regularExpression.pattern()); + for (int group = 0; group < instructions.length(); group++) { + String match = matches[group + 1]; + load(date, match, ""+instructions.charAt(group), input, regularExpression); + } + return date; + } + + public static Date parse(String input, String pattern){ + return new SimpleDateParser(pattern).parse(input); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/regex/Pattern.java b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/regex/Pattern.java new file mode 100644 index 0000000000..3a6d0b4e18 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/gwtwidgets/util/regex/Pattern.java @@ -0,0 +1,176 @@ +/* + * Copyright 2006 Robert Hanson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.itmill.toolkit.terminal.gwt.gwtwidgets.util.regex; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.JavaScriptObject; + +/** + *

+ * Implementation of the {@link java.util.regex.Pattern} class with a + * wrapper aroung the Javascript RegExp object. + * As most of the methods delegate to the JavaScript RegExp object, certain differences in the + * declaration and behaviour of regular expressions must be expected. + *

+ *

+ * Please note that neither the {@link java.util.regex.Pattern#compile(String)} method nor + * {@link Matcher} instances are supported. For the later, consider using {@link Pattern#match(String)}. + *

+ * + * @author George Georgovassilis + * + */ +public class Pattern { + + /** + * Declares that regular expressions should be matched across line borders. + */ + public final static int MULTILINE = 1; + + /** + * Declares that characters are matched reglardless of case. + */ + public final static int CASE_INSENSITIVE = 2; + + private JavaScriptObject regExp; + + private static JavaScriptObject createExpression(String pattern, int flags) { + String sFlags = ""; + if ((flags & MULTILINE) != 0) + sFlags += "m"; + if ((flags & CASE_INSENSITIVE) != 0) + sFlags += "i"; + return _createExpression(pattern, sFlags); + } + + private static native JavaScriptObject _createExpression(String pattern, + String flags)/*-{ + return new RegExp(pattern, flags); + }-*/; + + private native void _match(String text, List matches)/*-{ + var regExp = this.@com.itmill.toolkit.terminal.gwt.gwtwidgets.util.regex.Pattern::regExp; + var result = text.match(regExp); + if (result == null) return; + for (var i=0;itrue if matched. + */ + public static boolean matches(String regex, String input) { + return new Pattern(regex).matches(input); + } + + /** + * Escape a provided string so that it will be interpreted as a literal + * in regular expressions. + * The current implementation does escape each character even if not neccessary, + * generating verbose literals. + * @param input + * @return + */ + public static String quote(String input) { + String output = ""; + for (int i = 0; i < input.length(); i++) { + output += "\\" + input.charAt(i); + } + return output; + } + + /** + * Class constructor + * @param pattern Regular expression + */ + public Pattern(String pattern) { + this(pattern, 0); + } + + /** + * Class constructor + * @param pattern Regular expression + * @param flags + */ + public Pattern(String pattern, int flags) { + regExp = createExpression(pattern, flags); + } + + /** + * This method is borrowed from the JavaScript RegExp object. + * It parses a string and returns as an array any assignments to parenthesis groups + * in the pattern's regular expression + * @param text + * @return Array of strings following java's Pattern convention for groups: + * Group 0 is the entire input string and the remaining groups are the matched parenthesis. + * In case nothing was matched an empty array is returned. + */ + public String[] match(String text) { + List matches = new ArrayList(); + _match(text, matches); + String arr[] = new String[matches.size()]; + for (int i = 0; i < matches.size(); i++) + arr[i] = matches.get(i).toString(); + return arr; + } + + /** + * Determines wether a provided text matches the regular expression + * @param text + * @return + */ + public native boolean matches(String text)/*-{ + var regExp = this.@com.itmill.toolkit.terminal.gwt.gwtwidgets.util.regex.Pattern::regExp; + return regExp.test(text); + }-*/; + + /** + * Returns the regular expression for this pattern + * @return + */ + public native String pattern()/*-{ + var regExp = this.@com.itmill.toolkit.terminal.gwt.gwtwidgets.util.regex.Pattern::regExp; + return regExp.source; + }-*/; + + private native void _split(String input, List results)/*-{ + var regExp = this.@com.itmill.toolkit.terminal.gwt.gwtwidgets.util.regex.Pattern::regExp; + var parts = input.split(regExp); + for (var i=0;i