2 *******************************************************************************
\r
3 * Copyright (C) 1996-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
8 package com.ibm.icu.text;
\r
10 import java.io.IOException;
\r
11 import java.io.ObjectInputStream;
\r
12 import java.io.ObjectOutputStream;
\r
13 import java.lang.ref.WeakReference;
\r
14 import java.text.AttributedCharacterIterator;
\r
15 import java.text.AttributedString;
\r
16 import java.text.FieldPosition;
\r
17 import java.text.Format;
\r
18 import java.text.ParsePosition;
\r
19 import java.util.ArrayList;
\r
20 import java.util.Date;
\r
21 import java.util.HashMap;
\r
22 import java.util.List;
\r
23 import java.util.Locale;
\r
24 import java.util.MissingResourceException;
\r
26 import com.ibm.icu.impl.CalendarData;
\r
27 import com.ibm.icu.impl.DateNumberFormat;
\r
28 import com.ibm.icu.impl.ICUCache;
\r
29 import com.ibm.icu.impl.SimpleCache;
\r
30 import com.ibm.icu.impl.UCharacterProperty;
\r
31 import com.ibm.icu.impl.ZoneMeta;
\r
32 import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;
\r
33 import com.ibm.icu.lang.UCharacter;
\r
34 import com.ibm.icu.util.BasicTimeZone;
\r
35 import com.ibm.icu.util.Calendar;
\r
36 import com.ibm.icu.util.GregorianCalendar;
\r
37 import com.ibm.icu.util.HebrewCalendar;
\r
38 import com.ibm.icu.util.TimeZone;
\r
39 import com.ibm.icu.util.TimeZoneTransition;
\r
40 import com.ibm.icu.util.ULocale;
\r
44 * {@icuenhanced java.text.SimpleDateFormat}.{@icu _usage_}
\r
46 * <p><code>SimpleDateFormat</code> is a concrete class for formatting and
\r
47 * parsing dates in a locale-sensitive manner. It allows for formatting
\r
48 * (date -> text), parsing (text -> date), and normalization.
\r
51 * <code>SimpleDateFormat</code> allows you to start by choosing
\r
52 * any user-defined patterns for date-time formatting. However, you
\r
53 * are encouraged to create a date-time formatter with either
\r
54 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
\r
55 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
\r
56 * of these class methods can return a date/time formatter initialized
\r
57 * with a default format pattern. You may modify the format pattern
\r
58 * using the <code>applyPattern</code> methods as desired.
\r
59 * For more information on using these methods, see
\r
60 * {@link DateFormat}.
\r
63 * <strong>Time Format Syntax:</strong>
\r
65 * To specify the time format use a <em>time pattern</em> string.
\r
66 * In this pattern, all ASCII letters are reserved as pattern letters,
\r
67 * which are defined as the following:
\r
70 * Symbol Meaning Presentation Example
\r
71 * ------ ------- ------------ -------
\r
72 * G era designator (Text) AD
\r
73 * y† year (Number) 1996
\r
74 * Y* year (week of year) (Number) 1997
\r
75 * u* extended year (Number) 4601
\r
76 * M month in year (Text & Number) July & 07
\r
77 * d day in month (Number) 10
\r
78 * h hour in am/pm (1~12) (Number) 12
\r
79 * H hour in day (0~23) (Number) 0
\r
80 * m minute in hour (Number) 30
\r
81 * s second in minute (Number) 55
\r
82 * S fractional second (Number) 978
\r
83 * E day of week (Text) Tuesday
\r
84 * e* day of week (local 1~7) (Text & Number) Tuesday & 2
\r
85 * D day in year (Number) 189
\r
86 * F day of week in month (Number) 2 (2nd Wed in July)
\r
87 * w week in year (Number) 27
\r
88 * W week in month (Number) 2
\r
89 * a am/pm marker (Text) PM
\r
90 * k hour in day (1~24) (Number) 24
\r
91 * K hour in am/pm (0~11) (Number) 0
\r
92 * z time zone (Text) Pacific Standard Time
\r
93 * Z time zone (RFC 822) (Number) -0800
\r
94 * v time zone (generic) (Text) Pacific Time
\r
95 * V time zone (location) (Text) United States (Los Angeles)
\r
96 * g* Julian day (Number) 2451334
\r
97 * A* milliseconds in day (Number) 69540000
\r
98 * Q* quarter in year (Text & Number) Q1 & 01
\r
99 * c* stand alone day of week (Text & Number) Tuesday & 2
\r
100 * L* stand alone month (Text & Number) July & 07
\r
101 * q* stand alone quarter (Text & Number) Q1 & 01
\r
102 * ' escape for text (Delimiter) 'Date='
\r
103 * '' single quote (Literal) 'o''clock'
\r
106 * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
\r
107 * <tt><b>†</b></tt> ICU interprets a single 'y' differently than Java.</p>
\r
109 * The count of pattern letters determine the format.
\r
111 * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
\r
112 * < 4--use short or abbreviated form if one exists.
\r
114 * <strong>(Number)</strong>: the minimum number of digits. Shorter
\r
115 * numbers are zero-padded to this amount. Year is handled specially;
\r
116 * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
\r
117 * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
\r
118 * Unlike other fields, fractional seconds are padded on the right with zero.
\r
120 * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
\r
122 * Any characters in the pattern that are not in the ranges of ['a'..'z']
\r
123 * and ['A'..'Z'] will be treated as quoted text. For instance, characters
\r
124 * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
\r
125 * even they are not embraced within single quotes.
\r
127 * A pattern containing any invalid pattern letter will result in a thrown
\r
128 * exception during formatting or parsing.
\r
131 * <strong>Examples Using the US Locale:</strong>
\r
134 * Format Pattern Result
\r
135 * -------------- -------
\r
136 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
\r
137 * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
\r
138 * "h:mm a" ->> 12:08 PM
\r
139 * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
\r
140 * "K:mm a, vvv" ->> 0:00 PM, PT
\r
141 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
\r
144 * <strong>Code Sample:</strong>
\r
147 * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
\r
148 * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
\r
149 * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
\r
151 * // Format the current time.
\r
152 * SimpleDateFormat formatter
\r
153 * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
\r
154 * Date currentTime_1 = new Date();
\r
155 * String dateString = formatter.format(currentTime_1);
\r
157 * // Parse the previous string back into a Date.
\r
158 * ParsePosition pos = new ParsePosition(0);
\r
159 * Date currentTime_2 = formatter.parse(dateString, pos);
\r
162 * In the example, the time value <code>currentTime_2</code> obtained from
\r
163 * parsing will be equal to <code>currentTime_1</code>. However, they may not be
\r
164 * equal if the am/pm marker 'a' is left out from the format pattern while
\r
165 * the "hour in am/pm" pattern symbol is used. This information loss can
\r
166 * happen when formatting the time in PM.
\r
168 * <p>When parsing a date string using the abbreviated year pattern ("yy"),
\r
169 * SimpleDateFormat must interpret the abbreviated year
\r
170 * relative to some century. It does this by adjusting dates to be
\r
171 * within 80 years before and 20 years after the time the SimpleDateFormat
\r
172 * instance is created. For example, using a pattern of "MM/dd/yy" and a
\r
173 * SimpleDateFormat instance created on Jan 1, 1997, the string
\r
174 * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
\r
175 * would be interpreted as May 4, 1964.
\r
176 * During parsing, only strings consisting of exactly two digits, as defined by
\r
177 * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
\r
179 * Any other numeric string, such as a one digit string, a three or more digit
\r
180 * string, or a two digit string that isn't all digits (for example, "-1"), is
\r
181 * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
\r
182 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
\r
184 * <p>If the year pattern does not have exactly two 'y' characters, the year is
\r
185 * interpreted literally, regardless of the number of digits. So using the
\r
186 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
\r
188 * <p>When numeric fields abut one another directly, with no intervening delimiter
\r
189 * characters, they constitute a run of abutting numeric fields. Such runs are
\r
190 * parsed specially. For example, the format "HHmmss" parses the input text
\r
191 * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
\r
192 * parse "1234". In other words, the leftmost field of the run is flexible,
\r
193 * while the others keep a fixed width. If the parse fails anywhere in the run,
\r
194 * then the leftmost field is shortened by one character, and the entire run is
\r
195 * parsed again. This is repeated until either the parse succeeds or the
\r
196 * leftmost field is one character in length. If the parse still fails at that
\r
197 * point, the parse of the run fails.
\r
199 * <p>For time zones that have no names, use strings GMT+hours:minutes or
\r
200 * GMT-hours:minutes.
\r
202 * <p>The calendar defines what is the first day of the week, the first week
\r
203 * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
\r
204 * time zone. There is one common decimal format to handle all the numbers;
\r
205 * the digit count is handled programmatically according to the pattern.
\r
207 * <h4>Synchronization</h4>
\r
209 * Date formats are not synchronized. It is recommended to create separate
\r
210 * format instances for each thread. If multiple threads access a format
\r
211 * concurrently, it must be synchronized externally.
\r
213 * @see com.ibm.icu.util.Calendar
\r
214 * @see com.ibm.icu.util.GregorianCalendar
\r
215 * @see com.ibm.icu.util.TimeZone
\r
217 * @see DateFormatSymbols
\r
218 * @see DecimalFormat
\r
219 * @author Mark Davis, Chen-Lieh Huang, Alan Liu
\r
222 public class SimpleDateFormat extends DateFormat {
\r
224 // the official serial version ID which says cryptically
\r
225 // which version we're compatible with
\r
226 private static final long serialVersionUID = 4774881970558875024L;
\r
228 // the internal serial version which says which version was written
\r
229 // - 0 (default) for version up to JDK 1.1.3
\r
230 // - 1 for version from JDK 1.1.4, which includes a new field
\r
231 static final int currentSerialVersion = 1;
\r
233 static boolean DelayedHebrewMonthCheck = false;
\r
236 * From calendar field to its level.
\r
237 * Used to order calendar field.
\r
238 * For example, calendar fields can be defined in the following order:
\r
239 * year > month > date > am-pm > hour > minute
\r
240 * YEAR --> 10, MONTH -->20, DATE --> 30;
\r
241 * AM_PM -->40, HOUR --> 50, MINUTE -->60
\r
243 private static final int[] CALENDAR_FIELD_TO_LEVEL =
\r
247 /*dDEF*/ 30, 20, 30, 30,
\r
248 /*ahHm*/ 40, 50, 50, 60,
\r
258 * From calendar field letter to its level.
\r
259 * Used to order calendar field.
\r
260 * For example, calendar fields can be defined in the following order:
\r
261 * year > month > date > am-pm > hour > minute
\r
262 * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
\r
264 private static final int[] PATTERN_CHAR_TO_LEVEL =
\r
266 // A B C D E F G H I J K L M N O
\r
267 -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, -1,
\r
268 // P Q R S T U V W X Y Z
\r
269 -1, 20, -1, 80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,
\r
270 // a b c d e f g h i j k l m n o
\r
271 -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
\r
272 // p q r s t u v w x y z
\r
273 -1, 20, -1, 70, -1, 10, 0, 20, -1, 10, 0, -1, -1, -1, -1, -1
\r
278 * The version of the serialized data on the stream. Possible values:
\r
280 * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
\r
281 * has no <code>defaultCenturyStart</code> on stream.
\r
282 * <li><b>1</b> JDK 1.1.4 or later. This version adds
\r
283 * <code>defaultCenturyStart</code>.
\r
285 * When streaming out this class, the most recent format
\r
286 * and the highest allowable <code>serialVersionOnStream</code>
\r
290 private int serialVersionOnStream = currentSerialVersion;
\r
293 * The pattern string of this formatter. This is always a non-localized
\r
294 * pattern. May not be null. See class documentation for details.
\r
297 private String pattern;
\r
300 * The override string of this formatter. Used to override the
\r
301 * numbering system for one or more fields.
\r
304 private String override;
\r
307 * The hash map used for number format overrides.
\r
310 private HashMap<String, NumberFormat> numberFormatters;
\r
313 * The hash map used for number format overrides.
\r
316 private HashMap<Character, String> overrideMap;
\r
319 * The symbols used by this formatter for week names, month names,
\r
320 * etc. May not be null.
\r
322 * @see DateFormatSymbols
\r
324 private DateFormatSymbols formatData;
\r
326 private transient ULocale locale;
\r
329 * We map dates with two-digit years into the century starting at
\r
330 * <code>defaultCenturyStart</code>, which may be any date. May
\r
335 private Date defaultCenturyStart;
\r
337 private transient int defaultCenturyStartYear;
\r
339 // defaultCenturyBase is set when an instance is created
\r
340 // and may be used for calculating defaultCenturyStart when needed.
\r
341 private transient long defaultCenturyBase;
\r
343 // We need to preserve time zone type when parsing specific
\r
344 // time zone text (xxx Standard Time vs xxx Daylight Time)
\r
345 private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;
\r
346 private transient int tztype = TZTYPE_UNK;
\r
348 private static final int millisPerHour = 60 * 60 * 1000;
\r
349 private static final int millisPerMinute = 60 * 1000;
\r
350 private static final int millisPerSecond = 1000;
\r
352 // This prefix is designed to NEVER MATCH real text, in order to
\r
353 // suppress the parsing of negative numbers. Adjust as needed (if
\r
354 // this becomes valid Unicode).
\r
355 private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
\r
358 * If true, this object supports fast formatting using the
\r
359 * subFormat variant that takes a StringBuffer.
\r
361 private transient boolean useFastFormat;
\r
364 * Constructs a SimpleDateFormat using the default pattern for the default
\r
365 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
366 * generality, use the factory methods in the DateFormat class.
\r
371 public SimpleDateFormat() {
\r
372 this(getDefaultPattern(), null, null, null, null, true, null);
\r
376 * Constructs a SimpleDateFormat using the given pattern in the default
\r
377 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
378 * generality, use the factory methods in the DateFormat class.
\r
381 public SimpleDateFormat(String pattern)
\r
383 this(pattern, null, null, null, null, true, null);
\r
387 * Constructs a SimpleDateFormat using the given pattern and locale.
\r
388 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
389 * generality, use the factory methods in the DateFormat class.
\r
392 public SimpleDateFormat(String pattern, Locale loc)
\r
394 this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
\r
398 * Constructs a SimpleDateFormat using the given pattern and locale.
\r
399 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
400 * generality, use the factory methods in the DateFormat class.
\r
403 public SimpleDateFormat(String pattern, ULocale loc)
\r
405 this(pattern, null, null, null, loc, true, null);
\r
409 * Constructs a SimpleDateFormat using the given pattern , override and locale.
\r
410 * @param pattern The pattern to be used
\r
411 * @param override The override string. A numbering system override string can take one of the following forms:
\r
412 * 1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern.
\r
413 * 2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern
\r
414 * followed by an = sign, followed by the numbering system name. For example, to specify that just the year
\r
415 * be formatted using Hebrew digits, use the override "y=hebr". Multiple overrides can be specified in a single
\r
416 * string by separating them with a semi-colon. For example, the override string "m=thai;y=deva" would format using
\r
417 * Thai digits for the month and Devanagari digits for the year.
\r
418 * @param loc The locale to be used
\r
421 public SimpleDateFormat(String pattern, String override, ULocale loc)
\r
423 this(pattern, null, null, null, loc, false,override);
\r
427 * Constructs a SimpleDateFormat using the given pattern and
\r
428 * locale-specific symbol data.
\r
429 * Warning: uses default locale for digits!
\r
432 public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
\r
434 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
\r
439 * @deprecated This API is ICU internal only.
\r
441 public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
\r
443 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
\r
447 * Package-private constructor that allows a subclass to specify
\r
448 * whether it supports fast formatting.
\r
450 * TODO make this API public.
\r
452 SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
\r
453 boolean useFastFormat, String override) {
\r
454 this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
\r
458 * The constructor called from all other SimpleDateFormat constructors
\r
460 private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
\r
461 NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
\r
462 this.pattern = pattern;
\r
463 this.formatData = formatData;
\r
464 this.calendar = calendar;
\r
465 this.numberFormat = numberFormat;
\r
466 this.locale = locale; // time zone formatting
\r
467 this.useFastFormat = useFastFormat;
\r
468 this.override = override;
\r
473 * Creates an instance of SimpleDateForamt for the given format configuration
\r
474 * @param formatConfig the format configuration
\r
475 * @return A SimpleDateFormat instance
\r
477 * @deprecated This API is ICU internal only.
\r
479 public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
\r
481 String ostr = formatConfig.getOverrideString();
\r
482 boolean useFast = ( ostr != null && ostr.length() > 0 );
\r
484 return new SimpleDateFormat(formatConfig.getPatternString(),
\r
485 formatConfig.getDateFormatSymbols(),
\r
486 formatConfig.getCalendar(),
\r
488 formatConfig.getLocale(),
\r
490 formatConfig.getOverrideString());
\r
494 * Initialized fields
\r
496 private void initialize() {
\r
497 if (locale == null) {
\r
498 locale = ULocale.getDefault();
\r
500 if (formatData == null) {
\r
501 formatData = new DateFormatSymbols(locale);
\r
503 if (calendar == null) {
\r
504 calendar = Calendar.getInstance(locale);
\r
506 if (numberFormat == null) {
\r
507 NumberingSystem ns = NumberingSystem.getInstance(locale);
\r
508 if ( ns.isAlgorithmic() ) {
\r
509 numberFormat = NumberFormat.getInstance(locale);
\r
511 char digit0 = ns.getDescription().charAt(0);
\r
512 // Use a NumberFormat optimized for date formatting
\r
513 numberFormat = new DateNumberFormat(locale, digit0);
\r
516 // Note: deferring calendar calculation until when we really need it.
\r
517 // Instead, we just record time of construction for backward compatibility.
\r
518 defaultCenturyBase = System.currentTimeMillis();
\r
520 setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
\r
521 initLocalZeroPaddingNumberFormat();
\r
523 if (override != null) {
\r
524 initNumberFormatters(locale);
\r
529 // privates for the default pattern
\r
530 private static ULocale cachedDefaultLocale = null;
\r
531 private static String cachedDefaultPattern = null;
\r
532 private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
\r
535 * Returns the default date and time pattern (SHORT) for the default locale.
\r
536 * This method is only used by the default SimpleDateFormat constructor.
\r
538 private static synchronized String getDefaultPattern() {
\r
539 ULocale defaultLocale = ULocale.getDefault();
\r
540 if (!defaultLocale.equals(cachedDefaultLocale)) {
\r
541 cachedDefaultLocale = defaultLocale;
\r
542 Calendar cal = Calendar.getInstance(cachedDefaultLocale);
\r
544 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
\r
545 String[] dateTimePatterns = calData.getDateTimePatterns();
\r
547 if (dateTimePatterns.length >= 13)
\r
549 glueIndex += (SHORT + 1);
\r
551 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
\r
552 new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
\r
553 } catch (MissingResourceException e) {
\r
554 cachedDefaultPattern = FALLBACKPATTERN;
\r
557 return cachedDefaultPattern;
\r
560 /* Define one-century window into which to disambiguate dates using
\r
563 private void parseAmbiguousDatesAsAfter(Date startDate) {
\r
564 defaultCenturyStart = startDate;
\r
565 calendar.setTime(startDate);
\r
566 defaultCenturyStartYear = calendar.get(Calendar.YEAR);
\r
569 /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
\r
570 * The default start time is 80 years before the creation time of this object.
\r
572 private void initializeDefaultCenturyStart(long baseTime) {
\r
573 defaultCenturyBase = baseTime;
\r
574 // clone to avoid messing up date stored in calendar object
\r
575 // when this method is called while parsing
\r
576 Calendar tmpCal = (Calendar)calendar.clone();
\r
577 tmpCal.setTimeInMillis(baseTime);
\r
578 tmpCal.add(Calendar.YEAR, -80);
\r
579 defaultCenturyStart = tmpCal.getTime();
\r
580 defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
\r
583 /* Gets the default century start date for this object */
\r
584 private Date getDefaultCenturyStart() {
\r
585 if (defaultCenturyStart == null) {
\r
586 // not yet initialized
\r
587 initializeDefaultCenturyStart(defaultCenturyBase);
\r
589 return defaultCenturyStart;
\r
592 /* Gets the default century start year for this object */
\r
593 private int getDefaultCenturyStartYear() {
\r
594 if (defaultCenturyStart == null) {
\r
595 // not yet initialized
\r
596 initializeDefaultCenturyStart(defaultCenturyBase);
\r
598 return defaultCenturyStartYear;
\r
602 * Sets the 100-year period 2-digit years will be interpreted as being in
\r
603 * to begin on the date the user specifies.
\r
604 * @param startDate During parsing, two digit years will be placed in the range
\r
605 * <code>startDate</code> to <code>startDate + 100 years</code>.
\r
608 public void set2DigitYearStart(Date startDate) {
\r
609 parseAmbiguousDatesAsAfter(startDate);
\r
613 * Returns the beginning date of the 100-year period 2-digit years are interpreted
\r
615 * @return the start of the 100-year period into which two digit years are
\r
619 public Date get2DigitYearStart() {
\r
620 return getDefaultCenturyStart();
\r
624 * Formats a date or time, which is the standard millis
\r
625 * since January 1, 1970, 00:00:00 GMT.
\r
626 * <p>Example: using the US locale:
\r
627 * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
\r
628 * @param cal the calendar whose date-time value is to be formatted into a date-time string
\r
629 * @param toAppendTo where the new date-time text is to be appended
\r
630 * @param pos the formatting position. On input: an alignment field,
\r
631 * if desired. On output: the offsets of the alignment field.
\r
632 * @return the formatted date-time string.
\r
636 public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
\r
637 FieldPosition pos) {
\r
638 TimeZone backupTZ = null;
\r
639 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
\r
640 // Different calendar type
\r
641 // We use the time and time zone from the input calendar, but
\r
642 // do not use the input calendar for field calculation.
\r
643 calendar.setTimeInMillis(cal.getTimeInMillis());
\r
644 backupTZ = calendar.getTimeZone();
\r
645 calendar.setTimeZone(cal.getTimeZone());
\r
648 StringBuffer result = format(cal, toAppendTo, pos, null);
\r
649 if (backupTZ != null) {
\r
650 // Restore the original time zone
\r
651 calendar.setTimeZone(backupTZ);
\r
656 // The actual method to format date. If List attributes is not null,
\r
657 // then attribute information will be recorded.
\r
658 private StringBuffer format(Calendar cal, StringBuffer toAppendTo,
\r
659 FieldPosition pos, List<FieldPosition> attributes) {
\r
661 pos.setBeginIndex(0);
\r
662 pos.setEndIndex(0);
\r
664 // Careful: For best performance, minimize the number of calls
\r
665 // to StringBuffer.append() by consolidating appends when
\r
668 Object[] items = getPatternItems();
\r
669 for (int i = 0; i < items.length; i++) {
\r
670 if (items[i] instanceof String) {
\r
671 toAppendTo.append((String)items[i]);
\r
673 PatternItem item = (PatternItem)items[i];
\r
675 if (attributes != null) {
\r
676 // Save the current length
\r
677 start = toAppendTo.length();
\r
679 if (useFastFormat) {
\r
680 subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
\r
682 toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos,
\r
685 if (attributes != null) {
\r
686 // Check the sub format length
\r
687 int end = toAppendTo.length();
\r
688 if (end - start > 0) {
\r
689 // Append the attribute to the list
\r
690 DateFormat.Field attr = patternCharToDateFormatField(item.type);
\r
691 FieldPosition fp = new FieldPosition(attr);
\r
692 fp.setBeginIndex(start);
\r
693 fp.setEndIndex(end);
\r
694 attributes.add(fp);
\r
703 // Map pattern character to index
\r
704 private static final int PATTERN_CHAR_BASE = 0x40;
\r
705 private static final int[] PATTERN_CHAR_TO_INDEX =
\r
707 // A B C D E F G H I J K L M N O
\r
708 -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,
\r
709 // P Q R S T U V W X Y Z
\r
710 -1, 27, -1, 8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,
\r
711 // a b c d e f g h i j k l m n o
\r
712 -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
\r
713 // p q r s t u v w x y z
\r
714 -1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1
\r
717 // Map pattern character index to Calendar field number
\r
718 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
\r
720 /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
\r
721 /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
\r
722 /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
\r
723 /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
\r
724 /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
\r
725 /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
\r
726 /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
\r
727 /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
\r
728 /*v*/ Calendar.ZONE_OFFSET,
\r
729 /*c*/ Calendar.DOW_LOCAL,
\r
730 /*L*/ Calendar.MONTH,
\r
731 /*Qq*/ Calendar.MONTH, Calendar.MONTH,
\r
732 /*V*/ Calendar.ZONE_OFFSET,
\r
735 // Map pattern character index to DateFormat field number
\r
736 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
\r
737 /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
\r
738 /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
\r
739 /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
\r
740 /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
\r
741 /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
\r
742 /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
\r
743 /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
\r
744 /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
\r
745 /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD,
\r
746 /*c*/ DateFormat.STANDALONE_DAY_FIELD,
\r
747 /*L*/ DateFormat.STANDALONE_MONTH_FIELD,
\r
748 /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
\r
749 /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD,
\r
752 // Map pattern character index to DateFormat.Field
\r
753 private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
\r
754 /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
\r
755 /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
\r
756 /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
\r
757 /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
\r
758 /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
\r
759 /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
\r
760 /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
\r
761 /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
\r
762 /*v*/ DateFormat.Field.TIME_ZONE,
\r
763 /*c*/ DateFormat.Field.DAY_OF_WEEK,
\r
764 /*L*/ DateFormat.Field.MONTH,
\r
765 /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
\r
766 /*V*/ DateFormat.Field.TIME_ZONE,
\r
770 * Returns a DateFormat.Field constant associated with the specified format pattern
\r
773 * @param ch The pattern character
\r
774 * @return DateFormat.Field associated with the pattern character
\r
778 protected DateFormat.Field patternCharToDateFormatField(char ch) {
\r
779 int patternCharIndex = -1;
\r
780 if ('A' <= ch && ch <= 'z') {
\r
781 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
783 if (patternCharIndex != -1) {
\r
784 return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
\r
790 * Formats a single field, given its pattern character. Subclasses may
\r
791 * override this method in order to modify or add formatting
\r
793 * @param ch the pattern character
\r
794 * @param count the number of times ch is repeated in the pattern
\r
795 * @param beginOffset the offset of the output string at the start of
\r
796 * this field; used to set pos when appropriate
\r
797 * @param pos receives the position of a field, when appropriate
\r
798 * @param fmtData the symbols for this formatter
\r
801 protected String subFormat(char ch, int count, int beginOffset,
\r
802 FieldPosition pos, DateFormatSymbols fmtData,
\r
804 throws IllegalArgumentException
\r
806 // Note: formatData is ignored
\r
807 StringBuffer buf = new StringBuffer();
\r
808 subFormat(buf, ch, count, beginOffset, pos, cal);
\r
809 return buf.toString();
\r
813 * Formats a single field; useFastFormat variant. Reuses a
\r
814 * StringBuffer for results instead of creating a String on the
\r
815 * heap for each call.
\r
817 * NOTE We don't really need the beginOffset parameter, EXCEPT for
\r
818 * the need to support the slow subFormat variant (above) which
\r
819 * has to pass it in to us.
\r
822 * @deprecated This API is ICU internal only.
\r
824 @SuppressWarnings("fallthrough")
\r
825 protected void subFormat(StringBuffer buf,
\r
826 char ch, int count, int beginOffset,
\r
830 final boolean COMMONLY_USED = true;
\r
831 final int maxIntCount = Integer.MAX_VALUE;
\r
832 final int bufstart = buf.length();
\r
834 // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
\r
835 int patternCharIndex = -1;
\r
836 if ('A' <= ch && ch <= 'z') {
\r
837 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
840 if (patternCharIndex == -1) {
\r
841 throw new IllegalArgumentException("Illegal pattern character " +
\r
842 "'" + ch + "' in \"" +
\r
846 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
\r
847 int value = cal.get(field);
\r
849 String zoneString = null;
\r
851 NumberFormat currentNumberFormat = getNumberFormat(ch);
\r
853 switch (patternCharIndex) {
\r
854 case 0: // 'G' - ERA
\r
856 safeAppend(formatData.narrowEras, value, buf);
\r
857 } else if (count == 4) {
\r
858 safeAppend(formatData.eraNames, value, buf);
\r
860 safeAppend(formatData.eras, value, buf);
\r
863 case 1: // 'y' - YEAR
\r
864 /* According to the specification, if the number of pattern letters ('y') is 2,
\r
865 * the year is truncated to 2 digits; otherwise it is interpreted as a number.
\r
866 * But the original code process 'y', 'yy', 'yyy' in the same way. and process
\r
867 * patterns with 4 or more than 4 'y' characters in the same way.
\r
868 * So I change the codes to meet the specification. [Richard/GCl]
\r
871 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
\r
872 } else { //count = 1 or count > 2
\r
873 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
876 case 2: // 'M' - MONTH
\r
877 if ( cal.getType().equals("hebrew")) {
\r
878 boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR));
\r
879 if (isLeap && value == 6 && count >= 3 ) {
\r
880 value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar.
\r
882 if (!isLeap && value >= 6 && count < 3 ) {
\r
883 value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7.
\r
887 safeAppend(formatData.narrowMonths, value, buf);
\r
888 } else if (count == 4) {
\r
889 safeAppend(formatData.months, value, buf);
\r
890 } else if (count == 3) {
\r
891 safeAppend(formatData.shortMonths, value, buf);
\r
893 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
\r
896 case 4: // 'k' - HOUR_OF_DAY (1..24)
\r
898 zeroPaddingNumber(currentNumberFormat,buf,
\r
899 cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
\r
900 count, maxIntCount);
\r
902 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
905 case 8: // 'S' - FRACTIONAL_SECOND
\r
906 // Fractional seconds left-justify
\r
908 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
\r
909 numberFormat.setMaximumIntegerDigits(maxIntCount);
\r
911 value = (value + 50) / 100;
\r
912 } else if (count == 2) {
\r
913 value = (value + 5) / 10;
\r
915 FieldPosition p = new FieldPosition(-1);
\r
916 numberFormat.format((long) value, buf, p);
\r
918 numberFormat.setMinimumIntegerDigits(count - 3);
\r
919 numberFormat.format(0L, buf, p);
\r
923 case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
\r
925 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
928 // For alpha day-of-week, we don't want DOW_LOCAL,
\r
929 // we need the standard DAY_OF_WEEK.
\r
930 value = cal.get(Calendar.DAY_OF_WEEK);
\r
931 // fall through, do not break here
\r
932 case 9: // 'E' - DAY_OF_WEEK
\r
934 safeAppend(formatData.narrowWeekdays, value, buf);
\r
935 } else if (count == 4) {
\r
936 safeAppend(formatData.weekdays, value, buf);
\r
937 } else {// count <= 3, use abbreviated form if exists
\r
938 safeAppend(formatData.shortWeekdays, value, buf);
\r
941 case 14: // 'a' - AM_PM
\r
942 safeAppend(formatData.ampms, value, buf);
\r
944 case 15: // 'h' - HOUR (1..12)
\r
946 zeroPaddingNumber(currentNumberFormat,buf,
\r
947 cal.getLeastMaximum(Calendar.HOUR)+1,
\r
948 count, maxIntCount);
\r
950 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
953 case 17: // 'z' - ZONE_OFFSET
\r
955 // "z", "zz", "zzz"
\r
956 zoneString = formatData.getZoneStringFormat()
\r
957 .getSpecificShortString(cal, COMMONLY_USED);
\r
959 zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);
\r
961 if (zoneString != null && zoneString.length() != 0) {
\r
962 buf.append(zoneString);
\r
964 // Use localized GMT format as fallback
\r
965 appendGMT(currentNumberFormat,buf, cal);
\r
968 case 23: // 'Z' - TIMEZONE_RFC
\r
970 // RFC822 format, must use ASCII digits
\r
971 int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
\r
979 int offsetH = val / millisPerHour;
\r
980 val = val % millisPerHour;
\r
981 int offsetM = val / millisPerMinute;
\r
982 val = val % millisPerMinute;
\r
983 int offsetS = val / millisPerSecond;
\r
985 int num = 0, denom = 0;
\r
986 if (offsetS == 0) {
\r
987 val = offsetH*100 + offsetM; // HHmm
\r
991 val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss
\r
992 num = val % 1000000;
\r
995 while (denom >= 1) {
\r
996 char digit = (char)((num / denom) + '0');
\r
1002 // long form, localized GMT pattern
\r
1003 appendGMT(currentNumberFormat,buf, cal);
\r
1006 case 24: // 'v' - TIMEZONE_GENERIC
\r
1009 zoneString = formatData.getZoneStringFormat()
\r
1010 .getGenericShortString(cal, COMMONLY_USED);
\r
1011 } else if (count == 4) {
\r
1013 zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);
\r
1015 if (zoneString != null && zoneString.length() != 0) {
\r
1016 buf.append(zoneString);
\r
1018 // Use localized GMT format as fallback
\r
1019 appendGMT(currentNumberFormat,buf, cal);
\r
1022 case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone)
\r
1024 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
\r
1027 // For alpha day-of-week, we don't want DOW_LOCAL,
\r
1028 // we need the standard DAY_OF_WEEK.
\r
1029 value = cal.get(Calendar.DAY_OF_WEEK);
\r
1031 safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
\r
1032 } else if (count == 4) {
\r
1033 safeAppend(formatData.standaloneWeekdays, value, buf);
\r
1034 } else { // count == 3
\r
1035 safeAppend(formatData.standaloneShortWeekdays, value, buf);
\r
1038 case 26: // 'L' - STANDALONE MONTH
\r
1040 safeAppend(formatData.standaloneNarrowMonths, value, buf);
\r
1041 } else if (count == 4) {
\r
1042 safeAppend(formatData.standaloneMonths, value, buf);
\r
1043 } else if (count == 3) {
\r
1044 safeAppend(formatData.standaloneShortMonths, value, buf);
\r
1046 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
\r
1049 case 27: // 'Q' - QUARTER
\r
1051 safeAppend(formatData.quarters, value/3, buf);
\r
1052 } else if (count == 3) {
\r
1053 safeAppend(formatData.shortQuarters, value/3, buf);
\r
1055 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
\r
1058 case 28: // 'q' - STANDALONE QUARTER
\r
1060 safeAppend(formatData.standaloneQuarters, value/3, buf);
\r
1061 } else if (count == 3) {
\r
1062 safeAppend(formatData.standaloneShortQuarters, value/3, buf);
\r
1064 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
\r
1067 case 29: // 'V' - TIMEZONE_SPECIAL
\r
1070 zoneString = formatData.getZoneStringFormat()
\r
1071 .getSpecificShortString(cal, !COMMONLY_USED);
\r
1072 } else if (count == 4) {
\r
1074 zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);
\r
1076 if (zoneString != null && zoneString.length() != 0) {
\r
1077 buf.append(zoneString);
\r
1079 // Use localized GMT format as fallback
\r
1080 appendGMT(currentNumberFormat,buf, cal);
\r
1084 // case 3: // 'd' - DATE
\r
1085 // case 5: // 'H' - HOUR_OF_DAY (0..23)
\r
1086 // case 6: // 'm' - MINUTE
\r
1087 // case 7: // 's' - SECOND
\r
1088 // case 10: // 'D' - DAY_OF_YEAR
\r
1089 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
\r
1090 // case 12: // 'w' - WEEK_OF_YEAR
\r
1091 // case 13: // 'W' - WEEK_OF_MONTH
\r
1092 // case 16: // 'K' - HOUR (0..11)
\r
1093 // case 18: // 'Y' - YEAR_WOY
\r
1094 // case 20: // 'u' - EXTENDED_YEAR
\r
1095 // case 21: // 'g' - JULIAN_DAY
\r
1096 // case 22: // 'A' - MILLISECONDS_IN_DAY
\r
1098 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
1100 } // switch (patternCharIndex)
\r
1102 // Set the FieldPosition (for the first occurrence only)
\r
1103 if (pos.getBeginIndex() == pos.getEndIndex()) {
\r
1104 if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
\r
1105 pos.setBeginIndex(beginOffset);
\r
1106 pos.setEndIndex(beginOffset + buf.length() - bufstart);
\r
1107 } else if (pos.getFieldAttribute() ==
\r
1108 PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
\r
1109 pos.setBeginIndex(beginOffset);
\r
1110 pos.setEndIndex(beginOffset + buf.length() - bufstart);
\r
1115 private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
\r
1116 if (array != null && value >= 0 && value < array.length) {
\r
1117 appendTo.append(array[value]);
\r
1122 * PatternItem store parsed date/time field pattern information.
\r
1124 private static class PatternItem {
\r
1127 final boolean isNumeric;
\r
1129 PatternItem(char type, int length) {
\r
1131 this.length = length;
\r
1132 isNumeric = isNumeric(type, length);
\r
1136 private static ICUCache<String, Object[]> PARSED_PATTERN_CACHE =
\r
1137 new SimpleCache<String, Object[]>();
\r
1138 private transient Object[] patternItems;
\r
1141 * Returns parsed pattern items. Each item is either String or
\r
1144 private Object[] getPatternItems() {
\r
1145 if (patternItems != null) {
\r
1146 return patternItems;
\r
1149 patternItems = PARSED_PATTERN_CACHE.get(pattern);
\r
1150 if (patternItems != null) {
\r
1151 return patternItems;
\r
1154 boolean isPrevQuote = false;
\r
1155 boolean inQuote = false;
\r
1156 StringBuilder text = new StringBuilder();
\r
1157 char itemType = 0; // 0 for string literal, otherwise date/time pattern character
\r
1158 int itemLength = 1;
\r
1160 List<Object> items = new ArrayList<Object>();
\r
1162 for (int i = 0; i < pattern.length(); i++) {
\r
1163 char ch = pattern.charAt(i);
\r
1165 if (isPrevQuote) {
\r
1166 text.append('\'');
\r
1167 isPrevQuote = false;
\r
1169 isPrevQuote = true;
\r
1170 if (itemType != 0) {
\r
1171 items.add(new PatternItem(itemType, itemLength));
\r
1175 inQuote = !inQuote;
\r
1177 isPrevQuote = false;
\r
1181 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
\r
1182 // a date/time pattern character
\r
1183 if (ch == itemType) {
\r
1186 if (itemType == 0) {
\r
1187 if (text.length() > 0) {
\r
1188 items.add(text.toString());
\r
1189 text.setLength(0);
\r
1192 items.add(new PatternItem(itemType, itemLength));
\r
1198 // a string literal
\r
1199 if (itemType != 0) {
\r
1200 items.add(new PatternItem(itemType, itemLength));
\r
1208 // handle last item
\r
1209 if (itemType == 0) {
\r
1210 if (text.length() > 0) {
\r
1211 items.add(text.toString());
\r
1212 text.setLength(0);
\r
1215 items.add(new PatternItem(itemType, itemLength));
\r
1218 patternItems = items.toArray(new Object[items.size()]);
\r
1220 PARSED_PATTERN_CACHE.put(pattern, patternItems);
\r
1222 return patternItems;
\r
1226 * Time zone localized GMT format stuffs
\r
1228 private static final String STR_GMT = "GMT";
\r
1229 private static final String STR_UT = "UT";
\r
1230 private static final String STR_UTC = "UTC";
\r
1231 private static final int STR_GMT_LEN = 3;
\r
1232 private static final int STR_UT_LEN = 2;
\r
1233 private static final int STR_UTC_LEN = 3;
\r
1234 private static final char PLUS = '+';
\r
1235 private static final char MINUS = '-';
\r
1236 private static final char COLON = ':';
\r
1238 private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {
\r
1239 int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
\r
1241 if (isDefaultGMTFormat()) {
\r
1242 formatGMTDefault(currentNumberFormat,buf, offset);
\r
1244 int sign = DateFormatSymbols.OFFSET_POSITIVE;
\r
1247 sign = DateFormatSymbols.OFFSET_NEGATIVE;
\r
1249 int width = offset%(60*1000) == 0
\r
1250 ? DateFormatSymbols.OFFSET_HM
\r
1251 : DateFormatSymbols.OFFSET_HMS;
\r
1253 MessageFormat fmt = getGMTFormatter(sign, width);
\r
1254 fmt.format(new Object[] {new Long(offset)}, buf, null);
\r
1258 private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {
\r
1259 buf.append(STR_GMT);
\r
1260 if (offset >= 0) {
\r
1263 buf.append(MINUS);
\r
1266 offset /= 1000; // now in seconds
\r
1267 int sec = offset % 60;
\r
1269 int min = offset % 60;
\r
1270 int hour = offset / 60;
\r
1272 zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);
\r
1273 buf.append(COLON);
\r
1274 zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);
\r
1276 buf.append(COLON);
\r
1277 zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);
\r
1281 private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
\r
1282 if (!isDefaultGMTFormat()) {
\r
1283 int start = pos.getIndex();
\r
1284 String gmtPattern = formatData.gmtFormat;
\r
1287 boolean prefixMatch = false;
\r
1288 int prefixLen = gmtPattern.indexOf('{');
\r
1289 if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {
\r
1290 prefixMatch = true;
\r
1293 if (prefixMatch) {
\r
1295 MessageFormat fmt;
\r
1296 Object[] parsedObjects;
\r
1299 // Try negative Hms
\r
1300 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE,
\r
1301 DateFormatSymbols.OFFSET_HMS);
\r
1302 parsedObjects = fmt.parse(text, pos);
\r
1303 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
\r
1304 && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(
\r
1305 DateFormatSymbols.OFFSET_NEGATIVE)) {
\r
1306 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1307 return new Integer(-offset /* negative */);
\r
1310 // Reset ParsePosition
\r
1311 pos.setIndex(start);
\r
1312 pos.setErrorIndex(-1);
\r
1314 // Try positive Hms
\r
1315 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE,
\r
1316 DateFormatSymbols.OFFSET_HMS);
\r
1317 parsedObjects = fmt.parse(text, pos);
\r
1318 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
\r
1319 && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(
\r
1320 DateFormatSymbols.OFFSET_POSITIVE)) {
\r
1321 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1322 return new Integer(offset);
\r
1325 // Reset ParsePosition
\r
1326 pos.setIndex(start);
\r
1327 pos.setErrorIndex(-1);
\r
1329 // Try negative Hm
\r
1330 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE,
\r
1331 DateFormatSymbols.OFFSET_HM);
\r
1332 parsedObjects = fmt.parse(text, pos);
\r
1333 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
\r
1334 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1335 return new Integer(-offset /* negative */);
\r
1338 // Reset ParsePosition
\r
1339 pos.setIndex(start);
\r
1340 pos.setErrorIndex(-1);
\r
1342 // Try positive Hm
\r
1343 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE,
\r
1344 DateFormatSymbols.OFFSET_HM);
\r
1345 parsedObjects = fmt.parse(text, pos);
\r
1346 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
\r
1347 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1348 return new Integer(offset);
\r
1351 // Reset ParsePosition
\r
1352 pos.setIndex(start);
\r
1353 pos.setErrorIndex(-1);
\r
1357 return parseGMTDefault(text, pos, currentNumberFormat);
\r
1360 private Integer parseGMTDefault(String text, ParsePosition pos,
\r
1361 NumberFormat currentNumberFormat) {
\r
1362 int start = pos.getIndex();
\r
1364 if (start + STR_UT_LEN + 1 >= text.length()) {
\r
1365 pos.setErrorIndex(start);
\r
1371 if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
\r
1372 cur += STR_GMT_LEN;
\r
1373 } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
\r
1374 cur += STR_UT_LEN;
\r
1376 pos.setErrorIndex(start);
\r
1380 boolean negative = false;
\r
1381 if (text.charAt(cur) == MINUS) {
\r
1383 } else if (text.charAt(cur) != PLUS) {
\r
1384 pos.setErrorIndex(cur);
\r
1391 pos.setIndex(cur);
\r
1393 Number n = parseInt(text, 6, pos, false,currentNumberFormat);
\r
1394 numLen = pos.getIndex() - cur;
\r
1396 if (n == null || numLen <= 0 || numLen > 6) {
\r
1397 pos.setIndex(start);
\r
1398 pos.setErrorIndex(cur);
\r
1402 int numVal = n.intValue();
\r
1408 if (numLen <= 2) {
\r
1412 if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
\r
1414 pos.setIndex(cur);
\r
1415 n = parseInt(text, 2, pos, false,currentNumberFormat);
\r
1416 numLen = pos.getIndex() - cur;
\r
1417 if (n != null && numLen == 2) {
\r
1418 // got minute field
\r
1419 min = n.intValue();
\r
1421 if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
\r
1423 pos.setIndex(cur);
\r
1424 n = parseInt(text, 2, pos, false,currentNumberFormat);
\r
1425 numLen = pos.getIndex() - cur;
\r
1426 if (n != null && numLen == 2) {
\r
1427 // got second field
\r
1428 sec = n.intValue();
\r
1431 pos.setIndex(cur - 1);
\r
1432 pos.setErrorIndex(-1);
\r
1437 pos.setIndex(cur - 1);
\r
1438 pos.setErrorIndex(-1);
\r
1441 } else if (numLen == 3 || numLen == 4) {
\r
1443 hour = numVal / 100;
\r
1444 min = numVal % 100;
\r
1445 } else { // numLen == 5 || numLen == 6
\r
1446 // Hmmss or HHmmss
\r
1447 hour = numVal / 10000;
\r
1448 min = (numVal % 10000) / 100;
\r
1449 sec = numVal % 100;
\r
1452 int offset = ((hour*60 + min)*60 + sec)*1000;
\r
1456 return new Integer(offset);
\r
1459 transient private WeakReference<MessageFormat>[] gmtfmtCache;
\r
1461 @SuppressWarnings("unchecked")
\r
1462 private MessageFormat getGMTFormatter(int sign, int width) {
\r
1463 MessageFormat fmt = null;
\r
1464 if (gmtfmtCache == null) {
\r
1465 gmtfmtCache = new WeakReference[4];
\r
1467 int cacheIdx = sign*2 + width;
\r
1468 if (gmtfmtCache[cacheIdx] != null) {
\r
1469 fmt = gmtfmtCache[cacheIdx].get();
\r
1471 if (fmt == null) {
\r
1472 fmt = new MessageFormat(formatData.gmtFormat);
\r
1473 GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
\r
1474 SimpleDateFormat sdf = (SimpleDateFormat)this.clone();
\r
1475 sdf.setCalendar(gcal);
\r
1476 sdf.applyPattern(formatData.getGmtHourFormat(sign, width));
\r
1477 fmt.setFormat(0, sdf);
\r
1478 gmtfmtCache[cacheIdx] = new WeakReference<MessageFormat>(fmt);
\r
1483 transient private int[] gmtFormatHmsMinLen = null;
\r
1485 private int getGMTFormatMinHMSLen(int sign) {
\r
1486 if (gmtFormatHmsMinLen == null) {
\r
1487 gmtFormatHmsMinLen = new int[2];
\r
1488 Long offset = new Long(60*60*1000); // 1 hour
\r
1490 StringBuffer buf = new StringBuffer();
\r
1491 MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
\r
1492 fmtNeg.format(new Object[] {offset}, buf, null);
\r
1493 gmtFormatHmsMinLen[0] = buf.length();
\r
1496 MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
\r
1497 fmtPos.format(new Object[] {offset}, buf, null);
\r
1498 gmtFormatHmsMinLen[1] = buf.length();
\r
1500 return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];
\r
1503 private boolean isDefaultGMTFormat() {
\r
1505 if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {
\r
1508 // GMT offset hour patters
\r
1509 boolean res = true;
\r
1510 for (int sign = 0; sign < 2 && res; sign++) {
\r
1511 for (int width = 0; width < 2; width++) {
\r
1512 if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width]
\r
1513 .equals(formatData.getGmtHourFormat(sign, width))) {
\r
1524 * Internal method. Returns null if the value of an array is empty, or if the
\r
1525 * index is out of bounds
\r
1527 /* private String getZoneArrayValue(String[] zs, int ix) {
\r
1528 if (ix >= 0 && ix < zs.length) {
\r
1529 String result = zs[ix];
\r
1530 if (result != null && result.length() != 0) {
\r
1538 * Internal high-speed method. Reuses a StringBuffer for results
\r
1539 * instead of creating a String on the heap for each call.
\r
1541 * @deprecated This API is ICU internal only.
\r
1543 protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
\r
1544 int minDigits, int maxDigits) {
\r
1545 if (useLocalZeroPaddingNumberFormat) {
\r
1546 fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
\r
1548 nf.setMinimumIntegerDigits(minDigits);
\r
1549 nf.setMaximumIntegerDigits(maxDigits);
\r
1550 nf.format(value, buf, new FieldPosition(-1));
\r
1555 * Overrides superclass method
\r
1558 public void setNumberFormat(NumberFormat newNumberFormat) {
\r
1559 // Override this method to update local zero padding number formatter
\r
1560 super.setNumberFormat(newNumberFormat);
\r
1561 initLocalZeroPaddingNumberFormat();
\r
1564 private void initLocalZeroPaddingNumberFormat() {
\r
1565 if (numberFormat instanceof DecimalFormat) {
\r
1566 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
\r
1567 useLocalZeroPaddingNumberFormat = true;
\r
1568 } else if (numberFormat instanceof DateNumberFormat) {
\r
1569 zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();
\r
1570 useLocalZeroPaddingNumberFormat = true;
\r
1572 useLocalZeroPaddingNumberFormat = false;
\r
1575 if (useLocalZeroPaddingNumberFormat) {
\r
1576 decimalBuf = new char[10]; // sufficient for int numbers
\r
1580 // If true, use local version of zero padding number format
\r
1581 private transient boolean useLocalZeroPaddingNumberFormat;
\r
1582 private transient char zeroDigit;
\r
1583 private transient char[] decimalBuf;
\r
1586 * Lightweight zero padding integer number format function.
\r
1588 * Note: This implementation is almost equivalent to format method in DateNumberFormat.
\r
1589 * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
\r
1590 * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative
\r
1591 * date format test case, having local implementation is ~10% faster than using one in
\r
1592 * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference.
\r
1596 private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
\r
1597 int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
\r
1598 int index = limit - 1;
\r
1600 decimalBuf[index] = (char)((value % 10) + zeroDigit);
\r
1602 if (index == 0 || value == 0) {
\r
1607 int padding = minDigits - (limit - index);
\r
1608 while (padding > 0 && index > 0) {
\r
1609 decimalBuf[--index] = zeroDigit;
\r
1612 while (padding > 0) {
\r
1613 // when pattern width is longer than decimalBuf, need extra
\r
1614 // leading zeros - ticke#7595
\r
1615 buf.append(zeroDigit);
\r
1618 buf.append(decimalBuf, index, limit - index);
\r
1622 * Formats a number with the specified minimum and maximum number of digits.
\r
1625 protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
\r
1627 numberFormat.setMinimumIntegerDigits(minDigits);
\r
1628 numberFormat.setMaximumIntegerDigits(maxDigits);
\r
1629 return numberFormat.format(value);
\r
1633 * Format characters that indicate numeric fields. The character
\r
1634 * at index 0 is treated specially.
\r
1636 private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";
\r
1639 * Return true if the given format character, occuring count
\r
1640 * times, represents a numeric field.
\r
1642 private static final boolean isNumeric(char formatChar, int count) {
\r
1643 int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
\r
1644 return (i > 0 || (i == 0 && count < 3));
\r
1648 * Overrides DateFormat
\r
1652 public void parse(String text, Calendar cal, ParsePosition parsePos)
\r
1654 TimeZone backupTZ = null;
\r
1655 Calendar resultCal = null;
\r
1656 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
\r
1657 // Different calendar type
\r
1658 // We use the time/zone from the input calendar, but
\r
1659 // do not use the input calendar for field calculation.
\r
1660 calendar.setTimeInMillis(cal.getTimeInMillis());
\r
1661 backupTZ = calendar.getTimeZone();
\r
1662 calendar.setTimeZone(cal.getTimeZone());
\r
1667 int pos = parsePos.getIndex();
\r
1671 tztype = TZTYPE_UNK;
\r
1672 boolean[] ambiguousYear = { false };
\r
1674 // item index for the first numeric field within a contiguous numeric run
\r
1675 int numericFieldStart = -1;
\r
1676 // item length for the first numeric field within a contiguous numeric run
\r
1677 int numericFieldLength = 0;
\r
1678 // start index of numeric text run in the input text
\r
1679 int numericStartPos = 0;
\r
1681 Object[] items = getPatternItems();
\r
1683 while (i < items.length) {
\r
1684 if (items[i] instanceof PatternItem) {
\r
1685 // Handle pattern field
\r
1686 PatternItem field = (PatternItem)items[i];
\r
1687 if (field.isNumeric) {
\r
1688 // Handle fields within a run of abutting numeric fields. Take
\r
1689 // the pattern "HHmmss" as an example. We will try to parse
\r
1690 // 2/2/2 characters of the input text, then if that fails,
\r
1691 // 1/2/2. We only adjust the width of the leftmost field; the
\r
1692 // others remain fixed. This allows "123456" => 12:34:56, but
\r
1693 // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
\r
1694 // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
\r
1695 if (numericFieldStart == -1) {
\r
1696 // check if this field is followed by abutting another numeric field
\r
1697 if ((i + 1) < items.length
\r
1698 && (items[i + 1] instanceof PatternItem)
\r
1699 && ((PatternItem)items[i + 1]).isNumeric) {
\r
1700 // record the first numeric field within a numeric text run
\r
1701 numericFieldStart = i;
\r
1702 numericFieldLength = field.length;
\r
1703 numericStartPos = pos;
\r
1707 if (numericFieldStart != -1) {
\r
1708 // Handle a numeric field within abutting numeric fields
\r
1709 int len = field.length;
\r
1710 if (numericFieldStart == i) {
\r
1711 len = numericFieldLength;
\r
1714 // Parse a numeric field
\r
1715 pos = subParse(text, pos, field.type, len,
\r
1716 true, false, ambiguousYear, cal);
\r
1719 // If the parse fails anywhere in the numeric run, back up to the
\r
1720 // start of the run and use shorter pattern length for the first
\r
1722 --numericFieldLength;
\r
1723 if (numericFieldLength == 0) {
\r
1724 // can not make shorter any more
\r
1725 parsePos.setIndex(start);
\r
1726 parsePos.setErrorIndex(pos);
\r
1727 if (backupTZ != null) {
\r
1728 calendar.setTimeZone(backupTZ);
\r
1732 i = numericFieldStart;
\r
1733 pos = numericStartPos;
\r
1738 // Handle a non-numeric field or a non-abutting numeric field
\r
1739 numericFieldStart = -1;
\r
1742 pos = subParse(text, pos, field.type, field.length,
\r
1743 false, true, ambiguousYear, cal);
\r
1745 parsePos.setIndex(start);
\r
1746 parsePos.setErrorIndex(s);
\r
1747 if (backupTZ != null) {
\r
1748 calendar.setTimeZone(backupTZ);
\r
1754 // Handle literal pattern text literal
\r
1755 numericFieldStart = -1;
\r
1757 String patl = (String)items[i];
\r
1758 int plen = patl.length();
\r
1759 int tlen = text.length();
\r
1761 while (idx < plen && pos < tlen) {
\r
1762 char pch = patl.charAt(idx);
\r
1763 char ich = text.charAt(pos);
\r
1764 if (UCharacterProperty.isRuleWhiteSpace(pch)
\r
1765 && UCharacterProperty.isRuleWhiteSpace(ich)) {
\r
1766 // White space characters found in both patten and input.
\r
1767 // Skip contiguous white spaces.
\r
1768 while ((idx + 1) < plen &&
\r
1769 UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {
\r
1772 while ((pos + 1) < tlen &&
\r
1773 UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {
\r
1776 } else if (pch != ich) {
\r
1782 if (idx != plen) {
\r
1783 // Set the position of mismatch
\r
1784 parsePos.setIndex(start);
\r
1785 parsePos.setErrorIndex(pos);
\r
1786 if (backupTZ != null) {
\r
1787 calendar.setTimeZone(backupTZ);
\r
1795 // At this point the fields of Calendar have been set. Calendar
\r
1796 // will fill in default values for missing fields when the time
\r
1799 parsePos.setIndex(pos);
\r
1801 // This part is a problem: When we call parsedDate.after, we compute the time.
\r
1802 // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
\r
1803 // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
\r
1804 // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
\r
1805 // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
\r
1806 // on that day. It is therefore parsed out to fields as 3:30 am. Then we
\r
1807 // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
\r
1808 // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
\r
1810 Date parsedDate = cal.getTime();
\r
1811 if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
\r
1812 cal.add(Calendar.YEAR, 100);
\r
1813 parsedDate = cal.getTime();
\r
1816 // Because of the above condition, save off the fields in case we need to readjust.
\r
1817 // The procedure we use here is not particularly efficient, but there is no other
\r
1818 // way to do this given the API restrictions present in Calendar. We minimize
\r
1819 // inefficiency by only performing this computation when it might apply, that is,
\r
1820 // when the two-digit year is equal to the start year, and thus might fall at the
\r
1821 // front or the back of the default century. This only works because we adjust
\r
1822 // the year correctly to start with in other cases -- see subParse().
\r
1824 if (ambiguousYear[0] || tztype != TZTYPE_UNK) {
\r
1825 // We need a copy of the fields, and we need to avoid triggering a call to
\r
1826 // complete(), which will recalculate the fields. Since we can't access
\r
1827 // the fields[] array in Calendar, we clone the entire object. This will
\r
1828 // stop working if Calendar.clone() is ever rewritten to call complete().
\r
1830 if (ambiguousYear[0]) { // the two-digit year == the default start year
\r
1831 copy = (Calendar)cal.clone();
\r
1832 Date parsedDate = copy.getTime();
\r
1833 if (parsedDate.before(getDefaultCenturyStart())) {
\r
1834 // We can't use add here because that does a complete() first.
\r
1835 cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
\r
1838 if (tztype != TZTYPE_UNK) {
\r
1839 copy = (Calendar)cal.clone();
\r
1840 TimeZone tz = copy.getTimeZone();
\r
1841 BasicTimeZone btz = null;
\r
1842 if (tz instanceof BasicTimeZone) {
\r
1843 btz = (BasicTimeZone)tz;
\r
1846 // Get local millis
\r
1847 copy.set(Calendar.ZONE_OFFSET, 0);
\r
1848 copy.set(Calendar.DST_OFFSET, 0);
\r
1849 long localMillis = copy.getTimeInMillis();
\r
1851 // Make sure parsed time zone type (Standard or Daylight)
\r
1852 // matches the rule used by the parsed time zone.
\r
1853 int[] offsets = new int[2];
\r
1854 if (btz != null) {
\r
1855 if (tztype == TZTYPE_STD) {
\r
1856 btz.getOffsetFromLocal(localMillis,
\r
1857 BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
\r
1859 btz.getOffsetFromLocal(localMillis,
\r
1860 BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
\r
1863 // No good way to resolve ambiguous time at transition,
\r
1864 // but following code work in most case.
\r
1865 tz.getOffset(localMillis, true, offsets);
\r
1867 if (tztype == TZTYPE_STD && offsets[1] != 0
\r
1868 || tztype == TZTYPE_DST && offsets[1] == 0) {
\r
1869 // Roll back one day and try it again.
\r
1870 // Note: This code assumes 1. timezone transition only happens
\r
1871 // once within 24 hours at max
\r
1872 // 2. the difference of local offsets at the transition is
\r
1873 // less than 24 hours.
\r
1874 tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
\r
1878 // Now, compare the results with parsed type, either standard or
\r
1879 // daylight saving time
\r
1880 int resolvedSavings = offsets[1];
\r
1881 if (tztype == TZTYPE_STD) {
\r
1882 if (offsets[1] != 0) {
\r
1883 // Override DST_OFFSET = 0 in the result calendar
\r
1884 resolvedSavings = 0;
\r
1886 } else { // tztype == TZTYPE_DST
\r
1887 if (offsets[1] == 0) {
\r
1888 if (btz != null) {
\r
1889 long time = localMillis + offsets[0];
\r
1890 // We use the nearest daylight saving time rule.
\r
1891 TimeZoneTransition beforeTrs, afterTrs;
\r
1892 long beforeT = time, afterT = time;
\r
1893 int beforeSav = 0, afterSav = 0;
\r
1895 // Search for DST rule before or on the time
\r
1897 beforeTrs = btz.getPreviousTransition(beforeT, true);
\r
1898 if (beforeTrs == null) {
\r
1901 beforeT = beforeTrs.getTime() - 1;
\r
1902 beforeSav = beforeTrs.getFrom().getDSTSavings();
\r
1903 if (beforeSav != 0) {
\r
1908 // Search for DST rule after the time
\r
1910 afterTrs = btz.getNextTransition(afterT, false);
\r
1911 if (afterTrs == null) {
\r
1914 afterT = afterTrs.getTime();
\r
1915 afterSav = afterTrs.getTo().getDSTSavings();
\r
1916 if (afterSav != 0) {
\r
1921 if (beforeTrs != null && afterTrs != null) {
\r
1922 if (time - beforeT > afterT - time) {
\r
1923 resolvedSavings = afterSav;
\r
1925 resolvedSavings = beforeSav;
\r
1927 } else if (beforeTrs != null && beforeSav != 0) {
\r
1928 resolvedSavings = beforeSav;
\r
1929 } else if (afterTrs != null && afterSav != 0) {
\r
1930 resolvedSavings = afterSav;
\r
1932 resolvedSavings = btz.getDSTSavings();
\r
1935 resolvedSavings = tz.getDSTSavings();
\r
1937 if (resolvedSavings == 0) {
\r
1939 resolvedSavings = millisPerHour;
\r
1943 cal.set(Calendar.ZONE_OFFSET, offsets[0]);
\r
1944 cal.set(Calendar.DST_OFFSET, resolvedSavings);
\r
1948 // An IllegalArgumentException will be thrown by Calendar.getTime()
\r
1949 // if any fields are out of range, e.g., MONTH == 17.
\r
1950 catch (IllegalArgumentException e) {
\r
1951 parsePos.setErrorIndex(pos);
\r
1952 parsePos.setIndex(start);
\r
1953 if (backupTZ != null) {
\r
1954 calendar.setTimeZone(backupTZ);
\r
1958 // Set the parsed result if local calendar is used
\r
1959 // instead of the input calendar
\r
1960 if (resultCal != null) {
\r
1961 resultCal.setTimeZone(cal.getTimeZone());
\r
1962 resultCal.setTimeInMillis(cal.getTimeInMillis());
\r
1964 // Restore the original time zone if required
\r
1965 if (backupTZ != null) {
\r
1966 calendar.setTimeZone(backupTZ);
\r
1971 * Attempt to match the text at a given position against an array of
\r
1972 * strings. Since multiple strings in the array may match (for
\r
1973 * example, if the array contains "a", "ab", and "abc", all will match
\r
1974 * the input string "abcd") the longest match is returned. As a side
\r
1975 * effect, the given field of <code>cal</code> is set to the index
\r
1976 * of the best match, if there is one.
\r
1977 * @param text the time text being parsed.
\r
1978 * @param start where to start parsing.
\r
1979 * @param field the date field being parsed.
\r
1980 * @param data the string array to parsed.
\r
1981 * @return the new start position if matching succeeded; a negative
\r
1982 * number indicating matching failure, otherwise. As a side effect,
\r
1983 * sets the <code>cal</code> field <code>field</code> to the index
\r
1984 * of the best match, if matching succeeded.
\r
1987 protected int matchString(String text, int start, int field, String[] data, Calendar cal)
\r
1990 int count = data.length;
\r
1992 if (field == Calendar.DAY_OF_WEEK) i = 1;
\r
1994 // There may be multiple strings in the data[] array which begin with
\r
1995 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
\r
1996 // We keep track of the longest match, and return that. Note that this
\r
1997 // unfortunately requires us to test all array elements.
\r
1998 int bestMatchLength = 0, bestMatch = -1;
\r
1999 for (; i<count; ++i)
\r
2001 int length = data[i].length();
\r
2002 // Always compare if we have no match yet; otherwise only compare
\r
2003 // against potentially better matches (longer strings).
\r
2004 if (length > bestMatchLength &&
\r
2005 text.regionMatches(true, start, data[i], 0, length))
\r
2008 bestMatchLength = length;
\r
2011 if (bestMatch >= 0)
\r
2013 cal.set(field, bestMatch);
\r
2014 return start + bestMatchLength;
\r
2020 * Attempt to match the text at a given position against an array of quarter
\r
2021 * strings. Since multiple strings in the array may match (for
\r
2022 * example, if the array contains "a", "ab", and "abc", all will match
\r
2023 * the input string "abcd") the longest match is returned. As a side
\r
2024 * effect, the given field of <code>cal</code> is set to the index
\r
2025 * of the best match, if there is one.
\r
2026 * @param text the time text being parsed.
\r
2027 * @param start where to start parsing.
\r
2028 * @param field the date field being parsed.
\r
2029 * @param data the string array to parsed.
\r
2030 * @return the new start position if matching succeeded; a negative
\r
2031 * number indicating matching failure, otherwise. As a side effect,
\r
2032 * sets the <code>cal</code> field <code>field</code> to the index
\r
2033 * of the best match, if matching succeeded.
\r
2036 protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
\r
2039 int count = data.length;
\r
2041 // There may be multiple strings in the data[] array which begin with
\r
2042 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
\r
2043 // We keep track of the longest match, and return that. Note that this
\r
2044 // unfortunately requires us to test all array elements.
\r
2045 int bestMatchLength = 0, bestMatch = -1;
\r
2046 for (; i<count; ++i) {
\r
2047 int length = data[i].length();
\r
2048 // Always compare if we have no match yet; otherwise only compare
\r
2049 // against potentially better matches (longer strings).
\r
2050 if (length > bestMatchLength &&
\r
2051 text.regionMatches(true, start, data[i], 0, length)) {
\r
2053 bestMatchLength = length;
\r
2057 if (bestMatch >= 0) {
\r
2058 cal.set(field, bestMatch * 3);
\r
2059 return start + bestMatchLength;
\r
2066 * Protected method that converts one field of the input string into a
\r
2067 * numeric field value in <code>cal</code>. Returns -start (for
\r
2068 * ParsePosition) if failed. Subclasses may override this method to
\r
2069 * modify or add parsing capabilities.
\r
2070 * @param text the time text to be parsed.
\r
2071 * @param start where to start parsing.
\r
2072 * @param ch the pattern character for the date field text to be parsed.
\r
2073 * @param count the count of a pattern character.
\r
2074 * @param obeyCount if true, then the next field directly abuts this one,
\r
2075 * and we should use the count to know when to stop parsing.
\r
2076 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
\r
2077 * is true, then a two-digit year was parsed and may need to be readjusted.
\r
2078 * @return the new start position if matching succeeded; a negative
\r
2079 * number indicating matching failure, otherwise. As a side effect,
\r
2080 * set the appropriate field of <code>cal</code> with the parsed
\r
2084 protected int subParse(String text, int start, char ch, int count,
\r
2085 boolean obeyCount, boolean allowNegative,
\r
2086 boolean[] ambiguousYear, Calendar cal)
\r
2088 Number number = null;
\r
2089 NumberFormat currentNumberFormat = null;
\r
2092 ParsePosition pos = new ParsePosition(0);
\r
2094 //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c
\r
2095 int patternCharIndex = -1;
\r
2096 if ('A' <= ch && ch <= 'z') {
\r
2097 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
2100 if (patternCharIndex == -1) {
\r
2104 currentNumberFormat = getNumberFormat(ch);
\r
2106 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
\r
2108 // If there are any spaces here, skip over them. If we hit the end
\r
2109 // of the string, then fail.
\r
2111 if (start >= text.length()) {
\r
2114 int c = UTF16.charAt(text, start);
\r
2115 if (!UCharacter.isUWhiteSpace(c) || !UCharacterProperty.isRuleWhiteSpace(c)) {
\r
2118 start += UTF16.getCharCount(c);
\r
2120 pos.setIndex(start);
\r
2122 // We handle a few special cases here where we need to parse
\r
2123 // a number value. We handle further, more generic cases below. We need
\r
2124 // to handle some of them here because some fields require extra processing on
\r
2125 // the parsed value.
\r
2126 if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
\r
2127 patternCharIndex == 15 /*HOUR1_FIELD*/ ||
\r
2128 (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
\r
2129 patternCharIndex == 1 ||
\r
2130 patternCharIndex == 8)
\r
2132 // It would be good to unify this with the obeyCount logic below,
\r
2133 // but that's going to be difficult.
\r
2135 if ((start+count) > text.length()) return -start;
\r
2136 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
\r
2138 number = parseInt(text, pos, allowNegative,currentNumberFormat);
\r
2140 if (number == null) {
\r
2143 value = number.intValue();
\r
2146 switch (patternCharIndex)
\r
2148 case 0: // 'G' - ERA
\r
2150 return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
\r
2152 return matchString(text, start, Calendar.ERA, formatData.eras, cal);
\r
2154 case 1: // 'y' - YEAR
\r
2155 // If there are 3 or more YEAR pattern characters, this indicates
\r
2156 // that the year value is to be treated literally, without any
\r
2157 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
\r
2158 // we made adjustments to place the 2-digit year in the proper
\r
2159 // century, for parsed strings from "00" to "99". Any other string
\r
2160 // is treated literally: "2250", "-1", "1", "002".
\r
2161 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
\r
2162 if (count == 2 && (pos.getIndex() - start) == 2
\r
2163 && UCharacter.isDigit(text.charAt(start))
\r
2164 && UCharacter.isDigit(text.charAt(start+1)))
\r
2166 // Assume for example that the defaultCenturyStart is 6/18/1903.
\r
2167 // This means that two-digit years will be forced into the range
\r
2168 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
\r
2169 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
\r
2170 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
\r
2171 // other fields specify a date before 6/18, or 1903 if they specify a
\r
2172 // date afterwards. As a result, 03 is an ambiguous year. All other
\r
2173 // two-digit years are unambiguous.
\r
2174 int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
\r
2175 ambiguousYear[0] = value == ambiguousTwoDigitYear;
\r
2176 value += (getDefaultCenturyStartYear()/100)*100 +
\r
2177 (value < ambiguousTwoDigitYear ? 100 : 0);
\r
2179 cal.set(Calendar.YEAR, value);
\r
2181 // Delayed checking for adjustment of Hebrew month numbers in non-leap years.
\r
2182 if (DelayedHebrewMonthCheck) {
\r
2183 if (!HebrewCalendar.isLeapYear(value)) {
\r
2184 cal.add(Calendar.MONTH,1);
\r
2186 DelayedHebrewMonthCheck = false;
\r
2188 return pos.getIndex();
\r
2189 case 2: // 'M' - MONTH
\r
2190 if (count <= 2) { // i.e., M or MM.
\r
2191 // Don't want to parse the month if it is a string
\r
2192 // while pattern uses numeric style: M or MM.
\r
2193 // [We computed 'value' above.]
\r
2194 cal.set(Calendar.MONTH, value - 1);
\r
2195 // When parsing month numbers from the Hebrew Calendar, we might need
\r
2196 // to adjust the month depending on whether or not it was a leap year.
\r
2197 // We may or may not yet know what year it is, so might have to delay
\r
2198 // checking until the year is parsed.
\r
2199 if (cal.getType().equals("hebrew") && value >= 6) {
\r
2200 if (cal.isSet(Calendar.YEAR)) {
\r
2201 if (!HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR))) {
\r
2202 cal.set(Calendar.MONTH, value);
\r
2205 DelayedHebrewMonthCheck = true;
\r
2208 return pos.getIndex();
\r
2210 // count >= 3 // i.e., MMM or MMMM
\r
2211 // Want to be able to parse both short and long forms.
\r
2212 // Try count == 4 first:
\r
2213 int newStart = matchString(text, start, Calendar.MONTH,
\r
2214 formatData.months, cal);
\r
2215 if (newStart > 0) {
\r
2217 } else { // count == 4 failed, now try count == 3
\r
2218 return matchString(text, start, Calendar.MONTH,
\r
2219 formatData.shortMonths, cal);
\r
2222 case 26: // 'L' - STAND_ALONE_MONTH
\r
2223 if (count <= 2) { // i.e., M or MM.
\r
2224 // Don't want to parse the month if it is a string
\r
2225 // while pattern uses numeric style: M or MM.
\r
2226 // [We computed 'value' above.]
\r
2227 cal.set(Calendar.MONTH, value - 1);
\r
2228 return pos.getIndex();
\r
2230 // count >= 3 // i.e., MMM or MMMM
\r
2231 // Want to be able to parse both short and long forms.
\r
2232 // Try count == 4 first:
\r
2233 int newStart = matchString(text, start, Calendar.MONTH,
\r
2234 formatData.standaloneMonths, cal);
\r
2235 if (newStart > 0) {
\r
2237 } else { // count == 4 failed, now try count == 3
\r
2238 return matchString(text, start, Calendar.MONTH,
\r
2239 formatData.standaloneShortMonths, cal);
\r
2242 case 4: // 'k' - HOUR_OF_DAY (1..24)
\r
2243 // [We computed 'value' above.]
\r
2244 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) {
\r
2247 cal.set(Calendar.HOUR_OF_DAY, value);
\r
2248 return pos.getIndex();
\r
2249 case 8: // 'S' - FRACTIONAL_SECOND
\r
2250 // Fractional seconds left-justify
\r
2251 i = pos.getIndex() - start;
\r
2263 value = (value + (a>>1)) / a;
\r
2265 cal.set(Calendar.MILLISECOND, value);
\r
2266 return pos.getIndex();
\r
2267 case 9: { // 'E' - DAY_OF_WEEK
\r
2268 // Want to be able to parse both short and long forms.
\r
2269 // Try count == 4 (EEEE) first:
\r
2270 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2271 formatData.weekdays, cal);
\r
2272 if (newStart > 0) {
\r
2274 } else { // EEEE failed, now try EEE
\r
2275 return matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2276 formatData.shortWeekdays, cal);
\r
2279 case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
\r
2280 // Want to be able to parse both short and long forms.
\r
2281 // Try count == 4 (cccc) first:
\r
2282 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2283 formatData.standaloneWeekdays, cal);
\r
2284 if (newStart > 0) {
\r
2286 } else { // cccc failed, now try ccc
\r
2287 return matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2288 formatData.standaloneShortWeekdays, cal);
\r
2291 case 14: // 'a' - AM_PM
\r
2292 return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
\r
2293 case 15: // 'h' - HOUR (1..12)
\r
2294 // [We computed 'value' above.]
\r
2295 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) {
\r
2298 cal.set(Calendar.HOUR, value);
\r
2299 return pos.getIndex();
\r
2300 case 17: // 'z' - ZONE_OFFSET
\r
2301 case 23: // 'Z' - TIMEZONE_RFC
\r
2302 case 24: // 'v' - TIMEZONE_GENERIC
\r
2303 case 29: // 'V' - TIMEZONE_SPECIAL
\r
2305 TimeZone tz = null;
\r
2307 boolean parsed = false;
\r
2310 // Check if this is a long GMT offset string (either localized or default)
\r
2311 Integer gmtoff = parseGMT(text, pos, currentNumberFormat);
\r
2312 if (gmtoff != null) {
\r
2313 offset = gmtoff.intValue();
\r
2319 // Check if this is an RFC822 time zone offset.
\r
2320 // ICU supports the standard RFC822 format [+|-]HHmm
\r
2321 // and its extended form [+|-]HHmmSS.
\r
2325 char signChar = text.charAt(start);
\r
2326 if (signChar == '+') {
\r
2328 } else if (signChar == '-') {
\r
2331 // Not an RFC822 offset string
\r
2336 int orgPos = start + 1;
\r
2337 pos.setIndex(orgPos);
\r
2338 number = parseInt(text, 6, pos, false,currentNumberFormat);
\r
2339 int numLen = pos.getIndex() - orgPos;
\r
2340 if (numLen <= 0) {
\r
2344 // Followings are possible format (excluding sign char)
\r
2351 int val = number.intValue();
\r
2352 int hour = 0, min = 0, sec = 0;
\r
2365 hour = val / 10000;
\r
2366 min = (val % 10000) / 100;
\r
2370 if (hour > 23 || min > 59 || sec > 59) {
\r
2371 // Invalid value range
\r
2374 offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;
\r
2379 // Failed to parse. Reset the position.
\r
2380 pos.setIndex(start);
\r
2385 // offset was successfully parsed as either a long GMT string or
\r
2386 // RFC822 zone offset string. Create normalized zone ID for the
\r
2388 tz = ZoneMeta.getCustomTimeZone(offset);
\r
2389 cal.setTimeZone(tz);
\r
2390 return pos.getIndex();
\r
2394 // At this point, check for named time zones by looking through
\r
2395 // the locale data from the DateFormatZoneData strings.
\r
2396 // Want to be able to parse both short and long forms.
\r
2397 // optimize for calendar's current time zone
\r
2398 ZoneStringInfo zsinfo = null;
\r
2399 switch (patternCharIndex) {
\r
2400 case 17: // 'z' - ZONE_OFFSET
\r
2402 zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
\r
2404 zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);
\r
2407 case 24: // 'v' - TIMEZONE_GENERIC
\r
2409 zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);
\r
2410 } else if (count == 4) {
\r
2411 zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);
\r
2414 case 29: // 'V' - TIMEZONE_SPECIAL
\r
2416 zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
\r
2417 } else if (count == 4) {
\r
2418 zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);
\r
2422 if (zsinfo != null) {
\r
2423 if (zsinfo.isStandard()) {
\r
2424 tztype = TZTYPE_STD;
\r
2425 } else if (zsinfo.isDaylight()) {
\r
2426 tztype = TZTYPE_DST;
\r
2428 tz = TimeZone.getTimeZone(zsinfo.getID());
\r
2429 cal.setTimeZone(tz);
\r
2430 return start + zsinfo.getString().length();
\r
2433 // Final attempt - is this standalone GMT/UT/UTC?
\r
2435 if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
\r
2436 gmtLen = STR_GMT_LEN;
\r
2437 } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {
\r
2438 gmtLen = STR_UTC_LEN;
\r
2439 } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
\r
2440 gmtLen = STR_UT_LEN;
\r
2443 tz = TimeZone.getTimeZone("Etc/GMT");
\r
2444 cal.setTimeZone(tz);
\r
2445 return start + gmtLen;
\r
2448 // complete failure
\r
2452 case 27: // 'Q' - QUARTER
\r
2453 if (count <= 2) { // i.e., Q or QQ.
\r
2454 // Don't want to parse the quarter if it is a string
\r
2455 // while pattern uses numeric style: Q or QQ.
\r
2456 // [We computed 'value' above.]
\r
2457 cal.set(Calendar.MONTH, (value - 1) * 3);
\r
2458 return pos.getIndex();
\r
2460 // count >= 3 // i.e., QQQ or QQQQ
\r
2461 // Want to be able to parse both short and long forms.
\r
2462 // Try count == 4 first:
\r
2463 int newStart = matchQuarterString(text, start, Calendar.MONTH,
\r
2464 formatData.quarters, cal);
\r
2465 if (newStart > 0) {
\r
2467 } else { // count == 4 failed, now try count == 3
\r
2468 return matchQuarterString(text, start, Calendar.MONTH,
\r
2469 formatData.shortQuarters, cal);
\r
2473 case 28: // 'q' - STANDALONE QUARTER
\r
2474 if (count <= 2) { // i.e., q or qq.
\r
2475 // Don't want to parse the quarter if it is a string
\r
2476 // while pattern uses numeric style: q or qq.
\r
2477 // [We computed 'value' above.]
\r
2478 cal.set(Calendar.MONTH, (value - 1) * 3);
\r
2479 return pos.getIndex();
\r
2481 // count >= 3 // i.e., qqq or qqqq
\r
2482 // Want to be able to parse both short and long forms.
\r
2483 // Try count == 4 first:
\r
2484 int newStart = matchQuarterString(text, start, Calendar.MONTH,
\r
2485 formatData.standaloneQuarters, cal);
\r
2486 if (newStart > 0) {
\r
2488 } else { // count == 4 failed, now try count == 3
\r
2489 return matchQuarterString(text, start, Calendar.MONTH,
\r
2490 formatData.standaloneShortQuarters, cal);
\r
2495 // case 3: // 'd' - DATE
\r
2496 // case 5: // 'H' - HOUR_OF_DAY (0..23)
\r
2497 // case 6: // 'm' - MINUTE
\r
2498 // case 7: // 's' - SECOND
\r
2499 // case 10: // 'D' - DAY_OF_YEAR
\r
2500 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
\r
2501 // case 12: // 'w' - WEEK_OF_YEAR
\r
2502 // case 13: // 'W' - WEEK_OF_MONTH
\r
2503 // case 16: // 'K' - HOUR (0..11)
\r
2504 // case 18: // 'Y' - YEAR_WOY
\r
2505 // case 19: // 'e' - DOW_LOCAL
\r
2506 // case 20: // 'u' - EXTENDED_YEAR
\r
2507 // case 21: // 'g' - JULIAN_DAY
\r
2508 // case 22: // 'A' - MILLISECONDS_IN_DAY
\r
2510 // Handle "generic" fields
\r
2512 if ((start+count) > text.length()) return -start;
\r
2513 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
\r
2515 number = parseInt(text, pos, allowNegative,currentNumberFormat);
\r
2517 if (number != null) {
\r
2518 cal.set(field, number.intValue());
\r
2519 return pos.getIndex();
\r
2526 * Parse an integer using numberFormat. This method is semantically
\r
2527 * const, but actually may modify fNumberFormat.
\r
2529 private Number parseInt(String text,
\r
2530 ParsePosition pos,
\r
2531 boolean allowNegative,
\r
2532 NumberFormat fmt) {
\r
2533 return parseInt(text, -1, pos, allowNegative, fmt);
\r
2537 * Parse an integer using numberFormat up to maxDigits.
\r
2539 private Number parseInt(String text,
\r
2541 ParsePosition pos,
\r
2542 boolean allowNegative,
\r
2543 NumberFormat fmt) {
\r
2545 int oldPos = pos.getIndex();
\r
2546 if (allowNegative) {
\r
2547 number = fmt.parse(text, pos);
\r
2549 // Invalidate negative numbers
\r
2550 if (fmt instanceof DecimalFormat) {
\r
2551 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
\r
2552 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
\r
2553 number = fmt.parse(text, pos);
\r
2554 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
\r
2556 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
\r
2557 if (dateNumberFormat) {
\r
2558 ((DateNumberFormat)fmt).setParsePositiveOnly(true);
\r
2560 number = fmt.parse(text, pos);
\r
2561 if (dateNumberFormat) {
\r
2562 ((DateNumberFormat)fmt).setParsePositiveOnly(false);
\r
2566 if (maxDigits > 0) {
\r
2567 // adjust the result to fit into
\r
2568 // the maxDigits and move the position back
\r
2569 int nDigits = pos.getIndex() - oldPos;
\r
2570 if (nDigits > maxDigits) {
\r
2571 double val = number.doubleValue();
\r
2572 nDigits -= maxDigits;
\r
2573 while (nDigits > 0) {
\r
2577 pos.setIndex(oldPos + maxDigits);
\r
2578 number = new Integer((int)val);
\r
2586 * Translate a pattern, mapping each character in the from string to the
\r
2587 * corresponding character in the to string.
\r
2589 private String translatePattern(String pat, String from, String to) {
\r
2590 StringBuilder result = new StringBuilder();
\r
2591 boolean inQuote = false;
\r
2592 for (int i = 0; i < pat.length(); ++i) {
\r
2593 char c = pat.charAt(i);
\r
2600 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
\r
2601 int ci = from.indexOf(c);
\r
2603 c = to.charAt(ci);
\r
2605 // do not worry on translatepattern if the character is not listed
\r
2606 // we do the validity check elsewhere
\r
2612 throw new IllegalArgumentException("Unfinished quote in pattern");
\r
2614 return result.toString();
\r
2618 * Return a pattern string describing this date format.
\r
2621 public String toPattern() {
\r
2626 * Return a localized pattern string describing this date format.
\r
2629 public String toLocalizedPattern() {
\r
2630 return translatePattern(pattern,
\r
2631 DateFormatSymbols.patternChars,
\r
2632 formatData.localPatternChars);
\r
2636 * Apply the given unlocalized pattern string to this date format.
\r
2639 public void applyPattern(String pat)
\r
2641 this.pattern = pat;
\r
2642 setLocale(null, null);
\r
2643 // reset parsed pattern items
\r
2644 patternItems = null;
\r
2648 * Apply the given localized pattern string to this date format.
\r
2651 public void applyLocalizedPattern(String pat) {
\r
2652 this.pattern = translatePattern(pat,
\r
2653 formatData.localPatternChars,
\r
2654 DateFormatSymbols.patternChars);
\r
2655 setLocale(null, null);
\r
2659 * Gets the date/time formatting data.
\r
2660 * @return a copy of the date-time formatting data associated
\r
2661 * with this date-time formatter.
\r
2664 public DateFormatSymbols getDateFormatSymbols()
\r
2666 return (DateFormatSymbols)formatData.clone();
\r
2670 * Allows you to set the date/time formatting data.
\r
2671 * @param newFormatSymbols the new symbols
\r
2674 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
\r
2676 this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
\r
2677 gmtfmtCache = null;
\r
2681 * Method for subclasses to access the DateFormatSymbols.
\r
2684 protected DateFormatSymbols getSymbols() {
\r
2685 return formatData;
\r
2689 * Overrides Cloneable
\r
2692 public Object clone() {
\r
2693 SimpleDateFormat other = (SimpleDateFormat) super.clone();
\r
2694 other.formatData = (DateFormatSymbols) formatData.clone();
\r
2699 * Override hashCode.
\r
2700 * Generates the hash code for the SimpleDateFormat object
\r
2703 public int hashCode()
\r
2705 return pattern.hashCode();
\r
2706 // just enough fields for a reasonable distribution
\r
2710 * Override equals.
\r
2713 public boolean equals(Object obj)
\r
2715 if (!super.equals(obj)) return false; // super does class check
\r
2716 SimpleDateFormat that = (SimpleDateFormat) obj;
\r
2717 return (pattern.equals(that.pattern)
\r
2718 && formatData.equals(that.formatData));
\r
2722 * Override writeObject.
\r
2724 private void writeObject(ObjectOutputStream stream) throws IOException{
\r
2725 if (defaultCenturyStart == null) {
\r
2726 // if defaultCenturyStart is not yet initialized,
\r
2727 // calculate and set value before serialization.
\r
2728 initializeDefaultCenturyStart(defaultCenturyBase);
\r
2730 stream.defaultWriteObject();
\r
2734 * Override readObject.
\r
2736 private void readObject(ObjectInputStream stream)
\r
2737 throws IOException, ClassNotFoundException {
\r
2738 stream.defaultReadObject();
\r
2740 // don't have old serial data to test with
\r
2741 if (serialVersionOnStream < 1) {
\r
2742 // didn't have defaultCenturyStart field
\r
2743 defaultCenturyBase = System.currentTimeMillis();
\r
2747 // fill in dependent transient field
\r
2748 parseAmbiguousDatesAsAfter(defaultCenturyStart);
\r
2750 serialVersionOnStream = currentSerialVersion;
\r
2751 locale = getLocale(ULocale.VALID_LOCALE);
\r
2753 initLocalZeroPaddingNumberFormat();
\r
2757 * Format the object to an attributed string, and return the corresponding iterator
\r
2758 * Overrides superclass method.
\r
2760 * @param obj The object to format
\r
2761 * @return <code>AttributedCharacterIterator</code> describing the formatted value.
\r
2765 public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
\r
2766 Calendar cal = calendar;
\r
2767 if (obj instanceof Calendar) {
\r
2768 cal = (Calendar)obj;
\r
2769 } else if (obj instanceof Date) {
\r
2770 calendar.setTime((Date)obj);
\r
2771 } else if (obj instanceof Number) {
\r
2772 calendar.setTimeInMillis(((Number)obj).longValue());
\r
2774 throw new IllegalArgumentException("Cannot format given Object as a Date");
\r
2776 StringBuffer toAppendTo = new StringBuffer();
\r
2777 FieldPosition pos = new FieldPosition(0);
\r
2778 List<FieldPosition> attributes = new ArrayList<FieldPosition>();
\r
2779 format(cal, toAppendTo, pos, attributes);
\r
2781 AttributedString as = new AttributedString(toAppendTo.toString());
\r
2783 // add DateFormat field attributes to the AttributedString
\r
2784 for (int i = 0; i < attributes.size(); i++) {
\r
2785 FieldPosition fp = attributes.get(i);
\r
2786 Format.Field attribute = fp.getFieldAttribute();
\r
2787 as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
\r
2789 // return the CharacterIterator from AttributedString
\r
2790 return as.getIterator();
\r
2794 * Get the locale of this simple date formatter.
\r
2795 * It is package accessible. also used in DateIntervalFormat.
\r
2797 * @return locale in this simple date formatter
\r
2799 ULocale getLocale()
\r
2807 * Check whether the 'field' is smaller than all the fields covered in
\r
2808 * pattern, return true if it is.
\r
2809 * The sequence of calendar field,
\r
2810 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
\r
2811 * @param field the calendar field need to check against
\r
2812 * @return true if the 'field' is smaller than all the fields
\r
2813 * covered in pattern. false otherwise.
\r
2816 boolean isFieldUnitIgnored(int field) {
\r
2817 return isFieldUnitIgnored(pattern, field);
\r
2822 * Check whether the 'field' is smaller than all the fields covered in
\r
2823 * pattern, return true if it is.
\r
2824 * The sequence of calendar field,
\r
2825 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
\r
2826 * @param pattern the pattern to check against
\r
2827 * @param field the calendar field need to check against
\r
2828 * @return true if the 'field' is smaller than all the fields
\r
2829 * covered in pattern. false otherwise.
\r
2831 static boolean isFieldUnitIgnored(String pattern, int field) {
\r
2832 int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
\r
2835 boolean inQuote = false;
\r
2839 for (int i = 0; i < pattern.length(); ++i) {
\r
2840 ch = pattern.charAt(i);
\r
2841 if (ch != prevCh && count > 0) {
\r
2842 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
\r
2843 if ( fieldLevel <= level ) {
\r
2849 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
\r
2852 inQuote = ! inQuote;
\r
2854 } else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
\r
2855 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
\r
2862 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
\r
2863 if ( fieldLevel <= level ) {
\r
2872 * Format date interval by algorithm.
\r
2873 * It is supposed to be used only by CLDR survey tool.
\r
2875 * @param fromCalendar calendar set to the from date in date interval
\r
2876 * to be formatted into date interval stirng
\r
2877 * @param toCalendar calendar set to the to date in date interval
\r
2878 * to be formatted into date interval stirng
\r
2879 * @param appendTo Output parameter to receive result.
\r
2880 * Result is appended to existing contents.
\r
2881 * @param pos On input: an alignment field, if desired.
\r
2882 * On output: the offsets of the alignment field.
\r
2883 * @exception IllegalArgumentException when there is non-recognized
\r
2885 * @return Reference to 'appendTo' parameter.
\r
2887 * @deprecated This API is ICU internal only.
\r
2889 public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
\r
2890 Calendar toCalendar,
\r
2891 StringBuffer appendTo,
\r
2892 FieldPosition pos)
\r
2893 throws IllegalArgumentException
\r
2895 // not support different calendar types and time zones
\r
2896 if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
\r
2897 throw new IllegalArgumentException("can not format on two different calendars");
\r
2900 Object[] items = getPatternItems();
\r
2901 int diffBegin = -1;
\r
2904 /* look for different formatting string range */
\r
2905 // look for start of difference
\r
2907 for (int i = 0; i < items.length; i++) {
\r
2908 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
\r
2914 if ( diffBegin == -1 ) {
\r
2915 // no difference, single date format
\r
2916 return format(fromCalendar, appendTo, pos);
\r
2919 // look for end of difference
\r
2920 for (int i = items.length-1; i >= diffBegin; i--) {
\r
2921 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
\r
2926 } catch ( IllegalArgumentException e ) {
\r
2927 throw new IllegalArgumentException(e.toString());
\r
2930 // full range is different
\r
2931 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
\r
2932 format(fromCalendar, appendTo, pos);
\r
2933 appendTo.append(" \u2013 "); // default separator
\r
2934 format(toCalendar, appendTo, pos);
\r
2939 /* search for largest calendar field within the different range */
\r
2940 int highestLevel = 1000;
\r
2941 for (int i = diffBegin; i <= diffEnd; i++) {
\r
2942 if ( items[i] instanceof String) {
\r
2945 PatternItem item = (PatternItem)items[i];
\r
2946 char ch = item.type;
\r
2947 int patternCharIndex = -1;
\r
2948 if ('A' <= ch && ch <= 'z') {
\r
2949 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
\r
2952 if (patternCharIndex == -1) {
\r
2953 throw new IllegalArgumentException("Illegal pattern character " +
\r
2954 "'" + ch + "' in \"" +
\r
2958 if ( patternCharIndex < highestLevel ) {
\r
2959 highestLevel = patternCharIndex;
\r
2963 /* re-calculate diff range, including those calendar field which
\r
2964 is in lower level than the largest calendar field covered
\r
2965 in diff range calculated. */
\r
2967 for (int i = 0; i < diffBegin; i++) {
\r
2968 if ( lowerLevel(items, i, highestLevel) ) {
\r
2975 for (int i = items.length-1; i > diffEnd; i--) {
\r
2976 if ( lowerLevel(items, i, highestLevel) ) {
\r
2981 } catch ( IllegalArgumentException e ) {
\r
2982 throw new IllegalArgumentException(e.toString());
\r
2986 // full range is different
\r
2987 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
\r
2988 format(fromCalendar, appendTo, pos);
\r
2989 appendTo.append(" \u2013 "); // default separator
\r
2990 format(toCalendar, appendTo, pos);
\r
2997 pos.setBeginIndex(0);
\r
2998 pos.setEndIndex(0);
\r
3000 // formatting date 1
\r
3001 for (int i = 0; i <= diffEnd; i++) {
\r
3002 if (items[i] instanceof String) {
\r
3003 appendTo.append((String)items[i]);
\r
3005 PatternItem item = (PatternItem)items[i];
\r
3006 if (useFastFormat) {
\r
3007 subFormat(appendTo, item.type, item.length, appendTo.length(), pos,
\r
3010 appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos,
\r
3011 formatData, fromCalendar));
\r
3016 appendTo.append(" \u2013 "); // default separator
\r
3018 // formatting date 2
\r
3019 for (int i = diffBegin; i < items.length; i++) {
\r
3020 if (items[i] instanceof String) {
\r
3021 appendTo.append((String)items[i]);
\r
3023 PatternItem item = (PatternItem)items[i];
\r
3024 if (useFastFormat) {
\r
3025 subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);
\r
3027 appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos,
\r
3028 formatData, toCalendar));
\r
3037 * check whether the i-th item in 2 calendar is in different value.
\r
3039 * It is supposed to be used only by CLDR survey tool.
\r
3040 * It is used by intervalFormatByAlgorithm().
\r
3042 * @param fromCalendar one calendar
\r
3043 * @param toCalendar the other calendar
\r
3044 * @param items pattern items
\r
3045 * @param i the i-th item in pattern items
\r
3046 * @exception IllegalArgumentException when there is non-recognized
\r
3048 * @return true is i-th item in 2 calendar is in different
\r
3049 * value, false otherwise.
\r
3051 private boolean diffCalFieldValue(Calendar fromCalendar,
\r
3052 Calendar toCalendar,
\r
3054 int i) throws IllegalArgumentException {
\r
3055 if ( items[i] instanceof String) {
\r
3058 PatternItem item = (PatternItem)items[i];
\r
3059 char ch = item.type;
\r
3060 int patternCharIndex = -1;
\r
3061 if ('A' <= ch && ch <= 'z') {
\r
3062 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
3065 if (patternCharIndex == -1) {
\r
3066 throw new IllegalArgumentException("Illegal pattern character " +
\r
3067 "'" + ch + "' in \"" +
\r
3071 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
\r
3072 int value = fromCalendar.get(field);
\r
3073 int value_2 = toCalendar.get(field);
\r
3074 if ( value != value_2 ) {
\r
3082 * check whether the i-th item's level is lower than the input 'level'
\r
3084 * It is supposed to be used only by CLDR survey tool.
\r
3085 * It is used by intervalFormatByAlgorithm().
\r
3087 * @param items the pattern items
\r
3088 * @param i the i-th item in pattern items
\r
3089 * @param level the level with which the i-th pattern item compared to
\r
3090 * @exception IllegalArgumentException when there is non-recognized
\r
3092 * @return true if i-th pattern item is lower than 'level',
\r
3095 private boolean lowerLevel(Object[] items, int i, int level)
\r
3096 throws IllegalArgumentException {
\r
3097 if ( items[i] instanceof String) {
\r
3100 PatternItem item = (PatternItem)items[i];
\r
3101 char ch = item.type;
\r
3102 int patternCharIndex = -1;
\r
3103 if ('A' <= ch && ch <= 'z') {
\r
3104 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
\r
3107 if (patternCharIndex == -1) {
\r
3108 throw new IllegalArgumentException("Illegal pattern character " +
\r
3109 "'" + ch + "' in \"" +
\r
3113 if ( patternCharIndex >= level ) {
\r
3121 * @deprecated This API is ICU internal only.
\r
3123 protected NumberFormat getNumberFormat(char ch) {
\r
3125 Character ovrField;
\r
3126 ovrField = new Character(ch);
\r
3127 if (overrideMap != null && overrideMap.containsKey(ovrField)) {
\r
3128 String nsName = overrideMap.get(ovrField).toString();
\r
3129 NumberFormat nf = numberFormatters.get(nsName);
\r
3132 return numberFormat;
\r
3136 private void initNumberFormatters(ULocale loc) {
\r
3138 numberFormatters = new HashMap<String, NumberFormat>();
\r
3139 overrideMap = new HashMap<Character, String>();
\r
3140 processOverrideString(loc,override);
\r
3144 private void processOverrideString(ULocale loc, String str) {
\r
3146 if ( str == null || str.length() == 0 )
\r
3152 Character ovrField;
\r
3153 boolean moreToProcess = true;
\r
3154 boolean fullOverride;
\r
3156 while (moreToProcess) {
\r
3157 int delimiterPosition = str.indexOf(";",start);
\r
3158 if (delimiterPosition == -1) {
\r
3159 moreToProcess = false;
\r
3160 end = str.length();
\r
3162 end = delimiterPosition;
\r
3165 String currentString = str.substring(start,end);
\r
3166 int equalSignPosition = currentString.indexOf("=");
\r
3167 if (equalSignPosition == -1) { // Simple override string such as "hebrew"
\r
3168 nsName = currentString;
\r
3169 fullOverride = true;
\r
3170 } else { // Field specific override string such as "y=hebrew"
\r
3171 nsName = currentString.substring(equalSignPosition+1);
\r
3172 ovrField = new Character(currentString.charAt(0));
\r
3173 overrideMap.put(ovrField,nsName);
\r
3174 fullOverride = false;
\r
3177 ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
\r
3178 NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
\r
3180 if (fullOverride) {
\r
3181 setNumberFormat(nf);
\r
3183 // Since one or more of the override number formatters might be complex,
\r
3184 // we can't rely on the fast numfmt where we have a partial field override.
\r
3185 useLocalZeroPaddingNumberFormat = false;
\r
3188 if (!numberFormatters.containsKey(nsName)) {
\r
3189 numberFormatters.put(nsName,nf);
\r
3192 start = delimiterPosition + 1;
\r