-//##header\r
-/*\r
- *******************************************************************************\r
- * Copyright (C) 1996-2009, International Business Machines Corporation and *\r
- * others. All Rights Reserved. *\r
- *******************************************************************************\r
- */\r
-\r
-package com.ibm.icu.text;\r
-\r
-import java.io.IOException;\r
-import java.io.ObjectInputStream;\r
-import java.io.ObjectOutputStream;\r
-import java.lang.ref.WeakReference;\r
-import java.lang.Character;\r
-import java.text.FieldPosition;\r
-import java.text.ParsePosition;\r
-import java.util.ArrayList;\r
-import java.util.Date;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Locale;\r
-import java.util.MissingResourceException;\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-import java.text.AttributedCharacterIterator;\r
-import java.text.AttributedString;\r
-import java.text.Format;\r
-import java.util.LinkedList;\r
-//#endif\r
-\r
-import com.ibm.icu.impl.CalendarData;\r
-import com.ibm.icu.impl.DateNumberFormat;\r
-import com.ibm.icu.impl.ICUCache;\r
-import com.ibm.icu.impl.SimpleCache;\r
-import com.ibm.icu.impl.UCharacterProperty;\r
-import com.ibm.icu.impl.ZoneMeta;\r
-import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;\r
-import com.ibm.icu.lang.UCharacter;\r
-import com.ibm.icu.util.BasicTimeZone;\r
-import com.ibm.icu.util.Calendar;\r
-import com.ibm.icu.util.GregorianCalendar;\r
-import com.ibm.icu.util.TimeZone;\r
-import com.ibm.icu.util.TimeZoneTransition;\r
-import com.ibm.icu.util.ULocale;\r
-\r
-\r
-/**\r
- * <code>SimpleDateFormat</code> is a concrete class for formatting and\r
- * parsing dates in a locale-sensitive manner. It allows for formatting\r
- * (date -> text), parsing (text -> date), and normalization.\r
- *\r
- * <p>\r
- * <code>SimpleDateFormat</code> allows you to start by choosing\r
- * any user-defined patterns for date-time formatting. However, you\r
- * are encouraged to create a date-time formatter with either\r
- * <code>getTimeInstance</code>, <code>getDateInstance</code>, or\r
- * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each\r
- * of these class methods can return a date/time formatter initialized\r
- * with a default format pattern. You may modify the format pattern\r
- * using the <code>applyPattern</code> methods as desired.\r
- * For more information on using these methods, see\r
- * {@link DateFormat}.\r
- *\r
- * <p>\r
- * <strong>Time Format Syntax:</strong>\r
- * <p>\r
- * To specify the time format use a <em>time pattern</em> string.\r
- * In this pattern, all ASCII letters are reserved as pattern letters,\r
- * which are defined as the following:\r
- * <blockquote>\r
- * <pre>\r
- * Symbol Meaning Presentation Example\r
- * ------ ------- ------------ -------\r
- * G era designator (Text) AD\r
- * y† year (Number) 1996\r
- * Y* year (week of year) (Number) 1997\r
- * u* extended year (Number) 4601\r
- * M month in year (Text & Number) July & 07\r
- * d day in month (Number) 10\r
- * h hour in am/pm (1~12) (Number) 12\r
- * H hour in day (0~23) (Number) 0\r
- * m minute in hour (Number) 30\r
- * s second in minute (Number) 55\r
- * S fractional second (Number) 978\r
- * E day of week (Text) Tuesday\r
- * e* day of week (local 1~7) (Text & Number) Tuesday & 2\r
- * D day in year (Number) 189\r
- * F day of week in month (Number) 2 (2nd Wed in July)\r
- * w week in year (Number) 27\r
- * W week in month (Number) 2\r
- * a am/pm marker (Text) PM\r
- * k hour in day (1~24) (Number) 24\r
- * K hour in am/pm (0~11) (Number) 0\r
- * z time zone (Text) Pacific Standard Time\r
- * Z time zone (RFC 822) (Number) -0800\r
- * v time zone (generic) (Text) Pacific Time\r
- * V time zone (location) (Text) United States (Los Angeles)\r
- * g* Julian day (Number) 2451334\r
- * A* milliseconds in day (Number) 69540000\r
- * Q* quarter in year (Text & Number) Q1 & 01\r
- * c* stand alone day of week (Text & Number) Tuesday & 2\r
- * L* stand alone month (Text & Number) July & 07\r
- * q* stand alone quarter (Text & Number) Q1 & 01\r
- * ' escape for text (Delimiter) 'Date='\r
- * '' single quote (Literal) 'o''clock'\r
- * </pre>\r
- * </blockquote>\r
- * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>\r
- * <tt><b>†</b></tt> ICU interprets a single 'y' differently than Java.</p>\r
- * <p>\r
- * The count of pattern letters determine the format.\r
- * <p>\r
- * <strong>(Text)</strong>: 4 or more pattern letters--use full form,\r
- * < 4--use short or abbreviated form if one exists.\r
- * <p>\r
- * <strong>(Number)</strong>: the minimum number of digits. Shorter\r
- * numbers are zero-padded to this amount. Year is handled specially;\r
- * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.\r
- * (e.g., if "yyyy" produces "1997", "yy" produces "97".)\r
- * Unlike other fields, fractional seconds are padded on the right with zero.\r
- * <p>\r
- * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.\r
- * <p>\r
- * Any characters in the pattern that are not in the ranges of ['a'..'z']\r
- * and ['A'..'Z'] will be treated as quoted text. For instance, characters\r
- * like ':', '.', ' ', '#' and '@' will appear in the resulting time text\r
- * even they are not embraced within single quotes.\r
- * <p>\r
- * A pattern containing any invalid pattern letter will result in a thrown\r
- * exception during formatting or parsing.\r
- *\r
- * <p>\r
- * <strong>Examples Using the US Locale:</strong>\r
- * <blockquote>\r
- * <pre>\r
- * Format Pattern Result\r
- * -------------- -------\r
- * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time\r
- * "EEE, MMM d, ''yy" ->> Wed, July 10, '96\r
- * "h:mm a" ->> 12:08 PM\r
- * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time\r
- * "K:mm a, vvv" ->> 0:00 PM, PT\r
- * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM\r
- * </pre>\r
- * </blockquote>\r
- * <strong>Code Sample:</strong>\r
- * <blockquote>\r
- * <pre>\r
- * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");\r
- * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);\r
- * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);\r
- * <br>\r
- * // Format the current time.\r
- * SimpleDateFormat formatter\r
- * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");\r
- * Date currentTime_1 = new Date();\r
- * String dateString = formatter.format(currentTime_1);\r
- * <br>\r
- * // Parse the previous string back into a Date.\r
- * ParsePosition pos = new ParsePosition(0);\r
- * Date currentTime_2 = formatter.parse(dateString, pos);\r
- * </pre>\r
- * </blockquote>\r
- * In the example, the time value <code>currentTime_2</code> obtained from\r
- * parsing will be equal to <code>currentTime_1</code>. However, they may not be\r
- * equal if the am/pm marker 'a' is left out from the format pattern while\r
- * the "hour in am/pm" pattern symbol is used. This information loss can\r
- * happen when formatting the time in PM.\r
- *\r
- * <p>\r
- * When parsing a date string using the abbreviated year pattern ("yy"),\r
- * SimpleDateFormat must interpret the abbreviated year\r
- * relative to some century. It does this by adjusting dates to be\r
- * within 80 years before and 20 years after the time the SimpleDateFormat\r
- * instance is created. For example, using a pattern of "MM/dd/yy" and a\r
- * SimpleDateFormat instance created on Jan 1, 1997, the string\r
- * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"\r
- * would be interpreted as May 4, 1964.\r
- * During parsing, only strings consisting of exactly two digits, as defined by\r
- * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default\r
- * century.\r
- * Any other numeric string, such as a one digit string, a three or more digit\r
- * string, or a two digit string that isn't all digits (for example, "-1"), is\r
- * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the\r
- * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.\r
- *\r
- * <p>\r
- * If the year pattern does not have exactly two 'y' characters, the year is\r
- * interpreted literally, regardless of the number of digits. So using the\r
- * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.\r
- *\r
- * <p>\r
- * When numeric fields abut one another directly, with no intervening delimiter\r
- * characters, they constitute a run of abutting numeric fields. Such runs are\r
- * parsed specially. For example, the format "HHmmss" parses the input text\r
- * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to\r
- * parse "1234". In other words, the leftmost field of the run is flexible,\r
- * while the others keep a fixed width. If the parse fails anywhere in the run,\r
- * then the leftmost field is shortened by one character, and the entire run is\r
- * parsed again. This is repeated until either the parse succeeds or the\r
- * leftmost field is one character in length. If the parse still fails at that\r
- * point, the parse of the run fails.\r
- *\r
- * <p>\r
- * For time zones that have no names, use strings GMT+hours:minutes or\r
- * GMT-hours:minutes.\r
- *\r
- * <p>\r
- * The calendar defines what is the first day of the week, the first week\r
- * of the year, whether hours are zero based or not (0 vs 12 or 24), and the\r
- * time zone. There is one common decimal format to handle all the numbers;\r
- * the digit count is handled programmatically according to the pattern.\r
- *\r
- * <h4>Synchronization</h4>\r
- *\r
- * Date formats are not synchronized. It is recommended to create separate\r
- * format instances for each thread. If multiple threads access a format\r
- * concurrently, it must be synchronized externally.\r
- *\r
- * @see com.ibm.icu.util.Calendar\r
- * @see com.ibm.icu.util.GregorianCalendar\r
- * @see com.ibm.icu.util.TimeZone\r
- * @see DateFormat\r
- * @see DateFormatSymbols\r
- * @see DecimalFormat\r
- * @author Mark Davis, Chen-Lieh Huang, Alan Liu\r
- * @stable ICU 2.0\r
- */\r
-public class SimpleDateFormat extends DateFormat {\r
-\r
- // the official serial version ID which says cryptically\r
- // which version we're compatible with\r
- private static final long serialVersionUID = 4774881970558875024L;\r
-\r
- // the internal serial version which says which version was written\r
- // - 0 (default) for version up to JDK 1.1.3\r
- // - 1 for version from JDK 1.1.4, which includes a new field\r
- static final int currentSerialVersion = 1;\r
-\r
- \r
- /*\r
- * From calendar field to its level.\r
- * Used to order calendar field.\r
- * For example, calendar fields can be defined in the following order:\r
- * year > month > date > am-pm > hour > minute\r
- * YEAR --> 10, MONTH -->20, DATE --> 30; \r
- * AM_PM -->40, HOUR --> 50, MINUTE -->60\r
- */\r
- private static final int[] CALENDAR_FIELD_TO_LEVEL =\r
- {\r
- /*GyM*/ 0, 10, 20,\r
- /*wW*/ 20, 30,\r
- /*dDEF*/ 30, 20, 30, 30,\r
- /*ahHm*/ 40, 50, 50, 60,\r
- /*sS..*/ 70, 80, \r
- /*z?Y*/ 0, 0, 10, \r
- /*eug*/ 30, 10, 0,\r
- /*A*/ 40 \r
- };\r
-\r
-\r
- \r
- /*\r
- * From calendar field letter to its level.\r
- * Used to order calendar field.\r
- * For example, calendar fields can be defined in the following order:\r
- * year > month > date > am-pm > hour > minute\r
- * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60\r
- */\r
- private static final int[] PATTERN_CHAR_TO_LEVEL = \r
- {\r
- // A B C D E F G H I J K L M N O\r
- -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, -1,\r
- // P Q R S T U V W X Y Z\r
- -1, 20, -1, 80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,\r
- // a b c d e f g h i j k l m n o\r
- -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,\r
- // p q r s t u v w x y z\r
- -1, 20, -1, 70, -1, 10, 0, 20, -1, 10, 0, -1, -1, -1, -1, -1\r
- };\r
-\r
-\r
- /**\r
- * The version of the serialized data on the stream. Possible values:\r
- * <ul>\r
- * <li><b>0</b> or not present on stream: JDK 1.1.3. This version\r
- * has no <code>defaultCenturyStart</code> on stream.\r
- * <li><b>1</b> JDK 1.1.4 or later. This version adds\r
- * <code>defaultCenturyStart</code>.\r
- * </ul>\r
- * When streaming out this class, the most recent format\r
- * and the highest allowable <code>serialVersionOnStream</code>\r
- * is written.\r
- * @serial\r
- */\r
- private int serialVersionOnStream = currentSerialVersion;\r
-\r
- /**\r
- * The pattern string of this formatter. This is always a non-localized\r
- * pattern. May not be null. See class documentation for details.\r
- * @serial\r
- */\r
- private String pattern;\r
-\r
- /**\r
- * The override string of this formatter. Used to override the\r
- * numbering system for one or more fields.\r
- * @serial\r
- */\r
- private String override;\r
-\r
- /**\r
- * The hash map used for number format overrides.\r
- * @serial\r
- */\r
- private HashMap numberFormatters;\r
-\r
- /**\r
- * The hash map used for number format overrides.\r
- * @serial\r
- */\r
- private HashMap overrideMap;\r
-\r
- /**\r
- * The symbols used by this formatter for week names, month names,\r
- * etc. May not be null.\r
- * @serial\r
- * @see DateFormatSymbols\r
- */\r
- private DateFormatSymbols formatData;\r
-\r
- private transient ULocale locale;\r
-\r
- /**\r
- * We map dates with two-digit years into the century starting at\r
- * <code>defaultCenturyStart</code>, which may be any date. May\r
- * not be null.\r
- * @serial\r
- * @since JDK1.1.4\r
- */\r
- private Date defaultCenturyStart;\r
-\r
- private transient int defaultCenturyStartYear;\r
-\r
- // defaultCenturyBase is set when an instance is created\r
- // and may be used for calculating defaultCenturyStart when needed.\r
- private transient long defaultCenturyBase;\r
-\r
- // We need to preserve time zone type when parsing specific\r
- // time zone text (xxx Standard Time vs xxx Daylight Time)\r
- private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;\r
- private transient int tztype = TZTYPE_UNK;\r
-\r
- private static final int millisPerHour = 60 * 60 * 1000;\r
- private static final int millisPerMinute = 60 * 1000;\r
- private static final int millisPerSecond = 1000;\r
-\r
- // This prefix is designed to NEVER MATCH real text, in order to\r
- // suppress the parsing of negative numbers. Adjust as needed (if\r
- // this becomes valid Unicode).\r
- private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";\r
-\r
- /**\r
- * If true, this object supports fast formatting using the\r
- * subFormat variant that takes a StringBuffer.\r
- */\r
- private transient boolean useFastFormat;\r
-\r
- /**\r
- * Construct a SimpleDateFormat using the default pattern for the default\r
- * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
- * generality, use the factory methods in the DateFormat class.\r
- *\r
- * @see DateFormat\r
- * @stable ICU 2.0\r
- */\r
- public SimpleDateFormat() {\r
- this(getDefaultPattern(), null, null, null, null, true, null);\r
- }\r
-\r
- /**\r
- * Construct a SimpleDateFormat using the given pattern in the default\r
- * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
- * generality, use the factory methods in the DateFormat class.\r
- * @stable ICU 2.0\r
- */\r
- public SimpleDateFormat(String pattern)\r
- {\r
- this(pattern, null, null, null, null, true, null);\r
- }\r
-\r
- /**\r
- * Construct a SimpleDateFormat using the given pattern and locale.\r
- * <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
- * generality, use the factory methods in the DateFormat class.\r
- * @stable ICU 2.0\r
- */\r
- public SimpleDateFormat(String pattern, Locale loc)\r
- {\r
- this(pattern, null, null, null, ULocale.forLocale(loc), true, null);\r
- }\r
-\r
- /**\r
- * Construct a SimpleDateFormat using the given pattern and locale.\r
- * <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
- * generality, use the factory methods in the DateFormat class.\r
- * @stable ICU 3.2\r
- */\r
- public SimpleDateFormat(String pattern, ULocale loc)\r
- {\r
- this(pattern, null, null, null, loc, true, null);\r
- }\r
-\r
- /**\r
- * Construct a SimpleDateFormat using the given pattern , override and locale.\r
- * @draft ICU 4.2\r
- * @provisional This API might change or be removed in a future release.\r
- */\r
- public SimpleDateFormat(String pattern, String override, ULocale loc)\r
- {\r
- this(pattern, null, null, null, loc, false,override);\r
- }\r
-\r
- /**\r
- * Construct a SimpleDateFormat using the given pattern and\r
- * locale-specific symbol data.\r
- * Warning: uses default locale for digits!\r
- * @stable ICU 2.0\r
- */\r
- public SimpleDateFormat(String pattern, DateFormatSymbols formatData)\r
- {\r
- this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);\r
- }\r
-\r
- /**\r
- * @internal ICU 3.2\r
- * @deprecated This API is ICU internal only.\r
- */\r
- public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)\r
- {\r
- this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);\r
- }\r
-\r
- /**\r
- * Package-private constructor that allows a subclass to specify\r
- * whether it supports fast formatting.\r
- *\r
- * TODO make this API public.\r
- */\r
- SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,\r
- boolean useFastFormat, String override) {\r
- this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);\r
- }\r
-\r
- SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,\r
- boolean useFastFormat) {\r
- this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,null);\r
- }\r
-\r
- /*\r
- * The constructor called from all other SimpleDateFormat constructors\r
- */\r
- private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,\r
- NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {\r
- this.pattern = pattern;\r
- this.formatData = formatData;\r
- this.calendar = calendar;\r
- this.numberFormat = numberFormat;\r
- this.locale = locale; // time zone formatting\r
- this.useFastFormat = useFastFormat;\r
- this.override = override;\r
- initialize();\r
- }\r
-\r
- /**\r
- * Create an instance of SimpleDateForamt for the given format configuration\r
- * @param formatConfig the format configuration\r
- * @return A SimpleDateFormat instance\r
- * @internal ICU 3.8\r
- * @deprecated This API is ICU internal only.\r
- */\r
- public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {\r
- \r
- String ostr = formatConfig.getOverrideString();\r
- boolean useFast = ( ostr != null && ostr.length() > 0 );\r
-\r
- return new SimpleDateFormat(formatConfig.getPatternString(),\r
- formatConfig.getDateFormatSymbols(),\r
- formatConfig.getCalendar(),\r
- null,\r
- formatConfig.getLocale(),\r
- useFast,\r
- formatConfig.getOverrideString());\r
- }\r
-\r
- /*\r
- * Initialized fields\r
- */\r
- private void initialize() {\r
- if (locale == null) {\r
- locale = ULocale.getDefault();\r
- }\r
- if (formatData == null) {\r
- formatData = new DateFormatSymbols(locale);\r
- }\r
- if (calendar == null) {\r
- calendar = Calendar.getInstance(locale);\r
- }\r
- if (numberFormat == null) {\r
- NumberingSystem ns = NumberingSystem.getInstance(locale);\r
- if ( ns.isAlgorithmic() ) {\r
- numberFormat = NumberFormat.getInstance(locale);\r
- } else {\r
- char digit0 = ns.getDescription().charAt(0);\r
- // Use a NumberFormat optimized for date formatting\r
- numberFormat = new DateNumberFormat(locale, digit0);\r
- }\r
- }\r
- // Note: deferring calendar calculation until when we really need it.\r
- // Instead, we just record time of construction for backward compatibility.\r
- defaultCenturyBase = System.currentTimeMillis();\r
-\r
- setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));\r
- initLocalZeroPaddingNumberFormat();\r
-\r
- if (override != null) {\r
- initNumberFormatters(locale);\r
- }\r
-\r
- }\r
-\r
- // privates for the default pattern\r
- private static ULocale cachedDefaultLocale = null;\r
- private static String cachedDefaultPattern = null;\r
- private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";\r
-\r
- /*\r
- * Returns the default date and time pattern (SHORT) for the default locale.\r
- * This method is only used by the default SimpleDateFormat constructor.\r
- */\r
- private static synchronized String getDefaultPattern() {\r
- ULocale defaultLocale = ULocale.getDefault();\r
- if (!defaultLocale.equals(cachedDefaultLocale)) {\r
- cachedDefaultLocale = defaultLocale;\r
- Calendar cal = Calendar.getInstance(cachedDefaultLocale);\r
- try {\r
- CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());\r
- String[] dateTimePatterns = calData.getDateTimePatterns();\r
- int glueIndex = 8;\r
- if (dateTimePatterns.length >= 13)\r
- {\r
- glueIndex += (SHORT + 1);\r
- }\r
- cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],\r
- new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});\r
- } catch (MissingResourceException e) {\r
- cachedDefaultPattern = FALLBACKPATTERN;\r
- }\r
- }\r
- return cachedDefaultPattern;\r
- }\r
-\r
- /* Define one-century window into which to disambiguate dates using\r
- * two-digit years.\r
- */\r
- private void parseAmbiguousDatesAsAfter(Date startDate) {\r
- defaultCenturyStart = startDate;\r
- calendar.setTime(startDate);\r
- defaultCenturyStartYear = calendar.get(Calendar.YEAR);\r
- }\r
-\r
- /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.\r
- * The default start time is 80 years before the creation time of this object.\r
- */\r
- private void initializeDefaultCenturyStart(long baseTime) {\r
- defaultCenturyBase = baseTime;\r
- // clone to avoid messing up date stored in calendar object\r
- // when this method is called while parsing\r
- Calendar tmpCal = (Calendar)calendar.clone();\r
- tmpCal.setTimeInMillis(baseTime);\r
- tmpCal.add(Calendar.YEAR, -80);\r
- defaultCenturyStart = tmpCal.getTime();\r
- defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);\r
- }\r
-\r
- /* Gets the default century start date for this object */\r
- private Date getDefaultCenturyStart() {\r
- if (defaultCenturyStart == null) {\r
- // not yet initialized\r
- initializeDefaultCenturyStart(defaultCenturyBase);\r
- }\r
- return defaultCenturyStart;\r
- }\r
-\r
- /* Gets the default century start year for this object */\r
- private int getDefaultCenturyStartYear() {\r
- if (defaultCenturyStart == null) {\r
- // not yet initialized\r
- initializeDefaultCenturyStart(defaultCenturyBase);\r
- }\r
- return defaultCenturyStartYear;\r
- }\r
-\r
- /**\r
- * Sets the 100-year period 2-digit years will be interpreted as being in\r
- * to begin on the date the user specifies.\r
- * @param startDate During parsing, two digit years will be placed in the range\r
- * <code>startDate</code> to <code>startDate + 100 years</code>.\r
- * @stable ICU 2.0\r
- */\r
- public void set2DigitYearStart(Date startDate) {\r
- parseAmbiguousDatesAsAfter(startDate);\r
- }\r
-\r
- /**\r
- * Returns the beginning date of the 100-year period 2-digit years are interpreted\r
- * as being within.\r
- * @return the start of the 100-year period into which two digit years are\r
- * parsed\r
- * @stable ICU 2.0\r
- */\r
- public Date get2DigitYearStart() {\r
- return getDefaultCenturyStart();\r
- }\r
-\r
- /**\r
- * Overrides DateFormat.\r
- * <p>Formats a date or time, which is the standard millis\r
- * since January 1, 1970, 00:00:00 GMT.\r
- * <p>Example: using the US locale:\r
- * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT\r
- * @param cal the calendar whose date-time value is to be formatted into a date-time string\r
- * @param toAppendTo where the new date-time text is to be appended\r
- * @param pos the formatting position. On input: an alignment field,\r
- * if desired. On output: the offsets of the alignment field.\r
- * @return the formatted date-time string.\r
- * @see DateFormat\r
- * @stable ICU 2.0\r
- */\r
- public StringBuffer format(Calendar cal, StringBuffer toAppendTo,\r
- FieldPosition pos) {\r
- TimeZone backupTZ = null;\r
- if (cal != calendar && !cal.getType().equals(calendar.getType())) {\r
- // Different calendar type\r
- // We use the time and time zone from the input calendar, but\r
- // do not use the input calendar for field calculation.\r
- calendar.setTimeInMillis(cal.getTimeInMillis());\r
- backupTZ = calendar.getTimeZone();\r
- calendar.setTimeZone(cal.getTimeZone());\r
- cal = calendar;\r
- }\r
- StringBuffer result = format(cal, toAppendTo, pos, null);\r
- if (backupTZ != null) {\r
- // Restore the original time zone\r
- calendar.setTimeZone(backupTZ);\r
- }\r
- return result;\r
- }\r
-\r
- // The actual method to format date. If List attributes is not null,\r
- // then attribute information will be recorded.\r
- private StringBuffer format(Calendar cal, StringBuffer toAppendTo,\r
- FieldPosition pos, List attributes) {\r
- // Initialize\r
- pos.setBeginIndex(0);\r
- pos.setEndIndex(0);\r
-\r
- // Careful: For best performance, minimize the number of calls\r
- // to StringBuffer.append() by consolidating appends when\r
- // possible.\r
-\r
- Object[] items = getPatternItems();\r
- for (int i = 0; i < items.length; i++) {\r
- if (items[i] instanceof String) {\r
- toAppendTo.append((String)items[i]);\r
- } else {\r
- PatternItem item = (PatternItem)items[i];\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
- int start = 0;\r
- if (attributes != null) {\r
- // Save the current length\r
- start = toAppendTo.length();\r
- }\r
-//#endif\r
- if (useFastFormat) {\r
- subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);\r
- } else {\r
- toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));\r
- }\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
- if (attributes != null) {\r
- // Check the sub format length\r
- int end = toAppendTo.length();\r
- if (end - start > 0) {\r
- // Append the attribute to the list\r
- DateFormat.Field attr = patternCharToDateFormatField(item.type);\r
- FieldPosition fp = new FieldPosition(attr);\r
- fp.setBeginIndex(start);\r
- fp.setEndIndex(end);\r
- attributes.add(fp);\r
- }\r
- }\r
-//#endif\r
- }\r
- }\r
- return toAppendTo;\r
- \r
- }\r
-\r
- // Map pattern character to index\r
- private static final int PATTERN_CHAR_BASE = 0x40;\r
- private static final int[] PATTERN_CHAR_TO_INDEX =\r
- {\r
- // A B C D E F G H I J K L M N O\r
- -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,\r
- // P Q R S T U V W X Y Z\r
- -1, 27, -1, 8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,\r
- // a b c d e f g h i j k l m n o\r
- -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,\r
- // p q r s t u v w x y z\r
- -1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1\r
- };\r
- \r
- // Map pattern character index to Calendar field number\r
- private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =\r
- {\r
- /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,\r
- /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,\r
- /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,\r
- /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,\r
- /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,\r
- /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,\r
- /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,\r
- /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,\r
- /*v*/ Calendar.ZONE_OFFSET,\r
- /*c*/ Calendar.DOW_LOCAL,\r
- /*L*/ Calendar.MONTH,\r
- /*Qq*/ Calendar.MONTH, Calendar.MONTH,\r
- /*V*/ Calendar.ZONE_OFFSET,\r
- };\r
-\r
- // Map pattern character index to DateFormat field number\r
- private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {\r
- /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,\r
- /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,\r
- /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,\r
- /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,\r
- /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,\r
- /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,\r
- /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,\r
- /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,\r
- /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD, \r
- /*c*/ DateFormat.STANDALONE_DAY_FIELD,\r
- /*L*/ DateFormat.STANDALONE_MONTH_FIELD,\r
- /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,\r
- /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD, \r
- };\r
-\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
- // Map pattern character index to DateFormat.Field\r
- private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {\r
- /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,\r
- /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,\r
- /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,\r
- /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,\r
- /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,\r
- /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,\r
- /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,\r
- /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,\r
- /*v*/ DateFormat.Field.TIME_ZONE,\r
- /*c*/ DateFormat.Field.DAY_OF_WEEK,\r
- /*L*/ DateFormat.Field.MONTH,\r
- /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,\r
- /*V*/ DateFormat.Field.TIME_ZONE,\r
- };\r
-\r
- /**\r
- * Return a DateFormat.Field constant associated with the specified format pattern\r
- * character.\r
- * \r
- * @param ch The pattern character\r
- * @return DateFormat.Field associated with the pattern character\r
- * \r
- * @stable ICU 3.8\r
- */\r
- protected DateFormat.Field patternCharToDateFormatField(char ch) {\r
- int patternCharIndex = -1;\r
- if ('A' <= ch && ch <= 'z') {\r
- patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
- }\r
- if (patternCharIndex != -1) {\r
- return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];\r
- }\r
- return null;\r
- }\r
-//#endif\r
-\r
- /**\r
- * Format a single field, given its pattern character. Subclasses may\r
- * override this method in order to modify or add formatting\r
- * capabilities.\r
- * @param ch the pattern character\r
- * @param count the number of times ch is repeated in the pattern\r
- * @param beginOffset the offset of the output string at the start of\r
- * this field; used to set pos when appropriate\r
- * @param pos receives the position of a field, when appropriate\r
- * @param fmtData the symbols for this formatter\r
- * @stable ICU 2.0\r
- */\r
- protected String subFormat(char ch, int count, int beginOffset,\r
- FieldPosition pos, DateFormatSymbols fmtData,\r
- Calendar cal)\r
- throws IllegalArgumentException\r
- {\r
- // Note: formatData is ignored\r
- StringBuffer buf = new StringBuffer();\r
- subFormat(buf, ch, count, beginOffset, pos, cal);\r
- return buf.toString();\r
- }\r
-\r
- /**\r
- * Format a single field; useFastFormat variant. Reuses a\r
- * StringBuffer for results instead of creating a String on the\r
- * heap for each call.\r
- *\r
- * NOTE We don't really need the beginOffset parameter, EXCEPT for\r
- * the need to support the slow subFormat variant (above) which\r
- * has to pass it in to us.\r
- *\r
- * TODO make this API public\r
- *\r
- * @internal\r
- * @deprecated This API is ICU internal only.\r
- */\r
- protected void subFormat(StringBuffer buf,\r
- char ch, int count, int beginOffset,\r
- FieldPosition pos,\r
- Calendar cal) {\r
- final int maxIntCount = Integer.MAX_VALUE;\r
- final int bufstart = buf.length();\r
-\r
- // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);\r
- int patternCharIndex = -1;\r
- if ('A' <= ch && ch <= 'z') {\r
- patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
- }\r
-\r
- if (patternCharIndex == -1) {\r
- throw new IllegalArgumentException("Illegal pattern character " +\r
- "'" + ch + "' in \"" +\r
- new String(pattern) + '"');\r
- }\r
-\r
- final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
- int value = cal.get(field);\r
-\r
- String zoneString = null;\r
-\r
- NumberFormat currentNumberFormat = getNumberFormat(ch);\r
-\r
- switch (patternCharIndex) {\r
- case 0: // 'G' - ERA\r
- if (count == 5) {\r
- safeAppend(formatData.narrowEras, value, buf);\r
- } else if (count == 4) {\r
- safeAppend(formatData.eraNames, value, buf);\r
- } else {\r
- safeAppend(formatData.eras, value, buf);\r
- }\r
- break;\r
- case 1: // 'y' - YEAR\r
- /* According to the specification, if the number of pattern letters ('y') is 2,\r
- * the year is truncated to 2 digits; otherwise it is interpreted as a number.\r
- * But the original code process 'y', 'yy', 'yyy' in the same way. and process\r
- * patterns with 4 or more than 4 'y' characters in the same way.\r
- * So I change the codes to meet the specification. [Richard/GCl]\r
- */\r
- if (count == 2)\r
- zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96\r
- else //count = 1 or count > 2\r
- zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
- break;\r
- case 2: // 'M' - MONTH\r
- if (count == 5) {\r
- safeAppend(formatData.narrowMonths, value, buf);\r
- } else if (count == 4) {\r
- safeAppend(formatData.months, value, buf);\r
- } else if (count == 3) {\r
- safeAppend(formatData.shortMonths, value, buf);\r
- } else {\r
- zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);\r
- }\r
- break;\r
- case 4: // 'k' - HOUR_OF_DAY (1..24)\r
- if (value == 0)\r
- zeroPaddingNumber(currentNumberFormat,buf,\r
- cal.getMaximum(Calendar.HOUR_OF_DAY)+1,\r
- count, maxIntCount);\r
- else\r
- zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
- break;\r
- case 8: // 'S' - FRACTIONAL_SECOND\r
- // Fractional seconds left-justify\r
- {\r
- numberFormat.setMinimumIntegerDigits(Math.min(3, count));\r
- numberFormat.setMaximumIntegerDigits(maxIntCount);\r
- if (count == 1) {\r
- value = (value + 50) / 100;\r
- } else if (count == 2) {\r
- value = (value + 5) / 10;\r
- }\r
- FieldPosition p = new FieldPosition(-1);\r
- numberFormat.format((long) value, buf, p);\r
- if (count > 3) {\r
- numberFormat.setMinimumIntegerDigits(count - 3);\r
- numberFormat.format(0L, buf, p);\r
- }\r
- }\r
- break;\r
- case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)\r
- if (count < 3) {\r
- zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
- break;\r
- }\r
- // For alpha day-of-week, we don't want DOW_LOCAL,\r
- // we need the standard DAY_OF_WEEK.\r
- value = cal.get(Calendar.DAY_OF_WEEK);\r
- // fall through, do not break here\r
- case 9: // 'E' - DAY_OF_WEEK\r
- if (count == 5) {\r
- safeAppend(formatData.narrowWeekdays, value, buf);\r
- } else if (count == 4) {\r
- safeAppend(formatData.weekdays, value, buf);\r
- } else {// count <= 3, use abbreviated form if exists\r
- safeAppend(formatData.shortWeekdays, value, buf);\r
- }\r
- break;\r
- case 14: // 'a' - AM_PM\r
- safeAppend(formatData.ampms, value, buf);\r
- break;\r
- case 15: // 'h' - HOUR (1..12)\r
- if (value == 0)\r
- zeroPaddingNumber(currentNumberFormat,buf,\r
- cal.getLeastMaximum(Calendar.HOUR)+1,\r
- count, maxIntCount);\r
- else\r
- zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
- break;\r
- case 17: // 'z' - ZONE_OFFSET\r
- if (count < 4) {\r
- // "z", "zz", "zzz"\r
- zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);\r
- } else {\r
- zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);\r
- }\r
- if (zoneString != null && zoneString.length() != 0) {\r
- buf.append(zoneString);\r
- } else {\r
- // Use localized GMT format as fallback\r
- appendGMT(currentNumberFormat,buf, cal);\r
- }\r
- break;\r
- case 23: // 'Z' - TIMEZONE_RFC\r
- if (count < 4) {\r
- // RFC822 format, must use ASCII digits\r
- int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));\r
- char sign = '+';\r
- if (val < 0) {\r
- val = -val;\r
- sign = '-';\r
- }\r
- buf.append(sign);\r
-\r
- int offsetH = val / millisPerHour;\r
- val = val % millisPerHour;\r
- int offsetM = val / millisPerMinute;\r
- val = val % millisPerMinute;\r
- int offsetS = val / millisPerSecond;\r
-\r
- int num = 0, denom = 0;\r
- if (offsetS == 0) {\r
- val = offsetH*100 + offsetM; // HHmm\r
- num = val % 10000;\r
- denom = 1000;\r
- } else {\r
- val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss\r
- num = val % 1000000;\r
- denom = 100000;\r
- }\r
- while (denom >= 1) {\r
- char digit = (char)((num / denom) + '0');\r
- buf.append(digit);\r
- num = num % denom;\r
- denom /= 10;\r
- }\r
- } else {\r
- // long form, localized GMT pattern\r
- appendGMT(currentNumberFormat,buf, cal);\r
- }\r
- break;\r
- case 24: // 'v' - TIMEZONE_GENERIC\r
- if (count == 1) {\r
- // "v"\r
- zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);\r
- } else if (count == 4) {\r
- // "vvvv"\r
- zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);\r
- }\r
- if (zoneString != null && zoneString.length() != 0) {\r
- buf.append(zoneString);\r
- } else {\r
- // Use localized GMT format as fallback\r
- appendGMT(currentNumberFormat,buf, cal);\r
- }\r
- break;\r
- case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)\r
- if (count < 3) {\r
- zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);\r
- break;\r
- }\r
- // For alpha day-of-week, we don't want DOW_LOCAL,\r
- // we need the standard DAY_OF_WEEK.\r
- value = cal.get(Calendar.DAY_OF_WEEK);\r
- if (count == 5) {\r
- safeAppend(formatData.standaloneNarrowWeekdays, value, buf);\r
- } else if (count == 4) {\r
- safeAppend(formatData.standaloneWeekdays, value, buf);\r
- } else { // count == 3\r
- safeAppend(formatData.standaloneShortWeekdays, value, buf);\r
- }\r
- break;\r
- case 26: // 'L' - STANDALONE MONTH\r
- if (count == 5) {\r
- safeAppend(formatData.standaloneNarrowMonths, value, buf);\r
- } else if (count == 4) {\r
- safeAppend(formatData.standaloneMonths, value, buf);\r
- } else if (count == 3) {\r
- safeAppend(formatData.standaloneShortMonths, value, buf);\r
- } else {\r
- zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);\r
- }\r
- break;\r
- case 27: // 'Q' - QUARTER\r
- if (count >= 4) {\r
- safeAppend(formatData.quarters, value/3, buf);\r
- } else if (count == 3) {\r
- safeAppend(formatData.shortQuarters, value/3, buf);\r
- } else {\r
- zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);\r
- }\r
- break;\r
- case 28: // 'q' - STANDALONE QUARTER\r
- if (count >= 4) {\r
- safeAppend(formatData.standaloneQuarters, value/3, buf);\r
- } else if (count == 3) {\r
- safeAppend(formatData.standaloneShortQuarters, value/3, buf);\r
- } else {\r
- zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);\r
- }\r
- break;\r
- case 29: // 'V' - TIMEZONE_SPECIAL\r
- if (count == 1) {\r
- // "V"\r
- zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);\r
- } else if (count == 4) {\r
- // "VVVV"\r
- zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);\r
- }\r
- if (zoneString != null && zoneString.length() != 0) {\r
- buf.append(zoneString);\r
- } else {\r
- // Use localized GMT format as fallback\r
- appendGMT(currentNumberFormat,buf, cal);\r
- }\r
- break;\r
- default:\r
- // case 3: // 'd' - DATE\r
- // case 5: // 'H' - HOUR_OF_DAY (0..23)\r
- // case 6: // 'm' - MINUTE\r
- // case 7: // 's' - SECOND\r
- // case 10: // 'D' - DAY_OF_YEAR\r
- // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH\r
- // case 12: // 'w' - WEEK_OF_YEAR\r
- // case 13: // 'W' - WEEK_OF_MONTH\r
- // case 16: // 'K' - HOUR (0..11)\r
- // case 18: // 'Y' - YEAR_WOY\r
- // case 20: // 'u' - EXTENDED_YEAR\r
- // case 21: // 'g' - JULIAN_DAY\r
- // case 22: // 'A' - MILLISECONDS_IN_DAY\r
-\r
- zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
- break;\r
- } // switch (patternCharIndex)\r
-\r
- // Set the FieldPosition (for the first occurrence only)\r
- if (pos.getBeginIndex() == pos.getEndIndex()) {\r
- if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {\r
- pos.setBeginIndex(beginOffset);\r
- pos.setEndIndex(beginOffset + buf.length() - bufstart);\r
- }\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
- else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {\r
- pos.setBeginIndex(beginOffset);\r
- pos.setEndIndex(beginOffset + buf.length() - bufstart);\r
- }\r
-//#endif\r
- }\r
- }\r
-\r
- private static void safeAppend(String[] array, int value, StringBuffer appendTo) {\r
- if (array != null && value >= 0 && value < array.length) {\r
- appendTo.append(array[value]);\r
- }\r
- }\r
-\r
- /*\r
- * PatternItem store parsed date/time field pattern information.\r
- */\r
- private static class PatternItem {\r
- final char type;\r
- final int length;\r
- final boolean isNumeric;\r
-\r
- PatternItem(char type, int length) {\r
- this.type = type;\r
- this.length = length;\r
- isNumeric = isNumeric(type, length);\r
- }\r
- }\r
-\r
- private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();\r
- private transient Object[] patternItems;\r
-\r
- /*\r
- * Returns parsed pattern items. Each item is either String or\r
- * PatternItem.\r
- */\r
- private Object[] getPatternItems() {\r
- if (patternItems != null) {\r
- return patternItems;\r
- }\r
-\r
- patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);\r
- if (patternItems != null) {\r
- return patternItems;\r
- }\r
-\r
- boolean isPrevQuote = false;\r
- boolean inQuote = false;\r
- StringBuffer text = new StringBuffer();\r
- char itemType = 0; // 0 for string literal, otherwise date/time pattern character\r
- int itemLength = 1;\r
-\r
- List items = new ArrayList();\r
-\r
- for (int i = 0; i < pattern.length(); i++) {\r
- char ch = pattern.charAt(i);\r
- if (ch == '\'') {\r
- if (isPrevQuote) {\r
- text.append('\'');\r
- isPrevQuote = false;\r
- } else {\r
- isPrevQuote = true;\r
- if (itemType != 0) {\r
- items.add(new PatternItem(itemType, itemLength));\r
- itemType = 0;\r
- }\r
- }\r
- inQuote = !inQuote;\r
- } else {\r
- isPrevQuote = false;\r
- if (inQuote) {\r
- text.append(ch);\r
- } else {\r
- if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {\r
- // a date/time pattern character\r
- if (ch == itemType) {\r
- itemLength++;\r
- } else {\r
- if (itemType == 0) {\r
- if (text.length() > 0) {\r
- items.add(text.toString());\r
- text.setLength(0);\r
- }\r
- } else {\r
- items.add(new PatternItem(itemType, itemLength));\r
- }\r
- itemType = ch;\r
- itemLength = 1;\r
- }\r
- } else {\r
- // a string literal\r
- if (itemType != 0) {\r
- items.add(new PatternItem(itemType, itemLength));\r
- itemType = 0;\r
- }\r
- text.append(ch);\r
- }\r
- }\r
- }\r
- }\r
- // handle last item\r
- if (itemType == 0) {\r
- if (text.length() > 0) {\r
- items.add(text.toString());\r
- text.setLength(0);\r
- }\r
- } else {\r
- items.add(new PatternItem(itemType, itemLength));\r
- }\r
-\r
- patternItems = new Object[items.size()];\r
- items.toArray(patternItems);\r
-\r
- PARSED_PATTERN_CACHE.put(pattern, patternItems);\r
- \r
- return patternItems;\r
- }\r
-\r
- /*\r
- * Time zone localized GMT format stuffs\r
- */\r
- private static final String STR_GMT = "GMT";\r
- private static final String STR_UT = "UT";\r
- private static final String STR_UTC = "UTC";\r
- private static final int STR_GMT_LEN = 3;\r
- private static final int STR_UT_LEN = 2;\r
- private static final int STR_UTC_LEN = 3;\r
- private static final char PLUS = '+';\r
- private static final char MINUS = '-';\r
- private static final char COLON = ':';\r
-\r
- private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {\r
- int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);\r
-\r
- if (isDefaultGMTFormat()) {\r
- formatGMTDefault(currentNumberFormat,buf, offset);\r
- } else {\r
- int sign = DateFormatSymbols.OFFSET_POSITIVE;\r
- if (offset < 0) {\r
- offset = -offset;\r
- sign = DateFormatSymbols.OFFSET_NEGATIVE;\r
- }\r
- int width = offset%(60*1000) == 0 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;\r
-\r
- MessageFormat fmt = getGMTFormatter(sign, width);\r
- fmt.format(new Object[] {new Long(offset)}, buf, null);\r
- }\r
- }\r
-\r
- private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {\r
- buf.append(STR_GMT);\r
- if (offset >= 0) {\r
- buf.append(PLUS);\r
- } else {\r
- buf.append(MINUS);\r
- offset = -offset;\r
- }\r
- offset /= 1000; // now in seconds\r
- int sec = offset % 60;\r
- offset /= 60;\r
- int min = offset % 60;\r
- int hour = offset / 60;\r
-\r
- zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);\r
- buf.append(COLON);\r
- zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);\r
- if (sec != 0) {\r
- buf.append(COLON);\r
- zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);\r
- }\r
- }\r
-\r
- private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {\r
- if (!isDefaultGMTFormat()) {\r
- int start = pos.getIndex();\r
- String gmtPattern = formatData.gmtFormat;\r
-\r
- // Quick check\r
- boolean prefixMatch = false;\r
- int prefixLen = gmtPattern.indexOf('{');\r
- if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {\r
- prefixMatch = true;\r
- }\r
-\r
- if (prefixMatch) {\r
- // Prefix matched\r
- MessageFormat fmt;\r
- Object[] parsedObjects;\r
- int offset;\r
-\r
- // Try negative Hms\r
- fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);\r
- parsedObjects = fmt.parse(text, pos);\r
- if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)\r
- && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_NEGATIVE)) {\r
- offset = (int)((Date)parsedObjects[0]).getTime();\r
- return new Integer(-offset /* negative */);\r
- }\r
-\r
- // Reset ParsePosition\r
- pos.setIndex(start);\r
- pos.setErrorIndex(-1);\r
-\r
- // Try positive Hms\r
- fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);\r
- parsedObjects = fmt.parse(text, pos);\r
- if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)\r
- && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_POSITIVE)) {\r
- offset = (int)((Date)parsedObjects[0]).getTime();\r
- return new Integer(offset);\r
- }\r
-\r
- // Reset ParsePosition\r
- pos.setIndex(start);\r
- pos.setErrorIndex(-1);\r
-\r
- // Try negative Hm\r
- fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HM);\r
- parsedObjects = fmt.parse(text, pos);\r
- if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {\r
- offset = (int)((Date)parsedObjects[0]).getTime();\r
- return new Integer(-offset /* negative */);\r
- }\r
-\r
- // Reset ParsePosition\r
- pos.setIndex(start);\r
- pos.setErrorIndex(-1);\r
-\r
- // Try positive Hm\r
- fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HM);\r
- parsedObjects = fmt.parse(text, pos);\r
- if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {\r
- offset = (int)((Date)parsedObjects[0]).getTime();\r
- return new Integer(offset);\r
- }\r
-\r
- // Reset ParsePosition\r
- pos.setIndex(start);\r
- pos.setErrorIndex(-1);\r
- }\r
- }\r
-\r
- return parseGMTDefault(text, pos, currentNumberFormat);\r
- }\r
-\r
- private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {\r
- int start = pos.getIndex();\r
-\r
- if (start + STR_UT_LEN + 1 >= text.length()) {\r
- pos.setErrorIndex(start);\r
- return null;\r
- }\r
-\r
- int cur = start;\r
- // "GMT"\r
- if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {\r
- cur += STR_GMT_LEN;\r
- } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {\r
- cur += STR_UT_LEN;\r
- } else {\r
- pos.setErrorIndex(start);\r
- return null;\r
- }\r
- // Sign\r
- boolean negative = false;\r
- if (text.charAt(cur) == MINUS) {\r
- negative = true;\r
- } else if (text.charAt(cur) != PLUS) {\r
- pos.setErrorIndex(cur);\r
- return null;\r
- }\r
- cur++;\r
-\r
- // Numbers\r
- int numLen;\r
- pos.setIndex(cur);\r
-\r
- Number n = parseInt(text, 6, pos, false,currentNumberFormat);\r
- numLen = pos.getIndex() - cur;\r
-\r
- if (n == null || numLen <= 0 || numLen > 6) {\r
- pos.setIndex(start);\r
- pos.setErrorIndex(cur);\r
- return null;\r
- }\r
-\r
- int numVal = n.intValue();\r
-\r
- int hour = 0;\r
- int min = 0;\r
- int sec = 0;\r
-\r
- if (numLen <= 2) {\r
- // H[H][:mm[:ss]]\r
- hour = numVal;\r
- cur += numLen;\r
- if (cur + 2 < text.length() && text.charAt(cur) == COLON) {\r
- cur++;\r
- pos.setIndex(cur);\r
- n = parseInt(text, 2, pos, false,currentNumberFormat);\r
- numLen = pos.getIndex() - cur;\r
- if (n != null && numLen == 2) {\r
- // got minute field\r
- min = n.intValue();\r
- cur += numLen;\r
- if (cur + 2 < text.length() && text.charAt(cur) == COLON) {\r
- cur++;\r
- pos.setIndex(cur);\r
- n = parseInt(text, 2, pos, false,currentNumberFormat);\r
- numLen = pos.getIndex() - cur;\r
- if (n != null && numLen == 2) {\r
- // got second field\r
- sec = n.intValue();\r
- } else {\r
- // reset position\r
- pos.setIndex(cur - 1);\r
- pos.setErrorIndex(-1);\r
- }\r
- }\r
- } else {\r
- // reset postion\r
- pos.setIndex(cur - 1);\r
- pos.setErrorIndex(-1);\r
- }\r
- }\r
- } else if (numLen == 3 || numLen == 4) {\r
- // Hmm or HHmm\r
- hour = numVal / 100;\r
- min = numVal % 100;\r
- } else { // numLen == 5 || numLen == 6\r
- // Hmmss or HHmmss\r
- hour = numVal / 10000;\r
- min = (numVal % 10000) / 100;\r
- sec = numVal % 100;\r
- }\r
-\r
- int offset = ((hour*60 + min)*60 + sec)*1000;\r
- if (negative) {\r
- offset = -offset;\r
- }\r
- return new Integer(offset);\r
- }\r
-\r
- transient private WeakReference[] gmtfmtCache;\r
-\r
- private MessageFormat getGMTFormatter(int sign, int width) {\r
- MessageFormat fmt = null;\r
- if (gmtfmtCache == null) {\r
- gmtfmtCache = new WeakReference[4];\r
- }\r
- int cacheIdx = sign*2 + width;\r
- if (gmtfmtCache[cacheIdx] != null) {\r
- fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();\r
- }\r
- if (fmt == null) {\r
- fmt = new MessageFormat(formatData.gmtFormat);\r
- GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));\r
- SimpleDateFormat sdf = (SimpleDateFormat)this.clone();\r
- sdf.setCalendar(gcal);\r
- sdf.applyPattern(formatData.getGmtHourFormat(sign, width));\r
- fmt.setFormat(0, sdf);\r
- gmtfmtCache[cacheIdx] = new WeakReference(fmt);\r
- }\r
- return fmt;\r
- }\r
-\r
- transient private int[] gmtFormatHmsMinLen = null;\r
-\r
- private int getGMTFormatMinHMSLen(int sign) {\r
- if (gmtFormatHmsMinLen == null) {\r
- gmtFormatHmsMinLen = new int[2];\r
- Long offset = new Long(60*60*1000); // 1 hour\r
-\r
- StringBuffer buf = new StringBuffer();\r
- MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);\r
- fmtNeg.format(new Object[] {offset}, buf, null);\r
- gmtFormatHmsMinLen[0] = buf.length();\r
-\r
- buf.setLength(0);\r
- MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);\r
- fmtPos.format(new Object[] {offset}, buf, null);\r
- gmtFormatHmsMinLen[1] = buf.length();\r
- }\r
- return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];\r
- }\r
-\r
- private boolean isDefaultGMTFormat() {\r
- // GMT pattern\r
- if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {\r
- return false;\r
- }\r
- // GMT offset hour patters\r
- boolean res = true;\r
- for (int sign = 0; sign < 2 && res; sign++) {\r
- for (int width = 0; width < 2; width++) {\r
- if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width].equals(formatData.getGmtHourFormat(sign, width))) {\r
- res = false;\r
- break;\r
- }\r
- }\r
- }\r
- return res;\r
- }\r
-\r
- /*\r
- * Internal method. Returns null if the value of an array is empty, or if the\r
- * index is out of bounds\r
- */\r
-/* private String getZoneArrayValue(String[] zs, int ix) {\r
- if (ix >= 0 && ix < zs.length) {\r
- String result = zs[ix];\r
- if (result != null && result.length() != 0) {\r
- return result;\r
- }\r
- }\r
- return null;\r
- }*/\r
-\r
- /**\r
- * Internal high-speed method. Reuses a StringBuffer for results\r
- * instead of creating a String on the heap for each call.\r
- * @internal\r
- * @deprecated This API is ICU internal only.\r
- */\r
- protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,\r
- int minDigits, int maxDigits) {\r
- if (useLocalZeroPaddingNumberFormat) {\r
- fastZeroPaddingNumber(buf, value, minDigits, maxDigits);\r
- } else {\r
- nf.setMinimumIntegerDigits(minDigits);\r
- nf.setMaximumIntegerDigits(maxDigits);\r
- nf.format(value, buf, new FieldPosition(-1));\r
- }\r
- }\r
-\r
- /**\r
- * Overrides superclass method\r
- * @stable ICU 2.0\r
- */\r
- public void setNumberFormat(NumberFormat newNumberFormat) {\r
- // Override this method to update local zero padding number formatter\r
- super.setNumberFormat(newNumberFormat);\r
- initLocalZeroPaddingNumberFormat();\r
- }\r
-\r
- private void initLocalZeroPaddingNumberFormat() {\r
- if (numberFormat instanceof DecimalFormat) {\r
- zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();\r
- useLocalZeroPaddingNumberFormat = true;\r
- } else if (numberFormat instanceof DateNumberFormat) {\r
- zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();\r
- useLocalZeroPaddingNumberFormat = true;\r
- } else {\r
- useLocalZeroPaddingNumberFormat = false;\r
- }\r
-\r
- if (useLocalZeroPaddingNumberFormat) {\r
- decimalBuf = new char[10]; // sufficient for int numbers\r
- }\r
- }\r
-\r
- // If true, use local version of zero padding number format\r
- private transient boolean useLocalZeroPaddingNumberFormat;\r
- private transient char zeroDigit;\r
- private transient char[] decimalBuf;\r
-\r
- /*\r
- * Lightweight zero padding integer number format function.\r
- * \r
- * Note: This implementation is almost equivalent to format method in DateNumberFormat.\r
- * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,\r
- * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative\r
- * date format test case, having local implementation is ~10% faster than using one in\r
- * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference.\r
- * \r
- * -Yoshito\r
- */\r
- private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {\r
- int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;\r
- int index = limit - 1;\r
- while (true) {\r
- decimalBuf[index] = (char)((value % 10) + zeroDigit);\r
- value /= 10;\r
- if (index == 0 || value == 0) {\r
- break;\r
- }\r
- index--;\r
- }\r
- int padding = minDigits - (limit - index);\r
- for (; padding > 0; padding--) {\r
- decimalBuf[--index] = zeroDigit;\r
- }\r
- int length = limit - index;\r
- buf.append(decimalBuf, index, length); \r
- }\r
-\r
- /**\r
- * Formats a number with the specified minimum and maximum number of digits.\r
- * @stable ICU 2.0\r
- */\r
- protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)\r
- {\r
- numberFormat.setMinimumIntegerDigits(minDigits);\r
- numberFormat.setMaximumIntegerDigits(maxDigits);\r
- return numberFormat.format(value);\r
- }\r
-\r
- /**\r
- * Format characters that indicate numeric fields. The character\r
- * at index 0 is treated specially.\r
- */\r
- private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";\r
-\r
- /**\r
- * Return true if the given format character, occuring count\r
- * times, represents a numeric field.\r
- */\r
- private static final boolean isNumeric(char formatChar, int count) {\r
- int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);\r
- return (i > 0 || (i == 0 && count < 3));\r
- }\r
-\r
- /**\r
- * Overrides DateFormat\r
- * @see DateFormat\r
- * @stable ICU 2.0\r
- */\r
- public void parse(String text, Calendar cal, ParsePosition parsePos)\r
- {\r
- TimeZone backupTZ = null;\r
- Calendar resultCal = null;\r
- if (cal != calendar && !cal.getType().equals(calendar.getType())) {\r
- // Different calendar type\r
- // We use the time/zone from the input calendar, but\r
- // do not use the input calendar for field calculation.\r
- calendar.setTimeInMillis(cal.getTimeInMillis());\r
- backupTZ = calendar.getTimeZone();\r
- calendar.setTimeZone(cal.getTimeZone());\r
- resultCal = cal;\r
- cal = calendar;\r
- }\r
-\r
- int pos = parsePos.getIndex();\r
- int start = pos;\r
-\r
- // Reset tztype\r
- tztype = TZTYPE_UNK;\r
- boolean[] ambiguousYear = { false };\r
-\r
- // item index for the first numeric field within a contiguous numeric run\r
- int numericFieldStart = -1;\r
- // item length for the first numeric field within a contiguous numeric run\r
- int numericFieldLength = 0;\r
- // start index of numeric text run in the input text\r
- int numericStartPos = 0;\r
-\r
- Object[] items = getPatternItems();\r
- int i = 0;\r
- while (i < items.length) {\r
- if (items[i] instanceof PatternItem) {\r
- // Handle pattern field\r
- PatternItem field = (PatternItem)items[i];\r
- if (field.isNumeric) {\r
- // Handle fields within a run of abutting numeric fields. Take\r
- // the pattern "HHmmss" as an example. We will try to parse\r
- // 2/2/2 characters of the input text, then if that fails,\r
- // 1/2/2. We only adjust the width of the leftmost field; the\r
- // others remain fixed. This allows "123456" => 12:34:56, but\r
- // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we\r
- // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.\r
- if (numericFieldStart == -1) {\r
- // check if this field is followed by abutting another numeric field\r
- if ((i + 1) < items.length \r
- && (items[i + 1] instanceof PatternItem)\r
- && ((PatternItem)items[i + 1]).isNumeric) {\r
- // record the first numeric field within a numeric text run\r
- numericFieldStart = i;\r
- numericFieldLength = field.length;\r
- numericStartPos = pos; \r
- }\r
- }\r
- }\r
- if (numericFieldStart != -1) {\r
- // Handle a numeric field within abutting numeric fields\r
- int len = field.length;\r
- if (numericFieldStart == i) {\r
- len = numericFieldLength;\r
- }\r
-\r
- // Parse a numeric field\r
- pos = subParse(text, pos, field.type, len,\r
- true, false, ambiguousYear, cal);\r
-\r
- if (pos < 0) {\r
- // If the parse fails anywhere in the numeric run, back up to the\r
- // start of the run and use shorter pattern length for the first\r
- // numeric field.\r
- --numericFieldLength;\r
- if (numericFieldLength == 0) {\r
- // can not make shorter any more\r
- parsePos.setIndex(start);\r
- parsePos.setErrorIndex(pos);\r
- if (backupTZ != null) {\r
- calendar.setTimeZone(backupTZ);\r
- }\r
- return;\r
- }\r
- i = numericFieldStart;\r
- pos = numericStartPos;\r
- continue;\r
- }\r
-\r
- } else {\r
- // Handle a non-numeric field or a non-abutting numeric field\r
- numericFieldStart = -1;\r
-\r
- int s = pos;\r
- pos = subParse(text, pos, field.type, field.length,\r
- false, true, ambiguousYear, cal);\r
- if (pos < 0) {\r
- parsePos.setIndex(start);\r
- parsePos.setErrorIndex(s);\r
- if (backupTZ != null) {\r
- calendar.setTimeZone(backupTZ);\r
- }\r
- return;\r
- }\r
- }\r
- } else {\r
- // Handle literal pattern text literal\r
- numericFieldStart = -1;\r
-\r
- String patl = (String)items[i];\r
- int plen = patl.length();\r
- int tlen = text.length();\r
- int idx = 0;\r
- while (idx < plen && pos < tlen) {\r
- char pch = patl.charAt(idx);\r
- char ich = text.charAt(pos);\r
- if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {\r
- // White space characters found in both patten and input.\r
- // Skip contiguous white spaces.\r
- while ((idx + 1) < plen &&\r
- UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {\r
- ++idx;\r
- }\r
- while ((pos + 1) < tlen &&\r
- UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {\r
- ++pos;\r
- }\r
- } else if (pch != ich) {\r
- break;\r
- }\r
- ++idx;\r
- ++pos;\r
- }\r
- if (idx != plen) {\r
- // Set the position of mismatch\r
- parsePos.setIndex(start);\r
- parsePos.setErrorIndex(pos);\r
- if (backupTZ != null) {\r
- calendar.setTimeZone(backupTZ);\r
- }\r
- return;\r
- }\r
- }\r
- ++i;\r
- }\r
-\r
- // At this point the fields of Calendar have been set. Calendar\r
- // will fill in default values for missing fields when the time\r
- // is computed.\r
-\r
- parsePos.setIndex(pos);\r
-\r
- // This part is a problem: When we call parsedDate.after, we compute the time.\r
- // Take the date April 3 2004 at 2:30 am. When this is first set up, the year\r
- // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.\r
- // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am\r
- // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am\r
- // on that day. It is therefore parsed out to fields as 3:30 am. Then we\r
- // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is\r
- // a Saturday, so it can have a 2:30 am -- and it should. [LIU]\r
- /*\r
- Date parsedDate = cal.getTime();\r
- if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {\r
- cal.add(Calendar.YEAR, 100);\r
- parsedDate = cal.getTime();\r
- }\r
- */\r
- // Because of the above condition, save off the fields in case we need to readjust.\r
- // The procedure we use here is not particularly efficient, but there is no other\r
- // way to do this given the API restrictions present in Calendar. We minimize\r
- // inefficiency by only performing this computation when it might apply, that is,\r
- // when the two-digit year is equal to the start year, and thus might fall at the\r
- // front or the back of the default century. This only works because we adjust\r
- // the year correctly to start with in other cases -- see subParse().\r
- try {\r
- if (ambiguousYear[0] || tztype != TZTYPE_UNK) {\r
- // We need a copy of the fields, and we need to avoid triggering a call to\r
- // complete(), which will recalculate the fields. Since we can't access\r
- // the fields[] array in Calendar, we clone the entire object. This will\r
- // stop working if Calendar.clone() is ever rewritten to call complete().\r
- Calendar copy;\r
- if (ambiguousYear[0]) { // the two-digit year == the default start year\r
- copy = (Calendar)cal.clone();\r
- Date parsedDate = copy.getTime();\r
- if (parsedDate.before(getDefaultCenturyStart())) {\r
- // We can't use add here because that does a complete() first.\r
- cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);\r
- }\r
- }\r
- if (tztype != TZTYPE_UNK) {\r
- copy = (Calendar)cal.clone();\r
- TimeZone tz = copy.getTimeZone();\r
- BasicTimeZone btz = null;\r
- if (tz instanceof BasicTimeZone) {\r
- btz = (BasicTimeZone)tz;\r
- }\r
-\r
- // Get local millis\r
- copy.set(Calendar.ZONE_OFFSET, 0);\r
- copy.set(Calendar.DST_OFFSET, 0);\r
- long localMillis = copy.getTimeInMillis();\r
-\r
- // Make sure parsed time zone type (Standard or Daylight)\r
- // matches the rule used by the parsed time zone.\r
- int[] offsets = new int[2];\r
- if (btz != null) {\r
- if (tztype == TZTYPE_STD) {\r
- btz.getOffsetFromLocal(localMillis,\r
- BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);\r
- } else {\r
- btz.getOffsetFromLocal(localMillis,\r
- BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);\r
- }\r
- } else {\r
- // No good way to resolve ambiguous time at transition,\r
- // but following code work in most case.\r
- tz.getOffset(localMillis, true, offsets);\r
-\r
- if (tztype == TZTYPE_STD && offsets[1] != 0 || tztype == TZTYPE_DST && offsets[1] == 0) {\r
- // Roll back one day and try it again.\r
- // Note: This code assumes 1. timezone transition only happens once within 24 hours at max\r
- // 2. the difference of local offsets at the transition is less than 24 hours.\r
- tz.getOffset(localMillis - (24*60*60*1000), true, offsets);\r
- }\r
- }\r
-\r
- // Now, compare the results with parsed type, either standard or daylight saving time\r
- int resolvedSavings = offsets[1];\r
- if (tztype == TZTYPE_STD) {\r
- if (offsets[1] != 0) {\r
- // Override DST_OFFSET = 0 in the result calendar\r
- resolvedSavings = 0;\r
- }\r
- } else { // tztype == TZTYPE_DST\r
- if (offsets[1] == 0) {\r
- if (btz != null) {\r
- long time = localMillis + offsets[0];\r
- // We use the nearest daylight saving time rule.\r
- TimeZoneTransition beforeTrs, afterTrs;\r
- long beforeT = time, afterT = time;\r
- int beforeSav = 0, afterSav = 0;\r
-\r
- // Search for DST rule before or on the time\r
- while (true) {\r
- beforeTrs = btz.getPreviousTransition(beforeT, true);\r
- if (beforeTrs == null) {\r
- break;\r
- }\r
- beforeT = beforeTrs.getTime() - 1;\r
- beforeSav = beforeTrs.getFrom().getDSTSavings();\r
- if (beforeSav != 0) {\r
- break;\r
- }\r
- }\r
-\r
- // Search for DST rule after the time\r
- while (true) {\r
- afterTrs = btz.getNextTransition(afterT, false);\r
- if (afterTrs == null) {\r
- break;\r
- }\r
- afterT = afterTrs.getTime();\r
- afterSav = afterTrs.getTo().getDSTSavings();\r
- if (afterSav != 0) {\r
- break;\r
- }\r
- }\r
-\r
- if (beforeTrs != null && afterTrs != null) {\r
- if (time - beforeT > afterT - time) {\r
- resolvedSavings = afterSav;\r
- } else {\r
- resolvedSavings = beforeSav;\r
- }\r
- } else if (beforeTrs != null && beforeSav != 0) {\r
- resolvedSavings = beforeSav;\r
- } else if (afterTrs != null && afterSav != 0) {\r
- resolvedSavings = afterSav;\r
- } else {\r
- resolvedSavings = btz.getDSTSavings();\r
- }\r
- } else {\r
- resolvedSavings = tz.getDSTSavings();\r
- }\r
- if (resolvedSavings == 0) {\r
- // Final fallback\r
- resolvedSavings = millisPerHour;\r
- }\r
- }\r
- }\r
- cal.set(Calendar.ZONE_OFFSET, offsets[0]);\r
- cal.set(Calendar.DST_OFFSET, resolvedSavings);\r
- }\r
- }\r
- }\r
- // An IllegalArgumentException will be thrown by Calendar.getTime()\r
- // if any fields are out of range, e.g., MONTH == 17.\r
- catch (IllegalArgumentException e) {\r
- parsePos.setErrorIndex(pos);\r
- parsePos.setIndex(start);\r
- if (backupTZ != null) {\r
- calendar.setTimeZone(backupTZ);\r
- }\r
- return;\r
- }\r
- // Set the parsed result if local calendar is used\r
- // instead of the input calendar\r
- if (resultCal != null) {\r
- resultCal.setTimeZone(cal.getTimeZone());\r
- resultCal.setTimeInMillis(cal.getTimeInMillis());\r
- }\r
- // Restore the original time zone if required\r
- if (backupTZ != null) {\r
- calendar.setTimeZone(backupTZ);\r
- }\r
- }\r
-\r
- /**\r
- * Attempt to match the text at a given position against an array of\r
- * strings. Since multiple strings in the array may match (for\r
- * example, if the array contains "a", "ab", and "abc", all will match\r
- * the input string "abcd") the longest match is returned. As a side\r
- * effect, the given field of <code>cal</code> is set to the index\r
- * of the best match, if there is one.\r
- * @param text the time text being parsed.\r
- * @param start where to start parsing.\r
- * @param field the date field being parsed.\r
- * @param data the string array to parsed.\r
- * @return the new start position if matching succeeded; a negative\r
- * number indicating matching failure, otherwise. As a side effect,\r
- * sets the <code>cal</code> field <code>field</code> to the index\r
- * of the best match, if matching succeeded.\r
- * @stable ICU 2.0\r
- */\r
- protected int matchString(String text, int start, int field, String[] data, Calendar cal)\r
- {\r
- int i = 0;\r
- int count = data.length;\r
-\r
- if (field == Calendar.DAY_OF_WEEK) i = 1;\r
-\r
- // There may be multiple strings in the data[] array which begin with\r
- // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).\r
- // We keep track of the longest match, and return that. Note that this\r
- // unfortunately requires us to test all array elements.\r
- int bestMatchLength = 0, bestMatch = -1;\r
- for (; i<count; ++i)\r
- {\r
- int length = data[i].length();\r
- // Always compare if we have no match yet; otherwise only compare\r
- // against potentially better matches (longer strings).\r
- if (length > bestMatchLength &&\r
- text.regionMatches(true, start, data[i], 0, length))\r
- {\r
- bestMatch = i;\r
- bestMatchLength = length;\r
- }\r
- }\r
- if (bestMatch >= 0)\r
- {\r
- cal.set(field, bestMatch);\r
- return start + bestMatchLength;\r
- }\r
- return -start;\r
- }\r
-\r
- /**\r
- * Attempt to match the text at a given position against an array of quarter\r
- * strings. Since multiple strings in the array may match (for\r
- * example, if the array contains "a", "ab", and "abc", all will match\r
- * the input string "abcd") the longest match is returned. As a side\r
- * effect, the given field of <code>cal</code> is set to the index\r
- * of the best match, if there is one.\r
- * @param text the time text being parsed.\r
- * @param start where to start parsing.\r
- * @param field the date field being parsed.\r
- * @param data the string array to parsed.\r
- * @return the new start position if matching succeeded; a negative\r
- * number indicating matching failure, otherwise. As a side effect,\r
- * sets the <code>cal</code> field <code>field</code> to the index\r
- * of the best match, if matching succeeded.\r
- * @stable ICU 2.0\r
- */\r
- protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)\r
- {\r
- int i = 0;\r
- int count = data.length;\r
-\r
- // There may be multiple strings in the data[] array which begin with\r
- // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).\r
- // We keep track of the longest match, and return that. Note that this\r
- // unfortunately requires us to test all array elements.\r
- int bestMatchLength = 0, bestMatch = -1;\r
- for (; i<count; ++i) {\r
- int length = data[i].length();\r
- // Always compare if we have no match yet; otherwise only compare\r
- // against potentially better matches (longer strings).\r
- if (length > bestMatchLength &&\r
- text.regionMatches(true, start, data[i], 0, length)) {\r
- bestMatch = i;\r
- bestMatchLength = length;\r
- }\r
- }\r
- \r
- if (bestMatch >= 0) {\r
- cal.set(field, bestMatch * 3);\r
- return start + bestMatchLength;\r
- }\r
- \r
- return -start;\r
- }\r
- \r
- /**\r
- * Protected method that converts one field of the input string into a\r
- * numeric field value in <code>cal</code>. Returns -start (for\r
- * ParsePosition) if failed. Subclasses may override this method to\r
- * modify or add parsing capabilities.\r
- * @param text the time text to be parsed.\r
- * @param start where to start parsing.\r
- * @param ch the pattern character for the date field text to be parsed.\r
- * @param count the count of a pattern character.\r
- * @param obeyCount if true, then the next field directly abuts this one,\r
- * and we should use the count to know when to stop parsing.\r
- * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]\r
- * is true, then a two-digit year was parsed and may need to be readjusted.\r
- * @return the new start position if matching succeeded; a negative\r
- * number indicating matching failure, otherwise. As a side effect,\r
- * set the appropriate field of <code>cal</code> with the parsed\r
- * value.\r
- * @stable ICU 2.0\r
- */\r
- protected int subParse(String text, int start, char ch, int count,\r
- boolean obeyCount, boolean allowNegative,\r
- boolean[] ambiguousYear, Calendar cal)\r
- {\r
- Number number = null;\r
- NumberFormat currentNumberFormat = null;\r
- int value = 0;\r
- int i;\r
- ParsePosition pos = new ParsePosition(0);\r
- //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c\r
- int patternCharIndex = -1;\r
- if ('A' <= ch && ch <= 'z') {\r
- patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
- }\r
-\r
- if (patternCharIndex == -1) {\r
- return -start;\r
- }\r
-\r
- currentNumberFormat = getNumberFormat(ch);\r
- \r
- int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
-\r
- // If there are any spaces here, skip over them. If we hit the end\r
- // of the string, then fail.\r
- for (;;) {\r
- if (start >= text.length()) {\r
- return -start;\r
- }\r
- int c = UTF16.charAt(text, start);\r
- if (!UCharacter.isUWhiteSpace(c)) {\r
- break;\r
- }\r
- start += UTF16.getCharCount(c);\r
- }\r
- pos.setIndex(start);\r
-\r
- // We handle a few special cases here where we need to parse\r
- // a number value. We handle further, more generic cases below. We need\r
- // to handle some of them here because some fields require extra processing on\r
- // the parsed value.\r
- if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||\r
- patternCharIndex == 15 /*HOUR1_FIELD*/ ||\r
- (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||\r
- patternCharIndex == 1 ||\r
- patternCharIndex == 8)\r
- {\r
- // It would be good to unify this with the obeyCount logic below,\r
- // but that's going to be difficult.\r
- if (obeyCount)\r
- {\r
- if ((start+count) > text.length()) return -start;\r
- number = parseInt(text, count, pos, allowNegative,currentNumberFormat);\r
- }\r
- else number = parseInt(text, pos, allowNegative,currentNumberFormat);\r
- if (number == null)\r
- return -start;\r
- value = number.intValue();\r
- }\r
-\r
- switch (patternCharIndex)\r
- {\r
- case 0: // 'G' - ERA\r
- if (count == 4) {\r
- return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);\r
- } else {\r
- return matchString(text, start, Calendar.ERA, formatData.eras, cal);\r
- }\r
- case 1: // 'y' - YEAR\r
- // If there are 3 or more YEAR pattern characters, this indicates\r
- // that the year value is to be treated literally, without any\r
- // two-digit year adjustments (e.g., from "01" to 2001). Otherwise\r
- // we made adjustments to place the 2-digit year in the proper\r
- // century, for parsed strings from "00" to "99". Any other string\r
- // is treated literally: "2250", "-1", "1", "002".\r
- /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/\r
- if (count == 2 && (pos.getIndex() - start) == 2\r
- && UCharacter.isDigit(text.charAt(start))\r
- && UCharacter.isDigit(text.charAt(start+1)))\r
- {\r
- // Assume for example that the defaultCenturyStart is 6/18/1903.\r
- // This means that two-digit years will be forced into the range\r
- // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02\r
- // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond\r
- // to 1904, 1905, etc. If the year is 03, then it is 2003 if the\r
- // other fields specify a date before 6/18, or 1903 if they specify a\r
- // date afterwards. As a result, 03 is an ambiguous year. All other\r
- // two-digit years are unambiguous.\r
- int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;\r
- ambiguousYear[0] = value == ambiguousTwoDigitYear;\r
- value += (getDefaultCenturyStartYear()/100)*100 +\r
- (value < ambiguousTwoDigitYear ? 100 : 0);\r
- }\r
- cal.set(Calendar.YEAR, value);\r
- return pos.getIndex();\r
- case 2: // 'M' - MONTH\r
- if (count <= 2) // i.e., M or MM.\r
- {\r
- // Don't want to parse the month if it is a string\r
- // while pattern uses numeric style: M or MM.\r
- // [We computed 'value' above.]\r
- cal.set(Calendar.MONTH, value - 1);\r
- return pos.getIndex();\r
- }\r
- else\r
- {\r
- // count >= 3 // i.e., MMM or MMMM\r
- // Want to be able to parse both short and long forms.\r
- // Try count == 4 first:\r
- int newStart = matchString(text, start, Calendar.MONTH,\r
- formatData.months, cal);\r
- if (newStart > 0) {\r
- return newStart;\r
- } else { // count == 4 failed, now try count == 3\r
- return matchString(text, start, Calendar.MONTH,\r
- formatData.shortMonths, cal);\r
- }\r
- }\r
- case 26: // 'L' - STAND_ALONE_MONTH\r
- if (count <= 2) // i.e., M or MM.\r
- {\r
- // Don't want to parse the month if it is a string\r
- // while pattern uses numeric style: M or MM.\r
- // [We computed 'value' above.]\r
- cal.set(Calendar.MONTH, value - 1);\r
- return pos.getIndex();\r
- }\r
- else\r
- {\r
- // count >= 3 // i.e., MMM or MMMM\r
- // Want to be able to parse both short and long forms.\r
- // Try count == 4 first:\r
- int newStart = matchString(text, start, Calendar.MONTH,\r
- formatData.standaloneMonths, cal);\r
- if (newStart > 0) {\r
- return newStart;\r
- } else { // count == 4 failed, now try count == 3\r
- return matchString(text, start, Calendar.MONTH,\r
- formatData.standaloneShortMonths, cal);\r
- }\r
- }\r
- case 4: // 'k' - HOUR_OF_DAY (1..24)\r
- // [We computed 'value' above.]\r
- if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;\r
- cal.set(Calendar.HOUR_OF_DAY, value);\r
- return pos.getIndex();\r
- case 8: // 'S' - FRACTIONAL_SECOND\r
- // Fractional seconds left-justify\r
- i = pos.getIndex() - start;\r
- if (i < 3) {\r
- while (i < 3) {\r
- value *= 10;\r
- i++;\r
- }\r
- } else {\r
- int a = 1;\r
- while (i > 3) {\r
- a *= 10;\r
- i--;\r
- }\r
- value = (value + (a>>1)) / a;\r
- }\r
- cal.set(Calendar.MILLISECOND, value);\r
- return pos.getIndex();\r
- case 9: { // 'E' - DAY_OF_WEEK\r
- // Want to be able to parse both short and long forms.\r
- // Try count == 4 (EEEE) first:\r
- int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,\r
- formatData.weekdays, cal);\r
- if (newStart > 0) {\r
- return newStart;\r
- } else { // EEEE failed, now try EEE\r
- return matchString(text, start, Calendar.DAY_OF_WEEK,\r
- formatData.shortWeekdays, cal);\r
- }\r
- }\r
- case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK\r
- // Want to be able to parse both short and long forms.\r
- // Try count == 4 (cccc) first:\r
- int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,\r
- formatData.standaloneWeekdays, cal);\r
- if (newStart > 0) {\r
- return newStart;\r
- } else { // cccc failed, now try ccc\r
- return matchString(text, start, Calendar.DAY_OF_WEEK,\r
- formatData.standaloneShortWeekdays, cal);\r
- }\r
- }\r
- case 14: // 'a' - AM_PM\r
- return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);\r
- case 15: // 'h' - HOUR (1..12)\r
- // [We computed 'value' above.]\r
- if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;\r
- cal.set(Calendar.HOUR, value);\r
- return pos.getIndex();\r
- case 17: // 'z' - ZONE_OFFSET\r
- case 23: // 'Z' - TIMEZONE_RFC\r
- case 24: // 'v' - TIMEZONE_GENERIC\r
- case 29: // 'V' - TIMEZONE_SPECIAL\r
- {\r
- TimeZone tz = null;\r
- int offset = 0;\r
- boolean parsed = false;\r
-\r
- // Step 1\r
- // Check if this is a long GMT offset string (either localized or default)\r
- Integer gmtoff = parseGMT(text, pos, currentNumberFormat);\r
- if (gmtoff != null) {\r
- offset = gmtoff.intValue();\r
- parsed = true;\r
- }\r
-\r
- if (!parsed) {\r
- // Step 2\r
- // Check if this is an RFC822 time zone offset.\r
- // ICU supports the standard RFC822 format [+|-]HHmm\r
- // and its extended form [+|-]HHmmSS.\r
- \r
- do {\r
- int sign = 0;\r
- char signChar = text.charAt(start);\r
- if (signChar == '+') {\r
- sign = 1;\r
- } else if (signChar == '-') {\r
- sign = -1;\r
- } else {\r
- // Not an RFC822 offset string\r
- break;\r
- }\r
-\r
- // Parse digits\r
- int orgPos = start + 1;\r
- pos.setIndex(orgPos);\r
- number = parseInt(text, 6, pos, false,currentNumberFormat);\r
- int numLen = pos.getIndex() - orgPos;\r
- if (numLen <= 0) {\r
- break;\r
- }\r
-\r
- // Followings are possible format (excluding sign char)\r
- // HHmmSS\r
- // HmmSS\r
- // HHmm\r
- // Hmm\r
- // HH\r
- // H\r
- int val = number.intValue();\r
- int hour = 0, min = 0, sec = 0;\r
- switch(numLen) {\r
- case 1: // H\r
- case 2: // HH\r
- hour = val;\r
- break;\r
- case 3: // Hmm\r
- case 4: // HHmm\r
- hour = val / 100;\r
- min = val % 100;\r
- break;\r
- case 5: // Hmmss\r
- case 6: // HHmmss\r
- hour = val / 10000;\r
- min = (val % 10000) / 100;\r
- sec = val % 100;\r
- break;\r
- }\r
- if (hour > 23 || min > 59 || sec > 59) {\r
- // Invalid value range\r
- break;\r
- }\r
- offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;\r
- parsed = true;\r
- } while (false);\r
-\r
- if (!parsed) {\r
- // Failed to parse. Reset the position.\r
- pos.setIndex(start);\r
- }\r
- }\r
-\r
- if (parsed) {\r
- // offset was successfully parsed as either a long GMT string or RFC822 zone offset\r
- // string. Create normalized zone ID for the offset.\r
- tz = ZoneMeta.getCustomTimeZone(offset);\r
- cal.setTimeZone(tz);\r
- return pos.getIndex();\r
- }\r
-\r
- // Step 3\r
- // At this point, check for named time zones by looking through\r
- // the locale data from the DateFormatZoneData strings.\r
- // Want to be able to parse both short and long forms.\r
- // optimize for calendar's current time zone\r
- ZoneStringInfo zsinfo = null;\r
- switch (patternCharIndex) {\r
- case 17: // 'z' - ZONE_OFFSET\r
- if (count < 4) {\r
- zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);\r
- } else {\r
- zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);\r
- }\r
- break;\r
- case 24: // 'v' - TIMEZONE_GENERIC\r
- if (count == 1) {\r
- zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);\r
- } else if (count == 4) {\r
- zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);\r
- }\r
- break;\r
- case 29: // 'V' - TIMEZONE_SPECIAL\r
- if (count == 1) {\r
- zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);\r
- } else if (count == 4) {\r
- zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);\r
- }\r
- break;\r
- }\r
- if (zsinfo != null) {\r
- if (zsinfo.isStandard()) {\r
- tztype = TZTYPE_STD;\r
- } else if (zsinfo.isDaylight()) {\r
- tztype = TZTYPE_DST;\r
- }\r
- tz = TimeZone.getTimeZone(zsinfo.getID());\r
- cal.setTimeZone(tz);\r
- return start + zsinfo.getString().length();\r
- }\r
- // Step 4\r
- // Final attempt - is this standalone GMT/UT/UTC?\r
- int gmtLen = 0;\r
- if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {\r
- gmtLen = STR_GMT_LEN;\r
- } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {\r
- gmtLen = STR_UTC_LEN;\r
- } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {\r
- gmtLen = STR_UT_LEN;\r
- }\r
- if (gmtLen > 0) {\r
- tz = TimeZone.getTimeZone("Etc/GMT");\r
- cal.setTimeZone(tz);\r
- return start + gmtLen;\r
- }\r
-\r
- // complete failure\r
- return -start;\r
- }\r
-\r
- case 27: // 'Q' - QUARTER\r
- if (count <= 2) // i.e., Q or QQ.\r
- {\r
- // Don't want to parse the quarter if it is a string\r
- // while pattern uses numeric style: Q or QQ.\r
- // [We computed 'value' above.]\r
- cal.set(Calendar.MONTH, (value - 1) * 3);\r
- return pos.getIndex();\r
- }\r
- else\r
- {\r
- // count >= 3 // i.e., QQQ or QQQQ\r
- // Want to be able to parse both short and long forms.\r
- // Try count == 4 first:\r
- int newStart = matchQuarterString(text, start, Calendar.MONTH,\r
- formatData.quarters, cal);\r
- if (newStart > 0) {\r
- return newStart;\r
- } else { // count == 4 failed, now try count == 3\r
- return matchQuarterString(text, start, Calendar.MONTH,\r
- formatData.shortQuarters, cal);\r
- }\r
- }\r
- \r
- case 28: // 'q' - STANDALONE QUARTER\r
- if (count <= 2) // i.e., q or qq.\r
- {\r
- // Don't want to parse the quarter if it is a string\r
- // while pattern uses numeric style: q or qq.\r
- // [We computed 'value' above.]\r
- cal.set(Calendar.MONTH, (value - 1) * 3);\r
- return pos.getIndex();\r
- }\r
- else\r
- {\r
- // count >= 3 // i.e., qqq or qqqq\r
- // Want to be able to parse both short and long forms.\r
- // Try count == 4 first:\r
- int newStart = matchQuarterString(text, start, Calendar.MONTH,\r
- formatData.standaloneQuarters, cal);\r
- if (newStart > 0) {\r
- return newStart;\r
- } else { // count == 4 failed, now try count == 3\r
- return matchQuarterString(text, start, Calendar.MONTH,\r
- formatData.standaloneShortQuarters, cal);\r
- }\r
- }\r
-\r
- default:\r
- // case 3: // 'd' - DATE\r
- // case 5: // 'H' - HOUR_OF_DAY (0..23)\r
- // case 6: // 'm' - MINUTE\r
- // case 7: // 's' - SECOND\r
- // case 10: // 'D' - DAY_OF_YEAR\r
- // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH\r
- // case 12: // 'w' - WEEK_OF_YEAR\r
- // case 13: // 'W' - WEEK_OF_MONTH\r
- // case 16: // 'K' - HOUR (0..11)\r
- // case 18: // 'Y' - YEAR_WOY\r
- // case 19: // 'e' - DOW_LOCAL\r
- // case 20: // 'u' - EXTENDED_YEAR\r
- // case 21: // 'g' - JULIAN_DAY\r
- // case 22: // 'A' - MILLISECONDS_IN_DAY\r
-\r
- // Handle "generic" fields\r
- if (obeyCount)\r
- {\r
- if ((start+count) > text.length()) return -start;\r
- number = parseInt(text, count, pos, allowNegative,currentNumberFormat);\r
- }\r
- else number = parseInt(text, pos, allowNegative,currentNumberFormat);\r
- if (number != null) {\r
- cal.set(field, number.intValue());\r
- return pos.getIndex();\r
- }\r
- return -start;\r
- }\r
- }\r
-\r
- /**\r
- * Parse an integer using numberFormat. This method is semantically\r
- * const, but actually may modify fNumberFormat.\r
- */\r
- private Number parseInt(String text,\r
- ParsePosition pos,\r
- boolean allowNegative,\r
- NumberFormat fmt) {\r
- return parseInt(text, -1, pos, allowNegative, fmt);\r
- }\r
- \r
- /**\r
- * Parse an integer using numberFormat up to maxDigits.\r
- */\r
- private Number parseInt(String text,\r
- int maxDigits,\r
- ParsePosition pos,\r
- boolean allowNegative,\r
- NumberFormat fmt) {\r
- Number number;\r
- int oldPos = pos.getIndex();\r
- if (allowNegative) {\r
- number = fmt.parse(text, pos);\r
- } else {\r
- // Invalidate negative numbers\r
- if (fmt instanceof DecimalFormat) {\r
- String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();\r
- ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);\r
- number = fmt.parse(text, pos);\r
- ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);\r
- } else {\r
- boolean dateNumberFormat = (fmt instanceof DateNumberFormat);\r
- if (dateNumberFormat) {\r
- ((DateNumberFormat)fmt).setParsePositiveOnly(true);\r
- }\r
- number = fmt.parse(text, pos); \r
- if (dateNumberFormat) {\r
- ((DateNumberFormat)fmt).setParsePositiveOnly(false);\r
- }\r
- }\r
- }\r
- if (maxDigits > 0) {\r
- // adjust the result to fit into\r
- // the maxDigits and move the position back\r
- int nDigits = pos.getIndex() - oldPos;\r
- if (nDigits > maxDigits) {\r
- double val = number.doubleValue();\r
- nDigits -= maxDigits;\r
- while (nDigits > 0) {\r
- val /= 10;\r
- nDigits--;\r
- }\r
- pos.setIndex(oldPos + maxDigits);\r
- number = new Integer((int)val);\r
- }\r
- }\r
- return number;\r
- }\r
-\r
- \r
- /**\r
- * Translate a pattern, mapping each character in the from string to the\r
- * corresponding character in the to string.\r
- */\r
- private String translatePattern(String pat, String from, String to) {\r
- StringBuffer result = new StringBuffer();\r
- boolean inQuote = false;\r
- for (int i = 0; i < pat.length(); ++i) {\r
- char c = pat.charAt(i);\r
- if (inQuote) {\r
- if (c == '\'')\r
- inQuote = false;\r
- }\r
- else {\r
- if (c == '\'')\r
- inQuote = true;\r
- else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {\r
- int ci = from.indexOf(c);\r
- if (ci != -1) {\r
- c = to.charAt(ci);\r
- }\r
- // do not worry on translatepattern if the character is not listed\r
- // we do the validity check elsewhere\r
- }\r
- }\r
- result.append(c);\r
- }\r
- if (inQuote)\r
- throw new IllegalArgumentException("Unfinished quote in pattern");\r
- return result.toString();\r
- }\r
-\r
- /**\r
- * Return a pattern string describing this date format.\r
- * @stable ICU 2.0\r
- */\r
- public String toPattern() {\r
- return pattern;\r
- }\r
-\r
- /**\r
- * Return a localized pattern string describing this date format.\r
- * @stable ICU 2.0\r
- */\r
- public String toLocalizedPattern() {\r
- return translatePattern(pattern,\r
- DateFormatSymbols.patternChars,\r
- formatData.localPatternChars);\r
- }\r
-\r
- /**\r
- * Apply the given unlocalized pattern string to this date format.\r
- * @stable ICU 2.0\r
- */\r
- public void applyPattern(String pat)\r
- {\r
- this.pattern = pat;\r
- setLocale(null, null);\r
- // reset parsed pattern items\r
- patternItems = null;\r
- }\r
-\r
- /**\r
- * Apply the given localized pattern string to this date format.\r
- * @stable ICU 2.0\r
- */\r
- public void applyLocalizedPattern(String pat) {\r
- this.pattern = translatePattern(pat,\r
- formatData.localPatternChars,\r
- DateFormatSymbols.patternChars);\r
- setLocale(null, null);\r
- }\r
-\r
- /**\r
- * Gets the date/time formatting data.\r
- * @return a copy of the date-time formatting data associated\r
- * with this date-time formatter.\r
- * @stable ICU 2.0\r
- */\r
- public DateFormatSymbols getDateFormatSymbols()\r
- {\r
- return (DateFormatSymbols)formatData.clone();\r
- }\r
-\r
- /**\r
- * Allows you to set the date/time formatting data.\r
- * @param newFormatSymbols the new symbols\r
- * @stable ICU 2.0\r
- */\r
- public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)\r
- {\r
- this.formatData = (DateFormatSymbols)newFormatSymbols.clone();\r
- gmtfmtCache = null;\r
- }\r
-\r
- /**\r
- * Method for subclasses to access the DateFormatSymbols.\r
- * @stable ICU 2.0\r
- */\r
- protected DateFormatSymbols getSymbols() {\r
- return formatData;\r
- }\r
-\r
- /**\r
- * Overrides Cloneable\r
- * @stable ICU 2.0\r
- */\r
- public Object clone() {\r
- SimpleDateFormat other = (SimpleDateFormat) super.clone();\r
- other.formatData = (DateFormatSymbols) formatData.clone();\r
- return other;\r
- }\r
-\r
- /**\r
- * Override hashCode.\r
- * Generates the hash code for the SimpleDateFormat object\r
- * @stable ICU 2.0\r
- */\r
- public int hashCode()\r
- {\r
- return pattern.hashCode();\r
- // just enough fields for a reasonable distribution\r
- }\r
-\r
- /**\r
- * Override equals.\r
- * @stable ICU 2.0\r
- */\r
- public boolean equals(Object obj)\r
- {\r
- if (!super.equals(obj)) return false; // super does class check\r
- SimpleDateFormat that = (SimpleDateFormat) obj;\r
- return (pattern.equals(that.pattern)\r
- && formatData.equals(that.formatData));\r
- }\r
-\r
- /**\r
- * Override writeObject.\r
- */\r
- private void writeObject(ObjectOutputStream stream) throws IOException{\r
- if (defaultCenturyStart == null) {\r
- // if defaultCenturyStart is not yet initialized,\r
- // calculate and set value before serialization.\r
- initializeDefaultCenturyStart(defaultCenturyBase);\r
- }\r
- stream.defaultWriteObject();\r
- }\r
- \r
- /**\r
- * Override readObject.\r
- */\r
- private void readObject(ObjectInputStream stream)\r
- throws IOException, ClassNotFoundException {\r
- stream.defaultReadObject();\r
- ///CLOVER:OFF\r
- // don't have old serial data to test with\r
- if (serialVersionOnStream < 1) {\r
- // didn't have defaultCenturyStart field\r
- defaultCenturyBase = System.currentTimeMillis();\r
- }\r
- ///CLOVER:ON\r
- else {\r
- // fill in dependent transient field\r
- parseAmbiguousDatesAsAfter(defaultCenturyStart);\r
- }\r
- serialVersionOnStream = currentSerialVersion;\r
- locale = getLocale(ULocale.VALID_LOCALE);\r
-\r
- initLocalZeroPaddingNumberFormat();\r
- }\r
-\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
- /**\r
- * Format the object to an attributed string, and return the corresponding iterator\r
- * Overrides superclass method.\r
- * \r
- * @param obj The object to format\r
- * @return <code>AttributedCharacterIterator</code> describing the formatted value.\r
- * \r
- * @stable ICU 3.8\r
- */\r
- public AttributedCharacterIterator formatToCharacterIterator(Object obj) {\r
- Calendar cal = calendar;\r
- if (obj instanceof Calendar) {\r
- cal = (Calendar)obj;\r
- } else if (obj instanceof Date) {\r
- calendar.setTime((Date)obj);\r
- } else if (obj instanceof Number) {\r
- calendar.setTimeInMillis(((Number)obj).longValue());\r
- } else { \r
- throw new IllegalArgumentException("Cannot format given Object as a Date");\r
- }\r
- StringBuffer toAppendTo = new StringBuffer();\r
- FieldPosition pos = new FieldPosition(0);\r
- List attributes = new LinkedList();\r
- format(cal, toAppendTo, pos, attributes);\r
-\r
- AttributedString as = new AttributedString(toAppendTo.toString());\r
- \r
- // add DateFormat field attributes to the AttributedString\r
- for (int i = 0; i < attributes.size(); i++) {\r
- FieldPosition fp = (FieldPosition) attributes.get(i);\r
- Format.Field attribute = fp.getFieldAttribute();\r
- as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());\r
- }\r
- // return the CharacterIterator from AttributedString\r
- return as.getIterator();\r
- }\r
-//#endif\r
-\r
-\r
- /**\r
- * Get the locale of this simple date formatter.\r
- * It is package accessible. also used in DateIntervalFormat.\r
- *\r
- * @return locale in this simple date formatter\r
- */\r
- ULocale getLocale() \r
- {\r
- return locale;\r
- }\r
-\r
-\r
- \r
- /**\r
- * Check whether the 'field' is smaller than all the fields covered in\r
- * pattern, return true if it is.\r
- * The sequence of calendar field,\r
- * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...\r
- * @param field the calendar field need to check against\r
- * @return true if the 'field' is smaller than all the fields \r
- * covered in pattern. false otherwise.\r
- */\r
-\r
- boolean isFieldUnitIgnored(int field) {\r
- return isFieldUnitIgnored(pattern, field);\r
- }\r
-\r
-\r
- /*\r
- * Check whether the 'field' is smaller than all the fields covered in\r
- * pattern, return true if it is.\r
- * The sequence of calendar field,\r
- * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...\r
- * @param pattern the pattern to check against\r
- * @param field the calendar field need to check against\r
- * @return true if the 'field' is smaller than all the fields \r
- * covered in pattern. false otherwise.\r
- * @internal ICU 4.0\r
- */\r
- static boolean isFieldUnitIgnored(String pattern, int field) {\r
- int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];\r
- int level;\r
- char ch;\r
- boolean inQuote = false;\r
- char prevCh = 0;\r
- int count = 0;\r
- \r
- for (int i = 0; i < pattern.length(); ++i) {\r
- ch = pattern.charAt(i);\r
- if (ch != prevCh && count > 0) {\r
- level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];\r
- if ( fieldLevel <= level ) {\r
- return false;\r
- }\r
- count = 0;\r
- }\r
- if (ch == '\'') {\r
- if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {\r
- ++i;\r
- } else {\r
- inQuote = ! inQuote;\r
- }\r
- } \r
- else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) \r
- || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {\r
- prevCh = ch;\r
- ++count;\r
- }\r
- }\r
- if ( count > 0 ) {\r
- // last item\r
- level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];\r
- if ( fieldLevel <= level ) {\r
- return false;\r
- }\r
- }\r
- return true;\r
- }\r
-\r
-\r
- /**\r
- * Format date interval by algorithm. \r
- * It is supposed to be used only by CLDR survey tool.\r
- *\r
- * @param fromCalendar calendar set to the from date in date interval\r
- * to be formatted into date interval stirng\r
- * @param toCalendar calendar set to the to date in date interval\r
- * to be formatted into date interval stirng\r
- * @param appendTo Output parameter to receive result.\r
- * Result is appended to existing contents.\r
- * @param pos On input: an alignment field, if desired.\r
- * On output: the offsets of the alignment field.\r
- * @exception IllegalArgumentException when there is non-recognized\r
- * pattern letter\r
- * @return Reference to 'appendTo' parameter.\r
- * @internal ICU 4.0\r
- */\r
- public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,\r
- Calendar toCalendar,\r
- StringBuffer appendTo,\r
- FieldPosition pos)\r
- throws IllegalArgumentException\r
- {\r
- // not support different calendar types and time zones\r
- if ( !fromCalendar.isEquivalentTo(toCalendar) ) {\r
- throw new IllegalArgumentException("can not format on two different calendars");\r
- }\r
- \r
- Object[] items = getPatternItems();\r
- int diffBegin = -1;\r
- int diffEnd = -1;\r
-\r
- /* look for different formatting string range */\r
- // look for start of difference\r
- try {\r
- for (int i = 0; i < items.length; i++) {\r
- if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {\r
- diffBegin = i;\r
- break;\r
- }\r
- } \r
- \r
- if ( diffBegin == -1 ) { \r
- // no difference, single date format\r
- return format(fromCalendar, appendTo, pos);\r
- }\r
- \r
- // look for end of difference\r
- for (int i = items.length-1; i >= diffBegin; i--) {\r
- if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {\r
- diffEnd = i;\r
- break;\r
- }\r
- }\r
- } catch ( IllegalArgumentException e ) {\r
- throw new IllegalArgumentException(e.toString());\r
- }\r
-\r
- // full range is different\r
- if ( diffBegin == 0 && diffEnd == items.length-1 ) {\r
- format(fromCalendar, appendTo, pos);\r
- appendTo.append(" \u2013 "); // default separator\r
- format(toCalendar, appendTo, pos);\r
- return appendTo;\r
- }\r
-\r
-\r
- /* search for largest calendar field within the different range */\r
- int highestLevel = 1000;\r
- for (int i = diffBegin; i <= diffEnd; i++) {\r
- if ( items[i] instanceof String) {\r
- continue;\r
- } \r
- PatternItem item = (PatternItem)items[i];\r
- char ch = item.type; \r
- int patternCharIndex = -1;\r
- if ('A' <= ch && ch <= 'z') {\r
- patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];\r
- }\r
- \r
- if (patternCharIndex == -1) {\r
- throw new IllegalArgumentException("Illegal pattern character " +\r
- "'" + ch + "' in \"" +\r
- new String(pattern) + '"');\r
- }\r
- \r
- if ( patternCharIndex < highestLevel ) {\r
- highestLevel = patternCharIndex;\r
- }\r
- }\r
-\r
- /* re-calculate diff range, including those calendar field which\r
- is in lower level than the largest calendar field covered\r
- in diff range calculated. */\r
- try {\r
- for (int i = 0; i < diffBegin; i++) {\r
- if ( lowerLevel(items, i, highestLevel) ) {\r
- diffBegin = i;\r
- break;\r
- }\r
- }\r
- \r
- \r
- for (int i = items.length-1; i > diffEnd; i--) {\r
- if ( lowerLevel(items, i, highestLevel) ) {\r
- diffEnd = i;\r
- break;\r
- }\r
- }\r
- } catch ( IllegalArgumentException e ) {\r
- throw new IllegalArgumentException(e.toString());\r
- }\r
-\r
-\r
- // full range is different\r
- if ( diffBegin == 0 && diffEnd == items.length-1 ) {\r
- format(fromCalendar, appendTo, pos);\r
- appendTo.append(" \u2013 "); // default separator\r
- format(toCalendar, appendTo, pos);\r
- return appendTo;\r
- }\r
-\r
-\r
- // formatting\r
- // Initialize\r
- pos.setBeginIndex(0);\r
- pos.setEndIndex(0);\r
-\r
- // formatting date 1\r
- for (int i = 0; i <= diffEnd; i++) {\r
- if (items[i] instanceof String) {\r
- appendTo.append((String)items[i]);\r
- } else {\r
- PatternItem item = (PatternItem)items[i];\r
- if (useFastFormat) {\r
- subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);\r
- } else {\r
- appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));\r
- }\r
- }\r
- }\r
-\r
- appendTo.append(" \u2013 "); // default separator\r
-\r
- // formatting date 2\r
- for (int i = diffBegin; i < items.length; i++) {\r
- if (items[i] instanceof String) {\r
- appendTo.append((String)items[i]);\r
- } else {\r
- PatternItem item = (PatternItem)items[i];\r
- if (useFastFormat) {\r
- subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);\r
- } else {\r
- appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));\r
- }\r
- }\r
- }\r
- return appendTo;\r
- }\r
-\r
-\r
- /**\r
- * check whether the i-th item in 2 calendar is in different value.\r
- *\r
- * It is supposed to be used only by CLDR survey tool.\r
- * It is used by intervalFormatByAlgorithm().\r
- *\r
- * @param fromCalendar one calendar\r
- * @param toCalendar the other calendar\r
- * @param items pattern items\r
- * @param i the i-th item in pattern items\r
- * @exception IllegalArgumentException when there is non-recognized\r
- * pattern letter\r
- * @return true is i-th item in 2 calendar is in different \r
- * value, false otherwise.\r
- */\r
- private boolean diffCalFieldValue(Calendar fromCalendar,\r
- Calendar toCalendar,\r
- Object[] items,\r
- int i) throws IllegalArgumentException {\r
- if ( items[i] instanceof String) {\r
- return false;\r
- } \r
- PatternItem item = (PatternItem)items[i];\r
- char ch = item.type; \r
- int patternCharIndex = -1;\r
- if ('A' <= ch && ch <= 'z') {\r
- patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
- }\r
- \r
- if (patternCharIndex == -1) {\r
- throw new IllegalArgumentException("Illegal pattern character " +\r
- "'" + ch + "' in \"" +\r
- new String(pattern) + '"');\r
- }\r
- \r
- final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
- int value = fromCalendar.get(field);\r
- int value_2 = toCalendar.get(field);\r
- if ( value != value_2 ) {\r
- return true;\r
- }\r
- return false;\r
- }\r
-\r
-\r
- /**\r
- * check whether the i-th item's level is lower than the input 'level'\r
- *\r
- * It is supposed to be used only by CLDR survey tool.\r
- * It is used by intervalFormatByAlgorithm().\r
- *\r
- * @param items the pattern items\r
- * @param i the i-th item in pattern items\r
- * @param level the level with which the i-th pattern item compared to\r
- * @exception IllegalArgumentException when there is non-recognized\r
- * pattern letter\r
- * @return true if i-th pattern item is lower than 'level',\r
- * false otherwise\r
- */\r
- private boolean lowerLevel(Object[] items, int i, int level) \r
- throws IllegalArgumentException {\r
- if ( items[i] instanceof String) {\r
- return false;\r
- } \r
- PatternItem item = (PatternItem)items[i];\r
- char ch = item.type; \r
- int patternCharIndex = -1;\r
- if ('A' <= ch && ch <= 'z') {\r
- patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];\r
- }\r
- \r
- if (patternCharIndex == -1) {\r
- throw new IllegalArgumentException("Illegal pattern character " +\r
- "'" + ch + "' in \"" +\r
- new String(pattern) + '"');\r
- }\r
- \r
- if ( patternCharIndex >= level ) {\r
- return true;\r
- } \r
- return false;\r
- }\r
-\r
- protected NumberFormat getNumberFormat(char ch) {\r
-\r
- Character ovrField;\r
- ovrField = new Character(ch);\r
- if (overrideMap != null && overrideMap.containsKey(ovrField)) {\r
- String nsName = overrideMap.get(ovrField).toString();\r
- NumberFormat nf = (NumberFormat)numberFormatters.get(nsName);\r
- return nf;\r
- } else {\r
- return numberFormat;\r
- }\r
- }\r
-\r
- private void initNumberFormatters(ULocale loc) {\r
-\r
- numberFormatters = new HashMap();\r
- overrideMap = new HashMap();\r
- processOverrideString(loc,override); \r
-\r
- }\r
-\r
- private void processOverrideString(ULocale loc, String str) {\r
-\r
- if ( str == null || str.length() == 0 )\r
- return;\r
-\r
- int start = 0;\r
- int end;\r
- String nsName;\r
- Character ovrField;\r
- boolean moreToProcess = true;\r
- boolean fullOverride;\r
-\r
- while (moreToProcess) {\r
- int delimiterPosition = str.indexOf(";",start);\r
- if (delimiterPosition == -1) {\r
- moreToProcess = false;\r
- end = str.length();\r
- } else {\r
- end = delimiterPosition;\r
- }\r
-\r
- String currentString = str.substring(start,end);\r
- int equalSignPosition = currentString.indexOf("=");\r
- if (equalSignPosition == -1) { // Simple override string such as "hebrew"\r
- nsName = currentString;\r
- fullOverride = true;\r
- } else { // Field specific override string such as "y=hebrew"\r
- nsName = currentString.substring(equalSignPosition+1);\r
- ovrField = new Character(currentString.charAt(0));\r
- overrideMap.put(ovrField,nsName);\r
- fullOverride = false;\r
- }\r
-\r
- ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);\r
- NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);\r
- \r
- if (fullOverride) {\r
- setNumberFormat(nf);\r
- } else {\r
- // Since one or more of the override number formatters might be complex,\r
- // we can't rely on the fast numfmt where we have a partial field override.\r
- useLocalZeroPaddingNumberFormat = false;\r
- }\r
-\r
- if (!numberFormatters.containsKey(nsName)) {\r
- numberFormatters.put(nsName,nf);\r
- }\r
-\r
- start = delimiterPosition + 1;\r
-\r
- } \r
- }\r
-}\r
+//##header J2SE15
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2009, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+
+package com.ibm.icu.text;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+import java.lang.Character;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.MissingResourceException;
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.Format;
+import java.util.LinkedList;
+//#endif
+
+import com.ibm.icu.impl.CalendarData;
+import com.ibm.icu.impl.DateNumberFormat;
+import com.ibm.icu.impl.ICUCache;
+import com.ibm.icu.impl.SimpleCache;
+import com.ibm.icu.impl.UCharacterProperty;
+import com.ibm.icu.impl.ZoneMeta;
+import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;
+import com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.util.BasicTimeZone;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.GregorianCalendar;
+import com.ibm.icu.util.TimeZone;
+import com.ibm.icu.util.TimeZoneTransition;
+import com.ibm.icu.util.ULocale;
+
+
+/**
+ * <code>SimpleDateFormat</code> is a concrete class for formatting and
+ * parsing dates in a locale-sensitive manner. It allows for formatting
+ * (date -> text), parsing (text -> date), and normalization.
+ *
+ * <p>
+ * <code>SimpleDateFormat</code> allows you to start by choosing
+ * any user-defined patterns for date-time formatting. However, you
+ * are encouraged to create a date-time formatter with either
+ * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
+ * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
+ * of these class methods can return a date/time formatter initialized
+ * with a default format pattern. You may modify the format pattern
+ * using the <code>applyPattern</code> methods as desired.
+ * For more information on using these methods, see
+ * {@link DateFormat}.
+ *
+ * <p>
+ * <strong>Time Format Syntax:</strong>
+ * <p>
+ * To specify the time format use a <em>time pattern</em> string.
+ * In this pattern, all ASCII letters are reserved as pattern letters,
+ * which are defined as the following:
+ * <blockquote>
+ * <pre>
+ * Symbol Meaning Presentation Example
+ * ------ ------- ------------ -------
+ * G era designator (Text) AD
+ * y† year (Number) 1996
+ * Y* year (week of year) (Number) 1997
+ * u* extended year (Number) 4601
+ * M month in year (Text & Number) July & 07
+ * d day in month (Number) 10
+ * h hour in am/pm (1~12) (Number) 12
+ * H hour in day (0~23) (Number) 0
+ * m minute in hour (Number) 30
+ * s second in minute (Number) 55
+ * S fractional second (Number) 978
+ * E day of week (Text) Tuesday
+ * e* day of week (local 1~7) (Text & Number) Tuesday & 2
+ * D day in year (Number) 189
+ * F day of week in month (Number) 2 (2nd Wed in July)
+ * w week in year (Number) 27
+ * W week in month (Number) 2
+ * a am/pm marker (Text) PM
+ * k hour in day (1~24) (Number) 24
+ * K hour in am/pm (0~11) (Number) 0
+ * z time zone (Text) Pacific Standard Time
+ * Z time zone (RFC 822) (Number) -0800
+ * v time zone (generic) (Text) Pacific Time
+ * V time zone (location) (Text) United States (Los Angeles)
+ * g* Julian day (Number) 2451334
+ * A* milliseconds in day (Number) 69540000
+ * Q* quarter in year (Text & Number) Q1 & 01
+ * c* stand alone day of week (Text & Number) Tuesday & 2
+ * L* stand alone month (Text & Number) July & 07
+ * q* stand alone quarter (Text & Number) Q1 & 01
+ * ' escape for text (Delimiter) 'Date='
+ * '' single quote (Literal) 'o''clock'
+ * </pre>
+ * </blockquote>
+ * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
+ * <tt><b>†</b></tt> ICU interprets a single 'y' differently than Java.</p>
+ * <p>
+ * The count of pattern letters determine the format.
+ * <p>
+ * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
+ * < 4--use short or abbreviated form if one exists.
+ * <p>
+ * <strong>(Number)</strong>: the minimum number of digits. Shorter
+ * numbers are zero-padded to this amount. Year is handled specially;
+ * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
+ * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
+ * Unlike other fields, fractional seconds are padded on the right with zero.
+ * <p>
+ * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
+ * <p>
+ * Any characters in the pattern that are not in the ranges of ['a'..'z']
+ * and ['A'..'Z'] will be treated as quoted text. For instance, characters
+ * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
+ * even they are not embraced within single quotes.
+ * <p>
+ * A pattern containing any invalid pattern letter will result in a thrown
+ * exception during formatting or parsing.
+ *
+ * <p>
+ * <strong>Examples Using the US Locale:</strong>
+ * <blockquote>
+ * <pre>
+ * Format Pattern Result
+ * -------------- -------
+ * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
+ * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
+ * "h:mm a" ->> 12:08 PM
+ * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
+ * "K:mm a, vvv" ->> 0:00 PM, PT
+ * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
+ * </pre>
+ * </blockquote>
+ * <strong>Code Sample:</strong>
+ * <blockquote>
+ * <pre>
+ * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
+ * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
+ * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
+ * <br>
+ * // Format the current time.
+ * SimpleDateFormat formatter
+ * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
+ * Date currentTime_1 = new Date();
+ * String dateString = formatter.format(currentTime_1);
+ * <br>
+ * // Parse the previous string back into a Date.
+ * ParsePosition pos = new ParsePosition(0);
+ * Date currentTime_2 = formatter.parse(dateString, pos);
+ * </pre>
+ * </blockquote>
+ * In the example, the time value <code>currentTime_2</code> obtained from
+ * parsing will be equal to <code>currentTime_1</code>. However, they may not be
+ * equal if the am/pm marker 'a' is left out from the format pattern while
+ * the "hour in am/pm" pattern symbol is used. This information loss can
+ * happen when formatting the time in PM.
+ *
+ * <p>
+ * When parsing a date string using the abbreviated year pattern ("yy"),
+ * SimpleDateFormat must interpret the abbreviated year
+ * relative to some century. It does this by adjusting dates to be
+ * within 80 years before and 20 years after the time the SimpleDateFormat
+ * instance is created. For example, using a pattern of "MM/dd/yy" and a
+ * SimpleDateFormat instance created on Jan 1, 1997, the string
+ * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
+ * would be interpreted as May 4, 1964.
+ * During parsing, only strings consisting of exactly two digits, as defined by
+ * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
+ * century.
+ * Any other numeric string, such as a one digit string, a three or more digit
+ * string, or a two digit string that isn't all digits (for example, "-1"), is
+ * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
+ * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
+ *
+ * <p>
+ * If the year pattern does not have exactly two 'y' characters, the year is
+ * interpreted literally, regardless of the number of digits. So using the
+ * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
+ *
+ * <p>
+ * When numeric fields abut one another directly, with no intervening delimiter
+ * characters, they constitute a run of abutting numeric fields. Such runs are
+ * parsed specially. For example, the format "HHmmss" parses the input text
+ * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
+ * parse "1234". In other words, the leftmost field of the run is flexible,
+ * while the others keep a fixed width. If the parse fails anywhere in the run,
+ * then the leftmost field is shortened by one character, and the entire run is
+ * parsed again. This is repeated until either the parse succeeds or the
+ * leftmost field is one character in length. If the parse still fails at that
+ * point, the parse of the run fails.
+ *
+ * <p>
+ * For time zones that have no names, use strings GMT+hours:minutes or
+ * GMT-hours:minutes.
+ *
+ * <p>
+ * The calendar defines what is the first day of the week, the first week
+ * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
+ * time zone. There is one common decimal format to handle all the numbers;
+ * the digit count is handled programmatically according to the pattern.
+ *
+ * <h4>Synchronization</h4>
+ *
+ * Date formats are not synchronized. It is recommended to create separate
+ * format instances for each thread. If multiple threads access a format
+ * concurrently, it must be synchronized externally.
+ *
+ * @see com.ibm.icu.util.Calendar
+ * @see com.ibm.icu.util.GregorianCalendar
+ * @see com.ibm.icu.util.TimeZone
+ * @see DateFormat
+ * @see DateFormatSymbols
+ * @see DecimalFormat
+ * @author Mark Davis, Chen-Lieh Huang, Alan Liu
+ * @stable ICU 2.0
+ */
+public class SimpleDateFormat extends DateFormat {
+
+ // the official serial version ID which says cryptically
+ // which version we're compatible with
+ private static final long serialVersionUID = 4774881970558875024L;
+
+ // the internal serial version which says which version was written
+ // - 0 (default) for version up to JDK 1.1.3
+ // - 1 for version from JDK 1.1.4, which includes a new field
+ static final int currentSerialVersion = 1;
+
+
+ /*
+ * From calendar field to its level.
+ * Used to order calendar field.
+ * For example, calendar fields can be defined in the following order:
+ * year > month > date > am-pm > hour > minute
+ * YEAR --> 10, MONTH -->20, DATE --> 30;
+ * AM_PM -->40, HOUR --> 50, MINUTE -->60
+ */
+ private static final int[] CALENDAR_FIELD_TO_LEVEL =
+ {
+ /*GyM*/ 0, 10, 20,
+ /*wW*/ 20, 30,
+ /*dDEF*/ 30, 20, 30, 30,
+ /*ahHm*/ 40, 50, 50, 60,
+ /*sS..*/ 70, 80,
+ /*z?Y*/ 0, 0, 10,
+ /*eug*/ 30, 10, 0,
+ /*A*/ 40
+ };
+
+
+
+ /*
+ * From calendar field letter to its level.
+ * Used to order calendar field.
+ * For example, calendar fields can be defined in the following order:
+ * year > month > date > am-pm > hour > minute
+ * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
+ */
+ private static final int[] PATTERN_CHAR_TO_LEVEL =
+ {
+ // A B C D E F G H I J K L M N O
+ -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, -1,
+ // P Q R S T U V W X Y Z
+ -1, 20, -1, 80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,
+ // a b c d e f g h i j k l m n o
+ -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
+ // p q r s t u v w x y z
+ -1, 20, -1, 70, -1, 10, 0, 20, -1, 10, 0, -1, -1, -1, -1, -1
+ };
+
+
+ /**
+ * The version of the serialized data on the stream. Possible values:
+ * <ul>
+ * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
+ * has no <code>defaultCenturyStart</code> on stream.
+ * <li><b>1</b> JDK 1.1.4 or later. This version adds
+ * <code>defaultCenturyStart</code>.
+ * </ul>
+ * When streaming out this class, the most recent format
+ * and the highest allowable <code>serialVersionOnStream</code>
+ * is written.
+ * @serial
+ */
+ private int serialVersionOnStream = currentSerialVersion;
+
+ /**
+ * The pattern string of this formatter. This is always a non-localized
+ * pattern. May not be null. See class documentation for details.
+ * @serial
+ */
+ private String pattern;
+
+ /**
+ * The override string of this formatter. Used to override the
+ * numbering system for one or more fields.
+ * @serial
+ */
+ private String override;
+
+ /**
+ * The hash map used for number format overrides.
+ * @serial
+ */
+ private HashMap numberFormatters;
+
+ /**
+ * The hash map used for number format overrides.
+ * @serial
+ */
+ private HashMap overrideMap;
+
+ /**
+ * The symbols used by this formatter for week names, month names,
+ * etc. May not be null.
+ * @serial
+ * @see DateFormatSymbols
+ */
+ private DateFormatSymbols formatData;
+
+ private transient ULocale locale;
+
+ /**
+ * We map dates with two-digit years into the century starting at
+ * <code>defaultCenturyStart</code>, which may be any date. May
+ * not be null.
+ * @serial
+ * @since JDK1.1.4
+ */
+ private Date defaultCenturyStart;
+
+ private transient int defaultCenturyStartYear;
+
+ // defaultCenturyBase is set when an instance is created
+ // and may be used for calculating defaultCenturyStart when needed.
+ private transient long defaultCenturyBase;
+
+ // We need to preserve time zone type when parsing specific
+ // time zone text (xxx Standard Time vs xxx Daylight Time)
+ private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;
+ private transient int tztype = TZTYPE_UNK;
+
+ private static final int millisPerHour = 60 * 60 * 1000;
+ private static final int millisPerMinute = 60 * 1000;
+ private static final int millisPerSecond = 1000;
+
+ // This prefix is designed to NEVER MATCH real text, in order to
+ // suppress the parsing of negative numbers. Adjust as needed (if
+ // this becomes valid Unicode).
+ private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
+
+ /**
+ * If true, this object supports fast formatting using the
+ * subFormat variant that takes a StringBuffer.
+ */
+ private transient boolean useFastFormat;
+
+ /**
+ * Construct a SimpleDateFormat using the default pattern for the default
+ * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
+ * generality, use the factory methods in the DateFormat class.
+ *
+ * @see DateFormat
+ * @stable ICU 2.0
+ */
+ public SimpleDateFormat() {
+ this(getDefaultPattern(), null, null, null, null, true, null);
+ }
+
+ /**
+ * Construct a SimpleDateFormat using the given pattern in the default
+ * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
+ * generality, use the factory methods in the DateFormat class.
+ * @stable ICU 2.0
+ */
+ public SimpleDateFormat(String pattern)
+ {
+ this(pattern, null, null, null, null, true, null);
+ }
+
+ /**
+ * Construct a SimpleDateFormat using the given pattern and locale.
+ * <b>Note:</b> Not all locales support SimpleDateFormat; for full
+ * generality, use the factory methods in the DateFormat class.
+ * @stable ICU 2.0
+ */
+ public SimpleDateFormat(String pattern, Locale loc)
+ {
+ this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
+ }
+
+ /**
+ * Construct a SimpleDateFormat using the given pattern and locale.
+ * <b>Note:</b> Not all locales support SimpleDateFormat; for full
+ * generality, use the factory methods in the DateFormat class.
+ * @stable ICU 3.2
+ */
+ public SimpleDateFormat(String pattern, ULocale loc)
+ {
+ this(pattern, null, null, null, loc, true, null);
+ }
+
+ /**
+ * Construct a SimpleDateFormat using the given pattern , override and locale.
+ * @draft ICU 4.2
+ * @provisional This API might change or be removed in a future release.
+ */
+ public SimpleDateFormat(String pattern, String override, ULocale loc)
+ {
+ this(pattern, null, null, null, loc, false,override);
+ }
+
+ /**
+ * Construct a SimpleDateFormat using the given pattern and
+ * locale-specific symbol data.
+ * Warning: uses default locale for digits!
+ * @stable ICU 2.0
+ */
+ public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
+ {
+ this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
+ }
+
+ /**
+ * @internal ICU 3.2
+ * @deprecated This API is ICU internal only.
+ */
+ public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
+ {
+ this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
+ }
+
+ /**
+ * Package-private constructor that allows a subclass to specify
+ * whether it supports fast formatting.
+ *
+ * TODO make this API public.
+ */
+ SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
+ boolean useFastFormat, String override) {
+ this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
+ }
+
+ SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
+ boolean useFastFormat) {
+ this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,null);
+ }
+
+ /*
+ * The constructor called from all other SimpleDateFormat constructors
+ */
+ private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
+ NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
+ this.pattern = pattern;
+ this.formatData = formatData;
+ this.calendar = calendar;
+ this.numberFormat = numberFormat;
+ this.locale = locale; // time zone formatting
+ this.useFastFormat = useFastFormat;
+ this.override = override;
+ initialize();
+ }
+
+ /**
+ * Create an instance of SimpleDateForamt for the given format configuration
+ * @param formatConfig the format configuration
+ * @return A SimpleDateFormat instance
+ * @internal ICU 3.8
+ * @deprecated This API is ICU internal only.
+ */
+ public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
+
+ String ostr = formatConfig.getOverrideString();
+ boolean useFast = ( ostr != null && ostr.length() > 0 );
+
+ return new SimpleDateFormat(formatConfig.getPatternString(),
+ formatConfig.getDateFormatSymbols(),
+ formatConfig.getCalendar(),
+ null,
+ formatConfig.getLocale(),
+ useFast,
+ formatConfig.getOverrideString());
+ }
+
+ /*
+ * Initialized fields
+ */
+ private void initialize() {
+ if (locale == null) {
+ locale = ULocale.getDefault();
+ }
+ if (formatData == null) {
+ formatData = new DateFormatSymbols(locale);
+ }
+ if (calendar == null) {
+ calendar = Calendar.getInstance(locale);
+ }
+ if (numberFormat == null) {
+ NumberingSystem ns = NumberingSystem.getInstance(locale);
+ if ( ns.isAlgorithmic() ) {
+ numberFormat = NumberFormat.getInstance(locale);
+ } else {
+ char digit0 = ns.getDescription().charAt(0);
+ // Use a NumberFormat optimized for date formatting
+ numberFormat = new DateNumberFormat(locale, digit0);
+ }
+ }
+ // Note: deferring calendar calculation until when we really need it.
+ // Instead, we just record time of construction for backward compatibility.
+ defaultCenturyBase = System.currentTimeMillis();
+
+ setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
+ initLocalZeroPaddingNumberFormat();
+
+ if (override != null) {
+ initNumberFormatters(locale);
+ }
+
+ }
+
+ // privates for the default pattern
+ private static ULocale cachedDefaultLocale = null;
+ private static String cachedDefaultPattern = null;
+ private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
+
+ /*
+ * Returns the default date and time pattern (SHORT) for the default locale.
+ * This method is only used by the default SimpleDateFormat constructor.
+ */
+ private static synchronized String getDefaultPattern() {
+ ULocale defaultLocale = ULocale.getDefault();
+ if (!defaultLocale.equals(cachedDefaultLocale)) {
+ cachedDefaultLocale = defaultLocale;
+ Calendar cal = Calendar.getInstance(cachedDefaultLocale);
+ try {
+ CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
+ String[] dateTimePatterns = calData.getDateTimePatterns();
+ int glueIndex = 8;
+ if (dateTimePatterns.length >= 13)
+ {
+ glueIndex += (SHORT + 1);
+ }
+ cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
+ new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
+ } catch (MissingResourceException e) {
+ cachedDefaultPattern = FALLBACKPATTERN;
+ }
+ }
+ return cachedDefaultPattern;
+ }
+
+ /* Define one-century window into which to disambiguate dates using
+ * two-digit years.
+ */
+ private void parseAmbiguousDatesAsAfter(Date startDate) {
+ defaultCenturyStart = startDate;
+ calendar.setTime(startDate);
+ defaultCenturyStartYear = calendar.get(Calendar.YEAR);
+ }
+
+ /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
+ * The default start time is 80 years before the creation time of this object.
+ */
+ private void initializeDefaultCenturyStart(long baseTime) {
+ defaultCenturyBase = baseTime;
+ // clone to avoid messing up date stored in calendar object
+ // when this method is called while parsing
+ Calendar tmpCal = (Calendar)calendar.clone();
+ tmpCal.setTimeInMillis(baseTime);
+ tmpCal.add(Calendar.YEAR, -80);
+ defaultCenturyStart = tmpCal.getTime();
+ defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
+ }
+
+ /* Gets the default century start date for this object */
+ private Date getDefaultCenturyStart() {
+ if (defaultCenturyStart == null) {
+ // not yet initialized
+ initializeDefaultCenturyStart(defaultCenturyBase);
+ }
+ return defaultCenturyStart;
+ }
+
+ /* Gets the default century start year for this object */
+ private int getDefaultCenturyStartYear() {
+ if (defaultCenturyStart == null) {
+ // not yet initialized
+ initializeDefaultCenturyStart(defaultCenturyBase);
+ }
+ return defaultCenturyStartYear;
+ }
+
+ /**
+ * Sets the 100-year period 2-digit years will be interpreted as being in
+ * to begin on the date the user specifies.
+ * @param startDate During parsing, two digit years will be placed in the range
+ * <code>startDate</code> to <code>startDate + 100 years</code>.
+ * @stable ICU 2.0
+ */
+ public void set2DigitYearStart(Date startDate) {
+ parseAmbiguousDatesAsAfter(startDate);
+ }
+
+ /**
+ * Returns the beginning date of the 100-year period 2-digit years are interpreted
+ * as being within.
+ * @return the start of the 100-year period into which two digit years are
+ * parsed
+ * @stable ICU 2.0
+ */
+ public Date get2DigitYearStart() {
+ return getDefaultCenturyStart();
+ }
+
+ /**
+ * Overrides DateFormat.
+ * <p>Formats a date or time, which is the standard millis
+ * since January 1, 1970, 00:00:00 GMT.
+ * <p>Example: using the US locale:
+ * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
+ * @param cal the calendar whose date-time value is to be formatted into a date-time string
+ * @param toAppendTo where the new date-time text is to be appended
+ * @param pos the formatting position. On input: an alignment field,
+ * if desired. On output: the offsets of the alignment field.
+ * @return the formatted date-time string.
+ * @see DateFormat
+ * @stable ICU 2.0
+ */
+ public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
+ FieldPosition pos) {
+ TimeZone backupTZ = null;
+ if (cal != calendar && !cal.getType().equals(calendar.getType())) {
+ // Different calendar type
+ // We use the time and time zone from the input calendar, but
+ // do not use the input calendar for field calculation.
+ calendar.setTimeInMillis(cal.getTimeInMillis());
+ backupTZ = calendar.getTimeZone();
+ calendar.setTimeZone(cal.getTimeZone());
+ cal = calendar;
+ }
+ StringBuffer result = format(cal, toAppendTo, pos, null);
+ if (backupTZ != null) {
+ // Restore the original time zone
+ calendar.setTimeZone(backupTZ);
+ }
+ return result;
+ }
+
+ // The actual method to format date. If List attributes is not null,
+ // then attribute information will be recorded.
+ private StringBuffer format(Calendar cal, StringBuffer toAppendTo,
+ FieldPosition pos, List attributes) {
+ // Initialize
+ pos.setBeginIndex(0);
+ pos.setEndIndex(0);
+
+ // Careful: For best performance, minimize the number of calls
+ // to StringBuffer.append() by consolidating appends when
+ // possible.
+
+ Object[] items = getPatternItems();
+ for (int i = 0; i < items.length; i++) {
+ if (items[i] instanceof String) {
+ toAppendTo.append((String)items[i]);
+ } else {
+ PatternItem item = (PatternItem)items[i];
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+ int start = 0;
+ if (attributes != null) {
+ // Save the current length
+ start = toAppendTo.length();
+ }
+//#endif
+ if (useFastFormat) {
+ subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
+ } else {
+ toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));
+ }
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+ if (attributes != null) {
+ // Check the sub format length
+ int end = toAppendTo.length();
+ if (end - start > 0) {
+ // Append the attribute to the list
+ DateFormat.Field attr = patternCharToDateFormatField(item.type);
+ FieldPosition fp = new FieldPosition(attr);
+ fp.setBeginIndex(start);
+ fp.setEndIndex(end);
+ attributes.add(fp);
+ }
+ }
+//#endif
+ }
+ }
+ return toAppendTo;
+
+ }
+
+ // Map pattern character to index
+ private static final int PATTERN_CHAR_BASE = 0x40;
+ private static final int[] PATTERN_CHAR_TO_INDEX =
+ {
+ // A B C D E F G H I J K L M N O
+ -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,
+ // P Q R S T U V W X Y Z
+ -1, 27, -1, 8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,
+ // a b c d e f g h i j k l m n o
+ -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
+ // p q r s t u v w x y z
+ -1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1
+ };
+
+ // Map pattern character index to Calendar field number
+ private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
+ {
+ /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
+ /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
+ /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
+ /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
+ /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
+ /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
+ /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
+ /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
+ /*v*/ Calendar.ZONE_OFFSET,
+ /*c*/ Calendar.DOW_LOCAL,
+ /*L*/ Calendar.MONTH,
+ /*Qq*/ Calendar.MONTH, Calendar.MONTH,
+ /*V*/ Calendar.ZONE_OFFSET,
+ };
+
+ // Map pattern character index to DateFormat field number
+ private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
+ /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
+ /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
+ /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
+ /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
+ /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
+ /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
+ /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
+ /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
+ /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD,
+ /*c*/ DateFormat.STANDALONE_DAY_FIELD,
+ /*L*/ DateFormat.STANDALONE_MONTH_FIELD,
+ /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
+ /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD,
+ };
+
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+ // Map pattern character index to DateFormat.Field
+ private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
+ /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
+ /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
+ /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
+ /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
+ /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
+ /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
+ /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
+ /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
+ /*v*/ DateFormat.Field.TIME_ZONE,
+ /*c*/ DateFormat.Field.DAY_OF_WEEK,
+ /*L*/ DateFormat.Field.MONTH,
+ /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
+ /*V*/ DateFormat.Field.TIME_ZONE,
+ };
+
+ /**
+ * Return a DateFormat.Field constant associated with the specified format pattern
+ * character.
+ *
+ * @param ch The pattern character
+ * @return DateFormat.Field associated with the pattern character
+ *
+ * @stable ICU 3.8
+ */
+ protected DateFormat.Field patternCharToDateFormatField(char ch) {
+ int patternCharIndex = -1;
+ if ('A' <= ch && ch <= 'z') {
+ patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+ }
+ if (patternCharIndex != -1) {
+ return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
+ }
+ return null;
+ }
+//#endif
+
+ /**
+ * Format a single field, given its pattern character. Subclasses may
+ * override this method in order to modify or add formatting
+ * capabilities.
+ * @param ch the pattern character
+ * @param count the number of times ch is repeated in the pattern
+ * @param beginOffset the offset of the output string at the start of
+ * this field; used to set pos when appropriate
+ * @param pos receives the position of a field, when appropriate
+ * @param fmtData the symbols for this formatter
+ * @stable ICU 2.0
+ */
+ protected String subFormat(char ch, int count, int beginOffset,
+ FieldPosition pos, DateFormatSymbols fmtData,
+ Calendar cal)
+ throws IllegalArgumentException
+ {
+ // Note: formatData is ignored
+ StringBuffer buf = new StringBuffer();
+ subFormat(buf, ch, count, beginOffset, pos, cal);
+ return buf.toString();
+ }
+
+ /**
+ * Format a single field; useFastFormat variant. Reuses a
+ * StringBuffer for results instead of creating a String on the
+ * heap for each call.
+ *
+ * NOTE We don't really need the beginOffset parameter, EXCEPT for
+ * the need to support the slow subFormat variant (above) which
+ * has to pass it in to us.
+ *
+ * TODO make this API public
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected void subFormat(StringBuffer buf,
+ char ch, int count, int beginOffset,
+ FieldPosition pos,
+ Calendar cal) {
+ final int maxIntCount = Integer.MAX_VALUE;
+ final int bufstart = buf.length();
+
+ // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
+ int patternCharIndex = -1;
+ if ('A' <= ch && ch <= 'z') {
+ patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+ }
+
+ if (patternCharIndex == -1) {
+ throw new IllegalArgumentException("Illegal pattern character " +
+ "'" + ch + "' in \"" +
+ new String(pattern) + '"');
+ }
+
+ final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+ int value = cal.get(field);
+
+ String zoneString = null;
+
+ NumberFormat currentNumberFormat = getNumberFormat(ch);
+
+ switch (patternCharIndex) {
+ case 0: // 'G' - ERA
+ if (count == 5) {
+ safeAppend(formatData.narrowEras, value, buf);
+ } else if (count == 4) {
+ safeAppend(formatData.eraNames, value, buf);
+ } else {
+ safeAppend(formatData.eras, value, buf);
+ }
+ break;
+ case 1: // 'y' - YEAR
+ /* According to the specification, if the number of pattern letters ('y') is 2,
+ * the year is truncated to 2 digits; otherwise it is interpreted as a number.
+ * But the original code process 'y', 'yy', 'yyy' in the same way. and process
+ * patterns with 4 or more than 4 'y' characters in the same way.
+ * So I change the codes to meet the specification. [Richard/GCl]
+ */
+ if (count == 2)
+ zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
+ else //count = 1 or count > 2
+ zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+ break;
+ case 2: // 'M' - MONTH
+ if (count == 5) {
+ safeAppend(formatData.narrowMonths, value, buf);
+ } else if (count == 4) {
+ safeAppend(formatData.months, value, buf);
+ } else if (count == 3) {
+ safeAppend(formatData.shortMonths, value, buf);
+ } else {
+ zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
+ }
+ break;
+ case 4: // 'k' - HOUR_OF_DAY (1..24)
+ if (value == 0)
+ zeroPaddingNumber(currentNumberFormat,buf,
+ cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
+ count, maxIntCount);
+ else
+ zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+ break;
+ case 8: // 'S' - FRACTIONAL_SECOND
+ // Fractional seconds left-justify
+ {
+ numberFormat.setMinimumIntegerDigits(Math.min(3, count));
+ numberFormat.setMaximumIntegerDigits(maxIntCount);
+ if (count == 1) {
+ value = (value + 50) / 100;
+ } else if (count == 2) {
+ value = (value + 5) / 10;
+ }
+ FieldPosition p = new FieldPosition(-1);
+ numberFormat.format((long) value, buf, p);
+ if (count > 3) {
+ numberFormat.setMinimumIntegerDigits(count - 3);
+ numberFormat.format(0L, buf, p);
+ }
+ }
+ break;
+ case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
+ if (count < 3) {
+ zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+ break;
+ }
+ // For alpha day-of-week, we don't want DOW_LOCAL,
+ // we need the standard DAY_OF_WEEK.
+ value = cal.get(Calendar.DAY_OF_WEEK);
+ // fall through, do not break here
+ case 9: // 'E' - DAY_OF_WEEK
+ if (count == 5) {
+ safeAppend(formatData.narrowWeekdays, value, buf);
+ } else if (count == 4) {
+ safeAppend(formatData.weekdays, value, buf);
+ } else {// count <= 3, use abbreviated form if exists
+ safeAppend(formatData.shortWeekdays, value, buf);
+ }
+ break;
+ case 14: // 'a' - AM_PM
+ safeAppend(formatData.ampms, value, buf);
+ break;
+ case 15: // 'h' - HOUR (1..12)
+ if (value == 0)
+ zeroPaddingNumber(currentNumberFormat,buf,
+ cal.getLeastMaximum(Calendar.HOUR)+1,
+ count, maxIntCount);
+ else
+ zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+ break;
+ case 17: // 'z' - ZONE_OFFSET
+ if (count < 4) {
+ // "z", "zz", "zzz"
+ zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);
+ } else {
+ zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);
+ }
+ if (zoneString != null && zoneString.length() != 0) {
+ buf.append(zoneString);
+ } else {
+ // Use localized GMT format as fallback
+ appendGMT(currentNumberFormat,buf, cal);
+ }
+ break;
+ case 23: // 'Z' - TIMEZONE_RFC
+ if (count < 4) {
+ // RFC822 format, must use ASCII digits
+ int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
+ char sign = '+';
+ if (val < 0) {
+ val = -val;
+ sign = '-';
+ }
+ buf.append(sign);
+
+ int offsetH = val / millisPerHour;
+ val = val % millisPerHour;
+ int offsetM = val / millisPerMinute;
+ val = val % millisPerMinute;
+ int offsetS = val / millisPerSecond;
+
+ int num = 0, denom = 0;
+ if (offsetS == 0) {
+ val = offsetH*100 + offsetM; // HHmm
+ num = val % 10000;
+ denom = 1000;
+ } else {
+ val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss
+ num = val % 1000000;
+ denom = 100000;
+ }
+ while (denom >= 1) {
+ char digit = (char)((num / denom) + '0');
+ buf.append(digit);
+ num = num % denom;
+ denom /= 10;
+ }
+ } else {
+ // long form, localized GMT pattern
+ appendGMT(currentNumberFormat,buf, cal);
+ }
+ break;
+ case 24: // 'v' - TIMEZONE_GENERIC
+ if (count == 1) {
+ // "v"
+ zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);
+ } else if (count == 4) {
+ // "vvvv"
+ zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);
+ }
+ if (zoneString != null && zoneString.length() != 0) {
+ buf.append(zoneString);
+ } else {
+ // Use localized GMT format as fallback
+ appendGMT(currentNumberFormat,buf, cal);
+ }
+ break;
+ case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)
+ if (count < 3) {
+ zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
+ break;
+ }
+ // For alpha day-of-week, we don't want DOW_LOCAL,
+ // we need the standard DAY_OF_WEEK.
+ value = cal.get(Calendar.DAY_OF_WEEK);
+ if (count == 5) {
+ safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
+ } else if (count == 4) {
+ safeAppend(formatData.standaloneWeekdays, value, buf);
+ } else { // count == 3
+ safeAppend(formatData.standaloneShortWeekdays, value, buf);
+ }
+ break;
+ case 26: // 'L' - STANDALONE MONTH
+ if (count == 5) {
+ safeAppend(formatData.standaloneNarrowMonths, value, buf);
+ } else if (count == 4) {
+ safeAppend(formatData.standaloneMonths, value, buf);
+ } else if (count == 3) {
+ safeAppend(formatData.standaloneShortMonths, value, buf);
+ } else {
+ zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
+ }
+ break;
+ case 27: // 'Q' - QUARTER
+ if (count >= 4) {
+ safeAppend(formatData.quarters, value/3, buf);
+ } else if (count == 3) {
+ safeAppend(formatData.shortQuarters, value/3, buf);
+ } else {
+ zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
+ }
+ break;
+ case 28: // 'q' - STANDALONE QUARTER
+ if (count >= 4) {
+ safeAppend(formatData.standaloneQuarters, value/3, buf);
+ } else if (count == 3) {
+ safeAppend(formatData.standaloneShortQuarters, value/3, buf);
+ } else {
+ zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
+ }
+ break;
+ case 29: // 'V' - TIMEZONE_SPECIAL
+ if (count == 1) {
+ // "V"
+ zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);
+ } else if (count == 4) {
+ // "VVVV"
+ zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);
+ }
+ if (zoneString != null && zoneString.length() != 0) {
+ buf.append(zoneString);
+ } else {
+ // Use localized GMT format as fallback
+ appendGMT(currentNumberFormat,buf, cal);
+ }
+ break;
+ default:
+ // case 3: // 'd' - DATE
+ // case 5: // 'H' - HOUR_OF_DAY (0..23)
+ // case 6: // 'm' - MINUTE
+ // case 7: // 's' - SECOND
+ // case 10: // 'D' - DAY_OF_YEAR
+ // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
+ // case 12: // 'w' - WEEK_OF_YEAR
+ // case 13: // 'W' - WEEK_OF_MONTH
+ // case 16: // 'K' - HOUR (0..11)
+ // case 18: // 'Y' - YEAR_WOY
+ // case 20: // 'u' - EXTENDED_YEAR
+ // case 21: // 'g' - JULIAN_DAY
+ // case 22: // 'A' - MILLISECONDS_IN_DAY
+
+ zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+ break;
+ } // switch (patternCharIndex)
+
+ // Set the FieldPosition (for the first occurrence only)
+ if (pos.getBeginIndex() == pos.getEndIndex()) {
+ if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
+ pos.setBeginIndex(beginOffset);
+ pos.setEndIndex(beginOffset + buf.length() - bufstart);
+ }
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+ else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
+ pos.setBeginIndex(beginOffset);
+ pos.setEndIndex(beginOffset + buf.length() - bufstart);
+ }
+//#endif
+ }
+ }
+
+ private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
+ if (array != null && value >= 0 && value < array.length) {
+ appendTo.append(array[value]);
+ }
+ }
+
+ /*
+ * PatternItem store parsed date/time field pattern information.
+ */
+ private static class PatternItem {
+ final char type;
+ final int length;
+ final boolean isNumeric;
+
+ PatternItem(char type, int length) {
+ this.type = type;
+ this.length = length;
+ isNumeric = isNumeric(type, length);
+ }
+ }
+
+ private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();
+ private transient Object[] patternItems;
+
+ /*
+ * Returns parsed pattern items. Each item is either String or
+ * PatternItem.
+ */
+ private Object[] getPatternItems() {
+ if (patternItems != null) {
+ return patternItems;
+ }
+
+ patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);
+ if (patternItems != null) {
+ return patternItems;
+ }
+
+ boolean isPrevQuote = false;
+ boolean inQuote = false;
+ StringBuffer text = new StringBuffer();
+ char itemType = 0; // 0 for string literal, otherwise date/time pattern character
+ int itemLength = 1;
+
+ List items = new ArrayList();
+
+ for (int i = 0; i < pattern.length(); i++) {
+ char ch = pattern.charAt(i);
+ if (ch == '\'') {
+ if (isPrevQuote) {
+ text.append('\'');
+ isPrevQuote = false;
+ } else {
+ isPrevQuote = true;
+ if (itemType != 0) {
+ items.add(new PatternItem(itemType, itemLength));
+ itemType = 0;
+ }
+ }
+ inQuote = !inQuote;
+ } else {
+ isPrevQuote = false;
+ if (inQuote) {
+ text.append(ch);
+ } else {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ // a date/time pattern character
+ if (ch == itemType) {
+ itemLength++;
+ } else {
+ if (itemType == 0) {
+ if (text.length() > 0) {
+ items.add(text.toString());
+ text.setLength(0);
+ }
+ } else {
+ items.add(new PatternItem(itemType, itemLength));
+ }
+ itemType = ch;
+ itemLength = 1;
+ }
+ } else {
+ // a string literal
+ if (itemType != 0) {
+ items.add(new PatternItem(itemType, itemLength));
+ itemType = 0;
+ }
+ text.append(ch);
+ }
+ }
+ }
+ }
+ // handle last item
+ if (itemType == 0) {
+ if (text.length() > 0) {
+ items.add(text.toString());
+ text.setLength(0);
+ }
+ } else {
+ items.add(new PatternItem(itemType, itemLength));
+ }
+
+ patternItems = new Object[items.size()];
+ items.toArray(patternItems);
+
+ PARSED_PATTERN_CACHE.put(pattern, patternItems);
+
+ return patternItems;
+ }
+
+ /*
+ * Time zone localized GMT format stuffs
+ */
+ private static final String STR_GMT = "GMT";
+ private static final String STR_UT = "UT";
+ private static final String STR_UTC = "UTC";
+ private static final int STR_GMT_LEN = 3;
+ private static final int STR_UT_LEN = 2;
+ private static final int STR_UTC_LEN = 3;
+ private static final char PLUS = '+';
+ private static final char MINUS = '-';
+ private static final char COLON = ':';
+
+ private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {
+ int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
+
+ if (isDefaultGMTFormat()) {
+ formatGMTDefault(currentNumberFormat,buf, offset);
+ } else {
+ int sign = DateFormatSymbols.OFFSET_POSITIVE;
+ if (offset < 0) {
+ offset = -offset;
+ sign = DateFormatSymbols.OFFSET_NEGATIVE;
+ }
+ int width = offset%(60*1000) == 0 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;
+
+ MessageFormat fmt = getGMTFormatter(sign, width);
+ fmt.format(new Object[] {new Long(offset)}, buf, null);
+ }
+ }
+
+ private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {
+ buf.append(STR_GMT);
+ if (offset >= 0) {
+ buf.append(PLUS);
+ } else {
+ buf.append(MINUS);
+ offset = -offset;
+ }
+ offset /= 1000; // now in seconds
+ int sec = offset % 60;
+ offset /= 60;
+ int min = offset % 60;
+ int hour = offset / 60;
+
+ zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);
+ buf.append(COLON);
+ zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);
+ if (sec != 0) {
+ buf.append(COLON);
+ zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);
+ }
+ }
+
+ private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
+ if (!isDefaultGMTFormat()) {
+ int start = pos.getIndex();
+ String gmtPattern = formatData.gmtFormat;
+
+ // Quick check
+ boolean prefixMatch = false;
+ int prefixLen = gmtPattern.indexOf('{');
+ if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {
+ prefixMatch = true;
+ }
+
+ if (prefixMatch) {
+ // Prefix matched
+ MessageFormat fmt;
+ Object[] parsedObjects;
+ int offset;
+
+ // Try negative Hms
+ fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
+ parsedObjects = fmt.parse(text, pos);
+ if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
+ && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_NEGATIVE)) {
+ offset = (int)((Date)parsedObjects[0]).getTime();
+ return new Integer(-offset /* negative */);
+ }
+
+ // Reset ParsePosition
+ pos.setIndex(start);
+ pos.setErrorIndex(-1);
+
+ // Try positive Hms
+ fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
+ parsedObjects = fmt.parse(text, pos);
+ if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
+ && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_POSITIVE)) {
+ offset = (int)((Date)parsedObjects[0]).getTime();
+ return new Integer(offset);
+ }
+
+ // Reset ParsePosition
+ pos.setIndex(start);
+ pos.setErrorIndex(-1);
+
+ // Try negative Hm
+ fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HM);
+ parsedObjects = fmt.parse(text, pos);
+ if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
+ offset = (int)((Date)parsedObjects[0]).getTime();
+ return new Integer(-offset /* negative */);
+ }
+
+ // Reset ParsePosition
+ pos.setIndex(start);
+ pos.setErrorIndex(-1);
+
+ // Try positive Hm
+ fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HM);
+ parsedObjects = fmt.parse(text, pos);
+ if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
+ offset = (int)((Date)parsedObjects[0]).getTime();
+ return new Integer(offset);
+ }
+
+ // Reset ParsePosition
+ pos.setIndex(start);
+ pos.setErrorIndex(-1);
+ }
+ }
+
+ return parseGMTDefault(text, pos, currentNumberFormat);
+ }
+
+ private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
+ int start = pos.getIndex();
+
+ if (start + STR_UT_LEN + 1 >= text.length()) {
+ pos.setErrorIndex(start);
+ return null;
+ }
+
+ int cur = start;
+ // "GMT"
+ if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
+ cur += STR_GMT_LEN;
+ } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
+ cur += STR_UT_LEN;
+ } else {
+ pos.setErrorIndex(start);
+ return null;
+ }
+ // Sign
+ boolean negative = false;
+ if (text.charAt(cur) == MINUS) {
+ negative = true;
+ } else if (text.charAt(cur) != PLUS) {
+ pos.setErrorIndex(cur);
+ return null;
+ }
+ cur++;
+
+ // Numbers
+ int numLen;
+ pos.setIndex(cur);
+
+ Number n = parseInt(text, 6, pos, false,currentNumberFormat);
+ numLen = pos.getIndex() - cur;
+
+ if (n == null || numLen <= 0 || numLen > 6) {
+ pos.setIndex(start);
+ pos.setErrorIndex(cur);
+ return null;
+ }
+
+ int numVal = n.intValue();
+
+ int hour = 0;
+ int min = 0;
+ int sec = 0;
+
+ if (numLen <= 2) {
+ // H[H][:mm[:ss]]
+ hour = numVal;
+ cur += numLen;
+ if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
+ cur++;
+ pos.setIndex(cur);
+ n = parseInt(text, 2, pos, false,currentNumberFormat);
+ numLen = pos.getIndex() - cur;
+ if (n != null && numLen == 2) {
+ // got minute field
+ min = n.intValue();
+ cur += numLen;
+ if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
+ cur++;
+ pos.setIndex(cur);
+ n = parseInt(text, 2, pos, false,currentNumberFormat);
+ numLen = pos.getIndex() - cur;
+ if (n != null && numLen == 2) {
+ // got second field
+ sec = n.intValue();
+ } else {
+ // reset position
+ pos.setIndex(cur - 1);
+ pos.setErrorIndex(-1);
+ }
+ }
+ } else {
+ // reset postion
+ pos.setIndex(cur - 1);
+ pos.setErrorIndex(-1);
+ }
+ }
+ } else if (numLen == 3 || numLen == 4) {
+ // Hmm or HHmm
+ hour = numVal / 100;
+ min = numVal % 100;
+ } else { // numLen == 5 || numLen == 6
+ // Hmmss or HHmmss
+ hour = numVal / 10000;
+ min = (numVal % 10000) / 100;
+ sec = numVal % 100;
+ }
+
+ int offset = ((hour*60 + min)*60 + sec)*1000;
+ if (negative) {
+ offset = -offset;
+ }
+ return new Integer(offset);
+ }
+
+ transient private WeakReference[] gmtfmtCache;
+
+ private MessageFormat getGMTFormatter(int sign, int width) {
+ MessageFormat fmt = null;
+ if (gmtfmtCache == null) {
+ gmtfmtCache = new WeakReference[4];
+ }
+ int cacheIdx = sign*2 + width;
+ if (gmtfmtCache[cacheIdx] != null) {
+ fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();
+ }
+ if (fmt == null) {
+ fmt = new MessageFormat(formatData.gmtFormat);
+ GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
+ SimpleDateFormat sdf = (SimpleDateFormat)this.clone();
+ sdf.setCalendar(gcal);
+ sdf.applyPattern(formatData.getGmtHourFormat(sign, width));
+ fmt.setFormat(0, sdf);
+ gmtfmtCache[cacheIdx] = new WeakReference(fmt);
+ }
+ return fmt;
+ }
+
+ transient private int[] gmtFormatHmsMinLen = null;
+
+ private int getGMTFormatMinHMSLen(int sign) {
+ if (gmtFormatHmsMinLen == null) {
+ gmtFormatHmsMinLen = new int[2];
+ Long offset = new Long(60*60*1000); // 1 hour
+
+ StringBuffer buf = new StringBuffer();
+ MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
+ fmtNeg.format(new Object[] {offset}, buf, null);
+ gmtFormatHmsMinLen[0] = buf.length();
+
+ buf.setLength(0);
+ MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
+ fmtPos.format(new Object[] {offset}, buf, null);
+ gmtFormatHmsMinLen[1] = buf.length();
+ }
+ return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];
+ }
+
+ private boolean isDefaultGMTFormat() {
+ // GMT pattern
+ if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {
+ return false;
+ }
+ // GMT offset hour patters
+ boolean res = true;
+ for (int sign = 0; sign < 2 && res; sign++) {
+ for (int width = 0; width < 2; width++) {
+ if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width].equals(formatData.getGmtHourFormat(sign, width))) {
+ res = false;
+ break;
+ }
+ }
+ }
+ return res;
+ }
+
+ /*
+ * Internal method. Returns null if the value of an array is empty, or if the
+ * index is out of bounds
+ */
+/* private String getZoneArrayValue(String[] zs, int ix) {
+ if (ix >= 0 && ix < zs.length) {
+ String result = zs[ix];
+ if (result != null && result.length() != 0) {
+ return result;
+ }
+ }
+ return null;
+ }*/
+
+ /**
+ * Internal high-speed method. Reuses a StringBuffer for results
+ * instead of creating a String on the heap for each call.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
+ int minDigits, int maxDigits) {
+ if (useLocalZeroPaddingNumberFormat) {
+ fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
+ } else {
+ nf.setMinimumIntegerDigits(minDigits);
+ nf.setMaximumIntegerDigits(maxDigits);
+ nf.format(value, buf, new FieldPosition(-1));
+ }
+ }
+
+ /**
+ * Overrides superclass method
+ * @stable ICU 2.0
+ */
+ public void setNumberFormat(NumberFormat newNumberFormat) {
+ // Override this method to update local zero padding number formatter
+ super.setNumberFormat(newNumberFormat);
+ initLocalZeroPaddingNumberFormat();
+ }
+
+ private void initLocalZeroPaddingNumberFormat() {
+ if (numberFormat instanceof DecimalFormat) {
+ zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
+ useLocalZeroPaddingNumberFormat = true;
+ } else if (numberFormat instanceof DateNumberFormat) {
+ zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();
+ useLocalZeroPaddingNumberFormat = true;
+ } else {
+ useLocalZeroPaddingNumberFormat = false;
+ }
+
+ if (useLocalZeroPaddingNumberFormat) {
+ decimalBuf = new char[10]; // sufficient for int numbers
+ }
+ }
+
+ // If true, use local version of zero padding number format
+ private transient boolean useLocalZeroPaddingNumberFormat;
+ private transient char zeroDigit;
+ private transient char[] decimalBuf;
+
+ /*
+ * Lightweight zero padding integer number format function.
+ *
+ * Note: This implementation is almost equivalent to format method in DateNumberFormat.
+ * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
+ * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative
+ * date format test case, having local implementation is ~10% faster than using one in
+ * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference.
+ *
+ * -Yoshito
+ */
+ private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
+ int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
+ int index = limit - 1;
+ while (true) {
+ decimalBuf[index] = (char)((value % 10) + zeroDigit);
+ value /= 10;
+ if (index == 0 || value == 0) {
+ break;
+ }
+ index--;
+ }
+ int padding = minDigits - (limit - index);
+ for (; padding > 0; padding--) {
+ decimalBuf[--index] = zeroDigit;
+ }
+ int length = limit - index;
+ buf.append(decimalBuf, index, length);
+ }
+
+ /**
+ * Formats a number with the specified minimum and maximum number of digits.
+ * @stable ICU 2.0
+ */
+ protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
+ {
+ numberFormat.setMinimumIntegerDigits(minDigits);
+ numberFormat.setMaximumIntegerDigits(maxDigits);
+ return numberFormat.format(value);
+ }
+
+ /**
+ * Format characters that indicate numeric fields. The character
+ * at index 0 is treated specially.
+ */
+ private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";
+
+ /**
+ * Return true if the given format character, occuring count
+ * times, represents a numeric field.
+ */
+ private static final boolean isNumeric(char formatChar, int count) {
+ int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
+ return (i > 0 || (i == 0 && count < 3));
+ }
+
+ /**
+ * Overrides DateFormat
+ * @see DateFormat
+ * @stable ICU 2.0
+ */
+ public void parse(String text, Calendar cal, ParsePosition parsePos)
+ {
+ TimeZone backupTZ = null;
+ Calendar resultCal = null;
+ if (cal != calendar && !cal.getType().equals(calendar.getType())) {
+ // Different calendar type
+ // We use the time/zone from the input calendar, but
+ // do not use the input calendar for field calculation.
+ calendar.setTimeInMillis(cal.getTimeInMillis());
+ backupTZ = calendar.getTimeZone();
+ calendar.setTimeZone(cal.getTimeZone());
+ resultCal = cal;
+ cal = calendar;
+ }
+
+ int pos = parsePos.getIndex();
+ int start = pos;
+
+ // Reset tztype
+ tztype = TZTYPE_UNK;
+ boolean[] ambiguousYear = { false };
+
+ // item index for the first numeric field within a contiguous numeric run
+ int numericFieldStart = -1;
+ // item length for the first numeric field within a contiguous numeric run
+ int numericFieldLength = 0;
+ // start index of numeric text run in the input text
+ int numericStartPos = 0;
+
+ Object[] items = getPatternItems();
+ int i = 0;
+ while (i < items.length) {
+ if (items[i] instanceof PatternItem) {
+ // Handle pattern field
+ PatternItem field = (PatternItem)items[i];
+ if (field.isNumeric) {
+ // Handle fields within a run of abutting numeric fields. Take
+ // the pattern "HHmmss" as an example. We will try to parse
+ // 2/2/2 characters of the input text, then if that fails,
+ // 1/2/2. We only adjust the width of the leftmost field; the
+ // others remain fixed. This allows "123456" => 12:34:56, but
+ // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
+ // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
+ if (numericFieldStart == -1) {
+ // check if this field is followed by abutting another numeric field
+ if ((i + 1) < items.length
+ && (items[i + 1] instanceof PatternItem)
+ && ((PatternItem)items[i + 1]).isNumeric) {
+ // record the first numeric field within a numeric text run
+ numericFieldStart = i;
+ numericFieldLength = field.length;
+ numericStartPos = pos;
+ }
+ }
+ }
+ if (numericFieldStart != -1) {
+ // Handle a numeric field within abutting numeric fields
+ int len = field.length;
+ if (numericFieldStart == i) {
+ len = numericFieldLength;
+ }
+
+ // Parse a numeric field
+ pos = subParse(text, pos, field.type, len,
+ true, false, ambiguousYear, cal);
+
+ if (pos < 0) {
+ // If the parse fails anywhere in the numeric run, back up to the
+ // start of the run and use shorter pattern length for the first
+ // numeric field.
+ --numericFieldLength;
+ if (numericFieldLength == 0) {
+ // can not make shorter any more
+ parsePos.setIndex(start);
+ parsePos.setErrorIndex(pos);
+ if (backupTZ != null) {
+ calendar.setTimeZone(backupTZ);
+ }
+ return;
+ }
+ i = numericFieldStart;
+ pos = numericStartPos;
+ continue;
+ }
+
+ } else {
+ // Handle a non-numeric field or a non-abutting numeric field
+ numericFieldStart = -1;
+
+ int s = pos;
+ pos = subParse(text, pos, field.type, field.length,
+ false, true, ambiguousYear, cal);
+ if (pos < 0) {
+ parsePos.setIndex(start);
+ parsePos.setErrorIndex(s);
+ if (backupTZ != null) {
+ calendar.setTimeZone(backupTZ);
+ }
+ return;
+ }
+ }
+ } else {
+ // Handle literal pattern text literal
+ numericFieldStart = -1;
+
+ String patl = (String)items[i];
+ int plen = patl.length();
+ int tlen = text.length();
+ int idx = 0;
+ while (idx < plen && pos < tlen) {
+ char pch = patl.charAt(idx);
+ char ich = text.charAt(pos);
+ if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {
+ // White space characters found in both patten and input.
+ // Skip contiguous white spaces.
+ while ((idx + 1) < plen &&
+ UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {
+ ++idx;
+ }
+ while ((pos + 1) < tlen &&
+ UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {
+ ++pos;
+ }
+ } else if (pch != ich) {
+ break;
+ }
+ ++idx;
+ ++pos;
+ }
+ if (idx != plen) {
+ // Set the position of mismatch
+ parsePos.setIndex(start);
+ parsePos.setErrorIndex(pos);
+ if (backupTZ != null) {
+ calendar.setTimeZone(backupTZ);
+ }
+ return;
+ }
+ }
+ ++i;
+ }
+
+ // At this point the fields of Calendar have been set. Calendar
+ // will fill in default values for missing fields when the time
+ // is computed.
+
+ parsePos.setIndex(pos);
+
+ // This part is a problem: When we call parsedDate.after, we compute the time.
+ // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
+ // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
+ // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
+ // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
+ // on that day. It is therefore parsed out to fields as 3:30 am. Then we
+ // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
+ // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
+ /*
+ Date parsedDate = cal.getTime();
+ if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
+ cal.add(Calendar.YEAR, 100);
+ parsedDate = cal.getTime();
+ }
+ */
+ // Because of the above condition, save off the fields in case we need to readjust.
+ // The procedure we use here is not particularly efficient, but there is no other
+ // way to do this given the API restrictions present in Calendar. We minimize
+ // inefficiency by only performing this computation when it might apply, that is,
+ // when the two-digit year is equal to the start year, and thus might fall at the
+ // front or the back of the default century. This only works because we adjust
+ // the year correctly to start with in other cases -- see subParse().
+ try {
+ if (ambiguousYear[0] || tztype != TZTYPE_UNK) {
+ // We need a copy of the fields, and we need to avoid triggering a call to
+ // complete(), which will recalculate the fields. Since we can't access
+ // the fields[] array in Calendar, we clone the entire object. This will
+ // stop working if Calendar.clone() is ever rewritten to call complete().
+ Calendar copy;
+ if (ambiguousYear[0]) { // the two-digit year == the default start year
+ copy = (Calendar)cal.clone();
+ Date parsedDate = copy.getTime();
+ if (parsedDate.before(getDefaultCenturyStart())) {
+ // We can't use add here because that does a complete() first.
+ cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
+ }
+ }
+ if (tztype != TZTYPE_UNK) {
+ copy = (Calendar)cal.clone();
+ TimeZone tz = copy.getTimeZone();
+ BasicTimeZone btz = null;
+ if (tz instanceof BasicTimeZone) {
+ btz = (BasicTimeZone)tz;
+ }
+
+ // Get local millis
+ copy.set(Calendar.ZONE_OFFSET, 0);
+ copy.set(Calendar.DST_OFFSET, 0);
+ long localMillis = copy.getTimeInMillis();
+
+ // Make sure parsed time zone type (Standard or Daylight)
+ // matches the rule used by the parsed time zone.
+ int[] offsets = new int[2];
+ if (btz != null) {
+ if (tztype == TZTYPE_STD) {
+ btz.getOffsetFromLocal(localMillis,
+ BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
+ } else {
+ btz.getOffsetFromLocal(localMillis,
+ BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
+ }
+ } else {
+ // No good way to resolve ambiguous time at transition,
+ // but following code work in most case.
+ tz.getOffset(localMillis, true, offsets);
+
+ if (tztype == TZTYPE_STD && offsets[1] != 0 || tztype == TZTYPE_DST && offsets[1] == 0) {
+ // Roll back one day and try it again.
+ // Note: This code assumes 1. timezone transition only happens once within 24 hours at max
+ // 2. the difference of local offsets at the transition is less than 24 hours.
+ tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
+ }
+ }
+
+ // Now, compare the results with parsed type, either standard or daylight saving time
+ int resolvedSavings = offsets[1];
+ if (tztype == TZTYPE_STD) {
+ if (offsets[1] != 0) {
+ // Override DST_OFFSET = 0 in the result calendar
+ resolvedSavings = 0;
+ }
+ } else { // tztype == TZTYPE_DST
+ if (offsets[1] == 0) {
+ if (btz != null) {
+ long time = localMillis + offsets[0];
+ // We use the nearest daylight saving time rule.
+ TimeZoneTransition beforeTrs, afterTrs;
+ long beforeT = time, afterT = time;
+ int beforeSav = 0, afterSav = 0;
+
+ // Search for DST rule before or on the time
+ while (true) {
+ beforeTrs = btz.getPreviousTransition(beforeT, true);
+ if (beforeTrs == null) {
+ break;
+ }
+ beforeT = beforeTrs.getTime() - 1;
+ beforeSav = beforeTrs.getFrom().getDSTSavings();
+ if (beforeSav != 0) {
+ break;
+ }
+ }
+
+ // Search for DST rule after the time
+ while (true) {
+ afterTrs = btz.getNextTransition(afterT, false);
+ if (afterTrs == null) {
+ break;
+ }
+ afterT = afterTrs.getTime();
+ afterSav = afterTrs.getTo().getDSTSavings();
+ if (afterSav != 0) {
+ break;
+ }
+ }
+
+ if (beforeTrs != null && afterTrs != null) {
+ if (time - beforeT > afterT - time) {
+ resolvedSavings = afterSav;
+ } else {
+ resolvedSavings = beforeSav;
+ }
+ } else if (beforeTrs != null && beforeSav != 0) {
+ resolvedSavings = beforeSav;
+ } else if (afterTrs != null && afterSav != 0) {
+ resolvedSavings = afterSav;
+ } else {
+ resolvedSavings = btz.getDSTSavings();
+ }
+ } else {
+ resolvedSavings = tz.getDSTSavings();
+ }
+ if (resolvedSavings == 0) {
+ // Final fallback
+ resolvedSavings = millisPerHour;
+ }
+ }
+ }
+ cal.set(Calendar.ZONE_OFFSET, offsets[0]);
+ cal.set(Calendar.DST_OFFSET, resolvedSavings);
+ }
+ }
+ }
+ // An IllegalArgumentException will be thrown by Calendar.getTime()
+ // if any fields are out of range, e.g., MONTH == 17.
+ catch (IllegalArgumentException e) {
+ parsePos.setErrorIndex(pos);
+ parsePos.setIndex(start);
+ if (backupTZ != null) {
+ calendar.setTimeZone(backupTZ);
+ }
+ return;
+ }
+ // Set the parsed result if local calendar is used
+ // instead of the input calendar
+ if (resultCal != null) {
+ resultCal.setTimeZone(cal.getTimeZone());
+ resultCal.setTimeInMillis(cal.getTimeInMillis());
+ }
+ // Restore the original time zone if required
+ if (backupTZ != null) {
+ calendar.setTimeZone(backupTZ);
+ }
+ }
+
+ /**
+ * Attempt to match the text at a given position against an array of
+ * strings. Since multiple strings in the array may match (for
+ * example, if the array contains "a", "ab", and "abc", all will match
+ * the input string "abcd") the longest match is returned. As a side
+ * effect, the given field of <code>cal</code> is set to the index
+ * of the best match, if there is one.
+ * @param text the time text being parsed.
+ * @param start where to start parsing.
+ * @param field the date field being parsed.
+ * @param data the string array to parsed.
+ * @return the new start position if matching succeeded; a negative
+ * number indicating matching failure, otherwise. As a side effect,
+ * sets the <code>cal</code> field <code>field</code> to the index
+ * of the best match, if matching succeeded.
+ * @stable ICU 2.0
+ */
+ protected int matchString(String text, int start, int field, String[] data, Calendar cal)
+ {
+ int i = 0;
+ int count = data.length;
+
+ if (field == Calendar.DAY_OF_WEEK) i = 1;
+
+ // There may be multiple strings in the data[] array which begin with
+ // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
+ // We keep track of the longest match, and return that. Note that this
+ // unfortunately requires us to test all array elements.
+ int bestMatchLength = 0, bestMatch = -1;
+ for (; i<count; ++i)
+ {
+ int length = data[i].length();
+ // Always compare if we have no match yet; otherwise only compare
+ // against potentially better matches (longer strings).
+ if (length > bestMatchLength &&
+ text.regionMatches(true, start, data[i], 0, length))
+ {
+ bestMatch = i;
+ bestMatchLength = length;
+ }
+ }
+ if (bestMatch >= 0)
+ {
+ cal.set(field, bestMatch);
+ return start + bestMatchLength;
+ }
+ return -start;
+ }
+
+ /**
+ * Attempt to match the text at a given position against an array of quarter
+ * strings. Since multiple strings in the array may match (for
+ * example, if the array contains "a", "ab", and "abc", all will match
+ * the input string "abcd") the longest match is returned. As a side
+ * effect, the given field of <code>cal</code> is set to the index
+ * of the best match, if there is one.
+ * @param text the time text being parsed.
+ * @param start where to start parsing.
+ * @param field the date field being parsed.
+ * @param data the string array to parsed.
+ * @return the new start position if matching succeeded; a negative
+ * number indicating matching failure, otherwise. As a side effect,
+ * sets the <code>cal</code> field <code>field</code> to the index
+ * of the best match, if matching succeeded.
+ * @stable ICU 2.0
+ */
+ protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
+ {
+ int i = 0;
+ int count = data.length;
+
+ // There may be multiple strings in the data[] array which begin with
+ // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
+ // We keep track of the longest match, and return that. Note that this
+ // unfortunately requires us to test all array elements.
+ int bestMatchLength = 0, bestMatch = -1;
+ for (; i<count; ++i) {
+ int length = data[i].length();
+ // Always compare if we have no match yet; otherwise only compare
+ // against potentially better matches (longer strings).
+ if (length > bestMatchLength &&
+ text.regionMatches(true, start, data[i], 0, length)) {
+ bestMatch = i;
+ bestMatchLength = length;
+ }
+ }
+
+ if (bestMatch >= 0) {
+ cal.set(field, bestMatch * 3);
+ return start + bestMatchLength;
+ }
+
+ return -start;
+ }
+
+ /**
+ * Protected method that converts one field of the input string into a
+ * numeric field value in <code>cal</code>. Returns -start (for
+ * ParsePosition) if failed. Subclasses may override this method to
+ * modify or add parsing capabilities.
+ * @param text the time text to be parsed.
+ * @param start where to start parsing.
+ * @param ch the pattern character for the date field text to be parsed.
+ * @param count the count of a pattern character.
+ * @param obeyCount if true, then the next field directly abuts this one,
+ * and we should use the count to know when to stop parsing.
+ * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
+ * is true, then a two-digit year was parsed and may need to be readjusted.
+ * @return the new start position if matching succeeded; a negative
+ * number indicating matching failure, otherwise. As a side effect,
+ * set the appropriate field of <code>cal</code> with the parsed
+ * value.
+ * @stable ICU 2.0
+ */
+ protected int subParse(String text, int start, char ch, int count,
+ boolean obeyCount, boolean allowNegative,
+ boolean[] ambiguousYear, Calendar cal)
+ {
+ Number number = null;
+ NumberFormat currentNumberFormat = null;
+ int value = 0;
+ int i;
+ ParsePosition pos = new ParsePosition(0);
+ //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c
+ int patternCharIndex = -1;
+ if ('A' <= ch && ch <= 'z') {
+ patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+ }
+
+ if (patternCharIndex == -1) {
+ return -start;
+ }
+
+ currentNumberFormat = getNumberFormat(ch);
+
+ int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+
+ // If there are any spaces here, skip over them. If we hit the end
+ // of the string, then fail.
+ for (;;) {
+ if (start >= text.length()) {
+ return -start;
+ }
+ int c = UTF16.charAt(text, start);
+ if (!UCharacter.isUWhiteSpace(c)) {
+ break;
+ }
+ start += UTF16.getCharCount(c);
+ }
+ pos.setIndex(start);
+
+ // We handle a few special cases here where we need to parse
+ // a number value. We handle further, more generic cases below. We need
+ // to handle some of them here because some fields require extra processing on
+ // the parsed value.
+ if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
+ patternCharIndex == 15 /*HOUR1_FIELD*/ ||
+ (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
+ patternCharIndex == 1 ||
+ patternCharIndex == 8)
+ {
+ // It would be good to unify this with the obeyCount logic below,
+ // but that's going to be difficult.
+ if (obeyCount)
+ {
+ if ((start+count) > text.length()) return -start;
+ number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
+ }
+ else number = parseInt(text, pos, allowNegative,currentNumberFormat);
+ if (number == null)
+ return -start;
+ value = number.intValue();
+ }
+
+ switch (patternCharIndex)
+ {
+ case 0: // 'G' - ERA
+ if (count == 4) {
+ return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
+ } else {
+ return matchString(text, start, Calendar.ERA, formatData.eras, cal);
+ }
+ case 1: // 'y' - YEAR
+ // If there are 3 or more YEAR pattern characters, this indicates
+ // that the year value is to be treated literally, without any
+ // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
+ // we made adjustments to place the 2-digit year in the proper
+ // century, for parsed strings from "00" to "99". Any other string
+ // is treated literally: "2250", "-1", "1", "002".
+ /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
+ if (count == 2 && (pos.getIndex() - start) == 2
+ && UCharacter.isDigit(text.charAt(start))
+ && UCharacter.isDigit(text.charAt(start+1)))
+ {
+ // Assume for example that the defaultCenturyStart is 6/18/1903.
+ // This means that two-digit years will be forced into the range
+ // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
+ // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
+ // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
+ // other fields specify a date before 6/18, or 1903 if they specify a
+ // date afterwards. As a result, 03 is an ambiguous year. All other
+ // two-digit years are unambiguous.
+ int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
+ ambiguousYear[0] = value == ambiguousTwoDigitYear;
+ value += (getDefaultCenturyStartYear()/100)*100 +
+ (value < ambiguousTwoDigitYear ? 100 : 0);
+ }
+ cal.set(Calendar.YEAR, value);
+ return pos.getIndex();
+ case 2: // 'M' - MONTH
+ if (count <= 2) // i.e., M or MM.
+ {
+ // Don't want to parse the month if it is a string
+ // while pattern uses numeric style: M or MM.
+ // [We computed 'value' above.]
+ cal.set(Calendar.MONTH, value - 1);
+ return pos.getIndex();
+ }
+ else
+ {
+ // count >= 3 // i.e., MMM or MMMM
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 first:
+ int newStart = matchString(text, start, Calendar.MONTH,
+ formatData.months, cal);
+ if (newStart > 0) {
+ return newStart;
+ } else { // count == 4 failed, now try count == 3
+ return matchString(text, start, Calendar.MONTH,
+ formatData.shortMonths, cal);
+ }
+ }
+ case 26: // 'L' - STAND_ALONE_MONTH
+ if (count <= 2) // i.e., M or MM.
+ {
+ // Don't want to parse the month if it is a string
+ // while pattern uses numeric style: M or MM.
+ // [We computed 'value' above.]
+ cal.set(Calendar.MONTH, value - 1);
+ return pos.getIndex();
+ }
+ else
+ {
+ // count >= 3 // i.e., MMM or MMMM
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 first:
+ int newStart = matchString(text, start, Calendar.MONTH,
+ formatData.standaloneMonths, cal);
+ if (newStart > 0) {
+ return newStart;
+ } else { // count == 4 failed, now try count == 3
+ return matchString(text, start, Calendar.MONTH,
+ formatData.standaloneShortMonths, cal);
+ }
+ }
+ case 4: // 'k' - HOUR_OF_DAY (1..24)
+ // [We computed 'value' above.]
+ if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
+ cal.set(Calendar.HOUR_OF_DAY, value);
+ return pos.getIndex();
+ case 8: // 'S' - FRACTIONAL_SECOND
+ // Fractional seconds left-justify
+ i = pos.getIndex() - start;
+ if (i < 3) {
+ while (i < 3) {
+ value *= 10;
+ i++;
+ }
+ } else {
+ int a = 1;
+ while (i > 3) {
+ a *= 10;
+ i--;
+ }
+ value = (value + (a>>1)) / a;
+ }
+ cal.set(Calendar.MILLISECOND, value);
+ return pos.getIndex();
+ case 9: { // 'E' - DAY_OF_WEEK
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 (EEEE) first:
+ int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
+ formatData.weekdays, cal);
+ if (newStart > 0) {
+ return newStart;
+ } else { // EEEE failed, now try EEE
+ return matchString(text, start, Calendar.DAY_OF_WEEK,
+ formatData.shortWeekdays, cal);
+ }
+ }
+ case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 (cccc) first:
+ int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
+ formatData.standaloneWeekdays, cal);
+ if (newStart > 0) {
+ return newStart;
+ } else { // cccc failed, now try ccc
+ return matchString(text, start, Calendar.DAY_OF_WEEK,
+ formatData.standaloneShortWeekdays, cal);
+ }
+ }
+ case 14: // 'a' - AM_PM
+ return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
+ case 15: // 'h' - HOUR (1..12)
+ // [We computed 'value' above.]
+ if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;
+ cal.set(Calendar.HOUR, value);
+ return pos.getIndex();
+ case 17: // 'z' - ZONE_OFFSET
+ case 23: // 'Z' - TIMEZONE_RFC
+ case 24: // 'v' - TIMEZONE_GENERIC
+ case 29: // 'V' - TIMEZONE_SPECIAL
+ {
+ TimeZone tz = null;
+ int offset = 0;
+ boolean parsed = false;
+
+ // Step 1
+ // Check if this is a long GMT offset string (either localized or default)
+ Integer gmtoff = parseGMT(text, pos, currentNumberFormat);
+ if (gmtoff != null) {
+ offset = gmtoff.intValue();
+ parsed = true;
+ }
+
+ if (!parsed) {
+ // Step 2
+ // Check if this is an RFC822 time zone offset.
+ // ICU supports the standard RFC822 format [+|-]HHmm
+ // and its extended form [+|-]HHmmSS.
+
+ do {
+ int sign = 0;
+ char signChar = text.charAt(start);
+ if (signChar == '+') {
+ sign = 1;
+ } else if (signChar == '-') {
+ sign = -1;
+ } else {
+ // Not an RFC822 offset string
+ break;
+ }
+
+ // Parse digits
+ int orgPos = start + 1;
+ pos.setIndex(orgPos);
+ number = parseInt(text, 6, pos, false,currentNumberFormat);
+ int numLen = pos.getIndex() - orgPos;
+ if (numLen <= 0) {
+ break;
+ }
+
+ // Followings are possible format (excluding sign char)
+ // HHmmSS
+ // HmmSS
+ // HHmm
+ // Hmm
+ // HH
+ // H
+ int val = number.intValue();
+ int hour = 0, min = 0, sec = 0;
+ switch(numLen) {
+ case 1: // H
+ case 2: // HH
+ hour = val;
+ break;
+ case 3: // Hmm
+ case 4: // HHmm
+ hour = val / 100;
+ min = val % 100;
+ break;
+ case 5: // Hmmss
+ case 6: // HHmmss
+ hour = val / 10000;
+ min = (val % 10000) / 100;
+ sec = val % 100;
+ break;
+ }
+ if (hour > 23 || min > 59 || sec > 59) {
+ // Invalid value range
+ break;
+ }
+ offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;
+ parsed = true;
+ } while (false);
+
+ if (!parsed) {
+ // Failed to parse. Reset the position.
+ pos.setIndex(start);
+ }
+ }
+
+ if (parsed) {
+ // offset was successfully parsed as either a long GMT string or RFC822 zone offset
+ // string. Create normalized zone ID for the offset.
+ tz = ZoneMeta.getCustomTimeZone(offset);
+ cal.setTimeZone(tz);
+ return pos.getIndex();
+ }
+
+ // Step 3
+ // At this point, check for named time zones by looking through
+ // the locale data from the DateFormatZoneData strings.
+ // Want to be able to parse both short and long forms.
+ // optimize for calendar's current time zone
+ ZoneStringInfo zsinfo = null;
+ switch (patternCharIndex) {
+ case 17: // 'z' - ZONE_OFFSET
+ if (count < 4) {
+ zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
+ } else {
+ zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);
+ }
+ break;
+ case 24: // 'v' - TIMEZONE_GENERIC
+ if (count == 1) {
+ zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);
+ } else if (count == 4) {
+ zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);
+ }
+ break;
+ case 29: // 'V' - TIMEZONE_SPECIAL
+ if (count == 1) {
+ zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
+ } else if (count == 4) {
+ zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);
+ }
+ break;
+ }
+ if (zsinfo != null) {
+ if (zsinfo.isStandard()) {
+ tztype = TZTYPE_STD;
+ } else if (zsinfo.isDaylight()) {
+ tztype = TZTYPE_DST;
+ }
+ tz = TimeZone.getTimeZone(zsinfo.getID());
+ cal.setTimeZone(tz);
+ return start + zsinfo.getString().length();
+ }
+ // Step 4
+ // Final attempt - is this standalone GMT/UT/UTC?
+ int gmtLen = 0;
+ if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
+ gmtLen = STR_GMT_LEN;
+ } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {
+ gmtLen = STR_UTC_LEN;
+ } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
+ gmtLen = STR_UT_LEN;
+ }
+ if (gmtLen > 0) {
+ tz = TimeZone.getTimeZone("Etc/GMT");
+ cal.setTimeZone(tz);
+ return start + gmtLen;
+ }
+
+ // complete failure
+ return -start;
+ }
+
+ case 27: // 'Q' - QUARTER
+ if (count <= 2) // i.e., Q or QQ.
+ {
+ // Don't want to parse the quarter if it is a string
+ // while pattern uses numeric style: Q or QQ.
+ // [We computed 'value' above.]
+ cal.set(Calendar.MONTH, (value - 1) * 3);
+ return pos.getIndex();
+ }
+ else
+ {
+ // count >= 3 // i.e., QQQ or QQQQ
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 first:
+ int newStart = matchQuarterString(text, start, Calendar.MONTH,
+ formatData.quarters, cal);
+ if (newStart > 0) {
+ return newStart;
+ } else { // count == 4 failed, now try count == 3
+ return matchQuarterString(text, start, Calendar.MONTH,
+ formatData.shortQuarters, cal);
+ }
+ }
+
+ case 28: // 'q' - STANDALONE QUARTER
+ if (count <= 2) // i.e., q or qq.
+ {
+ // Don't want to parse the quarter if it is a string
+ // while pattern uses numeric style: q or qq.
+ // [We computed 'value' above.]
+ cal.set(Calendar.MONTH, (value - 1) * 3);
+ return pos.getIndex();
+ }
+ else
+ {
+ // count >= 3 // i.e., qqq or qqqq
+ // Want to be able to parse both short and long forms.
+ // Try count == 4 first:
+ int newStart = matchQuarterString(text, start, Calendar.MONTH,
+ formatData.standaloneQuarters, cal);
+ if (newStart > 0) {
+ return newStart;
+ } else { // count == 4 failed, now try count == 3
+ return matchQuarterString(text, start, Calendar.MONTH,
+ formatData.standaloneShortQuarters, cal);
+ }
+ }
+
+ default:
+ // case 3: // 'd' - DATE
+ // case 5: // 'H' - HOUR_OF_DAY (0..23)
+ // case 6: // 'm' - MINUTE
+ // case 7: // 's' - SECOND
+ // case 10: // 'D' - DAY_OF_YEAR
+ // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
+ // case 12: // 'w' - WEEK_OF_YEAR
+ // case 13: // 'W' - WEEK_OF_MONTH
+ // case 16: // 'K' - HOUR (0..11)
+ // case 18: // 'Y' - YEAR_WOY
+ // case 19: // 'e' - DOW_LOCAL
+ // case 20: // 'u' - EXTENDED_YEAR
+ // case 21: // 'g' - JULIAN_DAY
+ // case 22: // 'A' - MILLISECONDS_IN_DAY
+
+ // Handle "generic" fields
+ if (obeyCount)
+ {
+ if ((start+count) > text.length()) return -start;
+ number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
+ }
+ else number = parseInt(text, pos, allowNegative,currentNumberFormat);
+ if (number != null) {
+ cal.set(field, number.intValue());
+ return pos.getIndex();
+ }
+ return -start;
+ }
+ }
+
+ /**
+ * Parse an integer using numberFormat. This method is semantically
+ * const, but actually may modify fNumberFormat.
+ */
+ private Number parseInt(String text,
+ ParsePosition pos,
+ boolean allowNegative,
+ NumberFormat fmt) {
+ return parseInt(text, -1, pos, allowNegative, fmt);
+ }
+
+ /**
+ * Parse an integer using numberFormat up to maxDigits.
+ */
+ private Number parseInt(String text,
+ int maxDigits,
+ ParsePosition pos,
+ boolean allowNegative,
+ NumberFormat fmt) {
+ Number number;
+ int oldPos = pos.getIndex();
+ if (allowNegative) {
+ number = fmt.parse(text, pos);
+ } else {
+ // Invalidate negative numbers
+ if (fmt instanceof DecimalFormat) {
+ String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
+ ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
+ number = fmt.parse(text, pos);
+ ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
+ } else {
+ boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
+ if (dateNumberFormat) {
+ ((DateNumberFormat)fmt).setParsePositiveOnly(true);
+ }
+ number = fmt.parse(text, pos);
+ if (dateNumberFormat) {
+ ((DateNumberFormat)fmt).setParsePositiveOnly(false);
+ }
+ }
+ }
+ if (maxDigits > 0) {
+ // adjust the result to fit into
+ // the maxDigits and move the position back
+ int nDigits = pos.getIndex() - oldPos;
+ if (nDigits > maxDigits) {
+ double val = number.doubleValue();
+ nDigits -= maxDigits;
+ while (nDigits > 0) {
+ val /= 10;
+ nDigits--;
+ }
+ pos.setIndex(oldPos + maxDigits);
+ number = new Integer((int)val);
+ }
+ }
+ return number;
+ }
+
+
+ /**
+ * Translate a pattern, mapping each character in the from string to the
+ * corresponding character in the to string.
+ */
+ private String translatePattern(String pat, String from, String to) {
+ StringBuffer result = new StringBuffer();
+ boolean inQuote = false;
+ for (int i = 0; i < pat.length(); ++i) {
+ char c = pat.charAt(i);
+ if (inQuote) {
+ if (c == '\'')
+ inQuote = false;
+ }
+ else {
+ if (c == '\'')
+ inQuote = true;
+ else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ int ci = from.indexOf(c);
+ if (ci != -1) {
+ c = to.charAt(ci);
+ }
+ // do not worry on translatepattern if the character is not listed
+ // we do the validity check elsewhere
+ }
+ }
+ result.append(c);
+ }
+ if (inQuote)
+ throw new IllegalArgumentException("Unfinished quote in pattern");
+ return result.toString();
+ }
+
+ /**
+ * Return a pattern string describing this date format.
+ * @stable ICU 2.0
+ */
+ public String toPattern() {
+ return pattern;
+ }
+
+ /**
+ * Return a localized pattern string describing this date format.
+ * @stable ICU 2.0
+ */
+ public String toLocalizedPattern() {
+ return translatePattern(pattern,
+ DateFormatSymbols.patternChars,
+ formatData.localPatternChars);
+ }
+
+ /**
+ * Apply the given unlocalized pattern string to this date format.
+ * @stable ICU 2.0
+ */
+ public void applyPattern(String pat)
+ {
+ this.pattern = pat;
+ setLocale(null, null);
+ // reset parsed pattern items
+ patternItems = null;
+ }
+
+ /**
+ * Apply the given localized pattern string to this date format.
+ * @stable ICU 2.0
+ */
+ public void applyLocalizedPattern(String pat) {
+ this.pattern = translatePattern(pat,
+ formatData.localPatternChars,
+ DateFormatSymbols.patternChars);
+ setLocale(null, null);
+ }
+
+ /**
+ * Gets the date/time formatting data.
+ * @return a copy of the date-time formatting data associated
+ * with this date-time formatter.
+ * @stable ICU 2.0
+ */
+ public DateFormatSymbols getDateFormatSymbols()
+ {
+ return (DateFormatSymbols)formatData.clone();
+ }
+
+ /**
+ * Allows you to set the date/time formatting data.
+ * @param newFormatSymbols the new symbols
+ * @stable ICU 2.0
+ */
+ public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
+ {
+ this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
+ gmtfmtCache = null;
+ }
+
+ /**
+ * Method for subclasses to access the DateFormatSymbols.
+ * @stable ICU 2.0
+ */
+ protected DateFormatSymbols getSymbols() {
+ return formatData;
+ }
+
+ /**
+ * Overrides Cloneable
+ * @stable ICU 2.0
+ */
+ public Object clone() {
+ SimpleDateFormat other = (SimpleDateFormat) super.clone();
+ other.formatData = (DateFormatSymbols) formatData.clone();
+ return other;
+ }
+
+ /**
+ * Override hashCode.
+ * Generates the hash code for the SimpleDateFormat object
+ * @stable ICU 2.0
+ */
+ public int hashCode()
+ {
+ return pattern.hashCode();
+ // just enough fields for a reasonable distribution
+ }
+
+ /**
+ * Override equals.
+ * @stable ICU 2.0
+ */
+ public boolean equals(Object obj)
+ {
+ if (!super.equals(obj)) return false; // super does class check
+ SimpleDateFormat that = (SimpleDateFormat) obj;
+ return (pattern.equals(that.pattern)
+ && formatData.equals(that.formatData));
+ }
+
+ /**
+ * Override writeObject.
+ */
+ private void writeObject(ObjectOutputStream stream) throws IOException{
+ if (defaultCenturyStart == null) {
+ // if defaultCenturyStart is not yet initialized,
+ // calculate and set value before serialization.
+ initializeDefaultCenturyStart(defaultCenturyBase);
+ }
+ stream.defaultWriteObject();
+ }
+
+ /**
+ * Override readObject.
+ */
+ private void readObject(ObjectInputStream stream)
+ throws IOException, ClassNotFoundException {
+ stream.defaultReadObject();
+ ///CLOVER:OFF
+ // don't have old serial data to test with
+ if (serialVersionOnStream < 1) {
+ // didn't have defaultCenturyStart field
+ defaultCenturyBase = System.currentTimeMillis();
+ }
+ ///CLOVER:ON
+ else {
+ // fill in dependent transient field
+ parseAmbiguousDatesAsAfter(defaultCenturyStart);
+ }
+ serialVersionOnStream = currentSerialVersion;
+ locale = getLocale(ULocale.VALID_LOCALE);
+
+ initLocalZeroPaddingNumberFormat();
+ }
+
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+ /**
+ * Format the object to an attributed string, and return the corresponding iterator
+ * Overrides superclass method.
+ *
+ * @param obj The object to format
+ * @return <code>AttributedCharacterIterator</code> describing the formatted value.
+ *
+ * @stable ICU 3.8
+ */
+ public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+ Calendar cal = calendar;
+ if (obj instanceof Calendar) {
+ cal = (Calendar)obj;
+ } else if (obj instanceof Date) {
+ calendar.setTime((Date)obj);
+ } else if (obj instanceof Number) {
+ calendar.setTimeInMillis(((Number)obj).longValue());
+ } else {
+ throw new IllegalArgumentException("Cannot format given Object as a Date");
+ }
+ StringBuffer toAppendTo = new StringBuffer();
+ FieldPosition pos = new FieldPosition(0);
+ List attributes = new LinkedList();
+ format(cal, toAppendTo, pos, attributes);
+
+ AttributedString as = new AttributedString(toAppendTo.toString());
+
+ // add DateFormat field attributes to the AttributedString
+ for (int i = 0; i < attributes.size(); i++) {
+ FieldPosition fp = (FieldPosition) attributes.get(i);
+ Format.Field attribute = fp.getFieldAttribute();
+ as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
+ }
+ // return the CharacterIterator from AttributedString
+ return as.getIterator();
+ }
+//#endif
+
+
+ /**
+ * Get the locale of this simple date formatter.
+ * It is package accessible. also used in DateIntervalFormat.
+ *
+ * @return locale in this simple date formatter
+ */
+ ULocale getLocale()
+ {
+ return locale;
+ }
+
+
+
+ /**
+ * Check whether the 'field' is smaller than all the fields covered in
+ * pattern, return true if it is.
+ * The sequence of calendar field,
+ * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
+ * @param field the calendar field need to check against
+ * @return true if the 'field' is smaller than all the fields
+ * covered in pattern. false otherwise.
+ */
+
+ boolean isFieldUnitIgnored(int field) {
+ return isFieldUnitIgnored(pattern, field);
+ }
+
+
+ /*
+ * Check whether the 'field' is smaller than all the fields covered in
+ * pattern, return true if it is.
+ * The sequence of calendar field,
+ * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
+ * @param pattern the pattern to check against
+ * @param field the calendar field need to check against
+ * @return true if the 'field' is smaller than all the fields
+ * covered in pattern. false otherwise.
+ * @internal ICU 4.0
+ */
+ static boolean isFieldUnitIgnored(String pattern, int field) {
+ int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
+ int level;
+ char ch;
+ boolean inQuote = false;
+ char prevCh = 0;
+ int count = 0;
+
+ for (int i = 0; i < pattern.length(); ++i) {
+ ch = pattern.charAt(i);
+ if (ch != prevCh && count > 0) {
+ level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
+ if ( fieldLevel <= level ) {
+ return false;
+ }
+ count = 0;
+ }
+ if (ch == '\'') {
+ if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
+ ++i;
+ } else {
+ inQuote = ! inQuote;
+ }
+ }
+ else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
+ || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
+ prevCh = ch;
+ ++count;
+ }
+ }
+ if ( count > 0 ) {
+ // last item
+ level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
+ if ( fieldLevel <= level ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Format date interval by algorithm.
+ * It is supposed to be used only by CLDR survey tool.
+ *
+ * @param fromCalendar calendar set to the from date in date interval
+ * to be formatted into date interval stirng
+ * @param toCalendar calendar set to the to date in date interval
+ * to be formatted into date interval stirng
+ * @param appendTo Output parameter to receive result.
+ * Result is appended to existing contents.
+ * @param pos On input: an alignment field, if desired.
+ * On output: the offsets of the alignment field.
+ * @exception IllegalArgumentException when there is non-recognized
+ * pattern letter
+ * @return Reference to 'appendTo' parameter.
+ * @internal ICU 4.0
+ */
+ public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
+ Calendar toCalendar,
+ StringBuffer appendTo,
+ FieldPosition pos)
+ throws IllegalArgumentException
+ {
+ // not support different calendar types and time zones
+ if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
+ throw new IllegalArgumentException("can not format on two different calendars");
+ }
+
+ Object[] items = getPatternItems();
+ int diffBegin = -1;
+ int diffEnd = -1;
+
+ /* look for different formatting string range */
+ // look for start of difference
+ try {
+ for (int i = 0; i < items.length; i++) {
+ if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
+ diffBegin = i;
+ break;
+ }
+ }
+
+ if ( diffBegin == -1 ) {
+ // no difference, single date format
+ return format(fromCalendar, appendTo, pos);
+ }
+
+ // look for end of difference
+ for (int i = items.length-1; i >= diffBegin; i--) {
+ if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
+ diffEnd = i;
+ break;
+ }
+ }
+ } catch ( IllegalArgumentException e ) {
+ throw new IllegalArgumentException(e.toString());
+ }
+
+ // full range is different
+ if ( diffBegin == 0 && diffEnd == items.length-1 ) {
+ format(fromCalendar, appendTo, pos);
+ appendTo.append(" \u2013 "); // default separator
+ format(toCalendar, appendTo, pos);
+ return appendTo;
+ }
+
+
+ /* search for largest calendar field within the different range */
+ int highestLevel = 1000;
+ for (int i = diffBegin; i <= diffEnd; i++) {
+ if ( items[i] instanceof String) {
+ continue;
+ }
+ PatternItem item = (PatternItem)items[i];
+ char ch = item.type;
+ int patternCharIndex = -1;
+ if ('A' <= ch && ch <= 'z') {
+ patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
+ }
+
+ if (patternCharIndex == -1) {
+ throw new IllegalArgumentException("Illegal pattern character " +
+ "'" + ch + "' in \"" +
+ new String(pattern) + '"');
+ }
+
+ if ( patternCharIndex < highestLevel ) {
+ highestLevel = patternCharIndex;
+ }
+ }
+
+ /* re-calculate diff range, including those calendar field which
+ is in lower level than the largest calendar field covered
+ in diff range calculated. */
+ try {
+ for (int i = 0; i < diffBegin; i++) {
+ if ( lowerLevel(items, i, highestLevel) ) {
+ diffBegin = i;
+ break;
+ }
+ }
+
+
+ for (int i = items.length-1; i > diffEnd; i--) {
+ if ( lowerLevel(items, i, highestLevel) ) {
+ diffEnd = i;
+ break;
+ }
+ }
+ } catch ( IllegalArgumentException e ) {
+ throw new IllegalArgumentException(e.toString());
+ }
+
+
+ // full range is different
+ if ( diffBegin == 0 && diffEnd == items.length-1 ) {
+ format(fromCalendar, appendTo, pos);
+ appendTo.append(" \u2013 "); // default separator
+ format(toCalendar, appendTo, pos);
+ return appendTo;
+ }
+
+
+ // formatting
+ // Initialize
+ pos.setBeginIndex(0);
+ pos.setEndIndex(0);
+
+ // formatting date 1
+ for (int i = 0; i <= diffEnd; i++) {
+ if (items[i] instanceof String) {
+ appendTo.append((String)items[i]);
+ } else {
+ PatternItem item = (PatternItem)items[i];
+ if (useFastFormat) {
+ subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);
+ } else {
+ appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));
+ }
+ }
+ }
+
+ appendTo.append(" \u2013 "); // default separator
+
+ // formatting date 2
+ for (int i = diffBegin; i < items.length; i++) {
+ if (items[i] instanceof String) {
+ appendTo.append((String)items[i]);
+ } else {
+ PatternItem item = (PatternItem)items[i];
+ if (useFastFormat) {
+ subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);
+ } else {
+ appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));
+ }
+ }
+ }
+ return appendTo;
+ }
+
+
+ /**
+ * check whether the i-th item in 2 calendar is in different value.
+ *
+ * It is supposed to be used only by CLDR survey tool.
+ * It is used by intervalFormatByAlgorithm().
+ *
+ * @param fromCalendar one calendar
+ * @param toCalendar the other calendar
+ * @param items pattern items
+ * @param i the i-th item in pattern items
+ * @exception IllegalArgumentException when there is non-recognized
+ * pattern letter
+ * @return true is i-th item in 2 calendar is in different
+ * value, false otherwise.
+ */
+ private boolean diffCalFieldValue(Calendar fromCalendar,
+ Calendar toCalendar,
+ Object[] items,
+ int i) throws IllegalArgumentException {
+ if ( items[i] instanceof String) {
+ return false;
+ }
+ PatternItem item = (PatternItem)items[i];
+ char ch = item.type;
+ int patternCharIndex = -1;
+ if ('A' <= ch && ch <= 'z') {
+ patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+ }
+
+ if (patternCharIndex == -1) {
+ throw new IllegalArgumentException("Illegal pattern character " +
+ "'" + ch + "' in \"" +
+ new String(pattern) + '"');
+ }
+
+ final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+ int value = fromCalendar.get(field);
+ int value_2 = toCalendar.get(field);
+ if ( value != value_2 ) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * check whether the i-th item's level is lower than the input 'level'
+ *
+ * It is supposed to be used only by CLDR survey tool.
+ * It is used by intervalFormatByAlgorithm().
+ *
+ * @param items the pattern items
+ * @param i the i-th item in pattern items
+ * @param level the level with which the i-th pattern item compared to
+ * @exception IllegalArgumentException when there is non-recognized
+ * pattern letter
+ * @return true if i-th pattern item is lower than 'level',
+ * false otherwise
+ */
+ private boolean lowerLevel(Object[] items, int i, int level)
+ throws IllegalArgumentException {
+ if ( items[i] instanceof String) {
+ return false;
+ }
+ PatternItem item = (PatternItem)items[i];
+ char ch = item.type;
+ int patternCharIndex = -1;
+ if ('A' <= ch && ch <= 'z') {
+ patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
+ }
+
+ if (patternCharIndex == -1) {
+ throw new IllegalArgumentException("Illegal pattern character " +
+ "'" + ch + "' in \"" +
+ new String(pattern) + '"');
+ }
+
+ if ( patternCharIndex >= level ) {
+ return true;
+ }
+ return false;
+ }
+
+ protected NumberFormat getNumberFormat(char ch) {
+
+ Character ovrField;
+ ovrField = new Character(ch);
+ if (overrideMap != null && overrideMap.containsKey(ovrField)) {
+ String nsName = overrideMap.get(ovrField).toString();
+ NumberFormat nf = (NumberFormat)numberFormatters.get(nsName);
+ return nf;
+ } else {
+ return numberFormat;
+ }
+ }
+
+ private void initNumberFormatters(ULocale loc) {
+
+ numberFormatters = new HashMap();
+ overrideMap = new HashMap();
+ processOverrideString(loc,override);
+
+ }
+
+ private void processOverrideString(ULocale loc, String str) {
+
+ if ( str == null || str.length() == 0 )
+ return;
+
+ int start = 0;
+ int end;
+ String nsName;
+ Character ovrField;
+ boolean moreToProcess = true;
+ boolean fullOverride;
+
+ while (moreToProcess) {
+ int delimiterPosition = str.indexOf(";",start);
+ if (delimiterPosition == -1) {
+ moreToProcess = false;
+ end = str.length();
+ } else {
+ end = delimiterPosition;
+ }
+
+ String currentString = str.substring(start,end);
+ int equalSignPosition = currentString.indexOf("=");
+ if (equalSignPosition == -1) { // Simple override string such as "hebrew"
+ nsName = currentString;
+ fullOverride = true;
+ } else { // Field specific override string such as "y=hebrew"
+ nsName = currentString.substring(equalSignPosition+1);
+ ovrField = new Character(currentString.charAt(0));
+ overrideMap.put(ovrField,nsName);
+ fullOverride = false;
+ }
+
+ ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
+ NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
+
+ if (fullOverride) {
+ setNumberFormat(nf);
+ } else {
+ // Since one or more of the override number formatters might be complex,
+ // we can't rely on the fast numfmt where we have a partial field override.
+ useLocalZeroPaddingNumberFormat = false;
+ }
+
+ if (!numberFormatters.containsKey(nsName)) {
+ numberFormatters.put(nsName,nf);
+ }
+
+ start = delimiterPosition + 1;
+
+ }
+ }
+}