FileDocCategorySizeDatePackage
SimpleDateFormat.javaAPI DocAndroid 1.5 API49906Wed May 06 22:41:06 BST 2009java.text

SimpleDateFormat

public class SimpleDateFormat extends DateFormat
A concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for formatting (date to text), parsing (text to date) and normalization.

{@code SimpleDateFormat} allows you to start by choosing any user-defined patterns for date-time formatting. However, you are encouraged to create a date-time formatter with either {@code getTimeInstance}, {@code getDateInstance}, or {@code getDateTimeInstance} in {@code DateFormat}. Each of these class methods can return a date/time formatter initialized with a default format pattern. You may modify the format pattern using the {@code applyPattern} methods as desired. For more information on using these methods, see {@link DateFormat}.

Time Format Syntax

To specify the time format, use a time pattern string. In this pattern, all ASCII letters are reserved as pattern letters, which are defined as follows:

Symbol Meaning Presentation Example
G era designator (Text) AD
y year (Number) 1996
M month in year (Text & Number) July & 07
d day in month (Number) 10
h hour in am/pm (1˜12) (Number) 12
H hour in day (0˜23) (Number) 0
m minute in hour (Number) 30
s second in minute (Number) 55
S fractional second (Number) 978
E day of week (Text) Tuesday
D day in year (Number) 189
F day of week in month (Number) 2 (2nd Wed in July)
w week in year (Number) 27
W week in month (Number) 2
a am/pm marker (Text) PM
k hour in day (1˜24) (Number) 24
K hour in am/pm (0˜11) (Number) 0
z time zone (Text) Pacific Standard Time
Z time zone (RFC 822) (Number) -0800
v time zone (generic) (Text) Pacific Time
V time zone (location) (Text) United States (Los Angeles)
' escape for text (Delimiter) 'Date='
'' single quote (Literal) 'o''clock'

The count of pattern letters determines the format:

(Text): 4 or more pattern letters → use the full form, less than 4 pattern letters → use a short or abbreviated form if one exists.

(Number): the minimum number of digits. Shorter numbers are zero-padded to this amount. Year is handled specially; that is, if the count of 'y' is 2, the year will be truncated to 2 digits. (if "yyyy" produces "1997", "yy" produces "97".) Unlike other fields, fractional seconds are padded on the right with zero.

(Text & Number): 3 or over, use text, otherwise use number.

Any characters in the pattern that are not in the ranges of ['a'..'z'] and ['A'..'Z'] will be treated as quoted text. For instance, characters like ':', '.', ' ', '#' and '@' will appear in the resulting time text even they are not embraced within single quotes.

A pattern containing any invalid pattern letter will result in an exception thrown during formatting or parsing.

Examples Using the US Locale

Format Pattern Result
-------------- -------
"yyyy.MM.dd G 'at' HH:mm:ss vvvv" → 1996.07.10 AD at 15:08:56 Pacific Time
"EEE, MMM d, ''yy" → Wed, July 10, '96
"h:mm a" → 12:08 PM
"hh 'o''clock' a, zzzz" → 12 o'clock PM, Pacific Daylight Time
"K:mm a, vvv" → 0:00 PM, PT
"yyyyy.MMMMM.dd GGG hh:mm aaa" → 01996.July.10 AD 12:08 PM

Code Sample:

SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);
pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);

// Format the current time.
SimpleDateFormat formatter = new SimpleDateFormat(
"yyyy.MM.dd G 'at' hh:mm:ss a zzz");
Date currentTime_1 = new Date();
String dateString = formatter.format(currentTime_1);

// Parse the previous string back into a Date.
ParsePosition pos = new ParsePosition(0);
Date currentTime_2 = formatter.parse(dateString, pos);

In the example, the time value {@code currentTime_2} obtained from parsing will be equal to {@code currentTime_1}. However, they may not be equal if the am/pm marker 'a' is left out from the format pattern while the "hour in am/pm" pattern symbol is used. This information loss can happen when formatting the time in PM.

