diff options
Diffstat (limited to 'libjava/java/text/SimpleDateFormat.java')
-rw-r--r-- | libjava/java/text/SimpleDateFormat.java | 1237 |
1 files changed, 0 insertions, 1237 deletions
diff --git a/libjava/java/text/SimpleDateFormat.java b/libjava/java/text/SimpleDateFormat.java deleted file mode 100644 index 8a93b9f256d..00000000000 --- a/libjava/java/text/SimpleDateFormat.java +++ /dev/null @@ -1,1237 +0,0 @@ -/* SimpleDateFormat.java -- A class for parsing/formating simple - date constructs - Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005 - Free Software Foundation, Inc. - -This file is part of GNU Classpath. - -GNU Classpath is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2, or (at your option) -any later version. - -GNU Classpath is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with GNU Classpath; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is -making a combined work based on this library. Thus, the terms and -conditions of the GNU General Public License cover the whole -combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent -modules, and to copy and distribute the resulting executable under -terms of your choice, provided that you also meet, for each linked -independent module, the terms and conditions of the license of that -module. An independent module is a module which is not derived from -or based on this library. If you modify this library, you may extend -this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this -exception statement from your version. */ - - -package java.text; - -import gnu.java.text.AttributedFormatBuffer; -import gnu.java.text.FormatBuffer; -import gnu.java.text.FormatCharacterIterator; -import gnu.java.text.StringFormatBuffer; - -import java.io.IOException; -import java.io.InvalidObjectException; -import java.io.ObjectInputStream; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.Locale; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * SimpleDateFormat provides convenient methods for parsing and formatting - * dates using Gregorian calendars (see java.util.GregorianCalendar). - */ -public class SimpleDateFormat extends DateFormat -{ - /** - * This class is used by <code>SimpleDateFormat</code> as a - * compiled representation of a format string. The field - * ID, size, and character used are stored for each sequence - * of pattern characters. - */ - private class CompiledField - { - /** - * The ID of the field within the local pattern characters, - */ - private int field; - - /** - * The size of the character sequence. - */ - private int size; - - /** - * The character used. - */ - private char character; - - /** - * Constructs a compiled field using the - * the given field ID, size and character - * values. - * - * @param f the field ID. - * @param s the size of the field. - * @param c the character used. - */ - public CompiledField(int f, int s, char c) - { - field = f; - size = s; - character = c; - } - - /** - * Retrieves the ID of the field relative to - * the local pattern characters. - */ - public int getField() - { - return field; - } - - /** - * Retrieves the size of the character sequence. - */ - public int getSize() - { - return size; - } - - /** - * Retrieves the character used in the sequence. - */ - public char getCharacter() - { - return character; - } - - /** - * Returns a <code>String</code> representation - * of the compiled field, primarily for debugging - * purposes. - * - * @return a <code>String</code> representation. - */ - public String toString() - { - StringBuffer builder; - - builder = new StringBuffer(getClass().getName()); - builder.append("[field="); - builder.append(field); - builder.append(", size="); - builder.append(size); - builder.append(", character="); - builder.append(character); - builder.append("]"); - - return builder.toString(); - } - } - - /** - * A list of <code>CompiledField</code>s, - * representing the compiled version of the pattern. - * - * @see CompiledField - * @serial Ignored. - */ - private transient ArrayList tokens; - - /** - * The localised data used in formatting, - * such as the day and month names in the local - * language, and the localized pattern characters. - * - * @see DateFormatSymbols - * @serial The localisation data. May not be null. - */ - private DateFormatSymbols formatData; - - /** - * The date representing the start of the century - * used for interpreting two digit years. For - * example, 24/10/2004 would cause two digit - * years to be interpreted as representing - * the years between 2004 and 2104. - * - * @see get2DigitYearStart() - * @see set2DigitYearStart(java.util.Date) - * @see Date - * @serial The start date of the century for parsing two digit years. - * May not be null. - */ - private Date defaultCenturyStart; - - /** - * The year at which interpretation of two - * digit years starts. - * - * @see get2DigitYearStart() - * @see set2DigitYearStart(java.util.Date) - * @serial Ignored. - */ - private transient int defaultCentury; - - /** - * The non-localized pattern string. This - * only ever contains the pattern characters - * stored in standardChars. Localized patterns - * are translated to this form. - * - * @see applyPattern(String) - * @see applyLocalizedPattern(String) - * @see toPattern() - * @see toLocalizedPattern() - * @serial The non-localized pattern string. May not be null. - */ - private String pattern; - - /** - * The version of serialized data used by this class. - * Version 0 only includes the pattern and formatting - * data. Version 1 adds the start date for interpreting - * two digit years. - * - * @serial This specifies the version of the data being serialized. - * Version 0 (or no version) specifies just <code>pattern</code> - * and <code>formatData</code>. Version 1 adds - * the <code>defaultCenturyStart</code>. This implementation - * always writes out version 1 data. - */ - private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier - - /** - * For compatability. - */ - private static final long serialVersionUID = 4774881970558875024L; - - // This string is specified in the root of the CLDR. We set it here - // rather than doing a DateFormatSymbols(Locale.US).getLocalPatternChars() - // since someone could theoretically change those values (though unlikely). - private static final String standardChars = "GyMdkHmsSEDFwWahKzYeugAZ"; - - /** - * Reads the serialized version of this object. - * If the serialized data is only version 0, - * then the date for the start of the century - * for interpreting two digit years is computed. - * The pattern is parsed and compiled following the process - * of reading in the serialized data. - * - * @param stream the object stream to read the data from. - * @throws IOException if an I/O error occurs. - * @throws ClassNotFoundException if the class of the serialized data - * could not be found. - * @throws InvalidObjectException if the pattern is invalid. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException - { - stream.defaultReadObject(); - if (serialVersionOnStream < 1) - { - computeCenturyStart (); - serialVersionOnStream = 1; - } - else - // Ensure that defaultCentury gets set. - set2DigitYearStart(defaultCenturyStart); - - // Set up items normally taken care of by the constructor. - tokens = new ArrayList(); - try - { - compileFormat(pattern); - } - catch (IllegalArgumentException e) - { - throw new InvalidObjectException("The stream pattern was invalid."); - } - } - - /** - * Compiles the supplied non-localized pattern into a form - * from which formatting and parsing can be performed. - * This also detects errors in the pattern, which will - * be raised on later use of the compiled data. - * - * @param pattern the non-localized pattern to compile. - * @throws IllegalArgumentException if the pattern is invalid. - */ - private void compileFormat(String pattern) - { - // Any alphabetical characters are treated as pattern characters - // unless enclosed in single quotes. - - char thisChar; - int pos; - int field; - CompiledField current = null; - - for (int i=0; i<pattern.length(); i++) { - thisChar = pattern.charAt(i); - field = standardChars.indexOf(thisChar); - if (field == -1) { - current = null; - if ((thisChar >= 'A' && thisChar <= 'Z') - || (thisChar >= 'a' && thisChar <= 'z')) { - // Not a valid letter - throw new IllegalArgumentException("Invalid letter " + thisChar + - "encountered at character " + i - + "."); - } else if (thisChar == '\'') { - // Quoted text section; skip to next single quote - pos = pattern.indexOf('\'',i+1); - if (pos == -1) { - throw new IllegalArgumentException("Quotes starting at character " - + i + " not closed."); - } - if ((pos+1 < pattern.length()) && (pattern.charAt(pos+1) == '\'')) { - tokens.add(pattern.substring(i+1,pos+1)); - } else { - tokens.add(pattern.substring(i+1,pos)); - } - i = pos; - } else { - // A special character - tokens.add(new Character(thisChar)); - } - } else { - // A valid field - if ((current != null) && (field == current.field)) { - current.size++; - } else { - current = new CompiledField(field,1,thisChar); - tokens.add(current); - } - } - } - } - - /** - * Returns a string representation of this - * class. - * - * @return a string representation of the <code>SimpleDateFormat</code> - * instance. - */ - public String toString() - { - StringBuffer output = new StringBuffer(getClass().getName()); - output.append("[tokens="); - output.append(tokens); - output.append(", formatData="); - output.append(formatData); - output.append(", defaultCenturyStart="); - output.append(defaultCenturyStart); - output.append(", defaultCentury="); - output.append(defaultCentury); - output.append(", pattern="); - output.append(pattern); - output.append(", serialVersionOnStream="); - output.append(serialVersionOnStream); - output.append(", standardChars="); - output.append(standardChars); - output.append("]"); - return output.toString(); - } - - /** - * Constructs a SimpleDateFormat using the default pattern for - * the default locale. - */ - public SimpleDateFormat() - { - /* - * There does not appear to be a standard API for determining - * what the default pattern for a locale is, so use package-scope - * variables in DateFormatSymbols to encapsulate this. - */ - super(); - Locale locale = Locale.getDefault(); - calendar = new GregorianCalendar(locale); - computeCenturyStart(); - tokens = new ArrayList(); - formatData = new DateFormatSymbols(locale); - pattern = (formatData.dateFormats[DEFAULT] + ' ' - + formatData.timeFormats[DEFAULT]); - compileFormat(pattern); - numberFormat = NumberFormat.getInstance(locale); - numberFormat.setGroupingUsed (false); - numberFormat.setParseIntegerOnly (true); - numberFormat.setMaximumFractionDigits (0); - } - - /** - * Creates a date formatter using the specified non-localized pattern, - * with the default DateFormatSymbols for the default locale. - * - * @param pattern the pattern to use. - * @throws NullPointerException if the pattern is null. - * @throws IllegalArgumentException if the pattern is invalid. - */ - public SimpleDateFormat(String pattern) - { - this(pattern, Locale.getDefault()); - } - - /** - * Creates a date formatter using the specified non-localized pattern, - * with the default DateFormatSymbols for the given locale. - * - * @param pattern the non-localized pattern to use. - * @param locale the locale to use for the formatting symbols. - * @throws NullPointerException if the pattern is null. - * @throws IllegalArgumentException if the pattern is invalid. - */ - public SimpleDateFormat(String pattern, Locale locale) - { - super(); - calendar = new GregorianCalendar(locale); - computeCenturyStart(); - tokens = new ArrayList(); - formatData = new DateFormatSymbols(locale); - compileFormat(pattern); - this.pattern = pattern; - numberFormat = NumberFormat.getInstance(locale); - numberFormat.setGroupingUsed (false); - numberFormat.setParseIntegerOnly (true); - numberFormat.setMaximumFractionDigits (0); - } - - /** - * Creates a date formatter using the specified non-localized - * pattern. The specified DateFormatSymbols will be used when - * formatting. - * - * @param pattern the non-localized pattern to use. - * @param formatData the formatting symbols to use. - * @throws NullPointerException if the pattern or formatData is null. - * @throws IllegalArgumentException if the pattern is invalid. - */ - public SimpleDateFormat(String pattern, DateFormatSymbols formatData) - { - super(); - calendar = new GregorianCalendar(); - computeCenturyStart (); - tokens = new ArrayList(); - if (formatData == null) - throw new NullPointerException("formatData"); - this.formatData = formatData; - compileFormat(pattern); - this.pattern = pattern; - numberFormat = NumberFormat.getInstance(); - numberFormat.setGroupingUsed (false); - numberFormat.setParseIntegerOnly (true); - numberFormat.setMaximumFractionDigits (0); - } - - /** - * This method returns a string with the formatting pattern being used - * by this object. This string is unlocalized. - * - * @return The format string. - */ - public String toPattern() - { - return pattern; - } - - /** - * This method returns a string with the formatting pattern being used - * by this object. This string is localized. - * - * @return The format string. - */ - public String toLocalizedPattern() - { - String localChars = formatData.getLocalPatternChars(); - return translateLocalizedPattern(pattern, standardChars, localChars); - } - - /** - * This method sets the formatting pattern that should be used by this - * object. This string is not localized. - * - * @param pattern The new format pattern. - * @throws NullPointerException if the pattern is null. - * @throws IllegalArgumentException if the pattern is invalid. - */ - public void applyPattern(String pattern) - { - tokens = new ArrayList(); - compileFormat(pattern); - this.pattern = pattern; - } - - /** - * This method sets the formatting pattern that should be used by this - * object. This string is localized. - * - * @param pattern The new format pattern. - * @throws NullPointerException if the pattern is null. - * @throws IllegalArgumentException if the pattern is invalid. - */ - public void applyLocalizedPattern(String pattern) - { - String localChars = formatData.getLocalPatternChars(); - pattern = translateLocalizedPattern(pattern, localChars, standardChars); - applyPattern(pattern); - } - - /** - * Translates either from or to a localized variant of the pattern - * string. For example, in the German locale, 't' (for 'tag') is - * used instead of 'd' (for 'date'). This method translates - * a localized pattern (such as 'ttt') to a non-localized pattern - * (such as 'ddd'), or vice versa. Non-localized patterns use - * a standard set of characters, which match those of the U.S. English - * locale. - * - * @param pattern the pattern to translate. - * @param oldChars the old set of characters (used in the pattern). - * @param newChars the new set of characters (which will be used in the - * pattern). - * @return a version of the pattern using the characters in - * <code>newChars</code>. - */ - private String translateLocalizedPattern(String pattern, - String oldChars, String newChars) - { - int len = pattern.length(); - StringBuffer buf = new StringBuffer(len); - boolean quoted = false; - for (int i = 0; i < len; i++) - { - char ch = pattern.charAt(i); - if (ch == '\'') - quoted = ! quoted; - if (! quoted) - { - int j = oldChars.indexOf(ch); - if (j >= 0) - ch = newChars.charAt(j); - } - buf.append(ch); - } - return buf.toString(); - } - - /** - * Returns the start of the century used for two digit years. - * - * @return A <code>Date</code> representing the start of the century - * for two digit years. - */ - public Date get2DigitYearStart() - { - return defaultCenturyStart; - } - - /** - * Sets the start of the century used for two digit years. - * - * @param date A <code>Date</code> representing the start of the century for - * two digit years. - */ - public void set2DigitYearStart(Date date) - { - defaultCenturyStart = date; - calendar.clear(); - calendar.setTime(date); - int year = calendar.get(Calendar.YEAR); - defaultCentury = year - (year % 100); - } - - /** - * This method returns a copy of the format symbol information used - * for parsing and formatting dates. - * - * @return a copy of the date format symbols. - */ - public DateFormatSymbols getDateFormatSymbols() - { - return (DateFormatSymbols) formatData.clone(); - } - - /** - * This method sets the format symbols information used for parsing - * and formatting dates. - * - * @param formatData The date format symbols. - * @throws NullPointerException if <code>formatData</code> is null. - */ - public void setDateFormatSymbols(DateFormatSymbols formatData) - { - if (formatData == null) - { - throw new - NullPointerException("The supplied format data was null."); - } - this.formatData = formatData; - } - - /** - * This methods tests whether the specified object is equal to this - * object. This will be true if and only if the specified object: - * <p> - * <ul> - * <li>Is not <code>null</code>.</li> - * <li>Is an instance of <code>SimpleDateFormat</code>.</li> - * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>) - * level.</li> - * <li>Has the same formatting pattern.</li> - * <li>Is using the same formatting symbols.</li> - * <li>Is using the same century for two digit years.</li> - * </ul> - * - * @param obj The object to compare for equality against. - * - * @return <code>true</code> if the specified object is equal to this object, - * <code>false</code> otherwise. - */ - public boolean equals(Object o) - { - if (!super.equals(o)) - return false; - - if (!(o instanceof SimpleDateFormat)) - return false; - - SimpleDateFormat sdf = (SimpleDateFormat)o; - - if (defaultCentury != sdf.defaultCentury) - return false; - - if (!toPattern().equals(sdf.toPattern())) - return false; - - if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols())) - return false; - - return true; - } - - /** - * This method returns a hash value for this object. - * - * @return A hash value for this object. - */ - public int hashCode() - { - return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^ - getDateFormatSymbols().hashCode(); - } - - - /** - * Formats the date input according to the format string in use, - * appending to the specified StringBuffer. The input StringBuffer - * is returned as output for convenience. - */ - private void formatWithAttribute(Date date, FormatBuffer buffer, FieldPosition pos) - { - String temp; - AttributedCharacterIterator.Attribute attribute; - calendar.setTime(date); - - // go through vector, filling in fields where applicable, else toString - Iterator iter = tokens.iterator(); - while (iter.hasNext()) - { - Object o = iter.next(); - if (o instanceof CompiledField) - { - CompiledField cf = (CompiledField) o; - int beginIndex = buffer.length(); - - switch (cf.getField()) - { - case ERA_FIELD: - buffer.append (formatData.eras[calendar.get (Calendar.ERA)], DateFormat.Field.ERA); - break; - case YEAR_FIELD: - // If we have two digits, then we truncate. Otherwise, we - // use the size of the pattern, and zero pad. - buffer.setDefaultAttribute (DateFormat.Field.YEAR); - if (cf.getSize() == 2) - { - temp = "00"+String.valueOf (calendar.get (Calendar.YEAR)); - buffer.append (temp.substring (temp.length() - 2)); - } - else - withLeadingZeros (calendar.get (Calendar.YEAR), cf.getSize(), buffer); - break; - case MONTH_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.MONTH); - if (cf.getSize() < 3) - withLeadingZeros (calendar.get (Calendar.MONTH) + 1, cf.getSize(), buffer); - else if (cf.getSize() < 4) - buffer.append (formatData.shortMonths[calendar.get (Calendar.MONTH)]); - else - buffer.append (formatData.months[calendar.get (Calendar.MONTH)]); - break; - case DATE_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_MONTH); - withLeadingZeros (calendar.get (Calendar.DATE), cf.getSize(), buffer); - break; - case HOUR_OF_DAY1_FIELD: // 1-24 - buffer.setDefaultAttribute(DateFormat.Field.HOUR_OF_DAY1); - withLeadingZeros ( ((calendar.get (Calendar.HOUR_OF_DAY) + 23) % 24) + 1, - cf.getSize(), buffer); - break; - case HOUR_OF_DAY0_FIELD: // 0-23 - buffer.setDefaultAttribute (DateFormat.Field.HOUR_OF_DAY0); - withLeadingZeros (calendar.get (Calendar.HOUR_OF_DAY), cf.getSize(), buffer); - break; - case MINUTE_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.MINUTE); - withLeadingZeros (calendar.get (Calendar.MINUTE), - cf.getSize(), buffer); - break; - case SECOND_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.SECOND); - withLeadingZeros(calendar.get (Calendar.SECOND), - cf.getSize(), buffer); - break; - case MILLISECOND_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.MILLISECOND); - withLeadingZeros (calendar.get (Calendar.MILLISECOND), cf.getSize(), buffer); - break; - case DAY_OF_WEEK_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK); - if (cf.getSize() < 4) - buffer.append (formatData.shortWeekdays[calendar.get (Calendar.DAY_OF_WEEK)]); - else - buffer.append (formatData.weekdays[calendar.get (Calendar.DAY_OF_WEEK)]); - break; - case DAY_OF_YEAR_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_YEAR); - withLeadingZeros (calendar.get (Calendar.DAY_OF_YEAR), cf.getSize(), buffer); - break; - case DAY_OF_WEEK_IN_MONTH_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK_IN_MONTH); - withLeadingZeros (calendar.get (Calendar.DAY_OF_WEEK_IN_MONTH), - cf.getSize(), buffer); - break; - case WEEK_OF_YEAR_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_YEAR); - withLeadingZeros (calendar.get (Calendar.WEEK_OF_YEAR), - cf.getSize(), buffer); - break; - case WEEK_OF_MONTH_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_MONTH); - withLeadingZeros (calendar.get (Calendar.WEEK_OF_MONTH), - cf.getSize(), buffer); - break; - case AM_PM_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.AM_PM); - buffer.append (formatData.ampms[calendar.get (Calendar.AM_PM)]); - break; - case HOUR1_FIELD: // 1-12 - buffer.setDefaultAttribute (DateFormat.Field.HOUR1); - withLeadingZeros (((calendar.get (Calendar.HOUR) + 11) % 12) + 1, - cf.getSize(), buffer); - break; - case HOUR0_FIELD: // 0-11 - buffer.setDefaultAttribute (DateFormat.Field.HOUR0); - withLeadingZeros (calendar.get (Calendar.HOUR), cf.getSize(), buffer); - break; - case TIMEZONE_FIELD: - buffer.setDefaultAttribute (DateFormat.Field.TIME_ZONE); - TimeZone zone = calendar.getTimeZone(); - boolean isDST = calendar.get (Calendar.DST_OFFSET) != 0; - // FIXME: XXX: This should be a localized time zone. - String zoneID = zone.getDisplayName - (isDST, cf.getSize() > 3 ? TimeZone.LONG : TimeZone.SHORT); - buffer.append (zoneID); - break; - case RFC822_TIMEZONE_FIELD: - buffer.setDefaultAttribute(DateFormat.Field.RFC822_TIME_ZONE); - int pureMinutes = (calendar.get(Calendar.ZONE_OFFSET) + - calendar.get(Calendar.DST_OFFSET)) / (1000 * 60); - String sign = (pureMinutes < 0) ? "-" : "+"; - int hours = pureMinutes / 60; - int minutes = pureMinutes % 60; - buffer.append(sign); - withLeadingZeros(hours, 2, buffer); - withLeadingZeros(minutes, 2, buffer); - break; - default: - throw new IllegalArgumentException ("Illegal pattern character " + - cf.getCharacter()); - } - if (pos != null && (buffer.getDefaultAttribute() == pos.getFieldAttribute() - || cf.getField() == pos.getField())) - { - pos.setBeginIndex(beginIndex); - pos.setEndIndex(buffer.length()); - } - } - else - { - buffer.append(o.toString(), null); - } - } - } - - public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos) - { - formatWithAttribute(date, new StringFormatBuffer (buffer), pos); - - return buffer; - } - - public AttributedCharacterIterator formatToCharacterIterator(Object date) - throws IllegalArgumentException - { - if (date == null) - throw new NullPointerException("null argument"); - if (!(date instanceof Date)) - throw new IllegalArgumentException("argument should be an instance of java.util.Date"); - - AttributedFormatBuffer buf = new AttributedFormatBuffer(); - formatWithAttribute((Date)date, buf, - null); - buf.sync(); - - return new FormatCharacterIterator(buf.getBuffer().toString(), - buf.getRanges(), - buf.getAttributes()); - } - - private void withLeadingZeros(int value, int length, FormatBuffer buffer) - { - String valStr = String.valueOf(value); - for (length -= valStr.length(); length > 0; length--) - buffer.append('0'); - buffer.append(valStr); - } - - private boolean expect(String source, ParsePosition pos, char ch) - { - int x = pos.getIndex(); - boolean r = x < source.length() && source.charAt(x) == ch; - if (r) - pos.setIndex(x + 1); - else - pos.setErrorIndex(x); - return r; - } - - /** - * This method parses the specified string into a date. - * - * @param dateStr The date string to parse. - * @param pos The input and output parse position - * - * @return The parsed date, or <code>null</code> if the string cannot be - * parsed. - */ - public Date parse (String dateStr, ParsePosition pos) - { - int fmt_index = 0; - int fmt_max = pattern.length(); - - calendar.clear(); - boolean saw_timezone = false; - int quote_start = -1; - boolean is2DigitYear = false; - try - { - for (; fmt_index < fmt_max; ++fmt_index) - { - char ch = pattern.charAt(fmt_index); - if (ch == '\'') - { - int index = pos.getIndex(); - if (fmt_index < fmt_max - 1 - && pattern.charAt(fmt_index + 1) == '\'') - { - if (! expect (dateStr, pos, ch)) - return null; - ++fmt_index; - } - else - quote_start = quote_start < 0 ? fmt_index : -1; - continue; - } - - if (quote_start != -1 - || ((ch < 'a' || ch > 'z') - && (ch < 'A' || ch > 'Z'))) - { - if (! expect (dateStr, pos, ch)) - return null; - continue; - } - - // We've arrived at a potential pattern character in the - // pattern. - int fmt_count = 1; - while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch) - { - ++fmt_count; - } - - // We might need to limit the number of digits to parse in - // some cases. We look to the next pattern character to - // decide. - boolean limit_digits = false; - if (fmt_index < fmt_max - && standardChars.indexOf(pattern.charAt(fmt_index)) >= 0) - limit_digits = true; - --fmt_index; - - // We can handle most fields automatically: most either are - // numeric or are looked up in a string vector. In some cases - // we need an offset. When numeric, `offset' is added to the - // resulting value. When doing a string lookup, offset is the - // initial index into the string array. - int calendar_field; - boolean is_numeric = true; - int offset = 0; - boolean maybe2DigitYear = false; - boolean oneBasedHour = false; - boolean oneBasedHourOfDay = false; - Integer simpleOffset; - String[] set1 = null; - String[] set2 = null; - switch (ch) - { - case 'd': - calendar_field = Calendar.DATE; - break; - case 'D': - calendar_field = Calendar.DAY_OF_YEAR; - break; - case 'F': - calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH; - break; - case 'E': - is_numeric = false; - offset = 1; - calendar_field = Calendar.DAY_OF_WEEK; - set1 = formatData.getWeekdays(); - set2 = formatData.getShortWeekdays(); - break; - case 'w': - calendar_field = Calendar.WEEK_OF_YEAR; - break; - case 'W': - calendar_field = Calendar.WEEK_OF_MONTH; - break; - case 'M': - calendar_field = Calendar.MONTH; - if (fmt_count <= 2) - offset = -1; - else - { - is_numeric = false; - set1 = formatData.getMonths(); - set2 = formatData.getShortMonths(); - } - break; - case 'y': - calendar_field = Calendar.YEAR; - if (fmt_count <= 2) - maybe2DigitYear = true; - break; - case 'K': - calendar_field = Calendar.HOUR; - break; - case 'h': - calendar_field = Calendar.HOUR; - oneBasedHour = true; - break; - case 'H': - calendar_field = Calendar.HOUR_OF_DAY; - break; - case 'k': - calendar_field = Calendar.HOUR_OF_DAY; - oneBasedHourOfDay = true; - break; - case 'm': - calendar_field = Calendar.MINUTE; - break; - case 's': - calendar_field = Calendar.SECOND; - break; - case 'S': - calendar_field = Calendar.MILLISECOND; - break; - case 'a': - is_numeric = false; - calendar_field = Calendar.AM_PM; - set1 = formatData.getAmPmStrings(); - break; - case 'z': - case 'Z': - // We need a special case for the timezone, because it - // uses a different data structure than the other cases. - is_numeric = false; - calendar_field = Calendar.ZONE_OFFSET; - String[][] zoneStrings = formatData.getZoneStrings(); - int zoneCount = zoneStrings.length; - int index = pos.getIndex(); - boolean found_zone = false; - simpleOffset = computeOffset(dateStr.substring(index)); - if (simpleOffset != null) - { - found_zone = true; - saw_timezone = true; - calendar.set(Calendar.DST_OFFSET, 0); - offset = simpleOffset.intValue(); - } - else - { - for (int j = 0; j < zoneCount; j++) - { - String[] strings = zoneStrings[j]; - int k; - for (k = 0; k < strings.length; ++k) - { - if (dateStr.startsWith(strings[k], index)) - break; - } - if (k != strings.length) - { - found_zone = true; - saw_timezone = true; - TimeZone tz = TimeZone.getTimeZone (strings[0]); - // Check if it's a DST zone or ordinary - if(k == 3 || k == 4) - calendar.set (Calendar.DST_OFFSET, tz.getDSTSavings()); - else - calendar.set (Calendar.DST_OFFSET, 0); - offset = tz.getRawOffset (); - pos.setIndex(index + strings[k].length()); - break; - } - } - } - if (! found_zone) - { - pos.setErrorIndex(pos.getIndex()); - return null; - } - break; - default: - pos.setErrorIndex(pos.getIndex()); - return null; - } - - // Compute the value we should assign to the field. - int value; - int index = -1; - if (is_numeric) - { - numberFormat.setMinimumIntegerDigits(fmt_count); - if (limit_digits) - numberFormat.setMaximumIntegerDigits(fmt_count); - if (maybe2DigitYear) - index = pos.getIndex(); - Number n = numberFormat.parse(dateStr, pos); - if (pos == null || ! (n instanceof Long)) - return null; - value = n.intValue() + offset; - } - else if (set1 != null) - { - index = pos.getIndex(); - int i; - boolean found = false; - for (i = offset; i < set1.length; ++i) - { - if (set1[i] != null) - if (dateStr.toUpperCase().startsWith(set1[i].toUpperCase(), - index)) - { - found = true; - pos.setIndex(index + set1[i].length()); - break; - } - } - if (!found && set2 != null) - { - for (i = offset; i < set2.length; ++i) - { - if (set2[i] != null) - if (dateStr.toUpperCase().startsWith(set2[i].toUpperCase(), - index)) - { - found = true; - pos.setIndex(index + set2[i].length()); - break; - } - } - } - if (!found) - { - pos.setErrorIndex(index); - return null; - } - value = i; - } - else - value = offset; - - if (maybe2DigitYear) - { - // Parse into default century if the numeric year string has - // exactly 2 digits. - int digit_count = pos.getIndex() - index; - if (digit_count == 2) - { - is2DigitYear = true; - value += defaultCentury; - } - } - - // Calendar uses 0-based hours. - // I.e. 00:00 AM is midnight, not 12 AM or 24:00 - if (oneBasedHour && value == 12) - value = 0; - - if (oneBasedHourOfDay && value == 24) - value = 0; - - // Assign the value and move on. - calendar.set(calendar_field, value); - } - - if (is2DigitYear) - { - // Apply the 80-20 heuristic to dermine the full year based on - // defaultCenturyStart. - int year = calendar.get(Calendar.YEAR); - if (calendar.getTime().compareTo(defaultCenturyStart) < 0) - calendar.set(Calendar.YEAR, year + 100); - } - if (! saw_timezone) - { - // Use the real rules to determine whether or not this - // particular time is in daylight savings. - calendar.clear (Calendar.DST_OFFSET); - calendar.clear (Calendar.ZONE_OFFSET); - } - return calendar.getTime(); - } - catch (IllegalArgumentException x) - { - pos.setErrorIndex(pos.getIndex()); - return null; - } - } - - /** - * <p> - * Computes the time zone offset in milliseconds - * relative to GMT, based on the supplied - * <code>String</code> representation. - * </p> - * <p> - * The supplied <code>String</code> must be a three - * or four digit signed number, with an optional 'GMT' - * prefix. The first one or two digits represents the hours, - * while the last two represent the minutes. The - * two sets of digits can optionally be separated by - * ':'. The mandatory sign prefix (either '+' or '-') - * indicates the direction of the offset from GMT. - * </p> - * <p> - * For example, 'GMT+0200' specifies 2 hours after - * GMT, while '-05:00' specifies 5 hours prior to - * GMT. The special case of 'GMT' alone can be used - * to represent the offset, 0. - * </p> - * <p> - * If the <code>String</code> can not be parsed, - * the result will be null. The resulting offset - * is wrapped in an <code>Integer</code> object, in - * order to allow such failure to be represented. - * </p> - * - * @param zoneString a string in the form - * (GMT)? sign hours : minutes - * where sign = '+' or '-', hours - * is a one or two digits representing - * a number between 0 and 23, and - * minutes is two digits representing - * a number between 0 and 59. - * @return the parsed offset, or null if parsing - * failed. - */ - private Integer computeOffset(String zoneString) - { - Pattern pattern = - Pattern.compile("(GMT)?([+-])([012])?([0-9]):?([0-9]{2})"); - Matcher matcher = pattern.matcher(zoneString); - if (matcher.matches()) - { - int sign = matcher.group(2).equals("+") ? 1 : -1; - int hour = (Integer.parseInt(matcher.group(3)) * 10) - + Integer.parseInt(matcher.group(4)); - int minutes = Integer.parseInt(matcher.group(5)); - - if (hour > 23) - return null; - - int offset = sign * ((hour * 60) + minutes) * 60000; - return new Integer(offset); - } - else if (zoneString.startsWith("GMT")) - { - return new Integer(0); - } - return null; - } - - // Compute the start of the current century as defined by - // get2DigitYearStart. - private void computeCenturyStart() - { - int year = calendar.get(Calendar.YEAR); - calendar.set(Calendar.YEAR, year - 80); - set2DigitYearStart(calendar.getTime()); - } - - /** - * Returns a copy of this instance of - * <code>SimpleDateFormat</code>. The copy contains - * clones of the formatting symbols and the 2-digit - * year century start date. - */ - public Object clone() - { - SimpleDateFormat clone = (SimpleDateFormat) super.clone(); - clone.setDateFormatSymbols((DateFormatSymbols) formatData.clone()); - clone.set2DigitYearStart((Date) defaultCenturyStart.clone()); - return clone; - } - -} |