RandomSec/src/main/java/com/metaweb/gridworks/expr/util/CalendarParser.java

1943 lines
60 KiB
Java
Raw Normal View History

package com.metaweb.gridworks.expr.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// Taken from http://icecube.wisc.edu/~dglo/software/calparse/index.html
// Copyright Dave Glowacki. Released under the BSD license.
/**
* Date parser state.
*/
class ParserState {
/** bit indicating that the year comes before the month. */
static final int YEAR_BEFORE_MONTH = 0x4;
/** bit indicating that the year comes before the day. */
static final int YEAR_BEFORE_DAY = 0x2;
/** bit indicating that the month comes before the day. */
static final int MONTH_BEFORE_DAY = 0x1;
/** bit indicating that the year comes after the month. */
static final int YEAR_AFTER_MONTH = 0x0;
/** bit indicating that the year comes after the day. */
static final int YEAR_AFTER_DAY = 0x0;
/** bit indicating that the month comes after the day. */
static final int MONTH_AFTER_DAY = 0x0;
/** value indicating an unset variable. */
static final int UNSET = Integer.MIN_VALUE;
/** <tt>true</tt> if year should appear before month. */
private boolean yearBeforeMonth;
/** <tt>true</tt> if year should appear before day. */
private boolean yearBeforeDay;
/** <tt>true</tt> if month should appear before day. */
private boolean monthBeforeDay;
/** year. */
private int year;
/** month (0-11). */
private int month;
/** day of month. */
private int day;
/** hour (0-23). */
private int hour;
/** minute (0-59). */
private int minute;
/** second (0-59). */
private int second;
/** millisecond (0-999). */
private int milli;
/** <tt>true</tt> if time is after noon. */
private boolean timePostMeridian;
/** time zone (use default time zone if this is <tt>null</tt>). */
private TimeZone timeZone;
/**
* Create parser state for the specified order.
*
* @param order
* <tt>YY_MM_DD</tt>, <tt>MM_DD_YY</tt>, etc.
*/
ParserState(int order) {
yearBeforeMonth = (order & YEAR_BEFORE_MONTH) == YEAR_BEFORE_MONTH;
yearBeforeDay = (order & YEAR_BEFORE_DAY) == YEAR_BEFORE_DAY;
monthBeforeDay = (order & MONTH_BEFORE_DAY) == MONTH_BEFORE_DAY;
year = UNSET;
month = UNSET;
day = UNSET;
hour = UNSET;
minute = UNSET;
second = UNSET;
timePostMeridian = false;
}
/**
* Get day of month.
*
* @return day of month
*/
int getDate() {
return day;
}
/**
* Get hour.
*
* @return hour
*/
int getHour() {
return hour;
}
/**
* Get millisecond.
*
* @return millisecond
*/
int getMillisecond() {
return milli;
}
/**
* Get minute.
*
* @return minute
*/
int getMinute() {
return minute;
}
/**
* Get month.
*
* @return month
*/
int getMonth() {
return month;
}
/**
* Get second.
*
* @return second
*/
int getSecond() {
return second;
}
/**
* Get time zone.
*
* @return time zone (<tt>null</tt> if none was specified)
*/
TimeZone getTimeZone() {
return timeZone;
}
/**
* Get year.
*
* @return year
*/
int getYear() {
return year;
}
/**
* Is day of month value set?
*
* @return <tt>true</tt> if a value has been assigned
*/
boolean isDateSet() {
return (day != UNSET);
}
/**
* Is hour value set?
*
* @return <tt>true</tt> if a value has been assigned
*/
boolean isHourSet() {
return (hour != UNSET);
}
/**
* Is millisecond value set?
*
* @return <tt>true</tt> if a value has been assigned
*/
boolean isMillisecondSet() {
return (milli != UNSET);
}
/**
* Is minute value set?
*
* @return <tt>true</tt> if a value has been assigned
*/
boolean isMinuteSet() {
return (minute != UNSET);
}
/**
* Is a numeric month placed before a numeric day of month?
*
* @return <tt>true</tt> if month is before day of month
*/
boolean isMonthBeforeDay() {
return monthBeforeDay;
}
/**
* Is month value set?
*
* @return <tt>true</tt> if a value has been assigned
*/
boolean isMonthSet() {
return (month != UNSET);
}
/**
* Is second value set?
*
* @return <tt>true</tt> if a value has been assigned
*/
boolean isSecondSet() {
return (second != UNSET);
}
/**
* Is the time post-meridian (i.e. afternoon)?
*
* @return <tt>true</tt> if time is P.M.
*/
boolean isTimePostMeridian() {
return (timePostMeridian || hour > 12);
}
/**
* Is a numeric year placed before a numeric day of month?
*
* @return <tt>true</tt> if year is before day of month
*/
boolean isYearBeforeDay() {
return yearBeforeDay;
}
/**
* Is a numeric year placed before a numeric month?
*
* @return <tt>true</tt> if year is before month
*/
boolean isYearBeforeMonth() {
return yearBeforeMonth;
}
/**
* Is year value set?
*
* @return <tt>true</tt> if a value has been assigned
*/
boolean isYearSet() {
return (year != UNSET);
}
/**
* Fill the calendar with the parsed date.
*
* @param cal
* calendar to fill
* @param ignoreChanges
* if <tt>true</tt>, throw an exception when a date like
* <tt>Sept 31</tt> is changed to <tt>Oct 1</tt>
*
* @throws CalendarParserException
* if the date cannot be set for some reason
*/
void setCalendar(GregorianCalendar cal, boolean ignoreChanges)
throws CalendarParserException {
cal.clear();
if (year != UNSET && month != UNSET && day != UNSET) {
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month - 1);
cal.set(Calendar.DATE, day);
if (!ignoreChanges) {
final int calYear = cal.get(Calendar.YEAR);
final int calMonth = cal.get(Calendar.MONTH);
final int calDay = cal.get(Calendar.DATE);
if (calYear != year || (calMonth + 1) != month || calDay != day) {
throw new CalendarParserException("Date was set to "
+ calYear + "/" + (calMonth + 1) + "/" + calDay
+ " not requested " + year + "/" + month + "/"
+ day);
}
}
}
cal.clear(Calendar.HOUR);
cal.clear(Calendar.MINUTE);
cal.clear(Calendar.SECOND);
cal.clear(Calendar.MILLISECOND);
if (hour != UNSET && minute != UNSET) {
cal.set(Calendar.HOUR, hour);
cal.set(Calendar.MINUTE, minute);
if (second != UNSET) {
cal.set(Calendar.SECOND, second);
if (milli != UNSET) {
cal.set(Calendar.MILLISECOND, milli);
}
}
if (timeZone != null) {
cal.setTimeZone(timeZone);
}
}
}
/**
* Set the day of month value.
*
* @param val
* day of month value
*
* @throws CalendarParserException
* if the value is not a valid day of month
*/
void setDate(int val) throws CalendarParserException {
if (val < 1 || val > 31) {
throw new CalendarParserException("Bad day " + val);
}
day = val;
}
/**
* Set the hour value.
*
* @param val
* hour value
*
* @throws CalendarParserException
* if the value is not a valid hour
*/
void setHour(int val) throws CalendarParserException {
final int tmpHour;
if (timePostMeridian) {
tmpHour = val + 12;
timePostMeridian = false;
} else {
tmpHour = val;
}
if (tmpHour < 0 || tmpHour > 23) {
throw new CalendarParserException("Bad hour " + val);
}
hour = tmpHour;
}
/**
* Set the millisecond value.
*
* @param val
* millisecond value
*
* @throws CalendarParserException
* if the value is not a valid millisecond
*/
void setMillisecond(int val) throws CalendarParserException {
if (val < 0 || val > 999) {
throw new CalendarParserException("Bad millisecond " + val);
}
milli = val;
}
/**
* Set the minute value.
*
* @param val
* minute value
*
* @throws CalendarParserException
* if the value is not a valid minute
*/
void setMinute(int val) throws CalendarParserException {
if (val < 0 || val > 59) {
throw new CalendarParserException("Bad minute " + val);
}
minute = val;
}
/**
* Set the month value.
*
* @param val
* month value
*
* @throws CalendarParserException
* if the value is not a valid month
*/
void setMonth(int val) throws CalendarParserException {
if (val < 1 || val > 12) {
throw new CalendarParserException("Bad month " + val);
}
month = val;
}
/**
* Set the second value.
*
* @param val
* second value
*
* @throws CalendarParserException
* if the value is not a valid second
*/
void setSecond(int val) throws CalendarParserException {
if (val < 0 || val > 59) {
throw new CalendarParserException("Bad second " + val);
}
second = val;
}
/**
* Set the AM/PM indicator value.
*
* @param val
* <tt>true</tt> if time represented is after noon
*/
void setTimePostMeridian(boolean val) {
timePostMeridian = val;
}
/**
* Set the time zone.
*
* @param tz
* time zone
*/
void setTimeZone(TimeZone tz) {
timeZone = tz;
}
/**
* Set the year value.
*
* @param val
* year value
*
* @throws CalendarParserException
* if the value is not a valid year
*/
void setYear(int val) throws CalendarParserException {
if (val < 0) {
throw new CalendarParserException("Bad year " + val);
}
year = val;
}
}
/**
* A parser for arbitrary date/time strings.
*/
public class CalendarParser {
/** bit indicating that the year comes before the month. */
public static final int YEAR_BEFORE_MONTH = ParserState.YEAR_BEFORE_MONTH;
/** bit indicating that the year comes before the day. */
public static final int YEAR_BEFORE_DAY = ParserState.YEAR_BEFORE_DAY;
/** bit indicating that the month comes before the day. */
public static final int MONTH_BEFORE_DAY = ParserState.MONTH_BEFORE_DAY;
/** bit indicating that the year comes after the month. */
public static final int YEAR_AFTER_MONTH = ParserState.YEAR_AFTER_MONTH;
/** bit indicating that the year comes after the day. */
public static final int YEAR_AFTER_DAY = ParserState.YEAR_AFTER_DAY;
/** bit indicating that the month comes after the day. */
public static final int MONTH_AFTER_DAY = ParserState.MONTH_AFTER_DAY;
/** day/month/year order. */
public static final int DD_MM_YY = YEAR_AFTER_MONTH | YEAR_AFTER_DAY
| MONTH_AFTER_DAY;
/** month/day/year order. */
public static final int MM_DD_YY = YEAR_AFTER_MONTH | YEAR_AFTER_DAY
| MONTH_BEFORE_DAY;
/** month/year/day order. */
public static final int MM_YY_DD = YEAR_AFTER_MONTH | YEAR_BEFORE_DAY
| MONTH_BEFORE_DAY;
/** day/year/month order. */
public static final int DD_YY_MM = YEAR_BEFORE_MONTH | YEAR_AFTER_DAY
| MONTH_AFTER_DAY;
/** year/day/month order. */
public static final int YY_DD_MM = YEAR_BEFORE_MONTH | YEAR_BEFORE_DAY
| MONTH_AFTER_DAY;
/** year/month/day order. */
public static final int YY_MM_DD = YEAR_BEFORE_MONTH | YEAR_BEFORE_DAY
| MONTH_BEFORE_DAY;
/** list of time zone names. */
private static final String[] zoneNames = loadTimeZoneNames();
/** Unknown place in time parsing. */
private static final int PLACE_UNKNOWN = 0;
/** Parsing hour value from time string. */
private static final int PLACE_HOUR = 1;
/** Parsing minute value from time string. */
private static final int PLACE_MINUTE = 2;
/** Parsing second value from time string. */
private static final int PLACE_SECOND = 3;
/** Parsing millisecond value from time string. */
private static final int PLACE_MILLI = 4;
/** Adjustment for two-digit years will break in 2050. */
private static final int CENTURY_OFFSET = 2000;
/** value indicating an unset variable. */
private static final int UNSET = ParserState.UNSET;
/** set to <tt>true</tt> to enable debugging. */
private static final boolean DEBUG = false;
/** list of weekday names. */
private static final String[] WEEKDAY_NAMES = { "sunday", "monday",
"tuesday", "wednesday", "thursday", "friday", "saturday", };
/** list of month abbreviations and names. */
private static final String[][] MONTHS = { { "jan", "January" },
{ "feb", "February" }, { "mar", "March" }, { "apr", "April" },
{ "may", "May" }, { "jun", "June" }, { "jul", "July" },
{ "aug", "August" }, { "sep", "September" }, { "oct", "October" },
{ "nov", "November" }, { "dec", "December" }, };
/**
* Append formatted time string to the string buffer.
*
* @param buf
* string buffer
* @param cal
* object containing time
* @param needSpace
* <tt>true</tt> if a space character should be inserted before
* any data
*/
private static final void appendTimeString(StringBuffer buf, Calendar cal, boolean needSpace) {
final int hour = cal.get(Calendar.HOUR_OF_DAY);
final int minute = cal.get(Calendar.MINUTE);
final int second = cal.get(Calendar.SECOND);
final int milli = cal.get(Calendar.MILLISECOND);
if (hour != 0 || minute != 0 || second != 0 || milli != 0) {
if (needSpace) {
buf.append(' ');
}
if (hour < 10) {
buf.append(' ');
}
buf.append(hour);
if (minute < 10) {
buf.append(":0");
} else {
buf.append(':');
}
buf.append(minute);
if (second != 0 || milli != 0) {
if (second < 10) {
buf.append(":0");
} else {
buf.append(':');
}
buf.append(second);
if (milli != 0) {
if (milli < 10) {
buf.append(".00");
} else if (milli < 100) {
buf.append(".0");
} else {
buf.append('.');
}
buf.append(milli);
}
}
}
TimeZone tz = cal.getTimeZone();
if (tz.getRawOffset() == 0) {
buf.append(" GMT");
} else {
buf.append(' ');
int offset = tz.getRawOffset() / (60 * 1000);
if (offset < 0) {
buf.append('-');
offset = -offset;
} else {
buf.append('+');
}
int hrOff = offset / 60;
if (hrOff < 10) {
buf.append('0');
}
buf.append(hrOff);
buf.append(':');
int minOff = offset % 60;
if (minOff < 10) {
buf.append('0');
}
buf.append(minOff);
}
}
/**
* Return a string representation of the order value.
*
* @param order
* order
*
* @return order string
*/
public static final String getOrderString(int order) {
switch (order) {
case DD_MM_YY:
return "DD_MM_YY";
case MM_DD_YY:
return "MM_DD_YY";
case MM_YY_DD:
return "MM_YY_DD";
case DD_YY_MM:
return "DD_YY_MM";
case YY_DD_MM:
return "YY_DD_MM";
case YY_MM_DD:
return "YY_MM_DD";
default:
break;
}
return "??" + order + "??";
}
/**
* Translate a string representation of an ordinal number to the appropriate
* numeric value.<br>
* For example, <tt>"1st"</tt> would return <tt>1</tt>, <tt>"23rd"</tt>
* would return <tt>23</tt>, etc.
*
* @param str
* ordinal string
*
* @return the numeric value of the ordinal number, or
* <tt>CalendarParser.UNSET</tt> if the supplied string is not a
* valid ordinal number.
*/
private static final int getOrdinalNumber(String str) {
final int len = (str == null ? 0 : str.length());
if (len >= 3) {
String suffix = str.substring(len - 2);
if (suffix.equalsIgnoreCase("st") || suffix.equalsIgnoreCase("nd")
|| suffix.equalsIgnoreCase("rd")
|| suffix.equalsIgnoreCase("th")) {
try {
return Integer.parseInt(str.substring(0, len - 2));
} catch (NumberFormatException nfe) {
// fall through if number was not parsed
}
}
}
return UNSET;
}
/**
* Get name of current place in time.
*
* @param place
* place ID
*
* @return place name (<tt>"hour"</tt>, <tt>"minute"</tt>, etc.
*/
private static final String getTimePlaceString(int place) {
switch (place) {
case PLACE_HOUR:
return "hour";
case PLACE_MINUTE:
return "minute";
case PLACE_SECOND:
return "second";
case PLACE_MILLI:
return "millisecond";
default:
break;
}
return "unknown";
}
/**
* Determine is the supplied string is a value weekday name.
*
* @param str
* weekday name to check
*
* @return <tt>true</tt> if the supplied string is a weekday name.
*/
private static final boolean isWeekdayName(String str) {
if (str == null || str.length() < 3) {
return false;
}
String lstr = str.toLowerCase();
for (int i = 0; i < WEEKDAY_NAMES.length; i++) {
if (lstr.startsWith(WEEKDAY_NAMES[i])
|| WEEKDAY_NAMES[i].toLowerCase().startsWith(lstr)) {
return true;
}
}
return false;
}
/**
* Load list of time zones if sun.util.calendar.ZoneInfo exists.
*
* @return <tt>null</tt> if time zone list cannot be loaded.
*/
private static final String[] loadTimeZoneNames() {
Class<?> zoneInfo;
try {
zoneInfo = Class.forName("sun.util.calendar.ZoneInfo");
} catch (ClassNotFoundException cnfe) {
return null;
}
Method method;
try {
method = zoneInfo.getDeclaredMethod("getAvailableIDs", new Class[0]);
} catch (NoSuchMethodException nsme) {
return null;
}
Object result;
try {
result = method.invoke((Object) null, (Object) null);
} catch (IllegalAccessException iae) {
return null;
} catch (InvocationTargetException ite) {
return null;
}
String[] tmpList = (String[]) result;
int numSaved = 0;
String[] finalList = null;
for (int i = 0; i < 2; i++) {
if (i > 0) {
if (numSaved == 0) {
return null;
}
finalList = new String[numSaved];
numSaved = 0;
}
for (int j = 0; j < tmpList.length; j++) {
final int len = tmpList[j].length();
if ((len > 2 && Character.isUpperCase(tmpList[j].charAt(1)))
&& (len != 7 || !Character
.isDigit(tmpList[j].charAt(3)))) {
if (finalList == null) {
numSaved++;
} else {
finalList[numSaved++] = tmpList[j];
}
if (len == 3 && tmpList[j].charAt(1) == 'S'
&& tmpList[j].charAt(2) == 'T') {
if (finalList == null) {
numSaved++;
} else {
StringBuffer dst = new StringBuffer();
dst.append(tmpList[j].charAt(0));
dst.append("DT");
finalList[numSaved++] = dst.toString();
}
}
}
}
}
return finalList;
}
/**
* Convert the supplied month name to its numeric representation. <br>
* For example, <tt>"January"</tt> (or any substring) would return
* <tt>1</tt> and <tt>"December"</tt> would return <tt>12</tt>.
*
* @param str
* month name
*
* @return the numeric month, or <tt>CalendarParser.UNSET</tt> if the
* supplied string is not a valid month name.
*/
public static int monthNameToNumber(String str) {
if (str != null && str.length() >= 3) {
String lstr = str.toLowerCase();
for (int i = 0; i < MONTHS.length; i++) {
if (lstr.startsWith(MONTHS[i][0])
|| MONTHS[i][1].toLowerCase().startsWith(lstr)) {
return i + 1;
}
}
}
return UNSET;
}
/**
* Extract a date from a string, defaulting to YY-MM-DD order for
* all-numeric strings.
*
* @param dateStr
* date string
*
* @return parsed date
*
* @throws CalendarParserException
* if there was a problem parsing the string.
*/
public static final Calendar parse(String dateStr)
throws CalendarParserException {
return parse(dateStr, YY_MM_DD);
}
/**
* Extract a date from a string.
*
* @param dateStr
* date string
* @param order
* order in which pieces of numeric strings are assigned (should
* be one of <tt>YY_MM_DD</tt>, <tt>MM_DD_YY</tt>, etc.)
*
* @return parsed date
*
* @throws CalendarParserException
* if there was a problem parsing the string.
*/
public static final Calendar parse(String dateStr, int order)
throws CalendarParserException {
return parse(dateStr, order, true);
}
/**
* Extract a date from a string.
*
* @param dateStr
* date string
* @param order
* order in which pieces of numeric strings are assigned (should
* be one of <tt>YY_MM_DD</tt>, <tt>MM_DD_YY</tt>, etc.)
* @param ignoreChanges
* if <tt>true</tt>, ignore date changes such as <tt>Feb 31</tt>
* being changed to <tt>Mar 3</tt>.
*
* @return parsed date
*
* @throws CalendarParserException
* if there was a problem parsing the string.
*/
public static final Calendar parse(String dateStr, int order,
boolean ignoreChanges) throws CalendarParserException {
if (dateStr == null) {
return null;
}
return parseString(dateStr, order, ignoreChanges);
}
/**
* Parse a non-numeric token from the date string.
*
* @param dateStr
* full date string
* @param state
* parser state
* @param token
* string being parsed
*
* @throws CalendarParserException
* if there was a problem parsing the token
*/
private static final void parseNonNumericToken(String dateStr,
ParserState state, String token) throws CalendarParserException {
// if it's a weekday name, ignore it
if (isWeekdayName(token)) {
if (DEBUG) {
System.err.println("IGNORE \"" + token + "\" (weekday)");
}
return;
}
// if it looks like a time, deal with it
if (token.indexOf(':') > 0) {
final char firstChar = token.charAt(0);
if (Character.isDigit(firstChar)) {
parseTime(dateStr, state, token);
return;
} else if (firstChar == '+' || firstChar == '-') {
parseTimeZoneOffset(dateStr, state, token);
return;
} else {
throw new CalendarParserException("Unrecognized time \""
+ token + "\" in date \"" + dateStr + "\"");
}
}
// try to parse month name
int tmpMon = monthNameToNumber(token);
// if token isn't a month name ... PUKE
if (tmpMon != UNSET) {
// if month number is unset, set it and move on
if (!state.isMonthSet()) {
state.setMonth(tmpMon);
if (DEBUG) {
System.err.println("MONTH="
+ MONTHS[state.getMonth() - 1][0] + " (" + token
+ ") name");
}
return;
}
// try to move the current month value to the year or day
if (!state.isYearSet()) {
if (state.isDateSet() || state.isYearBeforeDay()) {
state.setYear(state.getMonth());
state.setMonth(tmpMon);
if (DEBUG) {
System.err.println("MONTH="
+ MONTHS[state.getMonth() - 1][0] + ", YEAR="
+ state.getYear() + " (" + token
+ ") name swap");
}
} else {
state.setDate(state.getMonth());
state.setMonth(tmpMon);
if (DEBUG) {
System.err.println("MONTH="
+ MONTHS[state.getMonth() - 1][0] + ", DAY="
+ state.getDate() + " (" + token
+ ") name swap");
}
}
return;
}
// year was already set, so try to move month value to day
if (!state.isDateSet()) {
state.setDate(state.getMonth());
state.setMonth(tmpMon);
if (DEBUG) {
System.err.println("MONTH="
+ MONTHS[state.getMonth() - 1][0] + ", DAY="
+ state.getDate() + " (" + token + ") name swap 2");
}
return;
}
// can't move month value to year or day ... PUKE
if (DEBUG) {
System.err.println("*** Too many numbers in \"" + dateStr
+ "\"");
}
throw new CalendarParserException("Too many numbers in"
+ " date \"" + dateStr + "\"");
}
// maybe it's an ordinal number list "1st", "23rd", etc.
int val = getOrdinalNumber(token);
if (val == UNSET) {
final String lToken = token.toLowerCase();
if (lToken.equals("am")) {
// don't need to do anything
if (DEBUG) {
System.err.println("TIME=AM (" + token + ")");
}
return;
} else if (lToken.equals("pm")) {
if (!state.isHourSet()) {
state.setTimePostMeridian(true);
} else {
state.setHour(state.getHour() + 12);
}
if (DEBUG) {
System.err.println("TIME=PM (" + token + ")");
}
return;
} else if (zoneNames != null) {
// maybe it's a time zone name
for (int z = 0; z < zoneNames.length; z++) {
if (token.equalsIgnoreCase(zoneNames[z])) {
TimeZone tz = TimeZone.getTimeZone(token);
if (tz.getRawOffset() != 0 || lToken.equals("gmt")) {
state.setTimeZone(tz);
return;
}
}
}
}
if (DEBUG) {
System.err.println("*** Unknown string \"" + token + "\"");
}
throw new CalendarParserException("Unknown string \"" + token
+ "\" in date \"" + dateStr + "\"");
}
// if no day yet, we're done
if (!state.isDateSet()) {
state.setDate(val);
if (DEBUG) {
System.err.println("DAY=" + state.getDate() + " (" + token
+ ") ord");
}
return;
}
// if either year or month is unset...
if (!state.isYearSet() || !state.isMonthSet()) {
// if day can't be a month, shift it into year
if (state.getDate() > 12) {
if (!state.isYearSet()) {
state.setYear(state.getDate());
state.setDate(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + ", DAY="
+ state.getDate() + " (" + token
+ ") ord>12 swap");
}
return;
}
// year was already set, maybe we can move it to month
if (state.getYear() <= 12) {
state.setMonth(state.getYear());
state.setYear(state.getDate());
state.setDate(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear()
+ ", MONTH=" + state.getMonth() + ", DAY="
+ state.getDate() + " (" + token
+ ") ord megaswap");
}
return;
}
// try to shift day value to either year or month
} else if (!state.isYearSet()) {
if (!state.isMonthSet() && !state.isYearBeforeMonth()) {
state.setMonth(state.getDate());
state.setDate(val);
if (DEBUG) {
System.err.println("MONTH=" + state.getMonth()
+ ", DAY=" + state.getDate() + " (" + token
+ ") ord swap");
}
return;
}
state.setYear(state.getDate());
state.setDate(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + ", DAY="
+ state.getDate() + " (" + token + ") ord swap");
}
return;
// year was set, so we know month is unset
} else {
state.setMonth(state.getDate());
state.setDate(val);
if (DEBUG) {
System.err.println("MONTH=" + state.getMonth() + ", DAY="
+ state.getDate() + " (" + token + ") ord swap#2");
}
return;
}
}
if (DEBUG) {
System.err.println("*** Extra number \"" + token + "\"");
}
throw new CalendarParserException("Cannot assign ordinal in \""
+ dateStr + "\"");
}
/**
* Split a large numeric value into a year/month/date values.
*
* @param dateStr
* full date string
* @param state
* parser state
* @param val
* numeric value to use
*
* @throws CalendarParserException
* if there was a problem splitting the value
*/
private static final void parseNumericBlob(String dateStr,
ParserState state, int val) throws CalendarParserException {
if (state.isYearSet() || state.isMonthSet() || state.isDateSet()) {
throw new CalendarParserException("Unknown value " + val
+ " in date \"" + dateStr + "\"");
}
int tmpVal = val;
if (state.isYearBeforeMonth()) {
if (state.isYearBeforeDay()) {
final int last = tmpVal % 100;
tmpVal /= 100;
final int middle = tmpVal % 100;
tmpVal /= 100;
state.setYear(tmpVal);
if (state.isMonthBeforeDay()) {
// YYYYMMDD
state.setMonth(middle);
state.setDate(last);
} else {
// YYYYDDMM
state.setDate(middle);
state.setMonth(last);
}
} else {
// DDYYYYMM
state.setMonth(tmpVal % 100);
tmpVal /= 100;
state.setYear(tmpVal % 10000);
tmpVal /= 10000;
state.setDate(tmpVal);
}
} else if (state.isYearBeforeDay()) {
// MMYYYYDD
state.setDate(tmpVal % 100);
tmpVal /= 100;
state.setYear(tmpVal % 10000);
tmpVal /= 10000;
state.setMonth(tmpVal);
} else {
state.setYear(tmpVal % 10000);
tmpVal /= 10000;
final int middle = tmpVal % 100;
tmpVal /= 100;
if (state.isMonthBeforeDay()) {
// MMDDYYYY
state.setDate(middle);
state.setMonth(tmpVal);
} else {
// DDMMYYYY
state.setDate(tmpVal);
state.setMonth(middle);
}
}
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + " MONTH="
+ state.getMonth() + " DAY=" + state.getDate() + " (" + val
+ ") blob");
}
}
/**
* Use a numeric token from the date string.
*
* @param dateStr
* full date string
* @param state
* parser state
* @param val
* numeric value to use
*
* @throws CalendarParserException
* if there was a problem parsing the token
*/
private static final void parseNumericToken(String dateStr,
ParserState state, int val) throws CalendarParserException {
// puke if we've already found 3 values
if (state.isYearSet() && state.isMonthSet() && state.isDateSet()) {
if (DEBUG) {
System.err.println("*** Extra number " + val);
}
throw new CalendarParserException("Extra value \"" + val
+ "\" in date \"" + dateStr + "\"");
}
// puke up on negative numbers
if (val < 0) {
if (DEBUG) {
System.err.println("*** Negative number " + val);
}
throw new CalendarParserException("Found negative number in"
+ " date \"" + dateStr + "\"");
}
if (val > 9999) {
parseNumericBlob(dateStr, state, val);
return;
}
// deal with obvious years first
if (val > 31) {
// if no year yet, assign it and move on
if (!state.isYearSet()) {
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + " (" + val
+ ") >31");
}
return;
}
// puke if the year value can't possibly be a day or month
if (state.getYear() > 31) {
if (DEBUG) {
System.err.println("*** Ambiguous year " + state.getYear()
+ " vs. " + val);
}
String errMsg = "Couldn't decide on year number in date \""
+ dateStr + "\"";
throw new CalendarParserException(errMsg);
}
// if the year value can't be a month...
if (state.getYear() > 12) {
// if day isn't set, use old val as day and new val as year
if (!state.isDateSet()) {
state.setDate(state.getYear());
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + ", DAY="
+ state.getDate() + " (" + val + ") >31 swap");
}
return;
}
// NOTE: both day and year are set
// try using day value as month so we can move year
// value to day and use new value as year
if (state.getDate() <= 12) {
state.setMonth(state.getDate());
state.setDate(state.getYear());
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear()
+ ", MONTH=" + state.getMonth() + ", DAY="
+ state.getDate() + " (" + val
+ ") >31 megaswap");
}
return;
}
if (DEBUG) {
System.err.println("*** Unassignable year-like"
+ " number " + val);
}
throw new CalendarParserException("Bad number " + val
+ " found in date \"" + dateStr + "\"");
}
// NOTE: year <= 12
if (!state.isDateSet() && !state.isMonthSet()) {
if (state.isMonthBeforeDay()) {
state.setMonth(state.getYear());
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear()
+ ", MONTH=" + state.getMonth() + " (" + val
+ ") >31 swap");
}
} else {
state.setDate(state.getYear());
state.setYear(val);
if (DEBUG) {
System.err
.println("YEAR=" + state.getYear() + ", DAY="
+ state.getDate() + " (" + val
+ ") >31 swap#2");
}
}
return;
}
if (!state.isDateSet()) {
state.setDate(state.getYear());
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + ", DAY="
+ state.getDate() + " (" + val + ") >31 day swap");
}
return;
}
// assume this was a mishandled month
state.setMonth(state.getYear());
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + ", MONTH="
+ state.getMonth() + " (" + val + ") >31 mon swap");
}
return;
}
// now deal with non-month values
if (val > 12) {
// if no year value yet...
if (!state.isYearSet()) {
// if the day is set, or if we assign year before day...
if (state.isDateSet() || state.isYearBeforeDay()) {
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + " ("
+ val + ") >12");
}
} else {
state.setDate(val);
if (DEBUG) {
System.err.println("DAY=" + state.getDate() + " ("
+ val + ") >12");
}
}
return;
}
// NOTE: year is set
// if no day value yet, assign it and move on
if (!state.isDateSet()) {
state.setDate(val);
if (DEBUG) {
System.err.println("DAY=" + state.getDate() + " (" + val
+ ") >12 !yr");
}
return;
}
// NOTE: both year and day are set
// XXX see if we can shift things around
if (DEBUG) {
System.err.println("*** Unassignable year/day number " + val);
}
throw new CalendarParserException("Bad number " + val
+ " found in date \"" + dateStr + "\"");
}
// NOTE: ambiguous value
// if year is set, this must be either the month or day
if (state.isYearSet()) {
if (state.isMonthSet()
|| (!state.isDateSet() && !state.isMonthBeforeDay())) {
state.setDate(val);
if (DEBUG) {
System.err.println("DAY=" + state.getDate() + " (" + val
+ ") ambig!yr");
}
} else {
state.setMonth(val);
if (DEBUG) {
System.err.println("MONTH=" + state.getMonth() + " (" + val
+ ") ambig!yr");
}
}
return;
}
// NOTE: year not set
// if month is set, this must be either the year or day
if (state.isMonthSet()) {
if (state.isDateSet() || state.isYearBeforeDay()) {
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + " (" + val
+ ") ambig!mo");
}
} else {
state.setDate(val);
if (DEBUG) {
System.err.println("DAY=" + state.getDate() + " (" + val
+ ") ambig!mo");
}
}
return;
}
// NOTE: neither year nor month is set
// if day is set, this must be either the year or month
if (state.isDateSet()) {
if (state.isYearBeforeMonth()) {
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + " (" + val
+ ") ambig!day");
}
} else {
state.setMonth(val);
if (DEBUG) {
System.err.println("MONTH=" + state.getMonth() + " (" + val
+ ") ambig!day");
}
}
return;
}
// NOTE: no value set yet
if (state.isYearBeforeMonth()) {
if (state.isYearBeforeDay()) {
state.setYear(val);
if (DEBUG) {
System.err.println("YEAR=" + state.getYear() + " (" + val
+ ") YM|YD");
}
} else {
state.setDate(val);
if (DEBUG) {
System.err.println("DAY=" + state.getDate() + " (" + val
+ ") YM!YD");
}
}
} else if (state.isMonthBeforeDay()) {
state.setMonth(val);
if (DEBUG) {
System.err.println("MONTH=" + state.getMonth() + " (" + val
+ ") !YM|MD");
}
} else {
state.setDate(val);
if (DEBUG) {
System.err.println("DAY=" + state.getDate() + " (" + val
+ ") !YM!MD");
}
}
}
/**
* Extract a date from the supplied string.
*
* @param dateStr
* string to parse
* @param order
* year/month/day order (YY_MM_DD, MM_DD_YY, etc.)
* @param ignoreChanges
* if <tt>true</tt>, ignore date changes such as <tt>Feb 31</tt>
* being changed to <tt>Mar 3</tt>.
*
* @return parsed date
*
* @throws CalendarParserException
* if no valid date was found.
*/
private static final Calendar parseString(String dateStr, int order,
boolean ignoreChanges) throws CalendarParserException {
ParserState state = new ParserState(order);
Pattern pat = Pattern.compile("([\\s/,]+|(\\S)\\-)");
Matcher matcher = pat.matcher(dateStr);
int prevEnd = 0;
while (prevEnd < dateStr.length()) {
String token;
if (!matcher.find()) {
token = dateStr.substring(prevEnd);
prevEnd = dateStr.length();
} else {
final boolean isMinus = (matcher.groupCount() == 2 && matcher
.group(2) != null);
if (!isMinus) {
token = dateStr.substring(prevEnd, matcher.start());
} else {
token = dateStr.substring(prevEnd, matcher.start())
+ matcher.group(2);
}
prevEnd = matcher.end();
}
if (DEBUG) {
System.err.println("YEAR "
+ (state.isYearSet() ? Integer
.toString(state.getYear()) : "UNSET")
+ ", MONTH "
+ (state.isMonthSet() ? Integer.toString(state
.getMonth()) : "UNSET")
+ ", DAY "
+ (state.isDateSet() ? Integer
.toString(state.getDate()) : "UNSET")
+ ", TOKEN=\"" + token + "\"");
}
// try to decipher next token as a number
try {
final int val = Integer.parseInt(token);
parseNumericToken(dateStr, state, val);
} catch (NumberFormatException e) {
parseNonNumericToken(dateStr, state, token);
}
}
// before checking for errors, check for missing year
if (!state.isDateSet() && state.getYear() <= 31) {
int tmp = state.getDate();
state.setDate(state.getYear());
state.setYear(tmp);
}
if (!state.isDateSet()) {
if (!state.isMonthSet()) {
if (!state.isYearSet()) {
throw new CalendarParserException("No date found in \""
+ dateStr + "\"");
} else {
throw new CalendarParserException("Day and month missing"
+ " from \"" + dateStr + "\"");
}
} else {
throw new CalendarParserException("Day missing from \""
+ dateStr + "\"");
}
} else if (!state.isMonthSet()) {
if (!state.isYearSet()) {
throw new CalendarParserException("Year and month missing"
+ " from \"" + dateStr + "\"");
} else {
throw new CalendarParserException("Month missing from \""
+ dateStr + "\"");
}
} else if (!state.isYearSet()) {
throw new CalendarParserException("Year missing from \"" + dateStr
+ "\"");
}
final int tmpYear = state.getYear();
if (tmpYear < 50) {
state.setYear(tmpYear + CENTURY_OFFSET);
} else if (tmpYear < 100) {
state.setYear(tmpYear + (CENTURY_OFFSET - 100));
}
GregorianCalendar cal = new GregorianCalendar();
state.setCalendar(cal, ignoreChanges);
if (DEBUG) {
System.err.println("Y" + state.getYear() + " M" + state.getMonth()
+ " D" + state.getDate() + " H" + state.getHour() + " M"
+ state.getMinute() + " S" + state.getSecond() + " L"
+ state.getMillisecond() + " => " + toString(cal));
}
return cal;
}
/**
* Parse a time string.
*
* @param dateStr
* full date string
* @param state
* parser state
* @param timeStr
* string containing colon-separated time
*
* @throws CalendarParserException
* if there is a problem with the time
*/
private static final void parseTime(String dateStr, ParserState state,
String timeStr) throws CalendarParserException {
int place = PLACE_HOUR;
String tmpTime;
final char lastChar = timeStr.charAt(timeStr.length() - 1);
if (lastChar != 'm' && lastChar != 'M') {
if (DEBUG) {
System.err.println("No AM/PM in \"" + timeStr + "\" (time)");
}
tmpTime = timeStr;
} else {
final char preLast = timeStr.charAt(timeStr.length() - 2);
if (preLast == 'a' || preLast == 'A') {
state.setTimePostMeridian(false);
} else if (preLast == 'p' || preLast == 'P') {
state.setTimePostMeridian(true);
} else {
throw new CalendarParserException("Bad time \"" + timeStr
+ "\" in date \"" + dateStr + "\"");
}
tmpTime = timeStr.substring(0, timeStr.length() - 2);
if (DEBUG) {
System.err.println("Found "
+ (state.isTimePostMeridian() ? "PM" : "AM")
+ ". now \"" + tmpTime + "\" (time)");
}
}
String[] tList = tmpTime.split("[:\\.]");
for (int i = 0; i < tList.length; i++) {
String token = tList[i];
if (DEBUG) {
System.err.println("HOUR "
+ (state.isHourSet() ? Integer
.toString(state.getHour()) : "UNSET")
+ ", MINUTE "
+ (state.isMinuteSet() ? Integer.toString(state
.getMinute()) : "UNSET")
+ ", SECOND "
+ (state.isSecondSet() ? Integer.toString(state
.getSecond()) : "UNSET")
+ ", MILLISECOND "
+ (state.isMillisecondSet() ? Integer.toString(state
.getMillisecond()) : "UNSET") + ", TOKEN=\""
+ token + "\"");
}
final int val;
try {
val = Integer.parseInt(token);
} catch (NumberFormatException nfe) {
throw new CalendarParserException("Bad "
+ getTimePlaceString(place) + " string \"" + token
+ "\" in \"" + dateStr + "\"");
}
switch (place) {
case PLACE_HOUR:
try {
state.setHour(val);
} catch (CalendarParserException dfe) {
throw new CalendarParserException(dfe.getMessage()
+ " in \"" + dateStr + "\"");
}
if (DEBUG) {
System.err.println("Set hour to " + val);
}
place = PLACE_MINUTE;
break;
case PLACE_MINUTE:
try {
state.setMinute(val);
} catch (CalendarParserException dfe) {
throw new CalendarParserException(dfe.getMessage()
+ " in \"" + dateStr + "\"");
}
if (DEBUG) {
System.err.println("Set minute to " + val);
}
place = PLACE_SECOND;
break;
case PLACE_SECOND:
try {
state.setSecond(val);
} catch (CalendarParserException dfe) {
throw new CalendarParserException(dfe.getMessage()
+ " in \"" + dateStr + "\"");
}
if (DEBUG) {
System.err.println("Set second to " + val);
}
place = PLACE_MILLI;
break;
case PLACE_MILLI:
try {
state.setMillisecond(val);
} catch (CalendarParserException dfe) {
throw new CalendarParserException(dfe.getMessage()
+ " in \"" + dateStr + "\"");
}
if (DEBUG) {
System.err.println("Set millisecond to " + val);
}
place = PLACE_UNKNOWN;
break;
default:
throw new CalendarParserException("Unexpected place value "
+ place);
}
}
}
/**
* Parse a time zone offset string.
*
* @param dateStr
* full date string
* @param state
* parser state
* @param zoneStr
* string containing colon-separated time zone offset
*
* @throws CalendarParserException
* if there is a problem with the time
*/
private static final void parseTimeZoneOffset(String dateStr,
ParserState state, String zoneStr) throws CalendarParserException {
int place = PLACE_HOUR;
final boolean isNegative = (zoneStr.charAt(0) == '-');
if (!isNegative && zoneStr.charAt(0) != '+') {
throw new CalendarParserException("Bad time zone offset \""
+ zoneStr + "\" in date \"" + dateStr + "\"");
}
int hour = UNSET;
int minute = UNSET;
String[] tList = zoneStr.substring(1).split(":");
for (int i = 0; i < tList.length; i++) {
String token = tList[i];
if (DEBUG) {
System.err
.println("TZ_HOUR "
+ (hour != UNSET ? Integer.toString(hour)
: "UNSET")
+ ", TZ_MINUTE "
+ (minute != UNSET ? Integer.toString(minute)
: "UNSET") + ", TOKEN=\"" + token
+ "\"");
}
final int val;
try {
val = Integer.parseInt(token);
} catch (NumberFormatException nfe) {
throw new CalendarParserException("Bad time zone "
+ getTimePlaceString(place) + " offset \"" + token
+ "\" in \"" + dateStr + "\"");
}
switch (place) {
case PLACE_HOUR:
hour = val;
if (DEBUG) {
System.err.println("Set time zone offset hour to " + val);
}
place = PLACE_MINUTE;
break;
case PLACE_MINUTE:
minute = val;
if (DEBUG) {
System.err.println("Set time zone offset minute to " + val);
}
place = PLACE_UNKNOWN;
break;
default:
throw new CalendarParserException("Unexpected place value "
+ place);
}
}
String customID = "GMT" + (isNegative ? "-" : "+") + hour + ":"
+ (minute < 10 ? "0" : "") + minute;
state.setTimeZone(TimeZone.getTimeZone(customID));
}
/**
* Return a printable representation of the date.
*
* @param cal
* calendar to convert to a string
*
* @return a printable string.
*/
public static final String prettyString(Calendar cal) {
if (cal == null) {
return null;
}
final int calYear = cal.get(Calendar.YEAR);
final int calMonth = cal.get(Calendar.MONTH);
final int calDay = cal.get(Calendar.DATE);
boolean needSpace = false;
StringBuffer buf = new StringBuffer();
if (calMonth >= 0 && calMonth < MONTHS.length) {
if (needSpace) {
buf.append(' ');
}
buf.append(MONTHS[calMonth][1]);
needSpace = true;
}
if (calDay > 0) {
if (needSpace) {
buf.append(' ');
}
buf.append(calDay);
if (calYear > UNSET) {
buf.append(',');
}
needSpace = true;
}
if (calYear > UNSET) {
if (needSpace) {
buf.append(' ');
}
buf.append(calYear);
}
appendTimeString(buf, cal, needSpace);
return buf.toString();
}
/**
* Return a basic representation of the string.
*
* @param cal
* calendar to convert to a string
*
* @return the basic string.
*/
public static final String toString(Calendar cal) {
if (cal == null) {
return null;
}
final int calYear = cal.get(Calendar.YEAR);
final int calMonth = cal.get(Calendar.MONTH);
final int calDay = cal.get(Calendar.DATE);
boolean needSpace = false;
StringBuffer buf = new StringBuffer();
if (calDay > 0) {
if (needSpace) {
buf.append(' ');
}
buf.append(calDay);
needSpace = true;
}
if (calMonth >= 0 && calMonth < MONTHS.length) {
if (needSpace) {
buf.append(' ');
}
buf.append(MONTHS[calMonth][1].substring(0, 3));
needSpace = true;
}
if (calYear > UNSET) {
if (needSpace) {
buf.append(' ');
}
buf.append(calYear);
}
appendTimeString(buf, cal, needSpace);
return buf.toString();
}
/**
* Return a string representation of the date suitable for use in an SQL
* statement.
*
* @param cal
* calendar to convert to a string
*
* @return the SQL-friendly string.
*/
public static final String toSQLString(Calendar cal) {
if (cal == null) {
return null;
}
final int calYear = cal.get(Calendar.YEAR);
final int calMonth = cal.get(Calendar.MONTH);
final int calDay = cal.get(Calendar.DATE);
StringBuffer buf = new StringBuffer();
buf.append(calYear);
buf.append('-');
if ((calMonth + 1) < 10) {
buf.append('0');
}
buf.append(calMonth + 1);
buf.append('-');
if (calDay < 10) {
buf.append('0');
}
buf.append(calDay);
appendTimeString(buf, cal, true);
return buf.toString();
}
}