When parsing a date string using the abbreviated year pattern ("yy"), {@code SimpleDateFormat} must interpret the abbreviated year relative to some century. It does this by adjusting dates to be within 80 years before and 20 years after the time the {@code SimpleDateFormat} instance is created. For example, using a pattern of "MM/dd/yy" and a {@code SimpleDateFormat} instance created on Jan 1, 1997, the string "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64" would be interpreted as May 4, 1964. During parsing, only strings consisting of exactly two digits, as defined by {@link java.lang.Character#isDigit(char)}, will be parsed into the default century. Any other numeric string, such as a one digit string, a three or more digit string, or a two digit string that isn't all digits (for example, "-1"), is interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.

If the year pattern does not have exactly two 'y' characters, the year is interpreted literally, regardless of the number of digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.

When numeric fields are adjacent directly, with no intervening delimiter characters, they constitute a run of adjacent numeric fields. Such runs are parsed specially. For example, the format "HHmmss" parses the input text "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to parse "1234". In other words, the leftmost field of the run is flexible, while the others keep a fixed width. If the parse fails anywhere in the run, then the leftmost field is shortened by one character, and the entire run is parsed again. This is repeated until either the parse succeeds or the leftmost field is one character in length. If the parse still fails at that point, the parse of the run fails.

For time zones that have no names, use the strings "GMT+hours:minutes" or "GMT-hours:minutes".

The calendar defines the first day of the week, the first week of the year, whether hours are zero based or not (0 vs. 12 or 24) and the time zone. There is one common decimal format to handle all the numbers; the digit count is handled programmatically according to the pattern.

Synchronization

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
see
Calendar
see
GregorianCalendar
see
TimeZone
see
DateFormat
see
DateFormatSymbols
see
DecimalFormat
since
Android 1.0

Fields Summary
private static final long
serialVersionUID
private static final String
patternChars
private String
pattern
private DateFormatSymbols
formatData
private transient int
creationYear
private Date
defaultCenturyStart
private static final ObjectStreamField[]
serialPersistentFields
Constructors Summary
public SimpleDateFormat()
Constructs a new {@code SimpleDateFormat} for formatting and parsing dates and times in the {@code SHORT} style for the default locale.

since
Android 1.0


                                  
      
        this(Locale.getDefault());
        pattern = defaultPattern();
        formatData = new DateFormatSymbols(Locale.getDefault());
    
public SimpleDateFormat(String pattern)
Constructs a new {@code SimpleDateFormat} using the specified non-localized pattern and the {@code DateFormatSymbols} and {@code Calendar} for the default locale.

param
pattern the pattern.
exception
IllegalArgumentException if {@code pattern} is not considered to be usable by this formatter.
since
Android 1.0

        this(pattern, Locale.getDefault());
    
public SimpleDateFormat(String template, DateFormatSymbols value)
Constructs a new {@code SimpleDateFormat} using the specified non-localized pattern and {@code DateFormatSymbols} and the {@code Calendar} for the default locale.

param
template the pattern.
param
value the DateFormatSymbols.
exception
IllegalArgumentException if the pattern is invalid.
since
Android 1.0

        this(Locale.getDefault());
        validatePattern(template);
        pattern = template;
        formatData = (DateFormatSymbols) value.clone();
    
public SimpleDateFormat(String template, Locale locale)
Constructs a new {@code SimpleDateFormat} using the specified non-localized pattern and the {@code DateFormatSymbols} and {@code Calendar} for the specified locale.

param
template the pattern.
param
locale the locale.
exception
IllegalArgumentException if the pattern is invalid.
since
Android 1.0

        this(locale);
        validatePattern(template);
        pattern = template;
        formatData = new DateFormatSymbols(locale);
    
private SimpleDateFormat(Locale locale)

        numberFormat = NumberFormat.getInstance(locale);
        numberFormat.setParseIntegerOnly(true);
        numberFormat.setGroupingUsed(false);
        calendar = new GregorianCalendar(locale);
        calendar.add(Calendar.YEAR, -80);
        creationYear = calendar.get(Calendar.YEAR);
        defaultCenturyStart = calendar.getTime();
    
Methods Summary
private voidappend(java.lang.StringBuffer buffer, java.text.FieldPosition position, java.util.Vector fields, char format, int count)

        int field = -1;
        int index = patternChars.indexOf(format);
        if (index == -1) {
            // text.03=Unknown pattern character - '{0}'
            throw new IllegalArgumentException(Messages.getString(
                    "text.03", format)); //$NON-NLS-1$
        }

        int beginPosition = buffer.length();
        Field dateFormatField = null;

        switch (index) {
            case ERA_FIELD:
                dateFormatField = Field.ERA;
                buffer.append(formatData.eras[calendar.get(Calendar.ERA)]);
                break;
            case YEAR_FIELD:
                dateFormatField = Field.YEAR;
                int year = calendar.get(Calendar.YEAR);
                if (count < 4) {
                    appendNumber(buffer, 2, year %= 100);
                } else {
                    appendNumber(buffer, count, year);
                }
                break;
            case MONTH_FIELD:
                dateFormatField = Field.MONTH;
                int month = calendar.get(Calendar.MONTH);
                if (count <= 2) {
                    appendNumber(buffer, count, month + 1);
                } else if (count == 3) {
                    buffer.append(formatData.shortMonths[month]);
                } else {
                    buffer.append(formatData.months[month]);
                }
                break;
            case DATE_FIELD:
                dateFormatField = Field.DAY_OF_MONTH;
                field = Calendar.DATE;
                break;
            case HOUR_OF_DAY1_FIELD: // k
                dateFormatField = Field.HOUR_OF_DAY1;
                int hour = calendar.get(Calendar.HOUR_OF_DAY);
                appendNumber(buffer, count, hour == 0 ? 24 : hour);
                break;
            case HOUR_OF_DAY0_FIELD: // H
                dateFormatField = Field.HOUR_OF_DAY0;
                field = Calendar.HOUR_OF_DAY;
                break;
            case MINUTE_FIELD:
                dateFormatField = Field.MINUTE;
                field = Calendar.MINUTE;
                break;
            case SECOND_FIELD:
                dateFormatField = Field.SECOND;
                field = Calendar.SECOND;
                break;
            case MILLISECOND_FIELD:
                dateFormatField = Field.MILLISECOND;
                int value = calendar.get(Calendar.MILLISECOND);
                appendNumber(buffer, count, value);
                break;
            case DAY_OF_WEEK_FIELD:
                dateFormatField = Field.DAY_OF_WEEK;
                int day = calendar.get(Calendar.DAY_OF_WEEK);
                if (count < 4) {
                    buffer.append(formatData.shortWeekdays[day]);
                } else {
                    buffer.append(formatData.weekdays[day]);
                }
                break;
            case DAY_OF_YEAR_FIELD:
                dateFormatField = Field.DAY_OF_YEAR;
                field = Calendar.DAY_OF_YEAR;
                break;
            case DAY_OF_WEEK_IN_MONTH_FIELD:
                dateFormatField = Field.DAY_OF_WEEK_IN_MONTH;
                field = Calendar.DAY_OF_WEEK_IN_MONTH;
                break;
            case WEEK_OF_YEAR_FIELD:
                dateFormatField = Field.WEEK_OF_YEAR;
                field = Calendar.WEEK_OF_YEAR;
                break;
            case WEEK_OF_MONTH_FIELD:
                dateFormatField = Field.WEEK_OF_MONTH;
                field = Calendar.WEEK_OF_MONTH;
                break;
            case AM_PM_FIELD:
                dateFormatField = Field.AM_PM;
                buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]);
                break;
            case HOUR1_FIELD: // h
                dateFormatField = Field.HOUR1;
                hour = calendar.get(Calendar.HOUR);
                appendNumber(buffer, count, hour == 0 ? 12 : hour);
                break;
            case HOUR0_FIELD: // K
                dateFormatField = Field.HOUR0;
                field = Calendar.HOUR;
                break;
            case TIMEZONE_FIELD: // z
                dateFormatField = Field.TIME_ZONE;
                appendTimeZone(buffer, count, true);
                break;
            case (TIMEZONE_FIELD + 1): // Z
                dateFormatField = Field.TIME_ZONE;
                appendTimeZone(buffer, count, false);
                break;
        }
        if (field != -1) {
            appendNumber(buffer, count, calendar.get(field));
        }

        if (fields != null) {
            position = new FieldPosition(dateFormatField);
            position.setBeginIndex(beginPosition);
            position.setEndIndex(buffer.length());
            fields.add(position);
        } else {
            // Set to the first occurrence
            if ((position.getFieldAttribute() == dateFormatField || (position
                    .getFieldAttribute() == null && position.getField() == index))
                    && position.getEndIndex() == 0) {
                position.setBeginIndex(beginPosition);
                position.setEndIndex(buffer.length());
            }
        }
    
