jtulach@1334: /* jtulach@1334: * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. jtulach@1334: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jtulach@1334: * jtulach@1334: * This code is free software; you can redistribute it and/or modify it jtulach@1334: * under the terms of the GNU General Public License version 2 only, as jtulach@1334: * published by the Free Software Foundation. Oracle designates this jtulach@1334: * particular file as subject to the "Classpath" exception as provided jtulach@1334: * by Oracle in the LICENSE file that accompanied this code. jtulach@1334: * jtulach@1334: * This code is distributed in the hope that it will be useful, but WITHOUT jtulach@1334: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jtulach@1334: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jtulach@1334: * version 2 for more details (a copy is included in the LICENSE file that jtulach@1334: * accompanied this code). jtulach@1334: * jtulach@1334: * You should have received a copy of the GNU General Public License version jtulach@1334: * 2 along with this work; if not, write to the Free Software Foundation, jtulach@1334: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jtulach@1334: * jtulach@1334: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jtulach@1334: * or visit www.oracle.com if you need additional information or have any jtulach@1334: * questions. jtulach@1334: */ jtulach@1334: jtulach@1334: /* jtulach@1334: * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved jtulach@1334: * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved jtulach@1334: * jtulach@1334: * The original version of this source code and documentation is copyrighted jtulach@1334: * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These jtulach@1334: * materials are provided under terms of a License Agreement between Taligent jtulach@1334: * and Sun. This technology is protected by multiple US and International jtulach@1334: * patents. This notice and attribution to Taligent may not be removed. jtulach@1334: * Taligent is a registered trademark of Taligent, Inc. jtulach@1334: * jtulach@1334: */ jtulach@1334: jtulach@1334: package java.text; jtulach@1334: jtulach@1334: import java.io.IOException; jtulach@1334: import java.io.InvalidObjectException; jtulach@1334: import java.io.ObjectInputStream; jtulach@1334: import java.util.Calendar; jtulach@1334: import java.util.Date; jtulach@1334: import java.util.GregorianCalendar; jtulach@1334: import java.util.Locale; jtulach@1334: import java.util.Map; jtulach@1334: import java.util.MissingResourceException; jtulach@1334: import java.util.ResourceBundle; jtulach@1334: import java.util.SimpleTimeZone; jtulach@1334: import java.util.TimeZone; jtulach@1334: import java.util.concurrent.ConcurrentHashMap; jtulach@1334: import java.util.concurrent.ConcurrentMap; jtulach@1334: import sun.util.calendar.CalendarUtils; jtulach@1334: import sun.util.calendar.ZoneInfoFile; jtulach@1334: import sun.util.resources.LocaleData; jtulach@1334: jtulach@1334: import static java.text.DateFormatSymbols.*; jtulach@1334: jtulach@1334: /** jtulach@1334: * SimpleDateFormat is a concrete class for formatting and jtulach@1334: * parsing dates in a locale-sensitive manner. It allows for formatting jtulach@1334: * (date -> text), parsing (text -> date), and normalization. jtulach@1334: * jtulach@1334: *

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

Date and Time Patterns

jtulach@1334: *

jtulach@1334: * Date and time formats are specified by date and time pattern jtulach@1334: * strings. jtulach@1334: * Within date and time pattern strings, unquoted letters from jtulach@1334: * 'A' to 'Z' and from 'a' to jtulach@1334: * 'z' are interpreted as pattern letters representing the jtulach@1334: * components of a date or time string. jtulach@1334: * Text can be quoted using single quotes (') to avoid jtulach@1334: * interpretation. jtulach@1334: * "''" represents a single quote. jtulach@1334: * All other characters are not interpreted; they're simply copied into the jtulach@1334: * output string during formatting or matched against the input string jtulach@1334: * during parsing. jtulach@1334: *

jtulach@1334: * The following pattern letters are defined (all other characters from jtulach@1334: * 'A' to 'Z' and from 'a' to jtulach@1334: * 'z' are reserved): jtulach@1334: *

jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: *
Letter jtulach@1334: * Date or Time Component jtulach@1334: * Presentation jtulach@1334: * Examples jtulach@1334: *
G jtulach@1334: * Era designator jtulach@1334: * Text jtulach@1334: * AD jtulach@1334: *
y jtulach@1334: * Year jtulach@1334: * Year jtulach@1334: * 1996; 96 jtulach@1334: *
Y jtulach@1334: * Week year jtulach@1334: * Year jtulach@1334: * 2009; 09 jtulach@1334: *
M jtulach@1334: * Month in year jtulach@1334: * Month jtulach@1334: * July; Jul; 07 jtulach@1334: *
w jtulach@1334: * Week in year jtulach@1334: * Number jtulach@1334: * 27 jtulach@1334: *
W jtulach@1334: * Week in month jtulach@1334: * Number jtulach@1334: * 2 jtulach@1334: *
D jtulach@1334: * Day in year jtulach@1334: * Number jtulach@1334: * 189 jtulach@1334: *
d jtulach@1334: * Day in month jtulach@1334: * Number jtulach@1334: * 10 jtulach@1334: *
F jtulach@1334: * Day of week in month jtulach@1334: * Number jtulach@1334: * 2 jtulach@1334: *
E jtulach@1334: * Day name in week jtulach@1334: * Text jtulach@1334: * Tuesday; Tue jtulach@1334: *
u jtulach@1334: * Day number of week (1 = Monday, ..., 7 = Sunday) jtulach@1334: * Number jtulach@1334: * 1 jtulach@1334: *
a jtulach@1334: * Am/pm marker jtulach@1334: * Text jtulach@1334: * PM jtulach@1334: *
H jtulach@1334: * Hour in day (0-23) jtulach@1334: * Number jtulach@1334: * 0 jtulach@1334: *
k jtulach@1334: * Hour in day (1-24) jtulach@1334: * Number jtulach@1334: * 24 jtulach@1334: *
K jtulach@1334: * Hour in am/pm (0-11) jtulach@1334: * Number jtulach@1334: * 0 jtulach@1334: *
h jtulach@1334: * Hour in am/pm (1-12) jtulach@1334: * Number jtulach@1334: * 12 jtulach@1334: *
m jtulach@1334: * Minute in hour jtulach@1334: * Number jtulach@1334: * 30 jtulach@1334: *
s jtulach@1334: * Second in minute jtulach@1334: * Number jtulach@1334: * 55 jtulach@1334: *
S jtulach@1334: * Millisecond jtulach@1334: * Number jtulach@1334: * 978 jtulach@1334: *
z jtulach@1334: * Time zone jtulach@1334: * General time zone jtulach@1334: * Pacific Standard Time; PST; GMT-08:00 jtulach@1334: *
Z jtulach@1334: * Time zone jtulach@1334: * RFC 822 time zone jtulach@1334: * -0800 jtulach@1334: *
X jtulach@1334: * Time zone jtulach@1334: * ISO 8601 time zone jtulach@1334: * -08; -0800; -08:00 jtulach@1334: *
jtulach@1334: *
jtulach@1334: * Pattern letters are usually repeated, as their number determines the jtulach@1334: * exact presentation: jtulach@1334: * jtulach@1334: * SimpleDateFormat also supports localized date and time jtulach@1334: * pattern strings. In these strings, the pattern letters described above jtulach@1334: * may be replaced with other, locale dependent, pattern letters. jtulach@1334: * SimpleDateFormat does not deal with the localization of text jtulach@1334: * other than the pattern letters; that's up to the client of the class. jtulach@1334: *

jtulach@1334: * jtulach@1334: *

Examples

jtulach@1334: * jtulach@1334: * The following examples show how date and time patterns are interpreted in jtulach@1334: * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time jtulach@1334: * in the U.S. Pacific Time time zone. jtulach@1334: *
jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: * jtulach@1334: *
Date and Time Pattern jtulach@1334: * Result jtulach@1334: *
"yyyy.MM.dd G 'at' HH:mm:ss z" jtulach@1334: * 2001.07.04 AD at 12:08:56 PDT jtulach@1334: *
"EEE, MMM d, ''yy" jtulach@1334: * Wed, Jul 4, '01 jtulach@1334: *
"h:mm a" jtulach@1334: * 12:08 PM jtulach@1334: *
"hh 'o''clock' a, zzzz" jtulach@1334: * 12 o'clock PM, Pacific Daylight Time jtulach@1334: *
"K:mm a, z" jtulach@1334: * 0:08 PM, PDT jtulach@1334: *
"yyyyy.MMMMM.dd GGG hh:mm aaa" jtulach@1334: * 02001.July.04 AD 12:08 PM jtulach@1334: *
"EEE, d MMM yyyy HH:mm:ss Z" jtulach@1334: * Wed, 4 Jul 2001 12:08:56 -0700 jtulach@1334: *
"yyMMddHHmmssZ" jtulach@1334: * 010704120856-0700 jtulach@1334: *
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" jtulach@1334: * 2001-07-04T12:08:56.235-0700 jtulach@1334: *
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX" jtulach@1334: * 2001-07-04T12:08:56.235-07:00 jtulach@1334: *
"YYYY-'W'ww-u" jtulach@1334: * 2001-W27-3 jtulach@1334: *
jtulach@1334: *
jtulach@1334: * jtulach@1334: *

Synchronization

jtulach@1334: * jtulach@1334: *

jtulach@1334: * Date formats are not synchronized. jtulach@1334: * It is recommended to create separate format instances for each thread. jtulach@1334: * If multiple threads access a format concurrently, it must be synchronized jtulach@1334: * externally. jtulach@1334: * jtulach@1334: * @see Java Tutorial jtulach@1334: * @see java.util.Calendar jtulach@1334: * @see java.util.TimeZone jtulach@1334: * @see DateFormat jtulach@1334: * @see DateFormatSymbols jtulach@1334: * @author Mark Davis, Chen-Lieh Huang, Alan Liu jtulach@1334: */ jtulach@1334: public class SimpleDateFormat extends DateFormat { jtulach@1334: jtulach@1334: // the official serial version ID which says cryptically jtulach@1334: // which version we're compatible with jtulach@1334: static final long serialVersionUID = 4774881970558875024L; jtulach@1334: jtulach@1334: // the internal serial version which says which version was written jtulach@1334: // - 0 (default) for version up to JDK 1.1.3 jtulach@1334: // - 1 for version from JDK 1.1.4, which includes a new field jtulach@1334: static final int currentSerialVersion = 1; jtulach@1334: jtulach@1334: /** jtulach@1334: * The version of the serialized data on the stream. Possible values: jtulach@1334: *

jtulach@1334: * When streaming out this class, the most recent format jtulach@1334: * and the highest allowable serialVersionOnStream jtulach@1334: * is written. jtulach@1334: * @serial jtulach@1334: * @since JDK1.1.4 jtulach@1334: */ jtulach@1334: private int serialVersionOnStream = currentSerialVersion; jtulach@1334: jtulach@1334: /** jtulach@1334: * The pattern string of this formatter. This is always a non-localized jtulach@1334: * pattern. May not be null. See class documentation for details. jtulach@1334: * @serial jtulach@1334: */ jtulach@1334: private String pattern; jtulach@1334: jtulach@1334: /** jtulach@1334: * Saved numberFormat and pattern. jtulach@1334: * @see SimpleDateFormat#checkNegativeNumberExpression jtulach@1334: */ jtulach@1334: transient private NumberFormat originalNumberFormat; jtulach@1334: transient private String originalNumberPattern; jtulach@1334: jtulach@1334: /** jtulach@1334: * The minus sign to be used with format and parse. jtulach@1334: */ jtulach@1334: transient private char minusSign = '-'; jtulach@1334: jtulach@1334: /** jtulach@1334: * True when a negative sign follows a number. jtulach@1334: * (True as default in Arabic.) jtulach@1334: */ jtulach@1334: transient private boolean hasFollowingMinusSign = false; jtulach@1334: jtulach@1334: /** jtulach@1334: * The compiled pattern. jtulach@1334: */ jtulach@1334: transient private char[] compiledPattern; jtulach@1334: jtulach@1334: /** jtulach@1334: * Tags for the compiled pattern. jtulach@1334: */ jtulach@1334: private final static int TAG_QUOTE_ASCII_CHAR = 100; jtulach@1334: private final static int TAG_QUOTE_CHARS = 101; jtulach@1334: jtulach@1334: /** jtulach@1334: * Locale dependent digit zero. jtulach@1334: * @see #zeroPaddingNumber jtulach@1334: * @see java.text.DecimalFormatSymbols#getZeroDigit jtulach@1334: */ jtulach@1334: transient private char zeroDigit; jtulach@1334: jtulach@1334: /** jtulach@1334: * The symbols used by this formatter for week names, month names, jtulach@1334: * etc. May not be null. jtulach@1334: * @serial jtulach@1334: * @see java.text.DateFormatSymbols jtulach@1334: */ jtulach@1334: private DateFormatSymbols formatData; jtulach@1334: jtulach@1334: /** jtulach@1334: * We map dates with two-digit years into the century starting at jtulach@1334: * defaultCenturyStart, which may be any date. May jtulach@1334: * not be null. jtulach@1334: * @serial jtulach@1334: * @since JDK1.1.4 jtulach@1334: */ jtulach@1334: private Date defaultCenturyStart; jtulach@1334: jtulach@1334: transient private int defaultCenturyStartYear; jtulach@1334: jtulach@1334: private static final int MILLIS_PER_MINUTE = 60 * 1000; jtulach@1334: jtulach@1334: // For time zones that have no names, use strings GMT+minutes and jtulach@1334: // GMT-minutes. For instance, in France the time zone is GMT+60. jtulach@1334: private static final String GMT = "GMT"; jtulach@1334: jtulach@1334: /** jtulach@1334: * Cache to hold the DateTimePatterns of a Locale. jtulach@1334: */ jtulach@1334: private static final ConcurrentMap cachedLocaleData jtulach@1334: = new ConcurrentHashMap(3); jtulach@1334: jtulach@1334: /** jtulach@1334: * Cache NumberFormat instances with Locale key. jtulach@1334: */ jtulach@1334: private static final ConcurrentMap cachedNumberFormatData jtulach@1334: = new ConcurrentHashMap(3); jtulach@1334: jtulach@1334: /** jtulach@1334: * The Locale used to instantiate this jtulach@1334: * SimpleDateFormat. The value may be null if this object jtulach@1334: * has been created by an older SimpleDateFormat and jtulach@1334: * deserialized. jtulach@1334: * jtulach@1334: * @serial jtulach@1334: * @since 1.6 jtulach@1334: */ jtulach@1334: private Locale locale; jtulach@1334: jtulach@1334: /** jtulach@1334: * Indicates whether this SimpleDateFormat should use jtulach@1334: * the DateFormatSymbols. If true, the format and parse methods jtulach@1334: * use the DateFormatSymbols values. If false, the format and jtulach@1334: * parse methods call Calendar.getDisplayName or jtulach@1334: * Calendar.getDisplayNames. jtulach@1334: */ jtulach@1334: transient boolean useDateFormatSymbols; jtulach@1334: jtulach@1334: /** jtulach@1334: * Constructs a SimpleDateFormat using the default pattern and jtulach@1334: * date format symbols for the default locale. jtulach@1334: * Note: This constructor may not support all locales. jtulach@1334: * For full coverage, use the factory methods in the {@link DateFormat} jtulach@1334: * class. jtulach@1334: */ jtulach@1334: public SimpleDateFormat() { jtulach@1334: this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT)); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Constructs a SimpleDateFormat using the given pattern and jtulach@1334: * the default date format symbols for the default locale. jtulach@1334: * Note: This constructor may not support all locales. jtulach@1334: * For full coverage, use the factory methods in the {@link DateFormat} jtulach@1334: * class. jtulach@1334: * jtulach@1334: * @param pattern the pattern describing the date and time format jtulach@1334: * @exception NullPointerException if the given pattern is null jtulach@1334: * @exception IllegalArgumentException if the given pattern is invalid jtulach@1334: */ jtulach@1334: public SimpleDateFormat(String pattern) jtulach@1334: { jtulach@1334: this(pattern, Locale.getDefault(Locale.Category.FORMAT)); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Constructs a SimpleDateFormat using the given pattern and jtulach@1334: * the default date format symbols for the given locale. jtulach@1334: * Note: This constructor may not support all locales. jtulach@1334: * For full coverage, use the factory methods in the {@link DateFormat} jtulach@1334: * class. jtulach@1334: * jtulach@1334: * @param pattern the pattern describing the date and time format jtulach@1334: * @param locale the locale whose date format symbols should be used jtulach@1334: * @exception NullPointerException if the given pattern or locale is null jtulach@1334: * @exception IllegalArgumentException if the given pattern is invalid jtulach@1334: */ jtulach@1334: public SimpleDateFormat(String pattern, Locale locale) jtulach@1334: { jtulach@1334: if (pattern == null || locale == null) { jtulach@1334: throw new NullPointerException(); jtulach@1334: } jtulach@1334: jtulach@1334: initializeCalendar(locale); jtulach@1334: this.pattern = pattern; jtulach@1334: this.formatData = DateFormatSymbols.getInstanceRef(locale); jtulach@1334: this.locale = locale; jtulach@1334: initialize(locale); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Constructs a SimpleDateFormat using the given pattern and jtulach@1334: * date format symbols. jtulach@1334: * jtulach@1334: * @param pattern the pattern describing the date and time format jtulach@1334: * @param formatSymbols the date format symbols to be used for formatting jtulach@1334: * @exception NullPointerException if the given pattern or formatSymbols is null jtulach@1334: * @exception IllegalArgumentException if the given pattern is invalid jtulach@1334: */ jtulach@1334: public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) jtulach@1334: { jtulach@1334: if (pattern == null || formatSymbols == null) { jtulach@1334: throw new NullPointerException(); jtulach@1334: } jtulach@1334: jtulach@1334: this.pattern = pattern; jtulach@1334: this.formatData = (DateFormatSymbols) formatSymbols.clone(); jtulach@1334: this.locale = Locale.getDefault(Locale.Category.FORMAT); jtulach@1334: initializeCalendar(this.locale); jtulach@1334: initialize(this.locale); jtulach@1334: useDateFormatSymbols = true; jtulach@1334: } jtulach@1334: jtulach@1334: /* Package-private, called by DateFormat factory methods */ jtulach@1334: SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) { jtulach@1334: if (loc == null) { jtulach@1334: throw new NullPointerException(); jtulach@1334: } jtulach@1334: jtulach@1334: this.locale = loc; jtulach@1334: // initialize calendar and related fields jtulach@1334: initializeCalendar(loc); jtulach@1334: jtulach@1334: /* try the cache first */ jtulach@1334: String[] dateTimePatterns = cachedLocaleData.get(loc); jtulach@1334: if (dateTimePatterns == null) { /* cache miss */ jtulach@1334: ResourceBundle r = LocaleData.getDateFormatData(loc); jtulach@1334: if (!isGregorianCalendar()) { jtulach@1334: try { jtulach@1334: dateTimePatterns = r.getStringArray(getCalendarName() + ".DateTimePatterns"); jtulach@1334: } catch (MissingResourceException e) { jtulach@1334: } jtulach@1334: } jtulach@1334: if (dateTimePatterns == null) { jtulach@1334: dateTimePatterns = r.getStringArray("DateTimePatterns"); jtulach@1334: } jtulach@1334: /* update cache */ jtulach@1334: cachedLocaleData.putIfAbsent(loc, dateTimePatterns); jtulach@1334: } jtulach@1334: formatData = DateFormatSymbols.getInstanceRef(loc); jtulach@1334: if ((timeStyle >= 0) && (dateStyle >= 0)) { jtulach@1334: Object[] dateTimeArgs = {dateTimePatterns[timeStyle], jtulach@1334: dateTimePatterns[dateStyle + 4]}; jtulach@1334: pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs); jtulach@1334: } jtulach@1334: else if (timeStyle >= 0) { jtulach@1334: pattern = dateTimePatterns[timeStyle]; jtulach@1334: } jtulach@1334: else if (dateStyle >= 0) { jtulach@1334: pattern = dateTimePatterns[dateStyle + 4]; jtulach@1334: } jtulach@1334: else { jtulach@1334: throw new IllegalArgumentException("No date or time style specified"); jtulach@1334: } jtulach@1334: jtulach@1334: initialize(loc); jtulach@1334: } jtulach@1334: jtulach@1334: /* Initialize compiledPattern and numberFormat fields */ jtulach@1334: private void initialize(Locale loc) { jtulach@1334: // Verify and compile the given pattern. jtulach@1334: compiledPattern = compile(pattern); jtulach@1334: jtulach@1334: /* try the cache first */ jtulach@1334: numberFormat = cachedNumberFormatData.get(loc); jtulach@1334: if (numberFormat == null) { /* cache miss */ jtulach@1334: numberFormat = NumberFormat.getIntegerInstance(loc); jtulach@1334: numberFormat.setGroupingUsed(false); jtulach@1334: jtulach@1334: /* update cache */ jtulach@1334: cachedNumberFormatData.putIfAbsent(loc, numberFormat); jtulach@1334: } jtulach@1334: numberFormat = (NumberFormat) numberFormat.clone(); jtulach@1334: jtulach@1334: initializeDefaultCentury(); jtulach@1334: } jtulach@1334: jtulach@1334: private void initializeCalendar(Locale loc) { jtulach@1334: if (calendar == null) { jtulach@1334: assert loc != null; jtulach@1334: // The format object must be constructed using the symbols for this zone. jtulach@1334: // However, the calendar should use the current default TimeZone. jtulach@1334: // If this is not contained in the locale zone strings, then the zone jtulach@1334: // will be formatted using generic GMT+/-H:MM nomenclature. jtulach@1334: calendar = Calendar.getInstance(TimeZone.getDefault(), loc); jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Returns the compiled form of the given pattern. The syntax of jtulach@1334: * the compiled pattern is: jtulach@1334: *
jtulach@1334: * CompiledPattern: jtulach@1334: * EntryList jtulach@1334: * EntryList: jtulach@1334: * Entry jtulach@1334: * EntryList Entry jtulach@1334: * Entry: jtulach@1334: * TagField jtulach@1334: * TagField data jtulach@1334: * TagField: jtulach@1334: * Tag Length jtulach@1334: * TaggedData jtulach@1334: * Tag: jtulach@1334: * pattern_char_index jtulach@1334: * TAG_QUOTE_CHARS jtulach@1334: * Length: jtulach@1334: * short_length jtulach@1334: * long_length jtulach@1334: * TaggedData: jtulach@1334: * TAG_QUOTE_ASCII_CHAR ascii_char jtulach@1334: * jtulach@1334: *
jtulach@1334: * jtulach@1334: * where `short_length' is an 8-bit unsigned integer between 0 and jtulach@1334: * 254. `long_length' is a sequence of an 8-bit integer 255 and a jtulach@1334: * 32-bit signed integer value which is split into upper and lower jtulach@1334: * 16-bit fields in two char's. `pattern_char_index' is an 8-bit jtulach@1334: * integer between 0 and 18. `ascii_char' is an 7-bit ASCII jtulach@1334: * character value. `data' depends on its Tag value. jtulach@1334: *

jtulach@1334: * If Length is short_length, Tag and short_length are packed in a jtulach@1334: * single char, as illustrated below. jtulach@1334: *

jtulach@1334: * char[0] = (Tag << 8) | short_length; jtulach@1334: *
jtulach@1334: * jtulach@1334: * If Length is long_length, Tag and 255 are packed in the first jtulach@1334: * char and a 32-bit integer, as illustrated below. jtulach@1334: *
jtulach@1334: * char[0] = (Tag << 8) | 255; jtulach@1334: * char[1] = (char) (long_length >>> 16); jtulach@1334: * char[2] = (char) (long_length & 0xffff); jtulach@1334: *
jtulach@1334: *

jtulach@1334: * If Tag is a pattern_char_index, its Length is the number of jtulach@1334: * pattern characters. For example, if the given pattern is jtulach@1334: * "yyyy", Tag is 1 and Length is 4, followed by no data. jtulach@1334: *

jtulach@1334: * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's jtulach@1334: * following the TagField. For example, if the given pattern is jtulach@1334: * "'o''clock'", Length is 7 followed by a char sequence of jtulach@1334: * o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k. jtulach@1334: *

jtulach@1334: * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII jtulach@1334: * character in place of Length. For example, if the given pattern jtulach@1334: * is "'o'", the TaggedData entry is jtulach@1334: * ((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o'). jtulach@1334: * jtulach@1334: * @exception NullPointerException if the given pattern is null jtulach@1334: * @exception IllegalArgumentException if the given pattern is invalid jtulach@1334: */ jtulach@1334: private char[] compile(String pattern) { jtulach@1334: int length = pattern.length(); jtulach@1334: boolean inQuote = false; jtulach@1334: StringBuilder compiledPattern = new StringBuilder(length * 2); jtulach@1334: StringBuilder tmpBuffer = null; jtulach@1334: int count = 0; jtulach@1334: int lastTag = -1; jtulach@1334: jtulach@1334: for (int i = 0; i < length; i++) { jtulach@1334: char c = pattern.charAt(i); jtulach@1334: jtulach@1334: if (c == '\'') { jtulach@1334: // '' is treated as a single quote regardless of being jtulach@1334: // in a quoted section. jtulach@1334: if ((i + 1) < length) { jtulach@1334: c = pattern.charAt(i + 1); jtulach@1334: if (c == '\'') { jtulach@1334: i++; jtulach@1334: if (count != 0) { jtulach@1334: encode(lastTag, count, compiledPattern); jtulach@1334: lastTag = -1; jtulach@1334: count = 0; jtulach@1334: } jtulach@1334: if (inQuote) { jtulach@1334: tmpBuffer.append(c); jtulach@1334: } else { jtulach@1334: compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); jtulach@1334: } jtulach@1334: continue; jtulach@1334: } jtulach@1334: } jtulach@1334: if (!inQuote) { jtulach@1334: if (count != 0) { jtulach@1334: encode(lastTag, count, compiledPattern); jtulach@1334: lastTag = -1; jtulach@1334: count = 0; jtulach@1334: } jtulach@1334: if (tmpBuffer == null) { jtulach@1334: tmpBuffer = new StringBuilder(length); jtulach@1334: } else { jtulach@1334: tmpBuffer.setLength(0); jtulach@1334: } jtulach@1334: inQuote = true; jtulach@1334: } else { jtulach@1334: int len = tmpBuffer.length(); jtulach@1334: if (len == 1) { jtulach@1334: char ch = tmpBuffer.charAt(0); jtulach@1334: if (ch < 128) { jtulach@1334: compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); jtulach@1334: } else { jtulach@1334: compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | 1)); jtulach@1334: compiledPattern.append(ch); jtulach@1334: } jtulach@1334: } else { jtulach@1334: encode(TAG_QUOTE_CHARS, len, compiledPattern); jtulach@1334: compiledPattern.append(tmpBuffer); jtulach@1334: } jtulach@1334: inQuote = false; jtulach@1334: } jtulach@1334: continue; jtulach@1334: } jtulach@1334: if (inQuote) { jtulach@1334: tmpBuffer.append(c); jtulach@1334: continue; jtulach@1334: } jtulach@1334: if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { jtulach@1334: if (count != 0) { jtulach@1334: encode(lastTag, count, compiledPattern); jtulach@1334: lastTag = -1; jtulach@1334: count = 0; jtulach@1334: } jtulach@1334: if (c < 128) { jtulach@1334: // In most cases, c would be a delimiter, such as ':'. jtulach@1334: compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); jtulach@1334: } else { jtulach@1334: // Take any contiguous non-ASCII alphabet characters and jtulach@1334: // put them in a single TAG_QUOTE_CHARS. jtulach@1334: int j; jtulach@1334: for (j = i + 1; j < length; j++) { jtulach@1334: char d = pattern.charAt(j); jtulach@1334: if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) { jtulach@1334: break; jtulach@1334: } jtulach@1334: } jtulach@1334: compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); jtulach@1334: for (; i < j; i++) { jtulach@1334: compiledPattern.append(pattern.charAt(i)); jtulach@1334: } jtulach@1334: i--; jtulach@1334: } jtulach@1334: continue; jtulach@1334: } jtulach@1334: jtulach@1334: int tag; jtulach@1334: if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) { jtulach@1334: throw new IllegalArgumentException("Illegal pattern character " + jtulach@1334: "'" + c + "'"); jtulach@1334: } jtulach@1334: if (lastTag == -1 || lastTag == tag) { jtulach@1334: lastTag = tag; jtulach@1334: count++; jtulach@1334: continue; jtulach@1334: } jtulach@1334: encode(lastTag, count, compiledPattern); jtulach@1334: lastTag = tag; jtulach@1334: count = 1; jtulach@1334: } jtulach@1334: jtulach@1334: if (inQuote) { jtulach@1334: throw new IllegalArgumentException("Unterminated quote"); jtulach@1334: } jtulach@1334: jtulach@1334: if (count != 0) { jtulach@1334: encode(lastTag, count, compiledPattern); jtulach@1334: } jtulach@1334: jtulach@1334: // Copy the compiled pattern to a char array jtulach@1334: int len = compiledPattern.length(); jtulach@1334: char[] r = new char[len]; jtulach@1334: compiledPattern.getChars(0, len, r, 0); jtulach@1334: return r; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Encodes the given tag and length and puts encoded char(s) into buffer. jtulach@1334: */ jtulach@1334: private static final void encode(int tag, int length, StringBuilder buffer) { jtulach@1334: if (tag == PATTERN_ISO_ZONE && length >= 4) { jtulach@1334: throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length); jtulach@1334: } jtulach@1334: if (length < 255) { jtulach@1334: buffer.append((char)(tag << 8 | length)); jtulach@1334: } else { jtulach@1334: buffer.append((char)((tag << 8) | 0xff)); jtulach@1334: buffer.append((char)(length >>> 16)); jtulach@1334: buffer.append((char)(length & 0xffff)); jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: /* Initialize the fields we use to disambiguate ambiguous years. Separate jtulach@1334: * so we can call it from readObject(). jtulach@1334: */ jtulach@1334: private void initializeDefaultCentury() { jtulach@1334: calendar.setTimeInMillis(System.currentTimeMillis()); jtulach@1334: calendar.add( Calendar.YEAR, -80 ); jtulach@1334: parseAmbiguousDatesAsAfter(calendar.getTime()); jtulach@1334: } jtulach@1334: jtulach@1334: /* Define one-century window into which to disambiguate dates using jtulach@1334: * two-digit years. jtulach@1334: */ jtulach@1334: private void parseAmbiguousDatesAsAfter(Date startDate) { jtulach@1334: defaultCenturyStart = startDate; jtulach@1334: calendar.setTime(startDate); jtulach@1334: defaultCenturyStartYear = calendar.get(Calendar.YEAR); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Sets the 100-year period 2-digit years will be interpreted as being in jtulach@1334: * to begin on the date the user specifies. jtulach@1334: * jtulach@1334: * @param startDate During parsing, two digit years will be placed in the range jtulach@1334: * startDate to startDate + 100 years. jtulach@1334: * @see #get2DigitYearStart jtulach@1334: * @since 1.2 jtulach@1334: */ jtulach@1334: public void set2DigitYearStart(Date startDate) { jtulach@1334: parseAmbiguousDatesAsAfter(new Date(startDate.getTime())); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Returns the beginning date of the 100-year period 2-digit years are interpreted jtulach@1334: * as being within. jtulach@1334: * jtulach@1334: * @return the start of the 100-year period into which two digit years are jtulach@1334: * parsed jtulach@1334: * @see #set2DigitYearStart jtulach@1334: * @since 1.2 jtulach@1334: */ jtulach@1334: public Date get2DigitYearStart() { jtulach@1334: return (Date) defaultCenturyStart.clone(); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Formats the given Date into a date/time string and appends jtulach@1334: * the result to the given StringBuffer. jtulach@1334: * jtulach@1334: * @param date the date-time value to be formatted into a date-time string. jtulach@1334: * @param toAppendTo where the new date-time text is to be appended. jtulach@1334: * @param pos the formatting position. On input: an alignment field, jtulach@1334: * if desired. On output: the offsets of the alignment field. jtulach@1334: * @return the formatted date-time string. jtulach@1334: * @exception NullPointerException if the given {@code date} is {@code null}. jtulach@1334: */ jtulach@1334: public StringBuffer format(Date date, StringBuffer toAppendTo, jtulach@1334: FieldPosition pos) jtulach@1334: { jtulach@1334: pos.beginIndex = pos.endIndex = 0; jtulach@1334: return format(date, toAppendTo, pos.getFieldDelegate()); jtulach@1334: } jtulach@1334: jtulach@1334: // Called from Format after creating a FieldDelegate jtulach@1334: private StringBuffer format(Date date, StringBuffer toAppendTo, jtulach@1334: FieldDelegate delegate) { jtulach@1334: // Convert input date to time field list jtulach@1334: calendar.setTime(date); jtulach@1334: jtulach@1334: boolean useDateFormatSymbols = useDateFormatSymbols(); jtulach@1334: jtulach@1334: for (int i = 0; i < compiledPattern.length; ) { jtulach@1334: int tag = compiledPattern[i] >>> 8; jtulach@1334: int count = compiledPattern[i++] & 0xff; jtulach@1334: if (count == 255) { jtulach@1334: count = compiledPattern[i++] << 16; jtulach@1334: count |= compiledPattern[i++]; jtulach@1334: } jtulach@1334: jtulach@1334: switch (tag) { jtulach@1334: case TAG_QUOTE_ASCII_CHAR: jtulach@1334: toAppendTo.append((char)count); jtulach@1334: break; jtulach@1334: jtulach@1334: case TAG_QUOTE_CHARS: jtulach@1334: toAppendTo.append(compiledPattern, i, count); jtulach@1334: i += count; jtulach@1334: break; jtulach@1334: jtulach@1334: default: jtulach@1334: subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); jtulach@1334: break; jtulach@1334: } jtulach@1334: } jtulach@1334: return toAppendTo; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Formats an Object producing an AttributedCharacterIterator. jtulach@1334: * You can use the returned AttributedCharacterIterator jtulach@1334: * to build the resulting String, as well as to determine information jtulach@1334: * about the resulting String. jtulach@1334: *

jtulach@1334: * Each attribute key of the AttributedCharacterIterator will be of type jtulach@1334: * DateFormat.Field, with the corresponding attribute value jtulach@1334: * being the same as the attribute key. jtulach@1334: * jtulach@1334: * @exception NullPointerException if obj is null. jtulach@1334: * @exception IllegalArgumentException if the Format cannot format the jtulach@1334: * given object, or if the Format's pattern string is invalid. jtulach@1334: * @param obj The object to format jtulach@1334: * @return AttributedCharacterIterator describing the formatted value. jtulach@1334: * @since 1.4 jtulach@1334: */ jtulach@1334: public AttributedCharacterIterator formatToCharacterIterator(Object obj) { jtulach@1334: StringBuffer sb = new StringBuffer(); jtulach@1334: CharacterIteratorFieldDelegate delegate = new jtulach@1334: CharacterIteratorFieldDelegate(); jtulach@1334: jtulach@1334: if (obj instanceof Date) { jtulach@1334: format((Date)obj, sb, delegate); jtulach@1334: } jtulach@1334: else if (obj instanceof Number) { jtulach@1334: format(new Date(((Number)obj).longValue()), sb, delegate); jtulach@1334: } jtulach@1334: else if (obj == null) { jtulach@1334: throw new NullPointerException( jtulach@1334: "formatToCharacterIterator must be passed non-null object"); jtulach@1334: } jtulach@1334: else { jtulach@1334: throw new IllegalArgumentException( jtulach@1334: "Cannot format given Object as a Date"); jtulach@1334: } jtulach@1334: return delegate.getIterator(sb.toString()); jtulach@1334: } jtulach@1334: jtulach@1334: // Map index into pattern character string to Calendar field number jtulach@1334: private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = jtulach@1334: { jtulach@1334: Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE, jtulach@1334: Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE, jtulach@1334: Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK, jtulach@1334: Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH, jtulach@1334: Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, jtulach@1334: Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET, jtulach@1334: Calendar.ZONE_OFFSET, jtulach@1334: // Pseudo Calendar fields jtulach@1334: CalendarBuilder.WEEK_YEAR, jtulach@1334: CalendarBuilder.ISO_DAY_OF_WEEK, jtulach@1334: Calendar.ZONE_OFFSET jtulach@1334: }; jtulach@1334: jtulach@1334: // Map index into pattern character string to DateFormat field number jtulach@1334: private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { jtulach@1334: DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, jtulach@1334: DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, jtulach@1334: DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD, jtulach@1334: DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD, jtulach@1334: DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, jtulach@1334: DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD, jtulach@1334: DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, jtulach@1334: DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, jtulach@1334: DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD, jtulach@1334: DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD, jtulach@1334: DateFormat.TIMEZONE_FIELD jtulach@1334: }; jtulach@1334: jtulach@1334: // Maps from DecimalFormatSymbols index to Field constant jtulach@1334: private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = { jtulach@1334: Field.ERA, Field.YEAR, Field.MONTH, Field.DAY_OF_MONTH, jtulach@1334: Field.HOUR_OF_DAY1, Field.HOUR_OF_DAY0, Field.MINUTE, jtulach@1334: Field.SECOND, Field.MILLISECOND, Field.DAY_OF_WEEK, jtulach@1334: Field.DAY_OF_YEAR, Field.DAY_OF_WEEK_IN_MONTH, jtulach@1334: Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH, jtulach@1334: Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE, jtulach@1334: Field.TIME_ZONE, jtulach@1334: Field.YEAR, Field.DAY_OF_WEEK, jtulach@1334: Field.TIME_ZONE jtulach@1334: }; jtulach@1334: jtulach@1334: /** jtulach@1334: * Private member function that does the real date/time formatting. jtulach@1334: */ jtulach@1334: private void subFormat(int patternCharIndex, int count, jtulach@1334: FieldDelegate delegate, StringBuffer buffer, jtulach@1334: boolean useDateFormatSymbols) jtulach@1334: { jtulach@1334: int maxIntCount = Integer.MAX_VALUE; jtulach@1334: String current = null; jtulach@1334: int beginOffset = buffer.length(); jtulach@1334: jtulach@1334: int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; jtulach@1334: int value; jtulach@1334: if (field == CalendarBuilder.WEEK_YEAR) { jtulach@1334: if (calendar.isWeekDateSupported()) { jtulach@1334: value = calendar.getWeekYear(); jtulach@1334: } else { jtulach@1334: // use calendar year 'y' instead jtulach@1334: patternCharIndex = PATTERN_YEAR; jtulach@1334: field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; jtulach@1334: value = calendar.get(field); jtulach@1334: } jtulach@1334: } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) { jtulach@1334: value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK)); jtulach@1334: } else { jtulach@1334: value = calendar.get(field); jtulach@1334: } jtulach@1334: jtulach@1334: int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; jtulach@1334: if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) { jtulach@1334: current = calendar.getDisplayName(field, style, locale); jtulach@1334: } jtulach@1334: jtulach@1334: // Note: zeroPaddingNumber() assumes that maxDigits is either jtulach@1334: // 2 or maxIntCount. If we make any changes to this, jtulach@1334: // zeroPaddingNumber() must be fixed. jtulach@1334: jtulach@1334: switch (patternCharIndex) { jtulach@1334: case PATTERN_ERA: // 'G' jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: String[] eras = formatData.getEras(); jtulach@1334: if (value < eras.length) jtulach@1334: current = eras[value]; jtulach@1334: } jtulach@1334: if (current == null) jtulach@1334: current = ""; jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_WEEK_YEAR: // 'Y' jtulach@1334: case PATTERN_YEAR: // 'y' jtulach@1334: if (calendar instanceof GregorianCalendar) { jtulach@1334: if (count != 2) jtulach@1334: zeroPaddingNumber(value, count, maxIntCount, buffer); jtulach@1334: else // count == 2 jtulach@1334: zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96 jtulach@1334: } else { jtulach@1334: if (current == null) { jtulach@1334: zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, jtulach@1334: maxIntCount, buffer); jtulach@1334: } jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_MONTH: // 'M' jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: String[] months; jtulach@1334: if (count >= 4) { jtulach@1334: months = formatData.getMonths(); jtulach@1334: current = months[value]; jtulach@1334: } else if (count == 3) { jtulach@1334: months = formatData.getShortMonths(); jtulach@1334: current = months[value]; jtulach@1334: } jtulach@1334: } else { jtulach@1334: if (count < 3) { jtulach@1334: current = null; jtulach@1334: } jtulach@1334: } jtulach@1334: if (current == null) { jtulach@1334: zeroPaddingNumber(value+1, count, maxIntCount, buffer); jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 jtulach@1334: if (current == null) { jtulach@1334: if (value == 0) jtulach@1334: zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1, jtulach@1334: count, maxIntCount, buffer); jtulach@1334: else jtulach@1334: zeroPaddingNumber(value, count, maxIntCount, buffer); jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_DAY_OF_WEEK: // 'E' jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: String[] weekdays; jtulach@1334: if (count >= 4) { jtulach@1334: weekdays = formatData.getWeekdays(); jtulach@1334: current = weekdays[value]; jtulach@1334: } else { // count < 4, use abbreviated form if exists jtulach@1334: weekdays = formatData.getShortWeekdays(); jtulach@1334: current = weekdays[value]; jtulach@1334: } jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_AM_PM: // 'a' jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: String[] ampm = formatData.getAmPmStrings(); jtulach@1334: current = ampm[value]; jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM jtulach@1334: if (current == null) { jtulach@1334: if (value == 0) jtulach@1334: zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1, jtulach@1334: count, maxIntCount, buffer); jtulach@1334: else jtulach@1334: zeroPaddingNumber(value, count, maxIntCount, buffer); jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_ZONE_NAME: // 'z' jtulach@1334: if (current == null) { jtulach@1334: if (formatData.locale == null || formatData.isZoneStringsSet) { jtulach@1334: int zoneIndex = jtulach@1334: formatData.getZoneIndex(calendar.getTimeZone().getID()); jtulach@1334: if (zoneIndex == -1) { jtulach@1334: value = calendar.get(Calendar.ZONE_OFFSET) + jtulach@1334: calendar.get(Calendar.DST_OFFSET); jtulach@1334: buffer.append(ZoneInfoFile.toCustomID(value)); jtulach@1334: } else { jtulach@1334: int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3; jtulach@1334: if (count < 4) { jtulach@1334: // Use the short name jtulach@1334: index++; jtulach@1334: } jtulach@1334: String[][] zoneStrings = formatData.getZoneStringsWrapper(); jtulach@1334: buffer.append(zoneStrings[zoneIndex][index]); jtulach@1334: } jtulach@1334: } else { jtulach@1334: TimeZone tz = calendar.getTimeZone(); jtulach@1334: boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0); jtulach@1334: int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG); jtulach@1334: buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale)); jtulach@1334: } jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form) jtulach@1334: value = (calendar.get(Calendar.ZONE_OFFSET) + jtulach@1334: calendar.get(Calendar.DST_OFFSET)) / 60000; jtulach@1334: jtulach@1334: int width = 4; jtulach@1334: if (value >= 0) { jtulach@1334: buffer.append('+'); jtulach@1334: } else { jtulach@1334: width++; jtulach@1334: } jtulach@1334: jtulach@1334: int num = (value / 60) * 100 + (value % 60); jtulach@1334: CalendarUtils.sprintf0d(buffer, num, width); jtulach@1334: break; jtulach@1334: jtulach@1334: case PATTERN_ISO_ZONE: // 'X' jtulach@1334: value = calendar.get(Calendar.ZONE_OFFSET) jtulach@1334: + calendar.get(Calendar.DST_OFFSET); jtulach@1334: jtulach@1334: if (value == 0) { jtulach@1334: buffer.append('Z'); jtulach@1334: break; jtulach@1334: } jtulach@1334: jtulach@1334: value /= 60000; jtulach@1334: if (value >= 0) { jtulach@1334: buffer.append('+'); jtulach@1334: } else { jtulach@1334: buffer.append('-'); jtulach@1334: value = -value; jtulach@1334: } jtulach@1334: jtulach@1334: CalendarUtils.sprintf0d(buffer, value / 60, 2); jtulach@1334: if (count == 1) { jtulach@1334: break; jtulach@1334: } jtulach@1334: jtulach@1334: if (count == 3) { jtulach@1334: buffer.append(':'); jtulach@1334: } jtulach@1334: CalendarUtils.sprintf0d(buffer, value % 60, 2); jtulach@1334: break; jtulach@1334: jtulach@1334: default: jtulach@1334: // case PATTERN_DAY_OF_MONTH: // 'd' jtulach@1334: // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 jtulach@1334: // case PATTERN_MINUTE: // 'm' jtulach@1334: // case PATTERN_SECOND: // 's' jtulach@1334: // case PATTERN_MILLISECOND: // 'S' jtulach@1334: // case PATTERN_DAY_OF_YEAR: // 'D' jtulach@1334: // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' jtulach@1334: // case PATTERN_WEEK_OF_YEAR: // 'w' jtulach@1334: // case PATTERN_WEEK_OF_MONTH: // 'W' jtulach@1334: // case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM jtulach@1334: // case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7 jtulach@1334: if (current == null) { jtulach@1334: zeroPaddingNumber(value, count, maxIntCount, buffer); jtulach@1334: } jtulach@1334: break; jtulach@1334: } // switch (patternCharIndex) jtulach@1334: jtulach@1334: if (current != null) { jtulach@1334: buffer.append(current); jtulach@1334: } jtulach@1334: jtulach@1334: int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]; jtulach@1334: Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex]; jtulach@1334: jtulach@1334: delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Formats a number with the specified minimum and maximum number of digits. jtulach@1334: */ jtulach@1334: private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer) jtulach@1334: { jtulach@1334: // Optimization for 1, 2 and 4 digit numbers. This should jtulach@1334: // cover most cases of formatting date/time related items. jtulach@1334: // Note: This optimization code assumes that maxDigits is jtulach@1334: // either 2 or Integer.MAX_VALUE (maxIntCount in format()). jtulach@1334: try { jtulach@1334: if (zeroDigit == 0) { jtulach@1334: zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit(); jtulach@1334: } jtulach@1334: if (value >= 0) { jtulach@1334: if (value < 100 && minDigits >= 1 && minDigits <= 2) { jtulach@1334: if (value < 10) { jtulach@1334: if (minDigits == 2) { jtulach@1334: buffer.append(zeroDigit); jtulach@1334: } jtulach@1334: buffer.append((char)(zeroDigit + value)); jtulach@1334: } else { jtulach@1334: buffer.append((char)(zeroDigit + value / 10)); jtulach@1334: buffer.append((char)(zeroDigit + value % 10)); jtulach@1334: } jtulach@1334: return; jtulach@1334: } else if (value >= 1000 && value < 10000) { jtulach@1334: if (minDigits == 4) { jtulach@1334: buffer.append((char)(zeroDigit + value / 1000)); jtulach@1334: value %= 1000; jtulach@1334: buffer.append((char)(zeroDigit + value / 100)); jtulach@1334: value %= 100; jtulach@1334: buffer.append((char)(zeroDigit + value / 10)); jtulach@1334: buffer.append((char)(zeroDigit + value % 10)); jtulach@1334: return; jtulach@1334: } jtulach@1334: if (minDigits == 2 && maxDigits == 2) { jtulach@1334: zeroPaddingNumber(value % 100, 2, 2, buffer); jtulach@1334: return; jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: } catch (Exception e) { jtulach@1334: } jtulach@1334: jtulach@1334: numberFormat.setMinimumIntegerDigits(minDigits); jtulach@1334: numberFormat.setMaximumIntegerDigits(maxDigits); jtulach@1334: numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE); jtulach@1334: } jtulach@1334: jtulach@1334: jtulach@1334: /** jtulach@1334: * Parses text from a string to produce a Date. jtulach@1334: *

jtulach@1334: * The method attempts to parse text starting at the index given by jtulach@1334: * pos. jtulach@1334: * If parsing succeeds, then the index of pos is updated jtulach@1334: * to the index after the last character used (parsing does not necessarily jtulach@1334: * use all characters up to the end of the string), and the parsed jtulach@1334: * date is returned. The updated pos can be used to jtulach@1334: * indicate the starting point for the next call to this method. jtulach@1334: * If an error occurs, then the index of pos is not jtulach@1334: * changed, the error index of pos is set to the index of jtulach@1334: * the character where the error occurred, and null is returned. jtulach@1334: * jtulach@1334: *

This parsing operation uses the {@link DateFormat#calendar jtulach@1334: * calendar} to produce a {@code Date}. All of the {@code jtulach@1334: * calendar}'s date-time fields are {@linkplain Calendar#clear() jtulach@1334: * cleared} before parsing, and the {@code calendar}'s default jtulach@1334: * values of the date-time fields are used for any missing jtulach@1334: * date-time information. For example, the year value of the jtulach@1334: * parsed {@code Date} is 1970 with {@link GregorianCalendar} if jtulach@1334: * no year value is given from the parsing operation. The {@code jtulach@1334: * TimeZone} value may be overwritten, depending on the given jtulach@1334: * pattern and the time zone value in {@code text}. Any {@code jtulach@1334: * TimeZone} value that has previously been set by a call to jtulach@1334: * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need jtulach@1334: * to be restored for further operations. jtulach@1334: * jtulach@1334: * @param text A String, part of which should be parsed. jtulach@1334: * @param pos A ParsePosition object with index and error jtulach@1334: * index information as described above. jtulach@1334: * @return A Date parsed from the string. In case of jtulach@1334: * error, returns null. jtulach@1334: * @exception NullPointerException if text or pos is null. jtulach@1334: */ jtulach@1334: public Date parse(String text, ParsePosition pos) jtulach@1334: { jtulach@1334: checkNegativeNumberExpression(); jtulach@1334: jtulach@1334: int start = pos.index; jtulach@1334: int oldStart = start; jtulach@1334: int textLength = text.length(); jtulach@1334: jtulach@1334: boolean[] ambiguousYear = {false}; jtulach@1334: jtulach@1334: CalendarBuilder calb = new CalendarBuilder(); jtulach@1334: jtulach@1334: for (int i = 0; i < compiledPattern.length; ) { jtulach@1334: int tag = compiledPattern[i] >>> 8; jtulach@1334: int count = compiledPattern[i++] & 0xff; jtulach@1334: if (count == 255) { jtulach@1334: count = compiledPattern[i++] << 16; jtulach@1334: count |= compiledPattern[i++]; jtulach@1334: } jtulach@1334: jtulach@1334: switch (tag) { jtulach@1334: case TAG_QUOTE_ASCII_CHAR: jtulach@1334: if (start >= textLength || text.charAt(start) != (char)count) { jtulach@1334: pos.index = oldStart; jtulach@1334: pos.errorIndex = start; jtulach@1334: return null; jtulach@1334: } jtulach@1334: start++; jtulach@1334: break; jtulach@1334: jtulach@1334: case TAG_QUOTE_CHARS: jtulach@1334: while (count-- > 0) { jtulach@1334: if (start >= textLength || text.charAt(start) != compiledPattern[i++]) { jtulach@1334: pos.index = oldStart; jtulach@1334: pos.errorIndex = start; jtulach@1334: return null; jtulach@1334: } jtulach@1334: start++; jtulach@1334: } jtulach@1334: break; jtulach@1334: jtulach@1334: default: jtulach@1334: // Peek the next pattern to determine if we need to jtulach@1334: // obey the number of pattern letters for jtulach@1334: // parsing. It's required when parsing contiguous jtulach@1334: // digit text (e.g., "20010704") with a pattern which jtulach@1334: // has no delimiters between fields, like "yyyyMMdd". jtulach@1334: boolean obeyCount = false; jtulach@1334: jtulach@1334: // In Arabic, a minus sign for a negative number is put after jtulach@1334: // the number. Even in another locale, a minus sign can be jtulach@1334: // put after a number using DateFormat.setNumberFormat(). jtulach@1334: // If both the minus sign and the field-delimiter are '-', jtulach@1334: // subParse() needs to determine whether a '-' after a number jtulach@1334: // in the given text is a delimiter or is a minus sign for the jtulach@1334: // preceding number. We give subParse() a clue based on the jtulach@1334: // information in compiledPattern. jtulach@1334: boolean useFollowingMinusSignAsDelimiter = false; jtulach@1334: jtulach@1334: if (i < compiledPattern.length) { jtulach@1334: int nextTag = compiledPattern[i] >>> 8; jtulach@1334: if (!(nextTag == TAG_QUOTE_ASCII_CHAR || jtulach@1334: nextTag == TAG_QUOTE_CHARS)) { jtulach@1334: obeyCount = true; jtulach@1334: } jtulach@1334: jtulach@1334: if (hasFollowingMinusSign && jtulach@1334: (nextTag == TAG_QUOTE_ASCII_CHAR || jtulach@1334: nextTag == TAG_QUOTE_CHARS)) { jtulach@1334: int c; jtulach@1334: if (nextTag == TAG_QUOTE_ASCII_CHAR) { jtulach@1334: c = compiledPattern[i] & 0xff; jtulach@1334: } else { jtulach@1334: c = compiledPattern[i+1]; jtulach@1334: } jtulach@1334: jtulach@1334: if (c == minusSign) { jtulach@1334: useFollowingMinusSignAsDelimiter = true; jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: start = subParse(text, start, tag, count, obeyCount, jtulach@1334: ambiguousYear, pos, jtulach@1334: useFollowingMinusSignAsDelimiter, calb); jtulach@1334: if (start < 0) { jtulach@1334: pos.index = oldStart; jtulach@1334: return null; jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: // At this point the fields of Calendar have been set. Calendar jtulach@1334: // will fill in default values for missing fields when the time jtulach@1334: // is computed. jtulach@1334: jtulach@1334: pos.index = start; jtulach@1334: jtulach@1334: Date parsedDate; jtulach@1334: try { jtulach@1334: parsedDate = calb.establish(calendar).getTime(); jtulach@1334: // If the year value is ambiguous, jtulach@1334: // then the two-digit year == the default start year jtulach@1334: if (ambiguousYear[0]) { jtulach@1334: if (parsedDate.before(defaultCenturyStart)) { jtulach@1334: parsedDate = calb.addYear(100).establish(calendar).getTime(); jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: // An IllegalArgumentException will be thrown by Calendar.getTime() jtulach@1334: // if any fields are out of range, e.g., MONTH == 17. jtulach@1334: catch (IllegalArgumentException e) { jtulach@1334: pos.errorIndex = start; jtulach@1334: pos.index = oldStart; jtulach@1334: return null; jtulach@1334: } jtulach@1334: jtulach@1334: return parsedDate; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Private code-size reduction function used by subParse. jtulach@1334: * @param text the time text being parsed. jtulach@1334: * @param start where to start parsing. jtulach@1334: * @param field the date field being parsed. jtulach@1334: * @param data the string array to parsed. jtulach@1334: * @return the new start position if matching succeeded; a negative number jtulach@1334: * indicating matching failure, otherwise. jtulach@1334: */ jtulach@1334: private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb) jtulach@1334: { jtulach@1334: int i = 0; jtulach@1334: int count = data.length; jtulach@1334: jtulach@1334: if (field == Calendar.DAY_OF_WEEK) i = 1; jtulach@1334: jtulach@1334: // There may be multiple strings in the data[] array which begin with jtulach@1334: // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). jtulach@1334: // We keep track of the longest match, and return that. Note that this jtulach@1334: // unfortunately requires us to test all array elements. jtulach@1334: int bestMatchLength = 0, bestMatch = -1; jtulach@1334: for (; i bestMatchLength && jtulach@1334: text.regionMatches(true, start, data[i], 0, length)) jtulach@1334: { jtulach@1334: bestMatch = i; jtulach@1334: bestMatchLength = length; jtulach@1334: } jtulach@1334: } jtulach@1334: if (bestMatch >= 0) jtulach@1334: { jtulach@1334: calb.set(field, bestMatch); jtulach@1334: return start + bestMatchLength; jtulach@1334: } jtulach@1334: return -start; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Performs the same thing as matchString(String, int, int, jtulach@1334: * String[]). This method takes a Map instead of jtulach@1334: * String[]. jtulach@1334: */ jtulach@1334: private int matchString(String text, int start, int field, jtulach@1334: Map data, CalendarBuilder calb) { jtulach@1334: if (data != null) { jtulach@1334: String bestMatch = null; jtulach@1334: jtulach@1334: for (String name : data.keySet()) { jtulach@1334: int length = name.length(); jtulach@1334: if (bestMatch == null || length > bestMatch.length()) { jtulach@1334: if (text.regionMatches(true, start, name, 0, length)) { jtulach@1334: bestMatch = name; jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: if (bestMatch != null) { jtulach@1334: calb.set(field, data.get(bestMatch)); jtulach@1334: return start + bestMatch.length(); jtulach@1334: } jtulach@1334: } jtulach@1334: return -start; jtulach@1334: } jtulach@1334: jtulach@1334: private int matchZoneString(String text, int start, String[] zoneNames) { jtulach@1334: for (int i = 1; i <= 4; ++i) { jtulach@1334: // Checking long and short zones [1 & 2], jtulach@1334: // and long and short daylight [3 & 4]. jtulach@1334: String zoneName = zoneNames[i]; jtulach@1334: if (text.regionMatches(true, start, jtulach@1334: zoneName, 0, zoneName.length())) { jtulach@1334: return i; jtulach@1334: } jtulach@1334: } jtulach@1334: return -1; jtulach@1334: } jtulach@1334: jtulach@1334: private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex, jtulach@1334: String[][] zoneStrings) { jtulach@1334: int index = standardIndex + 2; jtulach@1334: String zoneName = zoneStrings[zoneIndex][index]; jtulach@1334: if (text.regionMatches(true, start, jtulach@1334: zoneName, 0, zoneName.length())) { jtulach@1334: return true; jtulach@1334: } jtulach@1334: return false; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * find time zone 'text' matched zoneStrings and set to internal jtulach@1334: * calendar. jtulach@1334: */ jtulach@1334: private int subParseZoneString(String text, int start, CalendarBuilder calb) { jtulach@1334: boolean useSameName = false; // true if standard and daylight time use the same abbreviation. jtulach@1334: TimeZone currentTimeZone = getTimeZone(); jtulach@1334: jtulach@1334: // At this point, check for named time zones by looking through jtulach@1334: // the locale data from the TimeZoneNames strings. jtulach@1334: // Want to be able to parse both short and long forms. jtulach@1334: int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID()); jtulach@1334: TimeZone tz = null; jtulach@1334: String[][] zoneStrings = formatData.getZoneStringsWrapper(); jtulach@1334: String[] zoneNames = null; jtulach@1334: int nameIndex = 0; jtulach@1334: if (zoneIndex != -1) { jtulach@1334: zoneNames = zoneStrings[zoneIndex]; jtulach@1334: if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { jtulach@1334: if (nameIndex <= 2) { jtulach@1334: // Check if the standard name (abbr) and the daylight name are the same. jtulach@1334: useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); jtulach@1334: } jtulach@1334: tz = TimeZone.getTimeZone(zoneNames[0]); jtulach@1334: } jtulach@1334: } jtulach@1334: if (tz == null) { jtulach@1334: zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID()); jtulach@1334: if (zoneIndex != -1) { jtulach@1334: zoneNames = zoneStrings[zoneIndex]; jtulach@1334: if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { jtulach@1334: if (nameIndex <= 2) { jtulach@1334: useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); jtulach@1334: } jtulach@1334: tz = TimeZone.getTimeZone(zoneNames[0]); jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: if (tz == null) { jtulach@1334: int len = zoneStrings.length; jtulach@1334: for (int i = 0; i < len; i++) { jtulach@1334: zoneNames = zoneStrings[i]; jtulach@1334: if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { jtulach@1334: if (nameIndex <= 2) { jtulach@1334: useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); jtulach@1334: } jtulach@1334: tz = TimeZone.getTimeZone(zoneNames[0]); jtulach@1334: break; jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: if (tz != null) { // Matched any ? jtulach@1334: if (!tz.equals(currentTimeZone)) { jtulach@1334: setTimeZone(tz); jtulach@1334: } jtulach@1334: // If the time zone matched uses the same name jtulach@1334: // (abbreviation) for both standard and daylight time, jtulach@1334: // let the time zone in the Calendar decide which one. jtulach@1334: // jtulach@1334: // Also if tz.getDSTSaving() returns 0 for DST, use tz to jtulach@1334: // determine the local time. (6645292) jtulach@1334: int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0; jtulach@1334: if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) { jtulach@1334: calb.set(Calendar.ZONE_OFFSET, tz.getRawOffset()) jtulach@1334: .set(Calendar.DST_OFFSET, dstAmount); jtulach@1334: } jtulach@1334: return (start + zoneNames[nameIndex].length()); jtulach@1334: } jtulach@1334: return 0; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Parses numeric forms of time zone offset, such as "hh:mm", and jtulach@1334: * sets calb to the parsed value. jtulach@1334: * jtulach@1334: * @param text the text to be parsed jtulach@1334: * @param start the character position to start parsing jtulach@1334: * @param sign 1: positive; -1: negative jtulach@1334: * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's jtulach@1334: * @param colon true - colon required between hh and mm; false - no colon required jtulach@1334: * @param calb a CalendarBuilder in which the parsed value is stored jtulach@1334: * @return updated parsed position, or its negative value to indicate a parsing error jtulach@1334: */ jtulach@1334: private int subParseNumericZone(String text, int start, int sign, int count, jtulach@1334: boolean colon, CalendarBuilder calb) { jtulach@1334: int index = start; jtulach@1334: jtulach@1334: parse: jtulach@1334: try { jtulach@1334: char c = text.charAt(index++); jtulach@1334: // Parse hh jtulach@1334: int hours; jtulach@1334: if (!isDigit(c)) { jtulach@1334: break parse; jtulach@1334: } jtulach@1334: hours = c - '0'; jtulach@1334: c = text.charAt(index++); jtulach@1334: if (isDigit(c)) { jtulach@1334: hours = hours * 10 + (c - '0'); jtulach@1334: } else { jtulach@1334: // If no colon in RFC 822 or 'X' (ISO), two digits are jtulach@1334: // required. jtulach@1334: if (count > 0 || !colon) { jtulach@1334: break parse; jtulach@1334: } jtulach@1334: --index; jtulach@1334: } jtulach@1334: if (hours > 23) { jtulach@1334: break parse; jtulach@1334: } jtulach@1334: int minutes = 0; jtulach@1334: if (count != 1) { jtulach@1334: // Proceed with parsing mm jtulach@1334: c = text.charAt(index++); jtulach@1334: if (colon) { jtulach@1334: if (c != ':') { jtulach@1334: break parse; jtulach@1334: } jtulach@1334: c = text.charAt(index++); jtulach@1334: } jtulach@1334: if (!isDigit(c)) { jtulach@1334: break parse; jtulach@1334: } jtulach@1334: minutes = c - '0'; jtulach@1334: c = text.charAt(index++); jtulach@1334: if (!isDigit(c)) { jtulach@1334: break parse; jtulach@1334: } jtulach@1334: minutes = minutes * 10 + (c - '0'); jtulach@1334: if (minutes > 59) { jtulach@1334: break parse; jtulach@1334: } jtulach@1334: } jtulach@1334: minutes += hours * 60; jtulach@1334: calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign) jtulach@1334: .set(Calendar.DST_OFFSET, 0); jtulach@1334: return index; jtulach@1334: } catch (IndexOutOfBoundsException e) { jtulach@1334: } jtulach@1334: return 1 - index; // -(index - 1) jtulach@1334: } jtulach@1334: jtulach@1334: private boolean isDigit(char c) { jtulach@1334: return c >= '0' && c <= '9'; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Private member function that converts the parsed date strings into jtulach@1334: * timeFields. Returns -start (for ParsePosition) if failed. jtulach@1334: * @param text the time text to be parsed. jtulach@1334: * @param start where to start parsing. jtulach@1334: * @param ch the pattern character for the date field text to be parsed. jtulach@1334: * @param count the count of a pattern character. jtulach@1334: * @param obeyCount if true, then the next field directly abuts this one, jtulach@1334: * and we should use the count to know when to stop parsing. jtulach@1334: * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] jtulach@1334: * is true, then a two-digit year was parsed and may need to be readjusted. jtulach@1334: * @param origPos origPos.errorIndex is used to return an error index jtulach@1334: * at which a parse error occurred, if matching failure occurs. jtulach@1334: * @return the new start position if matching succeeded; -1 indicating jtulach@1334: * matching failure, otherwise. In case matching failure occurred, jtulach@1334: * an error index is set to origPos.errorIndex. jtulach@1334: */ jtulach@1334: private int subParse(String text, int start, int patternCharIndex, int count, jtulach@1334: boolean obeyCount, boolean[] ambiguousYear, jtulach@1334: ParsePosition origPos, jtulach@1334: boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) { jtulach@1334: Number number = null; jtulach@1334: int value = 0; jtulach@1334: ParsePosition pos = new ParsePosition(0); jtulach@1334: pos.index = start; jtulach@1334: if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) { jtulach@1334: // use calendar year 'y' instead jtulach@1334: patternCharIndex = PATTERN_YEAR; jtulach@1334: } jtulach@1334: int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; jtulach@1334: jtulach@1334: // If there are any spaces here, skip over them. If we hit the end jtulach@1334: // of the string, then fail. jtulach@1334: for (;;) { jtulach@1334: if (pos.index >= text.length()) { jtulach@1334: origPos.errorIndex = start; jtulach@1334: return -1; jtulach@1334: } jtulach@1334: char c = text.charAt(pos.index); jtulach@1334: if (c != ' ' && c != '\t') break; jtulach@1334: ++pos.index; jtulach@1334: } jtulach@1334: jtulach@1334: parsing: jtulach@1334: { jtulach@1334: // We handle a few special cases here where we need to parse jtulach@1334: // a number value. We handle further, more generic cases below. We need jtulach@1334: // to handle some of them here because some fields require extra processing on jtulach@1334: // the parsed value. jtulach@1334: if (patternCharIndex == PATTERN_HOUR_OF_DAY1 || jtulach@1334: patternCharIndex == PATTERN_HOUR1 || jtulach@1334: (patternCharIndex == PATTERN_MONTH && count <= 2) || jtulach@1334: patternCharIndex == PATTERN_YEAR || jtulach@1334: patternCharIndex == PATTERN_WEEK_YEAR) { jtulach@1334: // It would be good to unify this with the obeyCount logic below, jtulach@1334: // but that's going to be difficult. jtulach@1334: if (obeyCount) { jtulach@1334: if ((start+count) > text.length()) { jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: number = numberFormat.parse(text.substring(0, start+count), pos); jtulach@1334: } else { jtulach@1334: number = numberFormat.parse(text, pos); jtulach@1334: } jtulach@1334: if (number == null) { jtulach@1334: if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) { jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: } else { jtulach@1334: value = number.intValue(); jtulach@1334: jtulach@1334: if (useFollowingMinusSignAsDelimiter && (value < 0) && jtulach@1334: (((pos.index < text.length()) && jtulach@1334: (text.charAt(pos.index) != minusSign)) || jtulach@1334: ((pos.index == text.length()) && jtulach@1334: (text.charAt(pos.index-1) == minusSign)))) { jtulach@1334: value = -value; jtulach@1334: pos.index--; jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: boolean useDateFormatSymbols = useDateFormatSymbols(); jtulach@1334: jtulach@1334: int index; jtulach@1334: switch (patternCharIndex) { jtulach@1334: case PATTERN_ERA: // 'G' jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } else { jtulach@1334: Map map = calendar.getDisplayNames(field, jtulach@1334: Calendar.ALL_STYLES, jtulach@1334: locale); jtulach@1334: if ((index = matchString(text, start, field, map, calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } jtulach@1334: break parsing; jtulach@1334: jtulach@1334: case PATTERN_WEEK_YEAR: // 'Y' jtulach@1334: case PATTERN_YEAR: // 'y' jtulach@1334: if (!(calendar instanceof GregorianCalendar)) { jtulach@1334: // calendar might have text representations for year values, jtulach@1334: // such as "\u5143" in JapaneseImperialCalendar. jtulach@1334: int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; jtulach@1334: Map map = calendar.getDisplayNames(field, style, locale); jtulach@1334: if (map != null) { jtulach@1334: if ((index = matchString(text, start, field, map, calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } jtulach@1334: calb.set(field, value); jtulach@1334: return pos.index; jtulach@1334: } jtulach@1334: jtulach@1334: // If there are 3 or more YEAR pattern characters, this indicates jtulach@1334: // that the year value is to be treated literally, without any jtulach@1334: // two-digit year adjustments (e.g., from "01" to 2001). Otherwise jtulach@1334: // we made adjustments to place the 2-digit year in the proper jtulach@1334: // century, for parsed strings from "00" to "99". Any other string jtulach@1334: // is treated literally: "2250", "-1", "1", "002". jtulach@1334: if (count <= 2 && (pos.index - start) == 2 jtulach@1334: && Character.isDigit(text.charAt(start)) jtulach@1334: && Character.isDigit(text.charAt(start+1))) { jtulach@1334: // Assume for example that the defaultCenturyStart is 6/18/1903. jtulach@1334: // This means that two-digit years will be forced into the range jtulach@1334: // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 jtulach@1334: // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond jtulach@1334: // to 1904, 1905, etc. If the year is 03, then it is 2003 if the jtulach@1334: // other fields specify a date before 6/18, or 1903 if they specify a jtulach@1334: // date afterwards. As a result, 03 is an ambiguous year. All other jtulach@1334: // two-digit years are unambiguous. jtulach@1334: int ambiguousTwoDigitYear = defaultCenturyStartYear % 100; jtulach@1334: ambiguousYear[0] = value == ambiguousTwoDigitYear; jtulach@1334: value += (defaultCenturyStartYear/100)*100 + jtulach@1334: (value < ambiguousTwoDigitYear ? 100 : 0); jtulach@1334: } jtulach@1334: calb.set(field, value); jtulach@1334: return pos.index; jtulach@1334: jtulach@1334: case PATTERN_MONTH: // 'M' jtulach@1334: if (count <= 2) // i.e., M or MM. jtulach@1334: { jtulach@1334: // Don't want to parse the month if it is a string jtulach@1334: // while pattern uses numeric style: M or MM. jtulach@1334: // [We computed 'value' above.] jtulach@1334: calb.set(Calendar.MONTH, value - 1); jtulach@1334: return pos.index; jtulach@1334: } jtulach@1334: jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: // count >= 3 // i.e., MMM or MMMM jtulach@1334: // Want to be able to parse both short and long forms. jtulach@1334: // Try count == 4 first: jtulach@1334: int newStart = 0; jtulach@1334: if ((newStart = matchString(text, start, Calendar.MONTH, jtulach@1334: formatData.getMonths(), calb)) > 0) { jtulach@1334: return newStart; jtulach@1334: } jtulach@1334: // count == 4 failed, now try count == 3 jtulach@1334: if ((index = matchString(text, start, Calendar.MONTH, jtulach@1334: formatData.getShortMonths(), calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } else { jtulach@1334: Map map = calendar.getDisplayNames(field, jtulach@1334: Calendar.ALL_STYLES, jtulach@1334: locale); jtulach@1334: if ((index = matchString(text, start, field, map, calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } jtulach@1334: break parsing; jtulach@1334: jtulach@1334: case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 jtulach@1334: if (!isLenient()) { jtulach@1334: // Validate the hour value in non-lenient jtulach@1334: if (value < 1 || value > 24) { jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: } jtulach@1334: // [We computed 'value' above.] jtulach@1334: if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) jtulach@1334: value = 0; jtulach@1334: calb.set(Calendar.HOUR_OF_DAY, value); jtulach@1334: return pos.index; jtulach@1334: jtulach@1334: case PATTERN_DAY_OF_WEEK: // 'E' jtulach@1334: { jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: // Want to be able to parse both short and long forms. jtulach@1334: // Try count == 4 (DDDD) first: jtulach@1334: int newStart = 0; jtulach@1334: if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK, jtulach@1334: formatData.getWeekdays(), calb)) > 0) { jtulach@1334: return newStart; jtulach@1334: } jtulach@1334: // DDDD failed, now try DDD jtulach@1334: if ((index = matchString(text, start, Calendar.DAY_OF_WEEK, jtulach@1334: formatData.getShortWeekdays(), calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } else { jtulach@1334: int[] styles = { Calendar.LONG, Calendar.SHORT }; jtulach@1334: for (int style : styles) { jtulach@1334: Map map = calendar.getDisplayNames(field, style, locale); jtulach@1334: if ((index = matchString(text, start, field, map, calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: break parsing; jtulach@1334: jtulach@1334: case PATTERN_AM_PM: // 'a' jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: if ((index = matchString(text, start, Calendar.AM_PM, jtulach@1334: formatData.getAmPmStrings(), calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } else { jtulach@1334: Map map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale); jtulach@1334: if ((index = matchString(text, start, field, map, calb)) > 0) { jtulach@1334: return index; jtulach@1334: } jtulach@1334: } jtulach@1334: break parsing; jtulach@1334: jtulach@1334: case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM jtulach@1334: if (!isLenient()) { jtulach@1334: // Validate the hour value in non-lenient jtulach@1334: if (value < 1 || value > 12) { jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: } jtulach@1334: // [We computed 'value' above.] jtulach@1334: if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) jtulach@1334: value = 0; jtulach@1334: calb.set(Calendar.HOUR, value); jtulach@1334: return pos.index; jtulach@1334: jtulach@1334: case PATTERN_ZONE_NAME: // 'z' jtulach@1334: case PATTERN_ZONE_VALUE: // 'Z' jtulach@1334: { jtulach@1334: int sign = 0; jtulach@1334: try { jtulach@1334: char c = text.charAt(pos.index); jtulach@1334: if (c == '+') { jtulach@1334: sign = 1; jtulach@1334: } else if (c == '-') { jtulach@1334: sign = -1; jtulach@1334: } jtulach@1334: if (sign == 0) { jtulach@1334: // Try parsing a custom time zone "GMT+hh:mm" or "GMT". jtulach@1334: if ((c == 'G' || c == 'g') jtulach@1334: && (text.length() - start) >= GMT.length() jtulach@1334: && text.regionMatches(true, start, GMT, 0, GMT.length())) { jtulach@1334: pos.index = start + GMT.length(); jtulach@1334: jtulach@1334: if ((text.length() - pos.index) > 0) { jtulach@1334: c = text.charAt(pos.index); jtulach@1334: if (c == '+') { jtulach@1334: sign = 1; jtulach@1334: } else if (c == '-') { jtulach@1334: sign = -1; jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: if (sign == 0) { /* "GMT" without offset */ jtulach@1334: calb.set(Calendar.ZONE_OFFSET, 0) jtulach@1334: .set(Calendar.DST_OFFSET, 0); jtulach@1334: return pos.index; jtulach@1334: } jtulach@1334: jtulach@1334: // Parse the rest as "hh:mm" jtulach@1334: int i = subParseNumericZone(text, ++pos.index, jtulach@1334: sign, 0, true, calb); jtulach@1334: if (i > 0) { jtulach@1334: return i; jtulach@1334: } jtulach@1334: pos.index = -i; jtulach@1334: } else { jtulach@1334: // Try parsing the text as a time zone jtulach@1334: // name or abbreviation. jtulach@1334: int i = subParseZoneString(text, pos.index, calb); jtulach@1334: if (i > 0) { jtulach@1334: return i; jtulach@1334: } jtulach@1334: pos.index = -i; jtulach@1334: } jtulach@1334: } else { jtulach@1334: // Parse the rest as "hhmm" (RFC 822) jtulach@1334: int i = subParseNumericZone(text, ++pos.index, jtulach@1334: sign, 0, false, calb); jtulach@1334: if (i > 0) { jtulach@1334: return i; jtulach@1334: } jtulach@1334: pos.index = -i; jtulach@1334: } jtulach@1334: } catch (IndexOutOfBoundsException e) { jtulach@1334: } jtulach@1334: } jtulach@1334: break parsing; jtulach@1334: jtulach@1334: case PATTERN_ISO_ZONE: // 'X' jtulach@1334: { jtulach@1334: if ((text.length() - pos.index) <= 0) { jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: jtulach@1334: int sign = 0; jtulach@1334: char c = text.charAt(pos.index); jtulach@1334: if (c == 'Z') { jtulach@1334: calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0); jtulach@1334: return ++pos.index; jtulach@1334: } jtulach@1334: jtulach@1334: // parse text as "+/-hh[[:]mm]" based on count jtulach@1334: if (c == '+') { jtulach@1334: sign = 1; jtulach@1334: } else if (c == '-') { jtulach@1334: sign = -1; jtulach@1334: } else { jtulach@1334: ++pos.index; jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: int i = subParseNumericZone(text, ++pos.index, sign, count, jtulach@1334: count == 3, calb); jtulach@1334: if (i > 0) { jtulach@1334: return i; jtulach@1334: } jtulach@1334: pos.index = -i; jtulach@1334: } jtulach@1334: break parsing; jtulach@1334: jtulach@1334: default: jtulach@1334: // case PATTERN_DAY_OF_MONTH: // 'd' jtulach@1334: // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 jtulach@1334: // case PATTERN_MINUTE: // 'm' jtulach@1334: // case PATTERN_SECOND: // 's' jtulach@1334: // case PATTERN_MILLISECOND: // 'S' jtulach@1334: // case PATTERN_DAY_OF_YEAR: // 'D' jtulach@1334: // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' jtulach@1334: // case PATTERN_WEEK_OF_YEAR: // 'w' jtulach@1334: // case PATTERN_WEEK_OF_MONTH: // 'W' jtulach@1334: // case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM jtulach@1334: // case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field); jtulach@1334: jtulach@1334: // Handle "generic" fields jtulach@1334: if (obeyCount) { jtulach@1334: if ((start+count) > text.length()) { jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: number = numberFormat.parse(text.substring(0, start+count), pos); jtulach@1334: } else { jtulach@1334: number = numberFormat.parse(text, pos); jtulach@1334: } jtulach@1334: if (number != null) { jtulach@1334: value = number.intValue(); jtulach@1334: jtulach@1334: if (useFollowingMinusSignAsDelimiter && (value < 0) && jtulach@1334: (((pos.index < text.length()) && jtulach@1334: (text.charAt(pos.index) != minusSign)) || jtulach@1334: ((pos.index == text.length()) && jtulach@1334: (text.charAt(pos.index-1) == minusSign)))) { jtulach@1334: value = -value; jtulach@1334: pos.index--; jtulach@1334: } jtulach@1334: jtulach@1334: calb.set(field, value); jtulach@1334: return pos.index; jtulach@1334: } jtulach@1334: break parsing; jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: // Parsing failed. jtulach@1334: origPos.errorIndex = pos.index; jtulach@1334: return -1; jtulach@1334: } jtulach@1334: jtulach@1334: private final String getCalendarName() { jtulach@1334: return calendar.getClass().getName(); jtulach@1334: } jtulach@1334: jtulach@1334: private boolean useDateFormatSymbols() { jtulach@1334: if (useDateFormatSymbols) { jtulach@1334: return true; jtulach@1334: } jtulach@1334: return isGregorianCalendar() || locale == null; jtulach@1334: } jtulach@1334: jtulach@1334: private boolean isGregorianCalendar() { jtulach@1334: return "java.util.GregorianCalendar".equals(getCalendarName()); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Translates a pattern, mapping each character in the from string to the jtulach@1334: * corresponding character in the to string. jtulach@1334: * jtulach@1334: * @exception IllegalArgumentException if the given pattern is invalid jtulach@1334: */ jtulach@1334: private String translatePattern(String pattern, String from, String to) { jtulach@1334: StringBuilder result = new StringBuilder(); jtulach@1334: boolean inQuote = false; jtulach@1334: for (int i = 0; i < pattern.length(); ++i) { jtulach@1334: char c = pattern.charAt(i); jtulach@1334: if (inQuote) { jtulach@1334: if (c == '\'') jtulach@1334: inQuote = false; jtulach@1334: } jtulach@1334: else { jtulach@1334: if (c == '\'') jtulach@1334: inQuote = true; jtulach@1334: else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { jtulach@1334: int ci = from.indexOf(c); jtulach@1334: if (ci >= 0) { jtulach@1334: // patternChars is longer than localPatternChars due jtulach@1334: // to serialization compatibility. The pattern letters jtulach@1334: // unsupported by localPatternChars pass through. jtulach@1334: if (ci < to.length()) { jtulach@1334: c = to.charAt(ci); jtulach@1334: } jtulach@1334: } else { jtulach@1334: throw new IllegalArgumentException("Illegal pattern " + jtulach@1334: " character '" + jtulach@1334: c + "'"); jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: result.append(c); jtulach@1334: } jtulach@1334: if (inQuote) jtulach@1334: throw new IllegalArgumentException("Unfinished quote in pattern"); jtulach@1334: return result.toString(); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Returns a pattern string describing this date format. jtulach@1334: * jtulach@1334: * @return a pattern string describing this date format. jtulach@1334: */ jtulach@1334: public String toPattern() { jtulach@1334: return pattern; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Returns a localized pattern string describing this date format. jtulach@1334: * jtulach@1334: * @return a localized pattern string describing this date format. jtulach@1334: */ jtulach@1334: public String toLocalizedPattern() { jtulach@1334: return translatePattern(pattern, jtulach@1334: DateFormatSymbols.patternChars, jtulach@1334: formatData.getLocalPatternChars()); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Applies the given pattern string to this date format. jtulach@1334: * jtulach@1334: * @param pattern the new date and time pattern for this date format jtulach@1334: * @exception NullPointerException if the given pattern is null jtulach@1334: * @exception IllegalArgumentException if the given pattern is invalid jtulach@1334: */ jtulach@1334: public void applyPattern(String pattern) jtulach@1334: { jtulach@1334: compiledPattern = compile(pattern); jtulach@1334: this.pattern = pattern; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Applies the given localized pattern string to this date format. jtulach@1334: * jtulach@1334: * @param pattern a String to be mapped to the new date and time format jtulach@1334: * pattern for this format jtulach@1334: * @exception NullPointerException if the given pattern is null jtulach@1334: * @exception IllegalArgumentException if the given pattern is invalid jtulach@1334: */ jtulach@1334: public void applyLocalizedPattern(String pattern) { jtulach@1334: String p = translatePattern(pattern, jtulach@1334: formatData.getLocalPatternChars(), jtulach@1334: DateFormatSymbols.patternChars); jtulach@1334: compiledPattern = compile(p); jtulach@1334: this.pattern = p; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Gets a copy of the date and time format symbols of this date format. jtulach@1334: * jtulach@1334: * @return the date and time format symbols of this date format jtulach@1334: * @see #setDateFormatSymbols jtulach@1334: */ jtulach@1334: public DateFormatSymbols getDateFormatSymbols() jtulach@1334: { jtulach@1334: return (DateFormatSymbols)formatData.clone(); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Sets the date and time format symbols of this date format. jtulach@1334: * jtulach@1334: * @param newFormatSymbols the new date and time format symbols jtulach@1334: * @exception NullPointerException if the given newFormatSymbols is null jtulach@1334: * @see #getDateFormatSymbols jtulach@1334: */ jtulach@1334: public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) jtulach@1334: { jtulach@1334: this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); jtulach@1334: useDateFormatSymbols = true; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Creates a copy of this SimpleDateFormat. This also jtulach@1334: * clones the format's date format symbols. jtulach@1334: * jtulach@1334: * @return a clone of this SimpleDateFormat jtulach@1334: */ jtulach@1334: public Object clone() { jtulach@1334: SimpleDateFormat other = (SimpleDateFormat) super.clone(); jtulach@1334: other.formatData = (DateFormatSymbols) formatData.clone(); jtulach@1334: return other; jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Returns the hash code value for this SimpleDateFormat object. jtulach@1334: * jtulach@1334: * @return the hash code value for this SimpleDateFormat object. jtulach@1334: */ jtulach@1334: public int hashCode() jtulach@1334: { jtulach@1334: return pattern.hashCode(); jtulach@1334: // just enough fields for a reasonable distribution jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Compares the given object with this SimpleDateFormat for jtulach@1334: * equality. jtulach@1334: * jtulach@1334: * @return true if the given object is equal to this jtulach@1334: * SimpleDateFormat jtulach@1334: */ jtulach@1334: public boolean equals(Object obj) jtulach@1334: { jtulach@1334: if (!super.equals(obj)) return false; // super does class check jtulach@1334: SimpleDateFormat that = (SimpleDateFormat) obj; jtulach@1334: return (pattern.equals(that.pattern) jtulach@1334: && formatData.equals(that.formatData)); jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * After reading an object from the input stream, the format jtulach@1334: * pattern in the object is verified. jtulach@1334: *

jtulach@1334: * @exception InvalidObjectException if the pattern is invalid jtulach@1334: */ jtulach@1334: private void readObject(ObjectInputStream stream) jtulach@1334: throws IOException, ClassNotFoundException { jtulach@1334: stream.defaultReadObject(); jtulach@1334: jtulach@1334: try { jtulach@1334: compiledPattern = compile(pattern); jtulach@1334: } catch (Exception e) { jtulach@1334: throw new InvalidObjectException("invalid pattern"); jtulach@1334: } jtulach@1334: jtulach@1334: if (serialVersionOnStream < 1) { jtulach@1334: // didn't have defaultCenturyStart field jtulach@1334: initializeDefaultCentury(); jtulach@1334: } jtulach@1334: else { jtulach@1334: // fill in dependent transient field jtulach@1334: parseAmbiguousDatesAsAfter(defaultCenturyStart); jtulach@1334: } jtulach@1334: serialVersionOnStream = currentSerialVersion; jtulach@1334: jtulach@1334: // If the deserialized object has a SimpleTimeZone, try jtulach@1334: // to replace it with a ZoneInfo equivalent in order to jtulach@1334: // be compatible with the SimpleTimeZone-based jtulach@1334: // implementation as much as possible. jtulach@1334: TimeZone tz = getTimeZone(); jtulach@1334: if (tz instanceof SimpleTimeZone) { jtulach@1334: String id = tz.getID(); jtulach@1334: TimeZone zi = TimeZone.getTimeZone(id); jtulach@1334: if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) { jtulach@1334: setTimeZone(zi); jtulach@1334: } jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: /** jtulach@1334: * Analyze the negative subpattern of DecimalFormat and set/update values jtulach@1334: * as necessary. jtulach@1334: */ jtulach@1334: private void checkNegativeNumberExpression() { jtulach@1334: if ((numberFormat instanceof DecimalFormat) && jtulach@1334: !numberFormat.equals(originalNumberFormat)) { jtulach@1334: String numberPattern = ((DecimalFormat)numberFormat).toPattern(); jtulach@1334: if (!numberPattern.equals(originalNumberPattern)) { jtulach@1334: hasFollowingMinusSign = false; jtulach@1334: jtulach@1334: int separatorIndex = numberPattern.indexOf(';'); jtulach@1334: // If the negative subpattern is not absent, we have to analayze jtulach@1334: // it in order to check if it has a following minus sign. jtulach@1334: if (separatorIndex > -1) { jtulach@1334: int minusIndex = numberPattern.indexOf('-', separatorIndex); jtulach@1334: if ((minusIndex > numberPattern.lastIndexOf('0')) && jtulach@1334: (minusIndex > numberPattern.lastIndexOf('#'))) { jtulach@1334: hasFollowingMinusSign = true; jtulach@1334: minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign(); jtulach@1334: } jtulach@1334: } jtulach@1334: originalNumberPattern = numberPattern; jtulach@1334: } jtulach@1334: originalNumberFormat = numberFormat; jtulach@1334: } jtulach@1334: } jtulach@1334: jtulach@1334: }