3 *******************************************************************************
\r
4 * Copyright (C) 1996-2009, International Business Machines Corporation and *
\r
5 * others. All Rights Reserved. *
\r
6 *******************************************************************************
\r
9 package com.ibm.icu.text;
\r
11 import java.io.IOException;
\r
12 import java.io.ObjectInputStream;
\r
13 import java.io.ObjectOutputStream;
\r
14 import java.lang.ref.WeakReference;
\r
15 import java.lang.Character;
\r
16 import java.text.FieldPosition;
\r
17 import java.text.ParsePosition;
\r
18 import java.util.ArrayList;
\r
19 import java.util.Date;
\r
20 import java.util.HashMap;
\r
21 import java.util.List;
\r
22 import java.util.Locale;
\r
23 import java.util.MissingResourceException;
\r
24 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
26 import java.text.AttributedCharacterIterator;
\r
27 import java.text.AttributedString;
\r
28 import java.text.Format;
\r
29 import java.util.LinkedList;
\r
32 import com.ibm.icu.impl.CalendarData;
\r
33 import com.ibm.icu.impl.DateNumberFormat;
\r
34 import com.ibm.icu.impl.ICUCache;
\r
35 import com.ibm.icu.impl.SimpleCache;
\r
36 import com.ibm.icu.impl.UCharacterProperty;
\r
37 import com.ibm.icu.impl.ZoneMeta;
\r
38 import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;
\r
39 import com.ibm.icu.lang.UCharacter;
\r
40 import com.ibm.icu.util.BasicTimeZone;
\r
41 import com.ibm.icu.util.Calendar;
\r
42 import com.ibm.icu.util.GregorianCalendar;
\r
43 import com.ibm.icu.util.TimeZone;
\r
44 import com.ibm.icu.util.TimeZoneTransition;
\r
45 import com.ibm.icu.util.ULocale;
\r
49 * <code>SimpleDateFormat</code> is a concrete class for formatting and
\r
50 * parsing dates in a locale-sensitive manner. It allows for formatting
\r
51 * (date -> text), parsing (text -> date), and normalization.
\r
54 * <code>SimpleDateFormat</code> allows you to start by choosing
\r
55 * any user-defined patterns for date-time formatting. However, you
\r
56 * are encouraged to create a date-time formatter with either
\r
57 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
\r
58 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
\r
59 * of these class methods can return a date/time formatter initialized
\r
60 * with a default format pattern. You may modify the format pattern
\r
61 * using the <code>applyPattern</code> methods as desired.
\r
62 * For more information on using these methods, see
\r
63 * {@link DateFormat}.
\r
66 * <strong>Time Format Syntax:</strong>
\r
68 * To specify the time format use a <em>time pattern</em> string.
\r
69 * In this pattern, all ASCII letters are reserved as pattern letters,
\r
70 * which are defined as the following:
\r
73 * Symbol Meaning Presentation Example
\r
74 * ------ ------- ------------ -------
\r
75 * G era designator (Text) AD
\r
76 * y† year (Number) 1996
\r
77 * Y* year (week of year) (Number) 1997
\r
78 * u* extended year (Number) 4601
\r
79 * M month in year (Text & Number) July & 07
\r
80 * d day in month (Number) 10
\r
81 * h hour in am/pm (1~12) (Number) 12
\r
82 * H hour in day (0~23) (Number) 0
\r
83 * m minute in hour (Number) 30
\r
84 * s second in minute (Number) 55
\r
85 * S fractional second (Number) 978
\r
86 * E day of week (Text) Tuesday
\r
87 * e* day of week (local 1~7) (Text & Number) Tuesday & 2
\r
88 * D day in year (Number) 189
\r
89 * F day of week in month (Number) 2 (2nd Wed in July)
\r
90 * w week in year (Number) 27
\r
91 * W week in month (Number) 2
\r
92 * a am/pm marker (Text) PM
\r
93 * k hour in day (1~24) (Number) 24
\r
94 * K hour in am/pm (0~11) (Number) 0
\r
95 * z time zone (Text) Pacific Standard Time
\r
96 * Z time zone (RFC 822) (Number) -0800
\r
97 * v time zone (generic) (Text) Pacific Time
\r
98 * V time zone (location) (Text) United States (Los Angeles)
\r
99 * g* Julian day (Number) 2451334
\r
100 * A* milliseconds in day (Number) 69540000
\r
101 * Q* quarter in year (Text & Number) Q1 & 01
\r
102 * c* stand alone day of week (Text & Number) Tuesday & 2
\r
103 * L* stand alone month (Text & Number) July & 07
\r
104 * q* stand alone quarter (Text & Number) Q1 & 01
\r
105 * ' escape for text (Delimiter) 'Date='
\r
106 * '' single quote (Literal) 'o''clock'
\r
109 * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
\r
110 * <tt><b>†</b></tt> ICU interprets a single 'y' differently than Java.</p>
\r
112 * The count of pattern letters determine the format.
\r
114 * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
\r
115 * < 4--use short or abbreviated form if one exists.
\r
117 * <strong>(Number)</strong>: the minimum number of digits. Shorter
\r
118 * numbers are zero-padded to this amount. Year is handled specially;
\r
119 * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
\r
120 * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
\r
121 * Unlike other fields, fractional seconds are padded on the right with zero.
\r
123 * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
\r
125 * Any characters in the pattern that are not in the ranges of ['a'..'z']
\r
126 * and ['A'..'Z'] will be treated as quoted text. For instance, characters
\r
127 * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
\r
128 * even they are not embraced within single quotes.
\r
130 * A pattern containing any invalid pattern letter will result in a thrown
\r
131 * exception during formatting or parsing.
\r
134 * <strong>Examples Using the US Locale:</strong>
\r
137 * Format Pattern Result
\r
138 * -------------- -------
\r
139 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
\r
140 * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
\r
141 * "h:mm a" ->> 12:08 PM
\r
142 * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
\r
143 * "K:mm a, vvv" ->> 0:00 PM, PT
\r
144 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
\r
147 * <strong>Code Sample:</strong>
\r
150 * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
\r
151 * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
\r
152 * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
\r
154 * // Format the current time.
\r
155 * SimpleDateFormat formatter
\r
156 * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
\r
157 * Date currentTime_1 = new Date();
\r
158 * String dateString = formatter.format(currentTime_1);
\r
160 * // Parse the previous string back into a Date.
\r
161 * ParsePosition pos = new ParsePosition(0);
\r
162 * Date currentTime_2 = formatter.parse(dateString, pos);
\r
165 * In the example, the time value <code>currentTime_2</code> obtained from
\r
166 * parsing will be equal to <code>currentTime_1</code>. However, they may not be
\r
167 * equal if the am/pm marker 'a' is left out from the format pattern while
\r
168 * the "hour in am/pm" pattern symbol is used. This information loss can
\r
169 * happen when formatting the time in PM.
\r
172 * When parsing a date string using the abbreviated year pattern ("yy"),
\r
173 * SimpleDateFormat must interpret the abbreviated year
\r
174 * relative to some century. It does this by adjusting dates to be
\r
175 * within 80 years before and 20 years after the time the SimpleDateFormat
\r
176 * instance is created. For example, using a pattern of "MM/dd/yy" and a
\r
177 * SimpleDateFormat instance created on Jan 1, 1997, the string
\r
178 * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
\r
179 * would be interpreted as May 4, 1964.
\r
180 * During parsing, only strings consisting of exactly two digits, as defined by
\r
181 * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
\r
183 * Any other numeric string, such as a one digit string, a three or more digit
\r
184 * string, or a two digit string that isn't all digits (for example, "-1"), is
\r
185 * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
\r
186 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
\r
189 * If the year pattern does not have exactly two 'y' characters, the year is
\r
190 * interpreted literally, regardless of the number of digits. So using the
\r
191 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
\r
194 * When numeric fields abut one another directly, with no intervening delimiter
\r
195 * characters, they constitute a run of abutting numeric fields. Such runs are
\r
196 * parsed specially. For example, the format "HHmmss" parses the input text
\r
197 * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
\r
198 * parse "1234". In other words, the leftmost field of the run is flexible,
\r
199 * while the others keep a fixed width. If the parse fails anywhere in the run,
\r
200 * then the leftmost field is shortened by one character, and the entire run is
\r
201 * parsed again. This is repeated until either the parse succeeds or the
\r
202 * leftmost field is one character in length. If the parse still fails at that
\r
203 * point, the parse of the run fails.
\r
206 * For time zones that have no names, use strings GMT+hours:minutes or
\r
207 * GMT-hours:minutes.
\r
210 * The calendar defines what is the first day of the week, the first week
\r
211 * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
\r
212 * time zone. There is one common decimal format to handle all the numbers;
\r
213 * the digit count is handled programmatically according to the pattern.
\r
215 * <h4>Synchronization</h4>
\r
217 * Date formats are not synchronized. It is recommended to create separate
\r
218 * format instances for each thread. If multiple threads access a format
\r
219 * concurrently, it must be synchronized externally.
\r
221 * @see com.ibm.icu.util.Calendar
\r
222 * @see com.ibm.icu.util.GregorianCalendar
\r
223 * @see com.ibm.icu.util.TimeZone
\r
225 * @see DateFormatSymbols
\r
226 * @see DecimalFormat
\r
227 * @author Mark Davis, Chen-Lieh Huang, Alan Liu
\r
230 public class SimpleDateFormat extends DateFormat {
\r
232 // the official serial version ID which says cryptically
\r
233 // which version we're compatible with
\r
234 private static final long serialVersionUID = 4774881970558875024L;
\r
236 // the internal serial version which says which version was written
\r
237 // - 0 (default) for version up to JDK 1.1.3
\r
238 // - 1 for version from JDK 1.1.4, which includes a new field
\r
239 static final int currentSerialVersion = 1;
\r
243 * From calendar field to its level.
\r
244 * Used to order calendar field.
\r
245 * For example, calendar fields can be defined in the following order:
\r
246 * year > month > date > am-pm > hour > minute
\r
247 * YEAR --> 10, MONTH -->20, DATE --> 30;
\r
248 * AM_PM -->40, HOUR --> 50, MINUTE -->60
\r
250 private static final int[] CALENDAR_FIELD_TO_LEVEL =
\r
254 /*dDEF*/ 30, 20, 30, 30,
\r
255 /*ahHm*/ 40, 50, 50, 60,
\r
265 * From calendar field letter to its level.
\r
266 * Used to order calendar field.
\r
267 * For example, calendar fields can be defined in the following order:
\r
268 * year > month > date > am-pm > hour > minute
\r
269 * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
\r
271 private static final int[] PATTERN_CHAR_TO_LEVEL =
\r
273 // A B C D E F G H I J K L M N O
\r
274 -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, -1,
\r
275 // P Q R S T U V W X Y Z
\r
276 -1, 20, -1, 80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,
\r
277 // a b c d e f g h i j k l m n o
\r
278 -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
\r
279 // p q r s t u v w x y z
\r
280 -1, 20, -1, 70, -1, 10, 0, 20, -1, 10, 0, -1, -1, -1, -1, -1
\r
285 * The version of the serialized data on the stream. Possible values:
\r
287 * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
\r
288 * has no <code>defaultCenturyStart</code> on stream.
\r
289 * <li><b>1</b> JDK 1.1.4 or later. This version adds
\r
290 * <code>defaultCenturyStart</code>.
\r
292 * When streaming out this class, the most recent format
\r
293 * and the highest allowable <code>serialVersionOnStream</code>
\r
297 private int serialVersionOnStream = currentSerialVersion;
\r
300 * The pattern string of this formatter. This is always a non-localized
\r
301 * pattern. May not be null. See class documentation for details.
\r
304 private String pattern;
\r
307 * The override string of this formatter. Used to override the
\r
308 * numbering system for one or more fields.
\r
311 private String override;
\r
314 * The hash map used for number format overrides.
\r
317 private HashMap numberFormatters;
\r
320 * The hash map used for number format overrides.
\r
323 private HashMap overrideMap;
\r
326 * The symbols used by this formatter for week names, month names,
\r
327 * etc. May not be null.
\r
329 * @see DateFormatSymbols
\r
331 private DateFormatSymbols formatData;
\r
333 private transient ULocale locale;
\r
336 * We map dates with two-digit years into the century starting at
\r
337 * <code>defaultCenturyStart</code>, which may be any date. May
\r
342 private Date defaultCenturyStart;
\r
344 private transient int defaultCenturyStartYear;
\r
346 // defaultCenturyBase is set when an instance is created
\r
347 // and may be used for calculating defaultCenturyStart when needed.
\r
348 private transient long defaultCenturyBase;
\r
350 // We need to preserve time zone type when parsing specific
\r
351 // time zone text (xxx Standard Time vs xxx Daylight Time)
\r
352 private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;
\r
353 private transient int tztype = TZTYPE_UNK;
\r
355 private static final int millisPerHour = 60 * 60 * 1000;
\r
356 private static final int millisPerMinute = 60 * 1000;
\r
357 private static final int millisPerSecond = 1000;
\r
359 // This prefix is designed to NEVER MATCH real text, in order to
\r
360 // suppress the parsing of negative numbers. Adjust as needed (if
\r
361 // this becomes valid Unicode).
\r
362 private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
\r
365 * If true, this object supports fast formatting using the
\r
366 * subFormat variant that takes a StringBuffer.
\r
368 private transient boolean useFastFormat;
\r
371 * Construct a SimpleDateFormat using the default pattern for the default
\r
372 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
373 * generality, use the factory methods in the DateFormat class.
\r
378 public SimpleDateFormat() {
\r
379 this(getDefaultPattern(), null, null, null, null, true, null);
\r
383 * Construct a SimpleDateFormat using the given pattern in the default
\r
384 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
385 * generality, use the factory methods in the DateFormat class.
\r
388 public SimpleDateFormat(String pattern)
\r
390 this(pattern, null, null, null, null, true, null);
\r
394 * Construct a SimpleDateFormat using the given pattern and locale.
\r
395 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
396 * generality, use the factory methods in the DateFormat class.
\r
399 public SimpleDateFormat(String pattern, Locale loc)
\r
401 this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
\r
405 * Construct a SimpleDateFormat using the given pattern and locale.
\r
406 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
\r
407 * generality, use the factory methods in the DateFormat class.
\r
410 public SimpleDateFormat(String pattern, ULocale loc)
\r
412 this(pattern, null, null, null, loc, true, null);
\r
416 * Construct a SimpleDateFormat using the given pattern , override and locale.
\r
418 * @provisional This API might change or be removed in a future release.
\r
420 public SimpleDateFormat(String pattern, String override, ULocale loc)
\r
422 this(pattern, null, null, null, loc, false,override);
\r
426 * Construct a SimpleDateFormat using the given pattern and
\r
427 * locale-specific symbol data.
\r
428 * Warning: uses default locale for digits!
\r
431 public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
\r
433 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
\r
437 * @internal ICU 3.2
\r
438 * @deprecated This API is ICU internal only.
\r
440 public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
\r
442 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
\r
446 * Package-private constructor that allows a subclass to specify
\r
447 * whether it supports fast formatting.
\r
449 * TODO make this API public.
\r
451 SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
\r
452 boolean useFastFormat, String override) {
\r
453 this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
\r
456 SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
\r
457 boolean useFastFormat) {
\r
458 this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,null);
\r
462 * The constructor called from all other SimpleDateFormat constructors
\r
464 private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
\r
465 NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
\r
466 this.pattern = pattern;
\r
467 this.formatData = formatData;
\r
468 this.calendar = calendar;
\r
469 this.numberFormat = numberFormat;
\r
470 this.locale = locale; // time zone formatting
\r
471 this.useFastFormat = useFastFormat;
\r
472 this.override = override;
\r
477 * Create an instance of SimpleDateForamt for the given format configuration
\r
478 * @param formatConfig the format configuration
\r
479 * @return A SimpleDateFormat instance
\r
480 * @internal ICU 3.8
\r
481 * @deprecated This API is ICU internal only.
\r
483 public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
\r
485 String ostr = formatConfig.getOverrideString();
\r
486 boolean useFast = ( ostr != null && ostr.length() > 0 );
\r
488 return new SimpleDateFormat(formatConfig.getPatternString(),
\r
489 formatConfig.getDateFormatSymbols(),
\r
490 formatConfig.getCalendar(),
\r
492 formatConfig.getLocale(),
\r
494 formatConfig.getOverrideString());
\r
498 * Initialized fields
\r
500 private void initialize() {
\r
501 if (locale == null) {
\r
502 locale = ULocale.getDefault();
\r
504 if (formatData == null) {
\r
505 formatData = new DateFormatSymbols(locale);
\r
507 if (calendar == null) {
\r
508 calendar = Calendar.getInstance(locale);
\r
510 if (numberFormat == null) {
\r
511 NumberingSystem ns = NumberingSystem.getInstance(locale);
\r
512 if ( ns.isAlgorithmic() ) {
\r
513 numberFormat = NumberFormat.getInstance(locale);
\r
515 char digit0 = ns.getDescription().charAt(0);
\r
516 // Use a NumberFormat optimized for date formatting
\r
517 numberFormat = new DateNumberFormat(locale, digit0);
\r
520 // Note: deferring calendar calculation until when we really need it.
\r
521 // Instead, we just record time of construction for backward compatibility.
\r
522 defaultCenturyBase = System.currentTimeMillis();
\r
524 setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
\r
525 initLocalZeroPaddingNumberFormat();
\r
527 if (override != null) {
\r
528 initNumberFormatters(locale);
\r
533 // privates for the default pattern
\r
534 private static ULocale cachedDefaultLocale = null;
\r
535 private static String cachedDefaultPattern = null;
\r
536 private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
\r
539 * Returns the default date and time pattern (SHORT) for the default locale.
\r
540 * This method is only used by the default SimpleDateFormat constructor.
\r
542 private static synchronized String getDefaultPattern() {
\r
543 ULocale defaultLocale = ULocale.getDefault();
\r
544 if (!defaultLocale.equals(cachedDefaultLocale)) {
\r
545 cachedDefaultLocale = defaultLocale;
\r
546 Calendar cal = Calendar.getInstance(cachedDefaultLocale);
\r
548 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
\r
549 String[] dateTimePatterns = calData.getDateTimePatterns();
\r
551 if (dateTimePatterns.length >= 13)
\r
553 glueIndex += (SHORT + 1);
\r
555 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
\r
556 new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
\r
557 } catch (MissingResourceException e) {
\r
558 cachedDefaultPattern = FALLBACKPATTERN;
\r
561 return cachedDefaultPattern;
\r
564 /* Define one-century window into which to disambiguate dates using
\r
567 private void parseAmbiguousDatesAsAfter(Date startDate) {
\r
568 defaultCenturyStart = startDate;
\r
569 calendar.setTime(startDate);
\r
570 defaultCenturyStartYear = calendar.get(Calendar.YEAR);
\r
573 /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
\r
574 * The default start time is 80 years before the creation time of this object.
\r
576 private void initializeDefaultCenturyStart(long baseTime) {
\r
577 defaultCenturyBase = baseTime;
\r
578 // clone to avoid messing up date stored in calendar object
\r
579 // when this method is called while parsing
\r
580 Calendar tmpCal = (Calendar)calendar.clone();
\r
581 tmpCal.setTimeInMillis(baseTime);
\r
582 tmpCal.add(Calendar.YEAR, -80);
\r
583 defaultCenturyStart = tmpCal.getTime();
\r
584 defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
\r
587 /* Gets the default century start date for this object */
\r
588 private Date getDefaultCenturyStart() {
\r
589 if (defaultCenturyStart == null) {
\r
590 // not yet initialized
\r
591 initializeDefaultCenturyStart(defaultCenturyBase);
\r
593 return defaultCenturyStart;
\r
596 /* Gets the default century start year for this object */
\r
597 private int getDefaultCenturyStartYear() {
\r
598 if (defaultCenturyStart == null) {
\r
599 // not yet initialized
\r
600 initializeDefaultCenturyStart(defaultCenturyBase);
\r
602 return defaultCenturyStartYear;
\r
606 * Sets the 100-year period 2-digit years will be interpreted as being in
\r
607 * to begin on the date the user specifies.
\r
608 * @param startDate During parsing, two digit years will be placed in the range
\r
609 * <code>startDate</code> to <code>startDate + 100 years</code>.
\r
612 public void set2DigitYearStart(Date startDate) {
\r
613 parseAmbiguousDatesAsAfter(startDate);
\r
617 * Returns the beginning date of the 100-year period 2-digit years are interpreted
\r
619 * @return the start of the 100-year period into which two digit years are
\r
623 public Date get2DigitYearStart() {
\r
624 return getDefaultCenturyStart();
\r
628 * Overrides DateFormat.
\r
629 * <p>Formats a date or time, which is the standard millis
\r
630 * since January 1, 1970, 00:00:00 GMT.
\r
631 * <p>Example: using the US locale:
\r
632 * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
\r
633 * @param cal the calendar whose date-time value is to be formatted into a date-time string
\r
634 * @param toAppendTo where the new date-time text is to be appended
\r
635 * @param pos the formatting position. On input: an alignment field,
\r
636 * if desired. On output: the offsets of the alignment field.
\r
637 * @return the formatted date-time string.
\r
641 public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
\r
642 FieldPosition pos) {
\r
643 TimeZone backupTZ = null;
\r
644 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
\r
645 // Different calendar type
\r
646 // We use the time and time zone from the input calendar, but
\r
647 // do not use the input calendar for field calculation.
\r
648 calendar.setTimeInMillis(cal.getTimeInMillis());
\r
649 backupTZ = calendar.getTimeZone();
\r
650 calendar.setTimeZone(cal.getTimeZone());
\r
653 StringBuffer result = format(cal, toAppendTo, pos, null);
\r
654 if (backupTZ != null) {
\r
655 // Restore the original time zone
\r
656 calendar.setTimeZone(backupTZ);
\r
661 // The actual method to format date. If List attributes is not null,
\r
662 // then attribute information will be recorded.
\r
663 private StringBuffer format(Calendar cal, StringBuffer toAppendTo,
\r
664 FieldPosition pos, List attributes) {
\r
666 pos.setBeginIndex(0);
\r
667 pos.setEndIndex(0);
\r
669 // Careful: For best performance, minimize the number of calls
\r
670 // to StringBuffer.append() by consolidating appends when
\r
673 Object[] items = getPatternItems();
\r
674 for (int i = 0; i < items.length; i++) {
\r
675 if (items[i] instanceof String) {
\r
676 toAppendTo.append((String)items[i]);
\r
678 PatternItem item = (PatternItem)items[i];
\r
679 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
682 if (attributes != null) {
\r
683 // Save the current length
\r
684 start = toAppendTo.length();
\r
687 if (useFastFormat) {
\r
688 subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
\r
690 toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));
\r
692 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
694 if (attributes != null) {
\r
695 // Check the sub format length
\r
696 int end = toAppendTo.length();
\r
697 if (end - start > 0) {
\r
698 // Append the attribute to the list
\r
699 DateFormat.Field attr = patternCharToDateFormatField(item.type);
\r
700 FieldPosition fp = new FieldPosition(attr);
\r
701 fp.setBeginIndex(start);
\r
702 fp.setEndIndex(end);
\r
703 attributes.add(fp);
\r
713 // Map pattern character to index
\r
714 private static final int PATTERN_CHAR_BASE = 0x40;
\r
715 private static final int[] PATTERN_CHAR_TO_INDEX =
\r
717 // A B C D E F G H I J K L M N O
\r
718 -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,
\r
719 // P Q R S T U V W X Y Z
\r
720 -1, 27, -1, 8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,
\r
721 // a b c d e f g h i j k l m n o
\r
722 -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
\r
723 // p q r s t u v w x y z
\r
724 -1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1
\r
727 // Map pattern character index to Calendar field number
\r
728 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
\r
730 /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
\r
731 /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
\r
732 /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
\r
733 /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
\r
734 /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
\r
735 /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
\r
736 /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
\r
737 /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
\r
738 /*v*/ Calendar.ZONE_OFFSET,
\r
739 /*c*/ Calendar.DOW_LOCAL,
\r
740 /*L*/ Calendar.MONTH,
\r
741 /*Qq*/ Calendar.MONTH, Calendar.MONTH,
\r
742 /*V*/ Calendar.ZONE_OFFSET,
\r
745 // Map pattern character index to DateFormat field number
\r
746 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
\r
747 /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
\r
748 /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
\r
749 /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
\r
750 /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
\r
751 /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
\r
752 /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
\r
753 /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
\r
754 /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
\r
755 /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD,
\r
756 /*c*/ DateFormat.STANDALONE_DAY_FIELD,
\r
757 /*L*/ DateFormat.STANDALONE_MONTH_FIELD,
\r
758 /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
\r
759 /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD,
\r
762 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
764 // Map pattern character index to DateFormat.Field
\r
765 private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
\r
766 /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
\r
767 /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
\r
768 /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
\r
769 /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
\r
770 /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
\r
771 /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
\r
772 /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
\r
773 /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
\r
774 /*v*/ DateFormat.Field.TIME_ZONE,
\r
775 /*c*/ DateFormat.Field.DAY_OF_WEEK,
\r
776 /*L*/ DateFormat.Field.MONTH,
\r
777 /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
\r
778 /*V*/ DateFormat.Field.TIME_ZONE,
\r
782 * Return a DateFormat.Field constant associated with the specified format pattern
\r
785 * @param ch The pattern character
\r
786 * @return DateFormat.Field associated with the pattern character
\r
790 protected DateFormat.Field patternCharToDateFormatField(char ch) {
\r
791 int patternCharIndex = -1;
\r
792 if ('A' <= ch && ch <= 'z') {
\r
793 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
795 if (patternCharIndex != -1) {
\r
796 return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
\r
803 * Format a single field, given its pattern character. Subclasses may
\r
804 * override this method in order to modify or add formatting
\r
806 * @param ch the pattern character
\r
807 * @param count the number of times ch is repeated in the pattern
\r
808 * @param beginOffset the offset of the output string at the start of
\r
809 * this field; used to set pos when appropriate
\r
810 * @param pos receives the position of a field, when appropriate
\r
811 * @param fmtData the symbols for this formatter
\r
814 protected String subFormat(char ch, int count, int beginOffset,
\r
815 FieldPosition pos, DateFormatSymbols fmtData,
\r
817 throws IllegalArgumentException
\r
819 // Note: formatData is ignored
\r
820 StringBuffer buf = new StringBuffer();
\r
821 subFormat(buf, ch, count, beginOffset, pos, cal);
\r
822 return buf.toString();
\r
826 * Format a single field; useFastFormat variant. Reuses a
\r
827 * StringBuffer for results instead of creating a String on the
\r
828 * heap for each call.
\r
830 * NOTE We don't really need the beginOffset parameter, EXCEPT for
\r
831 * the need to support the slow subFormat variant (above) which
\r
832 * has to pass it in to us.
\r
834 * TODO make this API public
\r
837 * @deprecated This API is ICU internal only.
\r
839 protected void subFormat(StringBuffer buf,
\r
840 char ch, int count, int beginOffset,
\r
843 final int maxIntCount = Integer.MAX_VALUE;
\r
844 final int bufstart = buf.length();
\r
846 // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
\r
847 int patternCharIndex = -1;
\r
848 if ('A' <= ch && ch <= 'z') {
\r
849 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
852 if (patternCharIndex == -1) {
\r
853 throw new IllegalArgumentException("Illegal pattern character " +
\r
854 "'" + ch + "' in \"" +
\r
855 new String(pattern) + '"');
\r
858 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
\r
859 int value = cal.get(field);
\r
861 String zoneString = null;
\r
863 NumberFormat currentNumberFormat = getNumberFormat(ch);
\r
865 switch (patternCharIndex) {
\r
866 case 0: // 'G' - ERA
\r
868 safeAppend(formatData.narrowEras, value, buf);
\r
869 } else if (count == 4) {
\r
870 safeAppend(formatData.eraNames, value, buf);
\r
872 safeAppend(formatData.eras, value, buf);
\r
875 case 1: // 'y' - YEAR
\r
876 /* According to the specification, if the number of pattern letters ('y') is 2,
\r
877 * the year is truncated to 2 digits; otherwise it is interpreted as a number.
\r
878 * But the original code process 'y', 'yy', 'yyy' in the same way. and process
\r
879 * patterns with 4 or more than 4 'y' characters in the same way.
\r
880 * So I change the codes to meet the specification. [Richard/GCl]
\r
883 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
\r
884 else //count = 1 or count > 2
\r
885 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
887 case 2: // 'M' - MONTH
\r
889 safeAppend(formatData.narrowMonths, value, buf);
\r
890 } else if (count == 4) {
\r
891 safeAppend(formatData.months, value, buf);
\r
892 } else if (count == 3) {
\r
893 safeAppend(formatData.shortMonths, value, buf);
\r
895 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
\r
898 case 4: // 'k' - HOUR_OF_DAY (1..24)
\r
900 zeroPaddingNumber(currentNumberFormat,buf,
\r
901 cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
\r
902 count, maxIntCount);
\r
904 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
906 case 8: // 'S' - FRACTIONAL_SECOND
\r
907 // Fractional seconds left-justify
\r
909 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
\r
910 numberFormat.setMaximumIntegerDigits(maxIntCount);
\r
912 value = (value + 50) / 100;
\r
913 } else if (count == 2) {
\r
914 value = (value + 5) / 10;
\r
916 FieldPosition p = new FieldPosition(-1);
\r
917 numberFormat.format((long) value, buf, p);
\r
919 numberFormat.setMinimumIntegerDigits(count - 3);
\r
920 numberFormat.format(0L, buf, p);
\r
924 case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
\r
926 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
929 // For alpha day-of-week, we don't want DOW_LOCAL,
\r
930 // we need the standard DAY_OF_WEEK.
\r
931 value = cal.get(Calendar.DAY_OF_WEEK);
\r
932 // fall through, do not break here
\r
933 case 9: // 'E' - DAY_OF_WEEK
\r
935 safeAppend(formatData.narrowWeekdays, value, buf);
\r
936 } else if (count == 4) {
\r
937 safeAppend(formatData.weekdays, value, buf);
\r
938 } else {// count <= 3, use abbreviated form if exists
\r
939 safeAppend(formatData.shortWeekdays, value, buf);
\r
942 case 14: // 'a' - AM_PM
\r
943 safeAppend(formatData.ampms, value, buf);
\r
945 case 15: // 'h' - HOUR (1..12)
\r
947 zeroPaddingNumber(currentNumberFormat,buf,
\r
948 cal.getLeastMaximum(Calendar.HOUR)+1,
\r
949 count, maxIntCount);
\r
951 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
953 case 17: // 'z' - ZONE_OFFSET
\r
955 // "z", "zz", "zzz"
\r
956 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);
\r
958 zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);
\r
960 if (zoneString != null && zoneString.length() != 0) {
\r
961 buf.append(zoneString);
\r
963 // Use localized GMT format as fallback
\r
964 appendGMT(currentNumberFormat,buf, cal);
\r
967 case 23: // 'Z' - TIMEZONE_RFC
\r
969 // RFC822 format, must use ASCII digits
\r
970 int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
\r
978 int offsetH = val / millisPerHour;
\r
979 val = val % millisPerHour;
\r
980 int offsetM = val / millisPerMinute;
\r
981 val = val % millisPerMinute;
\r
982 int offsetS = val / millisPerSecond;
\r
984 int num = 0, denom = 0;
\r
985 if (offsetS == 0) {
\r
986 val = offsetH*100 + offsetM; // HHmm
\r
990 val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss
\r
991 num = val % 1000000;
\r
994 while (denom >= 1) {
\r
995 char digit = (char)((num / denom) + '0');
\r
1001 // long form, localized GMT pattern
\r
1002 appendGMT(currentNumberFormat,buf, cal);
\r
1005 case 24: // 'v' - TIMEZONE_GENERIC
\r
1008 zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);
\r
1009 } else if (count == 4) {
\r
1011 zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);
\r
1013 if (zoneString != null && zoneString.length() != 0) {
\r
1014 buf.append(zoneString);
\r
1016 // Use localized GMT format as fallback
\r
1017 appendGMT(currentNumberFormat,buf, cal);
\r
1020 case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)
\r
1022 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
\r
1025 // For alpha day-of-week, we don't want DOW_LOCAL,
\r
1026 // we need the standard DAY_OF_WEEK.
\r
1027 value = cal.get(Calendar.DAY_OF_WEEK);
\r
1029 safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
\r
1030 } else if (count == 4) {
\r
1031 safeAppend(formatData.standaloneWeekdays, value, buf);
\r
1032 } else { // count == 3
\r
1033 safeAppend(formatData.standaloneShortWeekdays, value, buf);
\r
1036 case 26: // 'L' - STANDALONE MONTH
\r
1038 safeAppend(formatData.standaloneNarrowMonths, value, buf);
\r
1039 } else if (count == 4) {
\r
1040 safeAppend(formatData.standaloneMonths, value, buf);
\r
1041 } else if (count == 3) {
\r
1042 safeAppend(formatData.standaloneShortMonths, value, buf);
\r
1044 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
\r
1047 case 27: // 'Q' - QUARTER
\r
1049 safeAppend(formatData.quarters, value/3, buf);
\r
1050 } else if (count == 3) {
\r
1051 safeAppend(formatData.shortQuarters, value/3, buf);
\r
1053 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
\r
1056 case 28: // 'q' - STANDALONE QUARTER
\r
1058 safeAppend(formatData.standaloneQuarters, value/3, buf);
\r
1059 } else if (count == 3) {
\r
1060 safeAppend(formatData.standaloneShortQuarters, value/3, buf);
\r
1062 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
\r
1065 case 29: // 'V' - TIMEZONE_SPECIAL
\r
1068 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);
\r
1069 } else if (count == 4) {
\r
1071 zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);
\r
1073 if (zoneString != null && zoneString.length() != 0) {
\r
1074 buf.append(zoneString);
\r
1076 // Use localized GMT format as fallback
\r
1077 appendGMT(currentNumberFormat,buf, cal);
\r
1081 // case 3: // 'd' - DATE
\r
1082 // case 5: // 'H' - HOUR_OF_DAY (0..23)
\r
1083 // case 6: // 'm' - MINUTE
\r
1084 // case 7: // 's' - SECOND
\r
1085 // case 10: // 'D' - DAY_OF_YEAR
\r
1086 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
\r
1087 // case 12: // 'w' - WEEK_OF_YEAR
\r
1088 // case 13: // 'W' - WEEK_OF_MONTH
\r
1089 // case 16: // 'K' - HOUR (0..11)
\r
1090 // case 18: // 'Y' - YEAR_WOY
\r
1091 // case 20: // 'u' - EXTENDED_YEAR
\r
1092 // case 21: // 'g' - JULIAN_DAY
\r
1093 // case 22: // 'A' - MILLISECONDS_IN_DAY
\r
1095 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
\r
1097 } // switch (patternCharIndex)
\r
1099 // Set the FieldPosition (for the first occurrence only)
\r
1100 if (pos.getBeginIndex() == pos.getEndIndex()) {
\r
1101 if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
\r
1102 pos.setBeginIndex(beginOffset);
\r
1103 pos.setEndIndex(beginOffset + buf.length() - bufstart);
\r
1105 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
1107 else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
\r
1108 pos.setBeginIndex(beginOffset);
\r
1109 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 PARSED_PATTERN_CACHE = new SimpleCache();
\r
1137 private transient Object[] patternItems;
\r
1140 * Returns parsed pattern items. Each item is either String or
\r
1143 private Object[] getPatternItems() {
\r
1144 if (patternItems != null) {
\r
1145 return patternItems;
\r
1148 patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);
\r
1149 if (patternItems != null) {
\r
1150 return patternItems;
\r
1153 boolean isPrevQuote = false;
\r
1154 boolean inQuote = false;
\r
1155 StringBuffer text = new StringBuffer();
\r
1156 char itemType = 0; // 0 for string literal, otherwise date/time pattern character
\r
1157 int itemLength = 1;
\r
1159 List items = new ArrayList();
\r
1161 for (int i = 0; i < pattern.length(); i++) {
\r
1162 char ch = pattern.charAt(i);
\r
1164 if (isPrevQuote) {
\r
1165 text.append('\'');
\r
1166 isPrevQuote = false;
\r
1168 isPrevQuote = true;
\r
1169 if (itemType != 0) {
\r
1170 items.add(new PatternItem(itemType, itemLength));
\r
1174 inQuote = !inQuote;
\r
1176 isPrevQuote = false;
\r
1180 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
\r
1181 // a date/time pattern character
\r
1182 if (ch == itemType) {
\r
1185 if (itemType == 0) {
\r
1186 if (text.length() > 0) {
\r
1187 items.add(text.toString());
\r
1188 text.setLength(0);
\r
1191 items.add(new PatternItem(itemType, itemLength));
\r
1197 // a string literal
\r
1198 if (itemType != 0) {
\r
1199 items.add(new PatternItem(itemType, itemLength));
\r
1207 // handle last item
\r
1208 if (itemType == 0) {
\r
1209 if (text.length() > 0) {
\r
1210 items.add(text.toString());
\r
1211 text.setLength(0);
\r
1214 items.add(new PatternItem(itemType, itemLength));
\r
1217 patternItems = new Object[items.size()];
\r
1218 items.toArray(patternItems);
\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 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;
\r
1251 MessageFormat fmt = getGMTFormatter(sign, width);
\r
1252 fmt.format(new Object[] {new Long(offset)}, buf, null);
\r
1256 private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {
\r
1257 buf.append(STR_GMT);
\r
1258 if (offset >= 0) {
\r
1261 buf.append(MINUS);
\r
1264 offset /= 1000; // now in seconds
\r
1265 int sec = offset % 60;
\r
1267 int min = offset % 60;
\r
1268 int hour = offset / 60;
\r
1270 zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);
\r
1271 buf.append(COLON);
\r
1272 zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);
\r
1274 buf.append(COLON);
\r
1275 zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);
\r
1279 private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
\r
1280 if (!isDefaultGMTFormat()) {
\r
1281 int start = pos.getIndex();
\r
1282 String gmtPattern = formatData.gmtFormat;
\r
1285 boolean prefixMatch = false;
\r
1286 int prefixLen = gmtPattern.indexOf('{');
\r
1287 if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {
\r
1288 prefixMatch = true;
\r
1291 if (prefixMatch) {
\r
1293 MessageFormat fmt;
\r
1294 Object[] parsedObjects;
\r
1297 // Try negative Hms
\r
1298 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
\r
1299 parsedObjects = fmt.parse(text, pos);
\r
1300 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
\r
1301 && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_NEGATIVE)) {
\r
1302 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1303 return new Integer(-offset /* negative */);
\r
1306 // Reset ParsePosition
\r
1307 pos.setIndex(start);
\r
1308 pos.setErrorIndex(-1);
\r
1310 // Try positive Hms
\r
1311 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
\r
1312 parsedObjects = fmt.parse(text, pos);
\r
1313 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
\r
1314 && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_POSITIVE)) {
\r
1315 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1316 return new Integer(offset);
\r
1319 // Reset ParsePosition
\r
1320 pos.setIndex(start);
\r
1321 pos.setErrorIndex(-1);
\r
1323 // Try negative Hm
\r
1324 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HM);
\r
1325 parsedObjects = fmt.parse(text, pos);
\r
1326 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
\r
1327 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1328 return new Integer(-offset /* negative */);
\r
1331 // Reset ParsePosition
\r
1332 pos.setIndex(start);
\r
1333 pos.setErrorIndex(-1);
\r
1335 // Try positive Hm
\r
1336 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HM);
\r
1337 parsedObjects = fmt.parse(text, pos);
\r
1338 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
\r
1339 offset = (int)((Date)parsedObjects[0]).getTime();
\r
1340 return new Integer(offset);
\r
1343 // Reset ParsePosition
\r
1344 pos.setIndex(start);
\r
1345 pos.setErrorIndex(-1);
\r
1349 return parseGMTDefault(text, pos, currentNumberFormat);
\r
1352 private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
\r
1353 int start = pos.getIndex();
\r
1355 if (start + STR_UT_LEN + 1 >= text.length()) {
\r
1356 pos.setErrorIndex(start);
\r
1362 if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
\r
1363 cur += STR_GMT_LEN;
\r
1364 } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
\r
1365 cur += STR_UT_LEN;
\r
1367 pos.setErrorIndex(start);
\r
1371 boolean negative = false;
\r
1372 if (text.charAt(cur) == MINUS) {
\r
1374 } else if (text.charAt(cur) != PLUS) {
\r
1375 pos.setErrorIndex(cur);
\r
1382 pos.setIndex(cur);
\r
1384 Number n = parseInt(text, 6, pos, false,currentNumberFormat);
\r
1385 numLen = pos.getIndex() - cur;
\r
1387 if (n == null || numLen <= 0 || numLen > 6) {
\r
1388 pos.setIndex(start);
\r
1389 pos.setErrorIndex(cur);
\r
1393 int numVal = n.intValue();
\r
1399 if (numLen <= 2) {
\r
1403 if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
\r
1405 pos.setIndex(cur);
\r
1406 n = parseInt(text, 2, pos, false,currentNumberFormat);
\r
1407 numLen = pos.getIndex() - cur;
\r
1408 if (n != null && numLen == 2) {
\r
1409 // got minute field
\r
1410 min = n.intValue();
\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 second field
\r
1419 sec = n.intValue();
\r
1422 pos.setIndex(cur - 1);
\r
1423 pos.setErrorIndex(-1);
\r
1428 pos.setIndex(cur - 1);
\r
1429 pos.setErrorIndex(-1);
\r
1432 } else if (numLen == 3 || numLen == 4) {
\r
1434 hour = numVal / 100;
\r
1435 min = numVal % 100;
\r
1436 } else { // numLen == 5 || numLen == 6
\r
1437 // Hmmss or HHmmss
\r
1438 hour = numVal / 10000;
\r
1439 min = (numVal % 10000) / 100;
\r
1440 sec = numVal % 100;
\r
1443 int offset = ((hour*60 + min)*60 + sec)*1000;
\r
1447 return new Integer(offset);
\r
1450 transient private WeakReference[] gmtfmtCache;
\r
1452 private MessageFormat getGMTFormatter(int sign, int width) {
\r
1453 MessageFormat fmt = null;
\r
1454 if (gmtfmtCache == null) {
\r
1455 gmtfmtCache = new WeakReference[4];
\r
1457 int cacheIdx = sign*2 + width;
\r
1458 if (gmtfmtCache[cacheIdx] != null) {
\r
1459 fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();
\r
1461 if (fmt == null) {
\r
1462 fmt = new MessageFormat(formatData.gmtFormat);
\r
1463 GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
\r
1464 SimpleDateFormat sdf = (SimpleDateFormat)this.clone();
\r
1465 sdf.setCalendar(gcal);
\r
1466 sdf.applyPattern(formatData.getGmtHourFormat(sign, width));
\r
1467 fmt.setFormat(0, sdf);
\r
1468 gmtfmtCache[cacheIdx] = new WeakReference(fmt);
\r
1473 transient private int[] gmtFormatHmsMinLen = null;
\r
1475 private int getGMTFormatMinHMSLen(int sign) {
\r
1476 if (gmtFormatHmsMinLen == null) {
\r
1477 gmtFormatHmsMinLen = new int[2];
\r
1478 Long offset = new Long(60*60*1000); // 1 hour
\r
1480 StringBuffer buf = new StringBuffer();
\r
1481 MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
\r
1482 fmtNeg.format(new Object[] {offset}, buf, null);
\r
1483 gmtFormatHmsMinLen[0] = buf.length();
\r
1486 MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
\r
1487 fmtPos.format(new Object[] {offset}, buf, null);
\r
1488 gmtFormatHmsMinLen[1] = buf.length();
\r
1490 return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];
\r
1493 private boolean isDefaultGMTFormat() {
\r
1495 if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {
\r
1498 // GMT offset hour patters
\r
1499 boolean res = true;
\r
1500 for (int sign = 0; sign < 2 && res; sign++) {
\r
1501 for (int width = 0; width < 2; width++) {
\r
1502 if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width].equals(formatData.getGmtHourFormat(sign, width))) {
\r
1512 * Internal method. Returns null if the value of an array is empty, or if the
\r
1513 * index is out of bounds
\r
1515 /* private String getZoneArrayValue(String[] zs, int ix) {
\r
1516 if (ix >= 0 && ix < zs.length) {
\r
1517 String result = zs[ix];
\r
1518 if (result != null && result.length() != 0) {
\r
1526 * Internal high-speed method. Reuses a StringBuffer for results
\r
1527 * instead of creating a String on the heap for each call.
\r
1529 * @deprecated This API is ICU internal only.
\r
1531 protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
\r
1532 int minDigits, int maxDigits) {
\r
1533 if (useLocalZeroPaddingNumberFormat) {
\r
1534 fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
\r
1536 nf.setMinimumIntegerDigits(minDigits);
\r
1537 nf.setMaximumIntegerDigits(maxDigits);
\r
1538 nf.format(value, buf, new FieldPosition(-1));
\r
1543 * Overrides superclass method
\r
1546 public void setNumberFormat(NumberFormat newNumberFormat) {
\r
1547 // Override this method to update local zero padding number formatter
\r
1548 super.setNumberFormat(newNumberFormat);
\r
1549 initLocalZeroPaddingNumberFormat();
\r
1552 private void initLocalZeroPaddingNumberFormat() {
\r
1553 if (numberFormat instanceof DecimalFormat) {
\r
1554 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
\r
1555 useLocalZeroPaddingNumberFormat = true;
\r
1556 } else if (numberFormat instanceof DateNumberFormat) {
\r
1557 zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();
\r
1558 useLocalZeroPaddingNumberFormat = true;
\r
1560 useLocalZeroPaddingNumberFormat = false;
\r
1563 if (useLocalZeroPaddingNumberFormat) {
\r
1564 decimalBuf = new char[10]; // sufficient for int numbers
\r
1568 // If true, use local version of zero padding number format
\r
1569 private transient boolean useLocalZeroPaddingNumberFormat;
\r
1570 private transient char zeroDigit;
\r
1571 private transient char[] decimalBuf;
\r
1574 * Lightweight zero padding integer number format function.
\r
1576 * Note: This implementation is almost equivalent to format method in DateNumberFormat.
\r
1577 * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
\r
1578 * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative
\r
1579 * date format test case, having local implementation is ~10% faster than using one in
\r
1580 * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference.
\r
1584 private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
\r
1585 int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
\r
1586 int index = limit - 1;
\r
1588 decimalBuf[index] = (char)((value % 10) + zeroDigit);
\r
1590 if (index == 0 || value == 0) {
\r
1595 int padding = minDigits - (limit - index);
\r
1596 for (; padding > 0; padding--) {
\r
1597 decimalBuf[--index] = zeroDigit;
\r
1599 int length = limit - index;
\r
1600 buf.append(decimalBuf, index, length);
\r
1604 * Formats a number with the specified minimum and maximum number of digits.
\r
1607 protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
\r
1609 numberFormat.setMinimumIntegerDigits(minDigits);
\r
1610 numberFormat.setMaximumIntegerDigits(maxDigits);
\r
1611 return numberFormat.format(value);
\r
1615 * Format characters that indicate numeric fields. The character
\r
1616 * at index 0 is treated specially.
\r
1618 private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";
\r
1621 * Return true if the given format character, occuring count
\r
1622 * times, represents a numeric field.
\r
1624 private static final boolean isNumeric(char formatChar, int count) {
\r
1625 int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
\r
1626 return (i > 0 || (i == 0 && count < 3));
\r
1630 * Overrides DateFormat
\r
1634 public void parse(String text, Calendar cal, ParsePosition parsePos)
\r
1636 TimeZone backupTZ = null;
\r
1637 Calendar resultCal = null;
\r
1638 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
\r
1639 // Different calendar type
\r
1640 // We use the time/zone from the input calendar, but
\r
1641 // do not use the input calendar for field calculation.
\r
1642 calendar.setTimeInMillis(cal.getTimeInMillis());
\r
1643 backupTZ = calendar.getTimeZone();
\r
1644 calendar.setTimeZone(cal.getTimeZone());
\r
1649 int pos = parsePos.getIndex();
\r
1653 tztype = TZTYPE_UNK;
\r
1654 boolean[] ambiguousYear = { false };
\r
1656 // item index for the first numeric field within a contiguous numeric run
\r
1657 int numericFieldStart = -1;
\r
1658 // item length for the first numeric field within a contiguous numeric run
\r
1659 int numericFieldLength = 0;
\r
1660 // start index of numeric text run in the input text
\r
1661 int numericStartPos = 0;
\r
1663 Object[] items = getPatternItems();
\r
1665 while (i < items.length) {
\r
1666 if (items[i] instanceof PatternItem) {
\r
1667 // Handle pattern field
\r
1668 PatternItem field = (PatternItem)items[i];
\r
1669 if (field.isNumeric) {
\r
1670 // Handle fields within a run of abutting numeric fields. Take
\r
1671 // the pattern "HHmmss" as an example. We will try to parse
\r
1672 // 2/2/2 characters of the input text, then if that fails,
\r
1673 // 1/2/2. We only adjust the width of the leftmost field; the
\r
1674 // others remain fixed. This allows "123456" => 12:34:56, but
\r
1675 // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
\r
1676 // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
\r
1677 if (numericFieldStart == -1) {
\r
1678 // check if this field is followed by abutting another numeric field
\r
1679 if ((i + 1) < items.length
\r
1680 && (items[i + 1] instanceof PatternItem)
\r
1681 && ((PatternItem)items[i + 1]).isNumeric) {
\r
1682 // record the first numeric field within a numeric text run
\r
1683 numericFieldStart = i;
\r
1684 numericFieldLength = field.length;
\r
1685 numericStartPos = pos;
\r
1689 if (numericFieldStart != -1) {
\r
1690 // Handle a numeric field within abutting numeric fields
\r
1691 int len = field.length;
\r
1692 if (numericFieldStart == i) {
\r
1693 len = numericFieldLength;
\r
1696 // Parse a numeric field
\r
1697 pos = subParse(text, pos, field.type, len,
\r
1698 true, false, ambiguousYear, cal);
\r
1701 // If the parse fails anywhere in the numeric run, back up to the
\r
1702 // start of the run and use shorter pattern length for the first
\r
1704 --numericFieldLength;
\r
1705 if (numericFieldLength == 0) {
\r
1706 // can not make shorter any more
\r
1707 parsePos.setIndex(start);
\r
1708 parsePos.setErrorIndex(pos);
\r
1709 if (backupTZ != null) {
\r
1710 calendar.setTimeZone(backupTZ);
\r
1714 i = numericFieldStart;
\r
1715 pos = numericStartPos;
\r
1720 // Handle a non-numeric field or a non-abutting numeric field
\r
1721 numericFieldStart = -1;
\r
1724 pos = subParse(text, pos, field.type, field.length,
\r
1725 false, true, ambiguousYear, cal);
\r
1727 parsePos.setIndex(start);
\r
1728 parsePos.setErrorIndex(s);
\r
1729 if (backupTZ != null) {
\r
1730 calendar.setTimeZone(backupTZ);
\r
1736 // Handle literal pattern text literal
\r
1737 numericFieldStart = -1;
\r
1739 String patl = (String)items[i];
\r
1740 int plen = patl.length();
\r
1741 int tlen = text.length();
\r
1743 while (idx < plen && pos < tlen) {
\r
1744 char pch = patl.charAt(idx);
\r
1745 char ich = text.charAt(pos);
\r
1746 if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {
\r
1747 // White space characters found in both patten and input.
\r
1748 // Skip contiguous white spaces.
\r
1749 while ((idx + 1) < plen &&
\r
1750 UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {
\r
1753 while ((pos + 1) < tlen &&
\r
1754 UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {
\r
1757 } else if (pch != ich) {
\r
1763 if (idx != plen) {
\r
1764 // Set the position of mismatch
\r
1765 parsePos.setIndex(start);
\r
1766 parsePos.setErrorIndex(pos);
\r
1767 if (backupTZ != null) {
\r
1768 calendar.setTimeZone(backupTZ);
\r
1776 // At this point the fields of Calendar have been set. Calendar
\r
1777 // will fill in default values for missing fields when the time
\r
1780 parsePos.setIndex(pos);
\r
1782 // This part is a problem: When we call parsedDate.after, we compute the time.
\r
1783 // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
\r
1784 // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
\r
1785 // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
\r
1786 // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
\r
1787 // on that day. It is therefore parsed out to fields as 3:30 am. Then we
\r
1788 // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
\r
1789 // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
\r
1791 Date parsedDate = cal.getTime();
\r
1792 if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
\r
1793 cal.add(Calendar.YEAR, 100);
\r
1794 parsedDate = cal.getTime();
\r
1797 // Because of the above condition, save off the fields in case we need to readjust.
\r
1798 // The procedure we use here is not particularly efficient, but there is no other
\r
1799 // way to do this given the API restrictions present in Calendar. We minimize
\r
1800 // inefficiency by only performing this computation when it might apply, that is,
\r
1801 // when the two-digit year is equal to the start year, and thus might fall at the
\r
1802 // front or the back of the default century. This only works because we adjust
\r
1803 // the year correctly to start with in other cases -- see subParse().
\r
1805 if (ambiguousYear[0] || tztype != TZTYPE_UNK) {
\r
1806 // We need a copy of the fields, and we need to avoid triggering a call to
\r
1807 // complete(), which will recalculate the fields. Since we can't access
\r
1808 // the fields[] array in Calendar, we clone the entire object. This will
\r
1809 // stop working if Calendar.clone() is ever rewritten to call complete().
\r
1811 if (ambiguousYear[0]) { // the two-digit year == the default start year
\r
1812 copy = (Calendar)cal.clone();
\r
1813 Date parsedDate = copy.getTime();
\r
1814 if (parsedDate.before(getDefaultCenturyStart())) {
\r
1815 // We can't use add here because that does a complete() first.
\r
1816 cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
\r
1819 if (tztype != TZTYPE_UNK) {
\r
1820 copy = (Calendar)cal.clone();
\r
1821 TimeZone tz = copy.getTimeZone();
\r
1822 BasicTimeZone btz = null;
\r
1823 if (tz instanceof BasicTimeZone) {
\r
1824 btz = (BasicTimeZone)tz;
\r
1827 // Get local millis
\r
1828 copy.set(Calendar.ZONE_OFFSET, 0);
\r
1829 copy.set(Calendar.DST_OFFSET, 0);
\r
1830 long localMillis = copy.getTimeInMillis();
\r
1832 // Make sure parsed time zone type (Standard or Daylight)
\r
1833 // matches the rule used by the parsed time zone.
\r
1834 int[] offsets = new int[2];
\r
1835 if (btz != null) {
\r
1836 if (tztype == TZTYPE_STD) {
\r
1837 btz.getOffsetFromLocal(localMillis,
\r
1838 BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
\r
1840 btz.getOffsetFromLocal(localMillis,
\r
1841 BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
\r
1844 // No good way to resolve ambiguous time at transition,
\r
1845 // but following code work in most case.
\r
1846 tz.getOffset(localMillis, true, offsets);
\r
1848 if (tztype == TZTYPE_STD && offsets[1] != 0 || tztype == TZTYPE_DST && offsets[1] == 0) {
\r
1849 // Roll back one day and try it again.
\r
1850 // Note: This code assumes 1. timezone transition only happens once within 24 hours at max
\r
1851 // 2. the difference of local offsets at the transition is less than 24 hours.
\r
1852 tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
\r
1856 // Now, compare the results with parsed type, either standard or daylight saving time
\r
1857 int resolvedSavings = offsets[1];
\r
1858 if (tztype == TZTYPE_STD) {
\r
1859 if (offsets[1] != 0) {
\r
1860 // Override DST_OFFSET = 0 in the result calendar
\r
1861 resolvedSavings = 0;
\r
1863 } else { // tztype == TZTYPE_DST
\r
1864 if (offsets[1] == 0) {
\r
1865 if (btz != null) {
\r
1866 long time = localMillis + offsets[0];
\r
1867 // We use the nearest daylight saving time rule.
\r
1868 TimeZoneTransition beforeTrs, afterTrs;
\r
1869 long beforeT = time, afterT = time;
\r
1870 int beforeSav = 0, afterSav = 0;
\r
1872 // Search for DST rule before or on the time
\r
1874 beforeTrs = btz.getPreviousTransition(beforeT, true);
\r
1875 if (beforeTrs == null) {
\r
1878 beforeT = beforeTrs.getTime() - 1;
\r
1879 beforeSav = beforeTrs.getFrom().getDSTSavings();
\r
1880 if (beforeSav != 0) {
\r
1885 // Search for DST rule after the time
\r
1887 afterTrs = btz.getNextTransition(afterT, false);
\r
1888 if (afterTrs == null) {
\r
1891 afterT = afterTrs.getTime();
\r
1892 afterSav = afterTrs.getTo().getDSTSavings();
\r
1893 if (afterSav != 0) {
\r
1898 if (beforeTrs != null && afterTrs != null) {
\r
1899 if (time - beforeT > afterT - time) {
\r
1900 resolvedSavings = afterSav;
\r
1902 resolvedSavings = beforeSav;
\r
1904 } else if (beforeTrs != null && beforeSav != 0) {
\r
1905 resolvedSavings = beforeSav;
\r
1906 } else if (afterTrs != null && afterSav != 0) {
\r
1907 resolvedSavings = afterSav;
\r
1909 resolvedSavings = btz.getDSTSavings();
\r
1912 resolvedSavings = tz.getDSTSavings();
\r
1914 if (resolvedSavings == 0) {
\r
1916 resolvedSavings = millisPerHour;
\r
1920 cal.set(Calendar.ZONE_OFFSET, offsets[0]);
\r
1921 cal.set(Calendar.DST_OFFSET, resolvedSavings);
\r
1925 // An IllegalArgumentException will be thrown by Calendar.getTime()
\r
1926 // if any fields are out of range, e.g., MONTH == 17.
\r
1927 catch (IllegalArgumentException e) {
\r
1928 parsePos.setErrorIndex(pos);
\r
1929 parsePos.setIndex(start);
\r
1930 if (backupTZ != null) {
\r
1931 calendar.setTimeZone(backupTZ);
\r
1935 // Set the parsed result if local calendar is used
\r
1936 // instead of the input calendar
\r
1937 if (resultCal != null) {
\r
1938 resultCal.setTimeZone(cal.getTimeZone());
\r
1939 resultCal.setTimeInMillis(cal.getTimeInMillis());
\r
1941 // Restore the original time zone if required
\r
1942 if (backupTZ != null) {
\r
1943 calendar.setTimeZone(backupTZ);
\r
1948 * Attempt to match the text at a given position against an array of
\r
1949 * strings. Since multiple strings in the array may match (for
\r
1950 * example, if the array contains "a", "ab", and "abc", all will match
\r
1951 * the input string "abcd") the longest match is returned. As a side
\r
1952 * effect, the given field of <code>cal</code> is set to the index
\r
1953 * of the best match, if there is one.
\r
1954 * @param text the time text being parsed.
\r
1955 * @param start where to start parsing.
\r
1956 * @param field the date field being parsed.
\r
1957 * @param data the string array to parsed.
\r
1958 * @return the new start position if matching succeeded; a negative
\r
1959 * number indicating matching failure, otherwise. As a side effect,
\r
1960 * sets the <code>cal</code> field <code>field</code> to the index
\r
1961 * of the best match, if matching succeeded.
\r
1964 protected int matchString(String text, int start, int field, String[] data, Calendar cal)
\r
1967 int count = data.length;
\r
1969 if (field == Calendar.DAY_OF_WEEK) i = 1;
\r
1971 // There may be multiple strings in the data[] array which begin with
\r
1972 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
\r
1973 // We keep track of the longest match, and return that. Note that this
\r
1974 // unfortunately requires us to test all array elements.
\r
1975 int bestMatchLength = 0, bestMatch = -1;
\r
1976 for (; i<count; ++i)
\r
1978 int length = data[i].length();
\r
1979 // Always compare if we have no match yet; otherwise only compare
\r
1980 // against potentially better matches (longer strings).
\r
1981 if (length > bestMatchLength &&
\r
1982 text.regionMatches(true, start, data[i], 0, length))
\r
1985 bestMatchLength = length;
\r
1988 if (bestMatch >= 0)
\r
1990 cal.set(field, bestMatch);
\r
1991 return start + bestMatchLength;
\r
1997 * Attempt to match the text at a given position against an array of quarter
\r
1998 * strings. Since multiple strings in the array may match (for
\r
1999 * example, if the array contains "a", "ab", and "abc", all will match
\r
2000 * the input string "abcd") the longest match is returned. As a side
\r
2001 * effect, the given field of <code>cal</code> is set to the index
\r
2002 * of the best match, if there is one.
\r
2003 * @param text the time text being parsed.
\r
2004 * @param start where to start parsing.
\r
2005 * @param field the date field being parsed.
\r
2006 * @param data the string array to parsed.
\r
2007 * @return the new start position if matching succeeded; a negative
\r
2008 * number indicating matching failure, otherwise. As a side effect,
\r
2009 * sets the <code>cal</code> field <code>field</code> to the index
\r
2010 * of the best match, if matching succeeded.
\r
2013 protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
\r
2016 int count = data.length;
\r
2018 // There may be multiple strings in the data[] array which begin with
\r
2019 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
\r
2020 // We keep track of the longest match, and return that. Note that this
\r
2021 // unfortunately requires us to test all array elements.
\r
2022 int bestMatchLength = 0, bestMatch = -1;
\r
2023 for (; i<count; ++i) {
\r
2024 int length = data[i].length();
\r
2025 // Always compare if we have no match yet; otherwise only compare
\r
2026 // against potentially better matches (longer strings).
\r
2027 if (length > bestMatchLength &&
\r
2028 text.regionMatches(true, start, data[i], 0, length)) {
\r
2030 bestMatchLength = length;
\r
2034 if (bestMatch >= 0) {
\r
2035 cal.set(field, bestMatch * 3);
\r
2036 return start + bestMatchLength;
\r
2043 * Protected method that converts one field of the input string into a
\r
2044 * numeric field value in <code>cal</code>. Returns -start (for
\r
2045 * ParsePosition) if failed. Subclasses may override this method to
\r
2046 * modify or add parsing capabilities.
\r
2047 * @param text the time text to be parsed.
\r
2048 * @param start where to start parsing.
\r
2049 * @param ch the pattern character for the date field text to be parsed.
\r
2050 * @param count the count of a pattern character.
\r
2051 * @param obeyCount if true, then the next field directly abuts this one,
\r
2052 * and we should use the count to know when to stop parsing.
\r
2053 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
\r
2054 * is true, then a two-digit year was parsed and may need to be readjusted.
\r
2055 * @return the new start position if matching succeeded; a negative
\r
2056 * number indicating matching failure, otherwise. As a side effect,
\r
2057 * set the appropriate field of <code>cal</code> with the parsed
\r
2061 protected int subParse(String text, int start, char ch, int count,
\r
2062 boolean obeyCount, boolean allowNegative,
\r
2063 boolean[] ambiguousYear, Calendar cal)
\r
2065 Number number = null;
\r
2066 NumberFormat currentNumberFormat = null;
\r
2069 ParsePosition pos = new ParsePosition(0);
\r
2070 //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c
\r
2071 int patternCharIndex = -1;
\r
2072 if ('A' <= ch && ch <= 'z') {
\r
2073 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
2076 if (patternCharIndex == -1) {
\r
2080 currentNumberFormat = getNumberFormat(ch);
\r
2082 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
\r
2084 // If there are any spaces here, skip over them. If we hit the end
\r
2085 // of the string, then fail.
\r
2087 if (start >= text.length()) {
\r
2090 int c = UTF16.charAt(text, start);
\r
2091 if (!UCharacter.isUWhiteSpace(c)) {
\r
2094 start += UTF16.getCharCount(c);
\r
2096 pos.setIndex(start);
\r
2098 // We handle a few special cases here where we need to parse
\r
2099 // a number value. We handle further, more generic cases below. We need
\r
2100 // to handle some of them here because some fields require extra processing on
\r
2101 // the parsed value.
\r
2102 if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
\r
2103 patternCharIndex == 15 /*HOUR1_FIELD*/ ||
\r
2104 (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
\r
2105 patternCharIndex == 1 ||
\r
2106 patternCharIndex == 8)
\r
2108 // It would be good to unify this with the obeyCount logic below,
\r
2109 // but that's going to be difficult.
\r
2112 if ((start+count) > text.length()) return -start;
\r
2113 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
\r
2115 else number = parseInt(text, pos, allowNegative,currentNumberFormat);
\r
2116 if (number == null)
\r
2118 value = number.intValue();
\r
2121 switch (patternCharIndex)
\r
2123 case 0: // 'G' - ERA
\r
2125 return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
\r
2127 return matchString(text, start, Calendar.ERA, formatData.eras, cal);
\r
2129 case 1: // 'y' - YEAR
\r
2130 // If there are 3 or more YEAR pattern characters, this indicates
\r
2131 // that the year value is to be treated literally, without any
\r
2132 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
\r
2133 // we made adjustments to place the 2-digit year in the proper
\r
2134 // century, for parsed strings from "00" to "99". Any other string
\r
2135 // is treated literally: "2250", "-1", "1", "002".
\r
2136 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
\r
2137 if (count == 2 && (pos.getIndex() - start) == 2
\r
2138 && UCharacter.isDigit(text.charAt(start))
\r
2139 && UCharacter.isDigit(text.charAt(start+1)))
\r
2141 // Assume for example that the defaultCenturyStart is 6/18/1903.
\r
2142 // This means that two-digit years will be forced into the range
\r
2143 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
\r
2144 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
\r
2145 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
\r
2146 // other fields specify a date before 6/18, or 1903 if they specify a
\r
2147 // date afterwards. As a result, 03 is an ambiguous year. All other
\r
2148 // two-digit years are unambiguous.
\r
2149 int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
\r
2150 ambiguousYear[0] = value == ambiguousTwoDigitYear;
\r
2151 value += (getDefaultCenturyStartYear()/100)*100 +
\r
2152 (value < ambiguousTwoDigitYear ? 100 : 0);
\r
2154 cal.set(Calendar.YEAR, value);
\r
2155 return pos.getIndex();
\r
2156 case 2: // 'M' - MONTH
\r
2157 if (count <= 2) // i.e., M or MM.
\r
2159 // Don't want to parse the month if it is a string
\r
2160 // while pattern uses numeric style: M or MM.
\r
2161 // [We computed 'value' above.]
\r
2162 cal.set(Calendar.MONTH, value - 1);
\r
2163 return pos.getIndex();
\r
2167 // count >= 3 // i.e., MMM or MMMM
\r
2168 // Want to be able to parse both short and long forms.
\r
2169 // Try count == 4 first:
\r
2170 int newStart = matchString(text, start, Calendar.MONTH,
\r
2171 formatData.months, cal);
\r
2172 if (newStart > 0) {
\r
2174 } else { // count == 4 failed, now try count == 3
\r
2175 return matchString(text, start, Calendar.MONTH,
\r
2176 formatData.shortMonths, cal);
\r
2179 case 26: // 'L' - STAND_ALONE_MONTH
\r
2180 if (count <= 2) // i.e., M or MM.
\r
2182 // Don't want to parse the month if it is a string
\r
2183 // while pattern uses numeric style: M or MM.
\r
2184 // [We computed 'value' above.]
\r
2185 cal.set(Calendar.MONTH, value - 1);
\r
2186 return pos.getIndex();
\r
2190 // count >= 3 // i.e., MMM or MMMM
\r
2191 // Want to be able to parse both short and long forms.
\r
2192 // Try count == 4 first:
\r
2193 int newStart = matchString(text, start, Calendar.MONTH,
\r
2194 formatData.standaloneMonths, cal);
\r
2195 if (newStart > 0) {
\r
2197 } else { // count == 4 failed, now try count == 3
\r
2198 return matchString(text, start, Calendar.MONTH,
\r
2199 formatData.standaloneShortMonths, cal);
\r
2202 case 4: // 'k' - HOUR_OF_DAY (1..24)
\r
2203 // [We computed 'value' above.]
\r
2204 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
\r
2205 cal.set(Calendar.HOUR_OF_DAY, value);
\r
2206 return pos.getIndex();
\r
2207 case 8: // 'S' - FRACTIONAL_SECOND
\r
2208 // Fractional seconds left-justify
\r
2209 i = pos.getIndex() - start;
\r
2221 value = (value + (a>>1)) / a;
\r
2223 cal.set(Calendar.MILLISECOND, value);
\r
2224 return pos.getIndex();
\r
2225 case 9: { // 'E' - DAY_OF_WEEK
\r
2226 // Want to be able to parse both short and long forms.
\r
2227 // Try count == 4 (EEEE) first:
\r
2228 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2229 formatData.weekdays, cal);
\r
2230 if (newStart > 0) {
\r
2232 } else { // EEEE failed, now try EEE
\r
2233 return matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2234 formatData.shortWeekdays, cal);
\r
2237 case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
\r
2238 // Want to be able to parse both short and long forms.
\r
2239 // Try count == 4 (cccc) first:
\r
2240 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2241 formatData.standaloneWeekdays, cal);
\r
2242 if (newStart > 0) {
\r
2244 } else { // cccc failed, now try ccc
\r
2245 return matchString(text, start, Calendar.DAY_OF_WEEK,
\r
2246 formatData.standaloneShortWeekdays, cal);
\r
2249 case 14: // 'a' - AM_PM
\r
2250 return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
\r
2251 case 15: // 'h' - HOUR (1..12)
\r
2252 // [We computed 'value' above.]
\r
2253 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;
\r
2254 cal.set(Calendar.HOUR, value);
\r
2255 return pos.getIndex();
\r
2256 case 17: // 'z' - ZONE_OFFSET
\r
2257 case 23: // 'Z' - TIMEZONE_RFC
\r
2258 case 24: // 'v' - TIMEZONE_GENERIC
\r
2259 case 29: // 'V' - TIMEZONE_SPECIAL
\r
2261 TimeZone tz = null;
\r
2263 boolean parsed = false;
\r
2266 // Check if this is a long GMT offset string (either localized or default)
\r
2267 Integer gmtoff = parseGMT(text, pos, currentNumberFormat);
\r
2268 if (gmtoff != null) {
\r
2269 offset = gmtoff.intValue();
\r
2275 // Check if this is an RFC822 time zone offset.
\r
2276 // ICU supports the standard RFC822 format [+|-]HHmm
\r
2277 // and its extended form [+|-]HHmmSS.
\r
2281 char signChar = text.charAt(start);
\r
2282 if (signChar == '+') {
\r
2284 } else if (signChar == '-') {
\r
2287 // Not an RFC822 offset string
\r
2292 int orgPos = start + 1;
\r
2293 pos.setIndex(orgPos);
\r
2294 number = parseInt(text, 6, pos, false,currentNumberFormat);
\r
2295 int numLen = pos.getIndex() - orgPos;
\r
2296 if (numLen <= 0) {
\r
2300 // Followings are possible format (excluding sign char)
\r
2307 int val = number.intValue();
\r
2308 int hour = 0, min = 0, sec = 0;
\r
2321 hour = val / 10000;
\r
2322 min = (val % 10000) / 100;
\r
2326 if (hour > 23 || min > 59 || sec > 59) {
\r
2327 // Invalid value range
\r
2330 offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;
\r
2335 // Failed to parse. Reset the position.
\r
2336 pos.setIndex(start);
\r
2341 // offset was successfully parsed as either a long GMT string or RFC822 zone offset
\r
2342 // string. Create normalized zone ID for the offset.
\r
2343 tz = ZoneMeta.getCustomTimeZone(offset);
\r
2344 cal.setTimeZone(tz);
\r
2345 return pos.getIndex();
\r
2349 // At this point, check for named time zones by looking through
\r
2350 // the locale data from the DateFormatZoneData strings.
\r
2351 // Want to be able to parse both short and long forms.
\r
2352 // optimize for calendar's current time zone
\r
2353 ZoneStringInfo zsinfo = null;
\r
2354 switch (patternCharIndex) {
\r
2355 case 17: // 'z' - ZONE_OFFSET
\r
2357 zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
\r
2359 zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);
\r
2362 case 24: // 'v' - TIMEZONE_GENERIC
\r
2364 zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);
\r
2365 } else if (count == 4) {
\r
2366 zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);
\r
2369 case 29: // 'V' - TIMEZONE_SPECIAL
\r
2371 zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
\r
2372 } else if (count == 4) {
\r
2373 zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);
\r
2377 if (zsinfo != null) {
\r
2378 if (zsinfo.isStandard()) {
\r
2379 tztype = TZTYPE_STD;
\r
2380 } else if (zsinfo.isDaylight()) {
\r
2381 tztype = TZTYPE_DST;
\r
2383 tz = TimeZone.getTimeZone(zsinfo.getID());
\r
2384 cal.setTimeZone(tz);
\r
2385 return start + zsinfo.getString().length();
\r
2388 // Final attempt - is this standalone GMT/UT/UTC?
\r
2390 if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
\r
2391 gmtLen = STR_GMT_LEN;
\r
2392 } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {
\r
2393 gmtLen = STR_UTC_LEN;
\r
2394 } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
\r
2395 gmtLen = STR_UT_LEN;
\r
2398 tz = TimeZone.getTimeZone("Etc/GMT");
\r
2399 cal.setTimeZone(tz);
\r
2400 return start + gmtLen;
\r
2403 // complete failure
\r
2407 case 27: // 'Q' - QUARTER
\r
2408 if (count <= 2) // i.e., Q or QQ.
\r
2410 // Don't want to parse the quarter if it is a string
\r
2411 // while pattern uses numeric style: Q or QQ.
\r
2412 // [We computed 'value' above.]
\r
2413 cal.set(Calendar.MONTH, (value - 1) * 3);
\r
2414 return pos.getIndex();
\r
2418 // count >= 3 // i.e., QQQ or QQQQ
\r
2419 // Want to be able to parse both short and long forms.
\r
2420 // Try count == 4 first:
\r
2421 int newStart = matchQuarterString(text, start, Calendar.MONTH,
\r
2422 formatData.quarters, cal);
\r
2423 if (newStart > 0) {
\r
2425 } else { // count == 4 failed, now try count == 3
\r
2426 return matchQuarterString(text, start, Calendar.MONTH,
\r
2427 formatData.shortQuarters, cal);
\r
2431 case 28: // 'q' - STANDALONE QUARTER
\r
2432 if (count <= 2) // i.e., q or qq.
\r
2434 // Don't want to parse the quarter if it is a string
\r
2435 // while pattern uses numeric style: q or qq.
\r
2436 // [We computed 'value' above.]
\r
2437 cal.set(Calendar.MONTH, (value - 1) * 3);
\r
2438 return pos.getIndex();
\r
2442 // count >= 3 // i.e., qqq or qqqq
\r
2443 // Want to be able to parse both short and long forms.
\r
2444 // Try count == 4 first:
\r
2445 int newStart = matchQuarterString(text, start, Calendar.MONTH,
\r
2446 formatData.standaloneQuarters, cal);
\r
2447 if (newStart > 0) {
\r
2449 } else { // count == 4 failed, now try count == 3
\r
2450 return matchQuarterString(text, start, Calendar.MONTH,
\r
2451 formatData.standaloneShortQuarters, cal);
\r
2456 // case 3: // 'd' - DATE
\r
2457 // case 5: // 'H' - HOUR_OF_DAY (0..23)
\r
2458 // case 6: // 'm' - MINUTE
\r
2459 // case 7: // 's' - SECOND
\r
2460 // case 10: // 'D' - DAY_OF_YEAR
\r
2461 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
\r
2462 // case 12: // 'w' - WEEK_OF_YEAR
\r
2463 // case 13: // 'W' - WEEK_OF_MONTH
\r
2464 // case 16: // 'K' - HOUR (0..11)
\r
2465 // case 18: // 'Y' - YEAR_WOY
\r
2466 // case 19: // 'e' - DOW_LOCAL
\r
2467 // case 20: // 'u' - EXTENDED_YEAR
\r
2468 // case 21: // 'g' - JULIAN_DAY
\r
2469 // case 22: // 'A' - MILLISECONDS_IN_DAY
\r
2471 // Handle "generic" fields
\r
2474 if ((start+count) > text.length()) return -start;
\r
2475 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
\r
2477 else number = parseInt(text, pos, allowNegative,currentNumberFormat);
\r
2478 if (number != null) {
\r
2479 cal.set(field, number.intValue());
\r
2480 return pos.getIndex();
\r
2487 * Parse an integer using numberFormat. This method is semantically
\r
2488 * const, but actually may modify fNumberFormat.
\r
2490 private Number parseInt(String text,
\r
2491 ParsePosition pos,
\r
2492 boolean allowNegative,
\r
2493 NumberFormat fmt) {
\r
2494 return parseInt(text, -1, pos, allowNegative, fmt);
\r
2498 * Parse an integer using numberFormat up to maxDigits.
\r
2500 private Number parseInt(String text,
\r
2502 ParsePosition pos,
\r
2503 boolean allowNegative,
\r
2504 NumberFormat fmt) {
\r
2506 int oldPos = pos.getIndex();
\r
2507 if (allowNegative) {
\r
2508 number = fmt.parse(text, pos);
\r
2510 // Invalidate negative numbers
\r
2511 if (fmt instanceof DecimalFormat) {
\r
2512 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
\r
2513 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
\r
2514 number = fmt.parse(text, pos);
\r
2515 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
\r
2517 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
\r
2518 if (dateNumberFormat) {
\r
2519 ((DateNumberFormat)fmt).setParsePositiveOnly(true);
\r
2521 number = fmt.parse(text, pos);
\r
2522 if (dateNumberFormat) {
\r
2523 ((DateNumberFormat)fmt).setParsePositiveOnly(false);
\r
2527 if (maxDigits > 0) {
\r
2528 // adjust the result to fit into
\r
2529 // the maxDigits and move the position back
\r
2530 int nDigits = pos.getIndex() - oldPos;
\r
2531 if (nDigits > maxDigits) {
\r
2532 double val = number.doubleValue();
\r
2533 nDigits -= maxDigits;
\r
2534 while (nDigits > 0) {
\r
2538 pos.setIndex(oldPos + maxDigits);
\r
2539 number = new Integer((int)val);
\r
2547 * Translate a pattern, mapping each character in the from string to the
\r
2548 * corresponding character in the to string.
\r
2550 private String translatePattern(String pat, String from, String to) {
\r
2551 StringBuffer result = new StringBuffer();
\r
2552 boolean inQuote = false;
\r
2553 for (int i = 0; i < pat.length(); ++i) {
\r
2554 char c = pat.charAt(i);
\r
2562 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
\r
2563 int ci = from.indexOf(c);
\r
2565 c = to.charAt(ci);
\r
2567 // do not worry on translatepattern if the character is not listed
\r
2568 // we do the validity check elsewhere
\r
2574 throw new IllegalArgumentException("Unfinished quote in pattern");
\r
2575 return result.toString();
\r
2579 * Return a pattern string describing this date format.
\r
2582 public String toPattern() {
\r
2587 * Return a localized pattern string describing this date format.
\r
2590 public String toLocalizedPattern() {
\r
2591 return translatePattern(pattern,
\r
2592 DateFormatSymbols.patternChars,
\r
2593 formatData.localPatternChars);
\r
2597 * Apply the given unlocalized pattern string to this date format.
\r
2600 public void applyPattern(String pat)
\r
2602 this.pattern = pat;
\r
2603 setLocale(null, null);
\r
2604 // reset parsed pattern items
\r
2605 patternItems = null;
\r
2609 * Apply the given localized pattern string to this date format.
\r
2612 public void applyLocalizedPattern(String pat) {
\r
2613 this.pattern = translatePattern(pat,
\r
2614 formatData.localPatternChars,
\r
2615 DateFormatSymbols.patternChars);
\r
2616 setLocale(null, null);
\r
2620 * Gets the date/time formatting data.
\r
2621 * @return a copy of the date-time formatting data associated
\r
2622 * with this date-time formatter.
\r
2625 public DateFormatSymbols getDateFormatSymbols()
\r
2627 return (DateFormatSymbols)formatData.clone();
\r
2631 * Allows you to set the date/time formatting data.
\r
2632 * @param newFormatSymbols the new symbols
\r
2635 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
\r
2637 this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
\r
2638 gmtfmtCache = null;
\r
2642 * Method for subclasses to access the DateFormatSymbols.
\r
2645 protected DateFormatSymbols getSymbols() {
\r
2646 return formatData;
\r
2650 * Overrides Cloneable
\r
2653 public Object clone() {
\r
2654 SimpleDateFormat other = (SimpleDateFormat) super.clone();
\r
2655 other.formatData = (DateFormatSymbols) formatData.clone();
\r
2660 * Override hashCode.
\r
2661 * Generates the hash code for the SimpleDateFormat object
\r
2664 public int hashCode()
\r
2666 return pattern.hashCode();
\r
2667 // just enough fields for a reasonable distribution
\r
2671 * Override equals.
\r
2674 public boolean equals(Object obj)
\r
2676 if (!super.equals(obj)) return false; // super does class check
\r
2677 SimpleDateFormat that = (SimpleDateFormat) obj;
\r
2678 return (pattern.equals(that.pattern)
\r
2679 && formatData.equals(that.formatData));
\r
2683 * Override writeObject.
\r
2685 private void writeObject(ObjectOutputStream stream) throws IOException{
\r
2686 if (defaultCenturyStart == null) {
\r
2687 // if defaultCenturyStart is not yet initialized,
\r
2688 // calculate and set value before serialization.
\r
2689 initializeDefaultCenturyStart(defaultCenturyBase);
\r
2691 stream.defaultWriteObject();
\r
2695 * Override readObject.
\r
2697 private void readObject(ObjectInputStream stream)
\r
2698 throws IOException, ClassNotFoundException {
\r
2699 stream.defaultReadObject();
\r
2701 // don't have old serial data to test with
\r
2702 if (serialVersionOnStream < 1) {
\r
2703 // didn't have defaultCenturyStart field
\r
2704 defaultCenturyBase = System.currentTimeMillis();
\r
2708 // fill in dependent transient field
\r
2709 parseAmbiguousDatesAsAfter(defaultCenturyStart);
\r
2711 serialVersionOnStream = currentSerialVersion;
\r
2712 locale = getLocale(ULocale.VALID_LOCALE);
\r
2714 initLocalZeroPaddingNumberFormat();
\r
2717 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
2720 * Format the object to an attributed string, and return the corresponding iterator
\r
2721 * Overrides superclass method.
\r
2723 * @param obj The object to format
\r
2724 * @return <code>AttributedCharacterIterator</code> describing the formatted value.
\r
2728 public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
\r
2729 Calendar cal = calendar;
\r
2730 if (obj instanceof Calendar) {
\r
2731 cal = (Calendar)obj;
\r
2732 } else if (obj instanceof Date) {
\r
2733 calendar.setTime((Date)obj);
\r
2734 } else if (obj instanceof Number) {
\r
2735 calendar.setTimeInMillis(((Number)obj).longValue());
\r
2737 throw new IllegalArgumentException("Cannot format given Object as a Date");
\r
2739 StringBuffer toAppendTo = new StringBuffer();
\r
2740 FieldPosition pos = new FieldPosition(0);
\r
2741 List attributes = new LinkedList();
\r
2742 format(cal, toAppendTo, pos, attributes);
\r
2744 AttributedString as = new AttributedString(toAppendTo.toString());
\r
2746 // add DateFormat field attributes to the AttributedString
\r
2747 for (int i = 0; i < attributes.size(); i++) {
\r
2748 FieldPosition fp = (FieldPosition) attributes.get(i);
\r
2749 Format.Field attribute = fp.getFieldAttribute();
\r
2750 as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
\r
2752 // return the CharacterIterator from AttributedString
\r
2753 return as.getIterator();
\r
2759 * Get the locale of this simple date formatter.
\r
2760 * It is package accessible. also used in DateIntervalFormat.
\r
2762 * @return locale in this simple date formatter
\r
2764 ULocale getLocale()
\r
2772 * Check whether the 'field' is smaller than all the fields covered in
\r
2773 * pattern, return true if it is.
\r
2774 * The sequence of calendar field,
\r
2775 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
\r
2776 * @param field the calendar field need to check against
\r
2777 * @return true if the 'field' is smaller than all the fields
\r
2778 * covered in pattern. false otherwise.
\r
2781 boolean isFieldUnitIgnored(int field) {
\r
2782 return isFieldUnitIgnored(pattern, field);
\r
2787 * Check whether the 'field' is smaller than all the fields covered in
\r
2788 * pattern, return true if it is.
\r
2789 * The sequence of calendar field,
\r
2790 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
\r
2791 * @param pattern the pattern to check against
\r
2792 * @param field the calendar field need to check against
\r
2793 * @return true if the 'field' is smaller than all the fields
\r
2794 * covered in pattern. false otherwise.
\r
2795 * @internal ICU 4.0
\r
2797 static boolean isFieldUnitIgnored(String pattern, int field) {
\r
2798 int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
\r
2801 boolean inQuote = false;
\r
2805 for (int i = 0; i < pattern.length(); ++i) {
\r
2806 ch = pattern.charAt(i);
\r
2807 if (ch != prevCh && count > 0) {
\r
2808 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
\r
2809 if ( fieldLevel <= level ) {
\r
2815 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
\r
2818 inQuote = ! inQuote;
\r
2821 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
\r
2822 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
\r
2827 if ( count > 0 ) {
\r
2829 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
\r
2830 if ( fieldLevel <= level ) {
\r
2839 * Format date interval by algorithm.
\r
2840 * It is supposed to be used only by CLDR survey tool.
\r
2842 * @param fromCalendar calendar set to the from date in date interval
\r
2843 * to be formatted into date interval stirng
\r
2844 * @param toCalendar calendar set to the to date in date interval
\r
2845 * to be formatted into date interval stirng
\r
2846 * @param appendTo Output parameter to receive result.
\r
2847 * Result is appended to existing contents.
\r
2848 * @param pos On input: an alignment field, if desired.
\r
2849 * On output: the offsets of the alignment field.
\r
2850 * @exception IllegalArgumentException when there is non-recognized
\r
2852 * @return Reference to 'appendTo' parameter.
\r
2853 * @internal ICU 4.0
\r
2855 public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
\r
2856 Calendar toCalendar,
\r
2857 StringBuffer appendTo,
\r
2858 FieldPosition pos)
\r
2859 throws IllegalArgumentException
\r
2861 // not support different calendar types and time zones
\r
2862 if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
\r
2863 throw new IllegalArgumentException("can not format on two different calendars");
\r
2866 Object[] items = getPatternItems();
\r
2867 int diffBegin = -1;
\r
2870 /* look for different formatting string range */
\r
2871 // look for start of difference
\r
2873 for (int i = 0; i < items.length; i++) {
\r
2874 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
\r
2880 if ( diffBegin == -1 ) {
\r
2881 // no difference, single date format
\r
2882 return format(fromCalendar, appendTo, pos);
\r
2885 // look for end of difference
\r
2886 for (int i = items.length-1; i >= diffBegin; i--) {
\r
2887 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
\r
2892 } catch ( IllegalArgumentException e ) {
\r
2893 throw new IllegalArgumentException(e.toString());
\r
2896 // full range is different
\r
2897 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
\r
2898 format(fromCalendar, appendTo, pos);
\r
2899 appendTo.append(" \u2013 "); // default separator
\r
2900 format(toCalendar, appendTo, pos);
\r
2905 /* search for largest calendar field within the different range */
\r
2906 int highestLevel = 1000;
\r
2907 for (int i = diffBegin; i <= diffEnd; i++) {
\r
2908 if ( items[i] instanceof String) {
\r
2911 PatternItem item = (PatternItem)items[i];
\r
2912 char ch = item.type;
\r
2913 int patternCharIndex = -1;
\r
2914 if ('A' <= ch && ch <= 'z') {
\r
2915 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
\r
2918 if (patternCharIndex == -1) {
\r
2919 throw new IllegalArgumentException("Illegal pattern character " +
\r
2920 "'" + ch + "' in \"" +
\r
2921 new String(pattern) + '"');
\r
2924 if ( patternCharIndex < highestLevel ) {
\r
2925 highestLevel = patternCharIndex;
\r
2929 /* re-calculate diff range, including those calendar field which
\r
2930 is in lower level than the largest calendar field covered
\r
2931 in diff range calculated. */
\r
2933 for (int i = 0; i < diffBegin; i++) {
\r
2934 if ( lowerLevel(items, i, highestLevel) ) {
\r
2941 for (int i = items.length-1; i > diffEnd; i--) {
\r
2942 if ( lowerLevel(items, i, highestLevel) ) {
\r
2947 } catch ( IllegalArgumentException e ) {
\r
2948 throw new IllegalArgumentException(e.toString());
\r
2952 // full range is different
\r
2953 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
\r
2954 format(fromCalendar, appendTo, pos);
\r
2955 appendTo.append(" \u2013 "); // default separator
\r
2956 format(toCalendar, appendTo, pos);
\r
2963 pos.setBeginIndex(0);
\r
2964 pos.setEndIndex(0);
\r
2966 // formatting date 1
\r
2967 for (int i = 0; i <= diffEnd; i++) {
\r
2968 if (items[i] instanceof String) {
\r
2969 appendTo.append((String)items[i]);
\r
2971 PatternItem item = (PatternItem)items[i];
\r
2972 if (useFastFormat) {
\r
2973 subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);
\r
2975 appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));
\r
2980 appendTo.append(" \u2013 "); // default separator
\r
2982 // formatting date 2
\r
2983 for (int i = diffBegin; i < items.length; i++) {
\r
2984 if (items[i] instanceof String) {
\r
2985 appendTo.append((String)items[i]);
\r
2987 PatternItem item = (PatternItem)items[i];
\r
2988 if (useFastFormat) {
\r
2989 subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);
\r
2991 appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));
\r
3000 * check whether the i-th item in 2 calendar is in different value.
\r
3002 * It is supposed to be used only by CLDR survey tool.
\r
3003 * It is used by intervalFormatByAlgorithm().
\r
3005 * @param fromCalendar one calendar
\r
3006 * @param toCalendar the other calendar
\r
3007 * @param items pattern items
\r
3008 * @param i the i-th item in pattern items
\r
3009 * @exception IllegalArgumentException when there is non-recognized
\r
3011 * @return true is i-th item in 2 calendar is in different
\r
3012 * value, false otherwise.
\r
3014 private boolean diffCalFieldValue(Calendar fromCalendar,
\r
3015 Calendar toCalendar,
\r
3017 int i) throws IllegalArgumentException {
\r
3018 if ( items[i] instanceof String) {
\r
3021 PatternItem item = (PatternItem)items[i];
\r
3022 char ch = item.type;
\r
3023 int patternCharIndex = -1;
\r
3024 if ('A' <= ch && ch <= 'z') {
\r
3025 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
\r
3028 if (patternCharIndex == -1) {
\r
3029 throw new IllegalArgumentException("Illegal pattern character " +
\r
3030 "'" + ch + "' in \"" +
\r
3031 new String(pattern) + '"');
\r
3034 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
\r
3035 int value = fromCalendar.get(field);
\r
3036 int value_2 = toCalendar.get(field);
\r
3037 if ( value != value_2 ) {
\r
3045 * check whether the i-th item's level is lower than the input 'level'
\r
3047 * It is supposed to be used only by CLDR survey tool.
\r
3048 * It is used by intervalFormatByAlgorithm().
\r
3050 * @param items the pattern items
\r
3051 * @param i the i-th item in pattern items
\r
3052 * @param level the level with which the i-th pattern item compared to
\r
3053 * @exception IllegalArgumentException when there is non-recognized
\r
3055 * @return true if i-th pattern item is lower than 'level',
\r
3058 private boolean lowerLevel(Object[] items, int i, int level)
\r
3059 throws IllegalArgumentException {
\r
3060 if ( items[i] instanceof String) {
\r
3063 PatternItem item = (PatternItem)items[i];
\r
3064 char ch = item.type;
\r
3065 int patternCharIndex = -1;
\r
3066 if ('A' <= ch && ch <= 'z') {
\r
3067 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
\r
3070 if (patternCharIndex == -1) {
\r
3071 throw new IllegalArgumentException("Illegal pattern character " +
\r
3072 "'" + ch + "' in \"" +
\r
3073 new String(pattern) + '"');
\r
3076 if ( patternCharIndex >= level ) {
\r
3082 protected NumberFormat getNumberFormat(char ch) {
\r
3084 Character ovrField;
\r
3085 ovrField = new Character(ch);
\r
3086 if (overrideMap != null && overrideMap.containsKey(ovrField)) {
\r
3087 String nsName = overrideMap.get(ovrField).toString();
\r
3088 NumberFormat nf = (NumberFormat)numberFormatters.get(nsName);
\r
3091 return numberFormat;
\r
3095 private void initNumberFormatters(ULocale loc) {
\r
3097 numberFormatters = new HashMap();
\r
3098 overrideMap = new HashMap();
\r
3099 processOverrideString(loc,override);
\r
3103 private void processOverrideString(ULocale loc, String str) {
\r
3105 if ( str == null || str.length() == 0 )
\r
3111 Character ovrField;
\r
3112 boolean moreToProcess = true;
\r
3113 boolean fullOverride;
\r
3115 while (moreToProcess) {
\r
3116 int delimiterPosition = str.indexOf(";",start);
\r
3117 if (delimiterPosition == -1) {
\r
3118 moreToProcess = false;
\r
3119 end = str.length();
\r
3121 end = delimiterPosition;
\r
3124 String currentString = str.substring(start,end);
\r
3125 int equalSignPosition = currentString.indexOf("=");
\r
3126 if (equalSignPosition == -1) { // Simple override string such as "hebrew"
\r
3127 nsName = currentString;
\r
3128 fullOverride = true;
\r
3129 } else { // Field specific override string such as "y=hebrew"
\r
3130 nsName = currentString.substring(equalSignPosition+1);
\r
3131 ovrField = new Character(currentString.charAt(0));
\r
3132 overrideMap.put(ovrField,nsName);
\r
3133 fullOverride = false;
\r
3136 ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
\r
3137 NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
\r
3139 if (fullOverride) {
\r
3140 setNumberFormat(nf);
\r
3142 // Since one or more of the override number formatters might be complex,
\r
3143 // we can't rely on the fast numfmt where we have a partial field override.
\r
3144 useLocalZeroPaddingNumberFormat = false;
\r
3147 if (!numberFormatters.containsKey(nsName)) {
\r
3148 numberFormatters.put(nsName,nf);
\r
3151 start = delimiterPosition + 1;
\r