private voidappendNumber(java.lang.StringBuffer buffer, int count, int value)

        int minimumIntegerDigits = numberFormat.getMinimumIntegerDigits();
        numberFormat.setMinimumIntegerDigits(count);
        numberFormat.format(new Integer(value), buffer, new FieldPosition(0));
        numberFormat.setMinimumIntegerDigits(minimumIntegerDigits);
    
private voidappendTimeZone(java.lang.StringBuffer buffer, int count, boolean generalTimezone)

        // cannot call TimeZone.getDisplayName() because it would not use
        // the DateFormatSymbols of this SimpleDateFormat

        if (generalTimezone) {
            String id = calendar.getTimeZone().getID();
            // BEGIN android-changed
            String[][] zones = formatData.internalZoneStrings();
            // END android-changed
            String[] zone = null;
            for (String[] element : zones) {
                if (id.equals(element[0])) {
                    zone = element;
                    break;
                }
            }
            if (zone == null) {
                int offset = calendar.get(Calendar.ZONE_OFFSET)
                        + calendar.get(Calendar.DST_OFFSET);
                char sign = '+";
                if (offset < 0) {
                    sign = '-";
                    offset = -offset;
                }
                buffer.append("GMT"); //$NON-NLS-1$
                buffer.append(sign);
                appendNumber(buffer, 2, offset / 3600000);
                buffer.append(':");
                appendNumber(buffer, 2, (offset % 3600000) / 60000);
            } else {
                int daylight = calendar.get(Calendar.DST_OFFSET) == 0 ? 0 : 2;
                if (count < 4) {
                    buffer.append(zone[2 + daylight]);
                } else {
                    buffer.append(zone[1 + daylight]);
                }
            }
        } else {
            int offset = calendar.get(Calendar.ZONE_OFFSET)
                    + calendar.get(Calendar.DST_OFFSET);
            char sign = '+";
            if (offset < 0) {
                sign = '-";
                offset = -offset;
            }
            buffer.append(sign);
            appendNumber(buffer, 2, offset / 3600000);
            appendNumber(buffer, 2, (offset % 3600000) / 60000);
        }
    
public voidapplyLocalizedPattern(java.lang.String template)
Changes the pattern of this simple date format to the specified pattern which uses localized pattern characters.

param
template the localized pattern.
since
Android 1.0

        pattern = convertPattern(template, formatData.getLocalPatternChars(),
                patternChars, true);
    
public voidapplyPattern(java.lang.String template)
Changes the pattern of this simple date format to the specified pattern which uses non-localized pattern characters.

param
template the non-localized pattern.
exception
IllegalArgumentException if the pattern is invalid.
since
Android 1.0

        validatePattern(template);
        pattern = template;
    
public java.lang.Objectclone()
Returns a new {@code SimpleDateFormat} with the same pattern and properties as this simple date format.

return
a shallow copy of this simple date format.
see
java.lang.Cloneable
since
Android 1.0

        SimpleDateFormat clone = (SimpleDateFormat) super.clone();
        clone.formatData = (DateFormatSymbols) formatData.clone();
        clone.defaultCenturyStart = new Date(defaultCenturyStart.getTime());
        return clone;
    
private static java.lang.StringdefaultPattern()

        ResourceBundle bundle = getBundle(Locale.getDefault());
        String styleName = getStyleName(SHORT);
        return bundle.getString("Date_" + styleName) + " " //$NON-NLS-1$ //$NON-NLS-2$
                + bundle.getString("Time_" + styleName); //$NON-NLS-1$
    
public booleanequals(java.lang.Object object)
Compares the specified object with this simple date format and indicates if they are equal. In order to be equal, {@code object} must be an instance of {@code SimpleDateFormat} and have the same {@code DateFormat} properties, pattern, {@code DateFormatSymbols} and creation year.

param
object the object to compare with this object.
return
{@code true} if the specified object is equal to this simple date format; {@code false} otherwise.
see
#hashCode
since
Android 1.0

        if (this == object) {
            return true;
        }
        if (!(object instanceof SimpleDateFormat)) {
            return false;
        }
        SimpleDateFormat simple = (SimpleDateFormat) object;
        return super.equals(object) && pattern.equals(simple.pattern)
                && formatData.equals(simple.formatData);
    
private java.util.Dateerror(java.text.ParsePosition position, int offset, java.util.TimeZone zone)

        position.setErrorIndex(offset);
        calendar.setTimeZone(zone);
        return null;
    
public java.lang.StringBufferformat(java.util.Date date, java.lang.StringBuffer buffer, java.text.FieldPosition field)
Formats the specified date as a string using the pattern of this date format and appends the string to the specified string buffer.

If the {@code field} member of {@code field} contains a value specifying a format field, then its {@code beginIndex} and {@code endIndex} members will be updated with the position of the first occurrence of this field in the formatted text.

param
date the date to format.
param
buffer the target string buffer to append the formatted date/time to.
param
field on input: an optional alignment field; on output: the offsets of the alignment field in the formatted text.
return
the string buffer.
throws
IllegalArgumentException if there are invalid characters in the pattern.
since
Android 1.0

        return formatImpl(date, buffer, field, null);
    
private java.lang.StringBufferformatImpl(java.util.Date date, java.lang.StringBuffer buffer, java.text.FieldPosition field, java.util.Vector fields)
Formats the date.

If the FieldPosition {@code field} is not null, and the field specified by this FieldPosition is formatted, set the begin and end index of the formatted field in the FieldPosition.

If the Vector {@code fields} is not null, find fields of this date, set FieldPositions with these fields, and add them to the fields vector.

param
date Date to Format
param
buffer StringBuffer to store the resulting formatted String
param
field FieldPosition to set begin and end index of the field specified, if it is part of the format for this date
param
fields Vector used to store the FieldPositions for each field in this date
return
the formatted Date
exception
IllegalArgumentException when the object cannot be formatted by this Format


        boolean quote = false;
        int next, last = -1, count = 0;
        calendar.setTime(date);
        if (field != null) {
            field.clear();
        }

        final int patternLength = pattern.length();
        for (int i = 0; i < patternLength; i++) {
            next = (pattern.charAt(i));
            if (next == '\'") {
                if (count > 0) {
                    append(buffer, field, fields, (char) last, count);
                    count = 0;
                }
                if (last == next) {
                    buffer.append('\'");
                    last = -1;
                } else {
                    last = next;
                }
                quote = !quote;
                continue;
            }
            if (!quote
                    && (last == next || (next >= 'a" && next <= 'z") || (next >= 'A" && next <= 'Z"))) {
                if (last == next) {
                    count++;
                } else {
                    if (count > 0) {
                        append(buffer, field, fields, (char) last, count);
                    }
                    last = next;
                    count = 1;
                }
            } else {
                if (count > 0) {
                    append(buffer, field, fields, (char) last, count);
                    count = 0;
                }
                last = -1;
                buffer.append((char) next);
            }
        }
        if (count > 0) {
            append(buffer, field, fields, (char) last, count);
        }
        return buffer;
    
public java.text.AttributedCharacterIteratorformatToCharacterIterator(java.lang.Object object)
Formats the specified object using the rules of this simple date format and returns an {@code AttributedCharacterIterator} with the formatted date and attributes.

param
object the object to format.
return
an {@code AttributedCharacterIterator} with the formatted date and attributes.
exception
IllegalArgumentException when the object cannot be formatted by this simple date format.
since
Android 1.0

        if (object == null) {
            throw new NullPointerException();
        }
        if (object instanceof Date) {
            return formatToCharacterIteratorImpl((Date) object);
        }
        if (object instanceof Number) {
            return formatToCharacterIteratorImpl(new Date(((Number) object)
                    .longValue()));
        }
        throw new IllegalArgumentException();
    
private java.text.AttributedCharacterIteratorformatToCharacterIteratorImpl(java.util.Date date)

        StringBuffer buffer = new StringBuffer();
        Vector<FieldPosition> fields = new Vector<FieldPosition>();

        // format the date, and find fields
        formatImpl(date, buffer, null, fields);

        // create and AttributedString with the formatted buffer
        AttributedString as = new AttributedString(buffer.toString());

        // add DateFormat field attributes to the AttributedString
        for (int i = 0; i < fields.size(); i++) {
            FieldPosition pos = fields.elementAt(i);
            Format.Field attribute = pos.getFieldAttribute();
            as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos
                    .getEndIndex());
        }

        // return the CharacterIterator from AttributedString
        return as.getIterator();
    
public java.util.Dateget2DigitYearStart()
Returns the date which is the start of the one hundred year period for two digits year values.

return
a date.
since
Android 1.0

        return defaultCenturyStart;
    
public java.text.DateFormatSymbolsgetDateFormatSymbols()
Returns the {@code DateFormatSymbols} used by this simple date format.

return
the {@code DateFormatSymbols} object.
since
Android 1.0

        // Return a clone so the arrays in the ResourceBundle are not modified
        return (DateFormatSymbols) formatData.clone();
    
public inthashCode()

        return super.hashCode() + pattern.hashCode() + formatData.hashCode()
                + creationYear;
    
private intparse(java.lang.String string, int offset, char format, int count)

        int index = patternChars.indexOf(format);
        if (index == -1) {
            // text.03=Unknown pattern character - '{0}'
            throw new IllegalArgumentException(Messages.getString(
                    "text.03", format)); //$NON-NLS-1$
        }
        int field = -1;
        int absolute = 0;
        if (count < 0) {
            count = -count;
            absolute = count;
        }
        switch (index) {
            case ERA_FIELD:
                return parseText(string, offset, formatData.eras, Calendar.ERA);
            case YEAR_FIELD:
                if (count >= 3) {
                    field = Calendar.YEAR;
                } else {
                    ParsePosition position = new ParsePosition(offset);
                    Number result = parseNumber(absolute, string, position);
                    if (result == null) {
                        return -position.getErrorIndex() - 1;
                    }
                    int year = result.intValue();
                    // A two digit year must be exactly two digits, i.e. 01
                    if ((position.getIndex() - offset) == 2 && year >= 0) {
                        year += creationYear / 100 * 100;
                        if (year < creationYear) {
                            year += 100;
                        }
                    }
                    calendar.set(Calendar.YEAR, year);
                    return position.getIndex();
                }
                break;
            case MONTH_FIELD:
                if (count <= 2) {
                    return parseNumber(absolute, string, offset,
                            Calendar.MONTH, -1);
                }
                index = parseText(string, offset, formatData.months,
                        Calendar.MONTH);
                if (index < 0) {
                    return parseText(string, offset, formatData.shortMonths,
                            Calendar.MONTH);
                }
                return index;
            case DATE_FIELD:
                field = Calendar.DATE;
                break;
            case HOUR_OF_DAY1_FIELD:
                ParsePosition position = new ParsePosition(offset);
                Number result = parseNumber(absolute, string, position);
                if (result == null) {
                    return -position.getErrorIndex() - 1;
                }
                int hour = result.intValue();
                if (hour == 24) {
                    hour = 0;
                }
                calendar.set(Calendar.HOUR_OF_DAY, hour);
                return position.getIndex();
            case HOUR_OF_DAY0_FIELD:
                field = Calendar.HOUR_OF_DAY;
                break;
            case MINUTE_FIELD:
                field = Calendar.MINUTE;
                break;
            case SECOND_FIELD:
                field = Calendar.SECOND;
                break;
            case MILLISECOND_FIELD:
                field = Calendar.MILLISECOND;
                break;
            case DAY_OF_WEEK_FIELD:
                index = parseText(string, offset, formatData.weekdays,
                        Calendar.DAY_OF_WEEK);
                if (index < 0) {
                    return parseText(string, offset, formatData.shortWeekdays,
                            Calendar.DAY_OF_WEEK);
                }
                return index;
            case DAY_OF_YEAR_FIELD:
                field = Calendar.DAY_OF_YEAR;
                break;
            case DAY_OF_WEEK_IN_MONTH_FIELD:
                field = Calendar.DAY_OF_WEEK_IN_MONTH;
                break;
            case WEEK_OF_YEAR_FIELD:
                field = Calendar.WEEK_OF_YEAR;
                break;
            case WEEK_OF_MONTH_FIELD:
                field = Calendar.WEEK_OF_MONTH;
                break;
            case AM_PM_FIELD:
                return parseText(string, offset, formatData.ampms,
                        Calendar.AM_PM);
            case HOUR1_FIELD:
                position = new ParsePosition(offset);
                result = parseNumber(absolute, string, position);
                if (result == null) {
                    return -position.getErrorIndex() - 1;
                }
                hour = result.intValue();
                if (hour == 12) {
                    hour = 0;
                }
                calendar.set(Calendar.HOUR, hour);
                return position.getIndex();
            case HOUR0_FIELD:
                field = Calendar.HOUR;
                break;
            case TIMEZONE_FIELD:
                return parseTimeZone(string, offset);
            case (TIMEZONE_FIELD + 1):
                return parseTimeZone(string, offset);
        }
        if (field != -1) {
            return parseNumber(absolute, string, offset, field, 0);
        }
        return offset;
    
public java.util.Dateparse(java.lang.String string, java.text.ParsePosition position)
Parses a date from the specified string starting at the index specified by {@code position}. If the string is successfully parsed then the index of the {@code ParsePosition} is updated to the index following the parsed text. On error, the index is unchanged and the error index of {@code ParsePosition} is set to the index where the error occurred.

param
string the string to parse using the pattern of this simple date format.
param
position input/output parameter, specifies the start index in {@code string} from where to start parsing. If parsing is successful, it is updated with the index following the parsed text; on error, the index is unchanged and the error index is set to the index where the error occurred.
return
the date resulting from the parse, or {@code null} if there is an error.
throws
IllegalArgumentException if there are invalid characters in the pattern.
since
Android 1.0

        boolean quote = false;
        int next, last = -1, count = 0, offset = position.getIndex();
        int length = string.length();
        calendar.clear();
        TimeZone zone = calendar.getTimeZone();
        final int patternLength = pattern.length();
        for (int i = 0; i < patternLength; i++) {
            next = pattern.charAt(i);
            if (next == '\'") {
                if (count > 0) {
                    if ((offset = parse(string, offset, (char) last, count)) < 0) {
                        return error(position, -offset - 1, zone);
                    }
                    count = 0;
                }
                if (last == next) {
                    if (offset >= length || string.charAt(offset) != '\'") {
                        return error(position, offset, zone);
                    }
                    offset++;
                    last = -1;
                } else {
                    last = next;
                }
                quote = !quote;
                continue;
            }
            if (!quote
                    && (last == next || (next >= 'a" && next <= 'z") || (next >= 'A" && next <= 'Z"))) {
                if (last == next) {
                    count++;
                } else {
                    if (count > 0) {
                        if ((offset = parse(string, offset, (char) last, -count)) < 0) {
                            return error(position, -offset - 1, zone);
                        }
                    }
                    last = next;
                    count = 1;
                }
            } else {
                if (count > 0) {
                    if ((offset = parse(string, offset, (char) last, count)) < 0) {
                        return error(position, -offset - 1, zone);
                    }
                    count = 0;
                }
                last = -1;
                if (offset >= length || string.charAt(offset) != next) {
                    return error(position, offset, zone);
                }
                offset++;
            }
        }
        if (count > 0) {
            if ((offset = parse(string, offset, (char) last, count)) < 0) {
                return error(position, -offset - 1, zone);
            }
        }
        Date date;
        try {
            date = calendar.getTime();
        } catch (IllegalArgumentException e) {
            return error(position, offset, zone);
        }
        position.setIndex(offset);
        calendar.setTimeZone(zone);
        return date;
    
private java.lang.NumberparseNumber(int max, java.lang.String string, java.text.ParsePosition position)

        int digit, length = string.length(), result = 0;
        int index = position.getIndex();
        if (max > 0 && max < length - index) {
            length = index + max;
        }
        while (index < length
                && (string.charAt(index) == ' " || string.charAt(index) == '\t")) {
            index++;
        }
        if (max == 0) {
            position.setIndex(index);
            return numberFormat.parse(string, position);
        }

        while (index < length
                && (digit = Character.digit(string.charAt(index), 10)) != -1) {
            index++;
            result = result * 10 + digit;
        }
        if (index == position.getIndex()) {
            position.setErrorIndex(index);
            return null;
        }
        position.setIndex(index);
        return new Integer(result);
    
private intparseNumber(int max, java.lang.String string, int offset, int field, int skew)

        ParsePosition position = new ParsePosition(offset);
        Number result = parseNumber(max, string, position);
        if (result == null) {
            return -position.getErrorIndex() - 1;
        }
        calendar.set(field, result.intValue() + skew);
        return position.getIndex();
    
private intparseText(java.lang.String string, int offset, java.lang.String[] text, int field)

        int found = -1;
        for (int i = 0; i < text.length; i++) {
            if (text[i].length() == 0) {
                continue;
            }
            if (string
                    .regionMatches(true, offset, text[i], 0, text[i].length())) {
                // Search for the longest match, in case some fields are subsets
                if (found == -1 || text[i].length() > text[found].length()) {
                    found = i;
                }
            }
        }
        if (found != -1) {
            calendar.set(field, found);
            return offset + text[found].length();
        }
        return -offset - 1;
    
private intparseTimeZone(java.lang.String string, int offset)

        // BEGIN android-changed
        String[][] zones = formatData.internalZoneStrings();
        // END android-changed
        boolean foundGMT = string.regionMatches(offset, "GMT", 0, 3); //$NON-NLS-1$
        if (foundGMT) {
            offset += 3;
        }
        char sign;
        if (offset < string.length()
                && ((sign = string.charAt(offset)) == '+" || sign == '-")) {
            ParsePosition position = new ParsePosition(offset + 1);
            Number result = numberFormat.parse(string, position);
            if (result == null) {
                return -position.getErrorIndex() - 1;
            }
            int hour = result.intValue();
            int raw = hour * 3600000;
            int index = position.getIndex();
            if (index < string.length() && string.charAt(index) == ':") {
                position.setIndex(index + 1);
                result = numberFormat.parse(string, position);
                if (result == null) {
                    return -position.getErrorIndex() - 1;
                }
                int minute = result.intValue();
                raw += minute * 60000;
            } else if (hour >= 24) {
                raw = (hour / 100 * 3600000) + (hour % 100 * 60000);
            }
            if (sign == '-") {
                raw = -raw;
            }
            calendar.setTimeZone(new SimpleTimeZone(raw, "")); //$NON-NLS-1$
            return position.getIndex();
        }
        if (foundGMT) {
            calendar.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
            return offset;
        }
        for (String[] element : zones) {
            for (int j = 1; j < 5; j++) {
                if (string.regionMatches(true, offset, element[j], 0,
                        element[j].length())) {
                    TimeZone zone = TimeZone.getTimeZone(element[0]);
                    if (zone == null) {
                        return -offset - 1;
                    }
                    int raw = zone.getRawOffset();
                    if (j >= 3 && zone.useDaylightTime()) {
                        raw += 3600000;
                    }
                    calendar.setTimeZone(new SimpleTimeZone(raw, "")); //$NON-NLS-1$
                    return offset + element[j].length();
                }
            }
        }
        return -offset - 1;
    
private voidreadObject(java.io.ObjectInputStream stream)

        ObjectInputStream.GetField fields = stream.readFields();
        int version = fields.get("serialVersionOnStream", 0); //$NON-NLS-1$
        Date date;
        if (version > 0) {
            date = (Date) fields.get("defaultCenturyStart", new Date()); //$NON-NLS-1$
        } else {
            date = new Date();
        }
        set2DigitYearStart(date);
        formatData = (DateFormatSymbols) fields.get("formatData", null); //$NON-NLS-1$
        pattern = (String) fields.get("pattern", ""); //$NON-NLS-1$ //$NON-NLS-2$
    
public voidset2DigitYearStart(java.util.Date date)
Sets the date which is the start of the one hundred year period for two digits year values.

param
date the new date.
since
Android 1.0

        defaultCenturyStart = date;
        Calendar cal = new GregorianCalendar();
        cal.setTime(date);
        creationYear = cal.get(Calendar.YEAR);
    
public voidsetDateFormatSymbols(java.text.DateFormatSymbols value)
Sets the {@code DateFormatSymbols} used by this simple date format.

param
value the new {@code DateFormatSymbols} object.
since
Android 1.0

        formatData = (DateFormatSymbols) value.clone();
    
public java.lang.StringtoLocalizedPattern()
Returns the pattern of this simple date format using localized pattern characters.

return
the localized pattern.
since
Android 1.0

        return convertPattern(pattern, patternChars, formatData
                .getLocalPatternChars(), false);
    
public java.lang.StringtoPattern()
Returns the pattern of this simple date format using non-localized pattern characters.

return
the non-localized pattern.
since
Android 1.0

        return pattern;
    
private voidvalidateFormat(char format)
Validates the format character.

param
format the format character
throws
IllegalArgumentException when the format character is invalid

        int index = patternChars.indexOf(format);
        if (index == -1) {
            // text.03=Unknown pattern character - '{0}'
            throw new IllegalArgumentException(Messages.getString(
                    "text.03", format)); //$NON-NLS-1$
        }
    
private voidvalidatePattern(java.lang.String template)
Validates the pattern.

param
template the pattern to validate.
throws
NullPointerException if the pattern is null
throws
IllegalArgumentException if the pattern is invalid

        boolean quote = false;
        int next, last = -1, count = 0;

        final int patternLength = template.length();
        for (int i = 0; i < patternLength; i++) {
            next = (template.charAt(i));
            if (next == '\'") {
                if (count > 0) {
                    validateFormat((char) last);
                    count = 0;
                }
                if (last == next) {
                    last = -1;
                } else {
                    last = next;
                }
                quote = !quote;
                continue;
            }
            if (!quote
                    && (last == next || (next >= 'a" && next <= 'z") || (next >= 'A" && next <= 'Z"))) {
                if (last == next) {
                    count++;
                } else {
                    if (count > 0) {
                        validateFormat((char) last);
                    }
                    last = next;
                    count = 1;
                }
            } else {
                if (count > 0) {
                    validateFormat((char) last);
                    count = 0;
                }
                last = -1;
            }
        }
        if (count > 0) {
            validateFormat((char) last);
        }

        if (quote) {
            // text.04=Unterminated quote {0}
            throw new IllegalArgumentException(Messages.getString("text.04")); //$NON-NLS-1$
        }

    
private voidwriteObject(java.io.ObjectOutputStream stream)

 //$NON-NLS-1$

          
        ObjectOutputStream.PutField fields = stream.putFields();
        fields.put("defaultCenturyStart", defaultCenturyStart); //$NON-NLS-1$
        fields.put("formatData", formatData); //$NON-NLS-1$
        fields.put("pattern", pattern); //$NON-NLS-1$
        fields.put("serialVersionOnStream", 1); //$NON-NLS-1$
        stream.writeFields();