Source Code Cross Referenced for SimpleDateFormat.java in  » Internationalization-Localization » icu4j » com » ibm » icu » text » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Internationalization Localization » icu4j » com.ibm.icu.text 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *******************************************************************************
0003:         * Copyright (C) 1996-2006, International Business Machines Corporation and    *
0004:         * others. All Rights Reserved.                                                *
0005:         *******************************************************************************
0006:         */
0007:
0008:        package com.ibm.icu.text;
0009:
0010:        import com.ibm.icu.util.Calendar;
0011:        import com.ibm.icu.lang.UCharacter;
0012:        import com.ibm.icu.impl.CalendarData;
0013:        import com.ibm.icu.impl.UCharacterProperty;
0014:        import com.ibm.icu.impl.ZoneMeta;
0015:        import com.ibm.icu.util.TimeZone;
0016:        import com.ibm.icu.util.ULocale;
0017:
0018:        import java.io.IOException;
0019:        import java.io.ObjectInputStream;
0020:        import java.lang.ref.SoftReference;
0021:        import java.text.FieldPosition;
0022:        import java.text.MessageFormat;
0023:        import java.text.ParsePosition;
0024:        import java.util.Date;
0025:        import java.util.HashMap;
0026:        import java.util.Hashtable;
0027:        import java.util.Locale;
0028:        import java.util.Map;
0029:
0030:        import com.ibm.icu.impl.LocaleUtility;
0031:
0032:        /**
0033:         * <code>SimpleDateFormat</code> is a concrete class for formatting and
0034:         * parsing dates in a locale-sensitive manner. It allows for formatting
0035:         * (date -> text), parsing (text -> date), and normalization.
0036:         *
0037:         * <p>
0038:         * <code>SimpleDateFormat</code> allows you to start by choosing
0039:         * any user-defined patterns for date-time formatting. However, you
0040:         * are encouraged to create a date-time formatter with either
0041:         * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
0042:         * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
0043:         * of these class methods can return a date/time formatter initialized
0044:         * with a default format pattern. You may modify the format pattern
0045:         * using the <code>applyPattern</code> methods as desired.
0046:         * For more information on using these methods, see
0047:         * {@link DateFormat}.
0048:         *
0049:         * <p>
0050:         * <strong>Time Format Syntax:</strong>
0051:         * <p>
0052:         * To specify the time format use a <em>time pattern</em> string.
0053:         * In this pattern, all ASCII letters are reserved as pattern letters,
0054:         * which are defined as the following:
0055:         * <blockquote>
0056:         * <pre>
0057:         * Symbol   Meaning                 Presentation        Example
0058:         * ------   -------                 ------------        -------
0059:         * G        era designator          (Text)              AD
0060:         * y&#x2020;       year                    (Number)            1996
0061:         * Y*       year (week of year)     (Number)            1997
0062:         * u*       extended year           (Number)            4601
0063:         * M        month in year           (Text & Number)     July & 07
0064:         * d        day in month            (Number)            10
0065:         * h        hour in am/pm (1~12)    (Number)            12
0066:         * H        hour in day (0~23)      (Number)            0
0067:         * m        minute in hour          (Number)            30
0068:         * s        second in minute        (Number)            55
0069:         * S        fractional second       (Number)            978
0070:         * E        day of week             (Text)              Tuesday
0071:         * e*       day of week (local 1~7) (Number)            2
0072:         * D        day in year             (Number)            189
0073:         * F        day of week in month    (Number)            2 (2nd Wed in July)
0074:         * w        week in year            (Number)            27
0075:         * W        week in month           (Number)            2
0076:         * a        am/pm marker            (Text)              PM
0077:         * k        hour in day (1~24)      (Number)            24
0078:         * K        hour in am/pm (0~11)    (Number)            0
0079:         * z        time zone               (Text)              Pacific Standard Time
0080:         * Z        time zone (RFC 822)     (Number)            -0800
0081:         * v        time zone (generic)     (Text)              Pacific Time
0082:         * g*       Julian day              (Number)            2451334
0083:         * A*       milliseconds in day     (Number)            69540000
0084:         * '        escape for text         (Delimiter)         'Date='
0085:         * ''       single quote            (Literal)           'o''clock'
0086:         * </pre>
0087:         * </blockquote>
0088:         * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
0089:         * <tt><b>&#x2020;</b></tt> ICU interprets a single 'y' differently than Java.</p>
0090:         * <p>
0091:         * The count of pattern letters determine the format.
0092:         * <p>
0093:         * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
0094:         * &lt; 4--use short or abbreviated form if one exists.
0095:         * <p>
0096:         * <strong>(Number)</strong>: the minimum number of digits. Shorter
0097:         * numbers are zero-padded to this amount. Year is handled specially;
0098:         * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
0099:         * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
0100:         * Unlike other fields, fractional seconds are padded on the right with zero.
0101:         * <p>
0102:         * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
0103:         * <p>
0104:         * Any characters in the pattern that are not in the ranges of ['a'..'z']
0105:         * and ['A'..'Z'] will be treated as quoted text. For instance, characters
0106:         * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
0107:         * even they are not embraced within single quotes.
0108:         * <p>
0109:         * A pattern containing any invalid pattern letter will result in a thrown
0110:         * exception during formatting or parsing.
0111:         *
0112:         * <p>
0113:         * <strong>Examples Using the US Locale:</strong>
0114:         * <blockquote>
0115:         * <pre>
0116:         * Format Pattern                         Result
0117:         * --------------                         -------
0118:         * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->>  1996.07.10 AD at 15:08:56 Pacific Time
0119:         * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96
0120:         * "h:mm a"                          ->>  12:08 PM
0121:         * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time
0122:         * "K:mm a, vvv"                     ->>  0:00 PM, PT
0123:         * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  01996.July.10 AD 12:08 PM
0124:         * </pre>
0125:         * </blockquote>
0126:         * <strong>Code Sample:</strong>
0127:         * <blockquote>
0128:         * <pre>
0129:         * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
0130:         * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
0131:         * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
0132:         * <br>
0133:         * // Format the current time.
0134:         * SimpleDateFormat formatter
0135:         *     = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
0136:         * Date currentTime_1 = new Date();
0137:         * String dateString = formatter.format(currentTime_1);
0138:         * <br>
0139:         * // Parse the previous string back into a Date.
0140:         * ParsePosition pos = new ParsePosition(0);
0141:         * Date currentTime_2 = formatter.parse(dateString, pos);
0142:         * </pre>
0143:         * </blockquote>
0144:         * In the example, the time value <code>currentTime_2</code> obtained from
0145:         * parsing will be equal to <code>currentTime_1</code>. However, they may not be
0146:         * equal if the am/pm marker 'a' is left out from the format pattern while
0147:         * the "hour in am/pm" pattern symbol is used. This information loss can
0148:         * happen when formatting the time in PM.
0149:         *
0150:         * <p>
0151:         * When parsing a date string using the abbreviated year pattern ("yy"),
0152:         * SimpleDateFormat must interpret the abbreviated year
0153:         * relative to some century.  It does this by adjusting dates to be
0154:         * within 80 years before and 20 years after the time the SimpleDateFormat
0155:         * instance is created. For example, using a pattern of "MM/dd/yy" and a
0156:         * SimpleDateFormat instance created on Jan 1, 1997,  the string
0157:         * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
0158:         * would be interpreted as May 4, 1964.
0159:         * During parsing, only strings consisting of exactly two digits, as defined by
0160:         * {@link java.lang.Character#isDigit(char)}, will be parsed into the default
0161:         * century.
0162:         * Any other numeric string, such as a one digit string, a three or more digit
0163:         * string, or a two digit string that isn't all digits (for example, "-1"), is
0164:         * interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the
0165:         * same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
0166:         *
0167:         * <p>
0168:         * If the year pattern does not have exactly two 'y' characters, the year is
0169:         * interpreted literally, regardless of the number of digits.  So using the
0170:         * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
0171:         *
0172:         * <p>
0173:         * When numeric fields abut one another directly, with no intervening delimiter
0174:         * characters, they constitute a run of abutting numeric fields.  Such runs are
0175:         * parsed specially.  For example, the format "HHmmss" parses the input text
0176:         * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
0177:         * parse "1234".  In other words, the leftmost field of the run is flexible,
0178:         * while the others keep a fixed width.  If the parse fails anywhere in the run,
0179:         * then the leftmost field is shortened by one character, and the entire run is
0180:         * parsed again. This is repeated until either the parse succeeds or the
0181:         * leftmost field is one character in length.  If the parse still fails at that
0182:         * point, the parse of the run fails.
0183:         *
0184:         * <p>
0185:         * For time zones that have no names, use strings GMT+hours:minutes or
0186:         * GMT-hours:minutes.
0187:         *
0188:         * <p>
0189:         * The calendar defines what is the first day of the week, the first week
0190:         * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
0191:         * time zone. There is one common decimal format to handle all the numbers;
0192:         * the digit count is handled programmatically according to the pattern.
0193:         *
0194:         * <h4>Synchronization</h4>
0195:         *
0196:         * Date formats are not synchronized. It is recommended to create separate
0197:         * format instances for each thread. If multiple threads access a format
0198:         * concurrently, it must be synchronized externally.
0199:         *
0200:         * @see          com.ibm.icu.util.Calendar
0201:         * @see          com.ibm.icu.util.GregorianCalendar
0202:         * @see          com.ibm.icu.util.TimeZone
0203:         * @see          DateFormat
0204:         * @see          DateFormatSymbols
0205:         * @see          DecimalFormat
0206:         * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
0207:         * @stable ICU 2.0
0208:         */
0209:        public class SimpleDateFormat extends DateFormat {
0210:
0211:            // the official serial version ID which says cryptically
0212:            // which version we're compatible with
0213:            private static final long serialVersionUID = 4774881970558875024L;
0214:
0215:            // the internal serial version which says which version was written
0216:            // - 0 (default) for version up to JDK 1.1.3
0217:            // - 1 for version from JDK 1.1.4, which includes a new field
0218:            static final int currentSerialVersion = 1;
0219:
0220:            /**
0221:             * The version of the serialized data on the stream.  Possible values:
0222:             * <ul>
0223:             * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version
0224:             * has no <code>defaultCenturyStart</code> on stream.
0225:             * <li><b>1</b> JDK 1.1.4 or later.  This version adds
0226:             * <code>defaultCenturyStart</code>.
0227:             * </ul>
0228:             * When streaming out this class, the most recent format
0229:             * and the highest allowable <code>serialVersionOnStream</code>
0230:             * is written.
0231:             * @serial
0232:             */
0233:            private int serialVersionOnStream = currentSerialVersion;
0234:
0235:            /**
0236:             * The pattern string of this formatter.  This is always a non-localized
0237:             * pattern.  May not be null.  See class documentation for details.
0238:             * @serial
0239:             */
0240:            private String pattern;
0241:
0242:            /**
0243:             * The symbols used by this formatter for week names, month names,
0244:             * etc.  May not be null.
0245:             * @serial
0246:             * @see DateFormatSymbols
0247:             */
0248:            private DateFormatSymbols formatData;
0249:
0250:            private transient ULocale locale;
0251:
0252:            /**
0253:             * We map dates with two-digit years into the century starting at
0254:             * <code>defaultCenturyStart</code>, which may be any date.  May
0255:             * not be null.
0256:             * @serial
0257:             * @since JDK1.1.4
0258:             */
0259:            private Date defaultCenturyStart;
0260:
0261:            transient private int defaultCenturyStartYear;
0262:
0263:            private transient TimeZone parsedTimeZone;
0264:
0265:            private static final int millisPerHour = 60 * 60 * 1000;
0266:            private static final int millisPerMinute = 60 * 1000;
0267:
0268:            // For time zones that have no names, use strings GMT+minutes and
0269:            // GMT-minutes. For instance, in France the time zone is GMT+60.
0270:            private static final String GMT_PLUS = "GMT+";
0271:            private static final String GMT_MINUS = "GMT-";
0272:            private static final String GMT = "GMT";
0273:
0274:            // This prefix is designed to NEVER MATCH real text, in order to
0275:            // suppress the parsing of negative numbers.  Adjust as needed (if
0276:            // this becomes valid Unicode).
0277:            private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
0278:
0279:            /**
0280:             * Cache to hold the DateTimePatterns of a Locale.
0281:             */
0282:            private static Hashtable cachedLocaleData = new Hashtable(3);
0283:
0284:            /**
0285:             * If true, this object supports fast formatting using the
0286:             * subFormat variant that takes a StringBuffer.
0287:             */
0288:            private transient boolean useFastFormat;
0289:
0290:            /**
0291:             * If true, this object supports fast number format
0292:             */
0293:            private transient boolean useFastZeroPaddingNumber;
0294:            private transient char zeroDigit;
0295:            private char[] decimalBuf = new char[10]; // 10 digit is good enough to store Interger.MAX_VALUE
0296:
0297:            /**
0298:             * Construct a SimpleDateFormat using the default pattern for the default
0299:             * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
0300:             * generality, use the factory methods in the DateFormat class.
0301:             *
0302:             * @see DateFormat
0303:             * @stable ICU 2.0
0304:             */
0305:            public SimpleDateFormat() {
0306:                this (SHORT, SHORT, ULocale.getDefault());
0307:            }
0308:
0309:            /**
0310:             * Construct a SimpleDateFormat using the given pattern in the default
0311:             * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
0312:             * generality, use the factory methods in the DateFormat class.
0313:             * @stable ICU 2.0
0314:             */
0315:            public SimpleDateFormat(String pattern) {
0316:                this (pattern, ULocale.getDefault());
0317:            }
0318:
0319:            /**
0320:             * Construct a SimpleDateFormat using the given pattern and locale.
0321:             * <b>Note:</b> Not all locales support SimpleDateFormat; for full
0322:             * generality, use the factory methods in the DateFormat class.
0323:             * @stable ICU 2.0
0324:             */
0325:            public SimpleDateFormat(String pattern, Locale loc) {
0326:                this (pattern, ULocale.forLocale(loc));
0327:            }
0328:
0329:            /**
0330:             * Construct a SimpleDateFormat using the given pattern and locale.
0331:             * <b>Note:</b> Not all locales support SimpleDateFormat; for full
0332:             * generality, use the factory methods in the DateFormat class.
0333:             * @draft ICU 3.2
0334:             * @provisional This API might change or be removed in a future release.
0335:             */
0336:            public SimpleDateFormat(String pattern, ULocale loc) {
0337:                this .pattern = pattern;
0338:                this .formatData = new DateFormatSymbols(loc);
0339:                initialize(loc);
0340:            }
0341:
0342:            /**
0343:             * Construct a SimpleDateFormat using the given pattern and
0344:             * locale-specific symbol data.
0345:             * Warning: uses default locale for digits!
0346:             * @stable ICU 2.0
0347:             */
0348:            public SimpleDateFormat(String pattern, DateFormatSymbols formatData) {
0349:                this (pattern, formatData, ULocale.getDefault());
0350:            }
0351:
0352:            /**
0353:             * @internal ICU 3.2
0354:             * @deprecated This API is ICU internal only.
0355:             */
0356:            public SimpleDateFormat(String pattern,
0357:                    DateFormatSymbols formatData, ULocale loc) {
0358:                this .pattern = pattern;
0359:                this .formatData = (DateFormatSymbols) formatData.clone();
0360:
0361:                initialize(loc);
0362:            }
0363:
0364:            /**
0365:             * Package-private constructor that allows a subclass to specify
0366:             * whether it supports fast formatting.
0367:             *
0368:             * TODO make this API public.
0369:             */
0370:            SimpleDateFormat(String pattern, DateFormatSymbols formatData,
0371:                    boolean useFastFormat) {
0372:                this .pattern = pattern;
0373:                this .formatData = (DateFormatSymbols) formatData.clone();
0374:                initialize(ULocale.getDefault());
0375:                // this.useFastFormat is set by initialize(); fix it up afterwards
0376:                this .useFastFormat = useFastFormat;
0377:            }
0378:
0379:            // try caching
0380:            private static final boolean CACHE = true;
0381:            private static long cacheAge;
0382:            private static SoftReference highCacheRef;
0383:
0384:            /* Package-private, called by DateFormat factory methods */
0385:            SimpleDateFormat(int timeStyle, int dateStyle, ULocale loc) {
0386:                // try a high level cache first!
0387:
0388:                Map map = null;
0389:                String key = null;
0390:                if (CACHE) {
0391:                    // age test is so we don't have to compute the century start all the time... once a day is enough.
0392:                    long time = System.currentTimeMillis();
0393:                    if (((time - cacheAge) < 1000 * 60 * 60 * 24L)
0394:                            && highCacheRef != null) {
0395:                        map = (Map) highCacheRef.get();
0396:                    }
0397:                    if (map == null) {
0398:                        map = new HashMap(3);
0399:                        highCacheRef = new SoftReference(map);
0400:                        cacheAge = time;
0401:                    }
0402:                    key = loc.toString() + timeStyle + dateStyle;
0403:                    SimpleDateFormat target = (SimpleDateFormat) map.get(key);
0404:                    if (target != null) { // kindof skanky
0405:                        //          if ("en_US22".equals(key))
0406:                        //              System.out.println("\nfound key: " + key + " pat: " + target.pattern +
0407:                        //                         " cal: " + target.calendar + " fmt: " + target.numberFormat);
0408:                        this .pattern = target.pattern;
0409:                        this .formatData = target.formatData;
0410:                        this .defaultCenturyStart = target.defaultCenturyStart;
0411:                        this .defaultCenturyStartYear = target.defaultCenturyStartYear;
0412:                        this .calendar = (Calendar) target.calendar.clone();
0413:                        this .calendar.setTimeZone(TimeZone.getDefault()); // might have changed since cached
0414:                        this .numberFormat = (NumberFormat) target.numberFormat
0415:                                .clone();
0416:                        return;
0417:                    }
0418:                }
0419:
0420:                /* try the cache first */
0421:                String[] dateTimePatterns = (String[]) cachedLocaleData
0422:                        .get(loc);
0423:                if (dateTimePatterns == null) { /* cache miss */
0424:                    CalendarData calData = new CalendarData(loc, null); // TODO: type?
0425:                    // TODO: get correct actual/valid locale here
0426:                    ULocale uloc = calData.getULocale();
0427:                    setLocale(uloc, uloc);
0428:
0429:                    dateTimePatterns = calData
0430:                            .getStringArray("DateTimePatterns");
0431:                    /* update cache */
0432:                    cachedLocaleData.put(loc, dateTimePatterns);
0433:                } else {
0434:                    // for now, just assume this is correct, so we have non-null locale info.
0435:                    // we may have to cache the result of calData.getULocale with the pattern strings
0436:                    // and set the locale with that.
0437:                    setLocale(loc, loc);
0438:                }
0439:                formatData = new DateFormatSymbols(loc);
0440:                if ((timeStyle >= 0) && (dateStyle >= 0)) {
0441:                    Object[] dateTimeArgs = { dateTimePatterns[timeStyle],
0442:                            dateTimePatterns[dateStyle + 4] };
0443:                    pattern = MessageFormat.format(dateTimePatterns[8],
0444:                            dateTimeArgs);
0445:                } else if (timeStyle >= 0) {
0446:                    pattern = dateTimePatterns[timeStyle];
0447:                } else if (dateStyle >= 0) {
0448:                    pattern = dateTimePatterns[dateStyle + 4];
0449:                } else {
0450:                    throw new IllegalArgumentException(
0451:                            "No date or time style specified");
0452:                }
0453:
0454:                initialize(loc);
0455:
0456:                if (CACHE) {
0457:                    //          if ("en_US22".equals(key))
0458:                    //          System.out.println("\nregister key: " + key + " pat: " + this.pattern +
0459:                    //                     " cal: " + this.calendar + " fmt: " + this.numberFormat);
0460:                    map.put(key, this .clone()); // ok if we stomp existing target due to threading
0461:                }
0462:            }
0463:
0464:            /* Initialize calendar and numberFormat fields */
0465:            private void initialize(ULocale loc) {
0466:                // time zone formatting
0467:                locale = loc;
0468:
0469:                // The format object must be constructed using the symbols for this zone.
0470:                // However, the calendar should use the current default TimeZone.
0471:                // If this is not contained in the locale zone strings, then the zone
0472:                // will be formatted using generic GMT+/-H:MM nomenclature.
0473:                calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
0474:                // TODO: convert to use ULocale APIs when we get to the text package
0475:                numberFormat = NumberFormat.getInstance(loc);
0476:                numberFormat.setGroupingUsed(false);
0477:                useFastZeroPaddingNumber = false;
0478:                ///CLOVER:OFF
0479:                // difficult to test for case where NumberFormat.getInstance does not
0480:                // return a DecimalFormat
0481:                if (numberFormat instanceof  DecimalFormat) {
0482:                    ((DecimalFormat) numberFormat)
0483:                            .setDecimalSeparatorAlwaysShown(false);
0484:                    zeroDigit = ((DecimalFormat) numberFormat)
0485:                            .getDecimalFormatSymbols().getZeroDigit();
0486:                    if (numberFormat.getClass().getName().equals(
0487:                            "com.ibm.icu.text.DecimalFormat")) {
0488:                        useFastZeroPaddingNumber = true;
0489:                    }
0490:                }
0491:                ///CLOVER:ON
0492:                numberFormat.setParseIntegerOnly(true); /* So that dd.MM.yy can be parsed */
0493:                numberFormat.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
0494:
0495:                initializeDefaultCentury();
0496:
0497:                // Currently, we only support fast formatting in SimpleDateFormat
0498:                // itself.  TODO add constructor parameters to allow subclasses
0499:                // to say that they implement fast formatting.
0500:                useFastFormat = (getClass() == SimpleDateFormat.class);
0501:            }
0502:
0503:            /* Initialize the fields we use to disambiguate ambiguous years. Separate
0504:             * so we can call it from readObject().
0505:             */
0506:            private void initializeDefaultCentury() {
0507:                calendar.setTime(new Date());
0508:                calendar.add(Calendar.YEAR, -80);
0509:                parseAmbiguousDatesAsAfter(calendar.getTime());
0510:            }
0511:
0512:            /* Define one-century window into which to disambiguate dates using
0513:             * two-digit years.
0514:             */
0515:            private void parseAmbiguousDatesAsAfter(Date startDate) {
0516:                defaultCenturyStart = startDate;
0517:                calendar.setTime(startDate);
0518:                defaultCenturyStartYear = calendar.get(Calendar.YEAR);
0519:            }
0520:
0521:            /**
0522:             * Sets the 100-year period 2-digit years will be interpreted as being in
0523:             * to begin on the date the user specifies.
0524:             * @param startDate During parsing, two digit years will be placed in the range
0525:             * <code>startDate</code> to <code>startDate + 100 years</code>.
0526:             * @stable ICU 2.0
0527:             */
0528:            public void set2DigitYearStart(Date startDate) {
0529:                parseAmbiguousDatesAsAfter(startDate);
0530:            }
0531:
0532:            /**
0533:             * Returns the beginning date of the 100-year period 2-digit years are interpreted
0534:             * as being within.
0535:             * @return the start of the 100-year period into which two digit years are
0536:             * parsed
0537:             * @stable ICU 2.0
0538:             */
0539:            public Date get2DigitYearStart() {
0540:                return defaultCenturyStart;
0541:            }
0542:
0543:            /**
0544:             * Overrides DateFormat.
0545:             * <p>Formats a date or time, which is the standard millis
0546:             * since January 1, 1970, 00:00:00 GMT.
0547:             * <p>Example: using the US locale:
0548:             * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
0549:             * @param cal the calendar whose date-time value is to be formatted into a date-time string
0550:             * @param toAppendTo where the new date-time text is to be appended
0551:             * @param pos the formatting position. On input: an alignment field,
0552:             * if desired. On output: the offsets of the alignment field.
0553:             * @return the formatted date-time string.
0554:             * @see DateFormat
0555:             * @stable ICU 2.0
0556:             */
0557:            public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
0558:                    FieldPosition pos) {
0559:                if (!useFastFormat) {
0560:                    return slowFormat(cal, toAppendTo, pos);
0561:                }
0562:
0563:                // Initialize
0564:                pos.setBeginIndex(0);
0565:                pos.setEndIndex(0);
0566:
0567:                // Careful: For best performance, minimize the number of calls
0568:                // to StringBuffer.append() by consolidating appends when
0569:                // possible.
0570:
0571:                int j, n = pattern.length();
0572:                for (int i = 0; i < n;) {
0573:                    char ch = pattern.charAt(i);
0574:                    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
0575:                        // ch is a date-time pattern character to be interpreted
0576:                        // by subFormat(); count the number of times it is repeated
0577:                        for (j = i + 1; j < n && pattern.charAt(j) == ch; ++j) {
0578:                        }
0579:                        subFormat(toAppendTo, ch, j - i, toAppendTo.length(),
0580:                                pos, cal);
0581:                        i = j;
0582:                    } else if (ch == '\'') {
0583:                        // Handle an entire quoted string, included embedded
0584:                        // doubled apostrophes (as in 'o''clock').
0585:                        int start = i + 1;
0586:                        for (;;) {
0587:                            ++i; // i points after '
0588:                            if (i == n) { // trailing ' (pathological)
0589:                                break;
0590:                            }
0591:
0592:                            for (j = i; j < n && pattern.charAt(j) != '\''; ++j) {
0593:                            }
0594:                            // j points to next ' or EOS
0595:
0596:                            if (j == start) { // '' outside of quotes
0597:                                toAppendTo.append('\'');
0598:                                ++i;
0599:                                break;
0600:                            }
0601:
0602:                            // look ahead to detect '' within quotes
0603:                            int k = j, jj = j + 1;
0604:                            if (jj < n && pattern.charAt(jj) == '\'') {
0605:                                ++k;
0606:                            }
0607:
0608:                            // append this run, and if there is '' within
0609:                            // quotes, append a trailing ' as well
0610:                            toAppendTo.append(pattern.substring(i, k));
0611:
0612:                            i = jj;
0613:
0614:                            if (k == j) {
0615:                                break;
0616:                            }
0617:                        }
0618:                    } else {
0619:                        // Append unquoted literal characters
0620:                        toAppendTo.append(ch);
0621:                        ++i;
0622:                    }
0623:                }
0624:
0625:                return toAppendTo;
0626:            }
0627:
0628:            private StringBuffer slowFormat(Calendar cal,
0629:                    StringBuffer toAppendTo, FieldPosition pos) {
0630:                // Initialize
0631:                pos.setBeginIndex(0);
0632:                pos.setEndIndex(0);
0633:
0634:                boolean inQuote = false; // true when between single quotes
0635:                char prevCh = 0; // previous pattern character
0636:                int count = 0; // number of time prevCh repeated
0637:                for (int i = 0; i < pattern.length(); ++i) {
0638:                    char ch = pattern.charAt(i);
0639:                    // Use subFormat() to format a repeated pattern character
0640:                    // when a different pattern or non-pattern character is seen
0641:                    if (ch != prevCh && count > 0) {
0642:                        toAppendTo.append(subFormat(prevCh, count, toAppendTo
0643:                                .length(), pos, formatData, cal));
0644:                        count = 0;
0645:                    }
0646:                    if (ch == '\'') {
0647:                        // Consecutive single quotes are a single quote literal,
0648:                        // either outside of quotes or between quotes
0649:                        if ((i + 1) < pattern.length()
0650:                                && pattern.charAt(i + 1) == '\'') {
0651:                            toAppendTo.append('\'');
0652:                            ++i;
0653:                        } else {
0654:                            inQuote = !inQuote;
0655:                        }
0656:                    } else if (!inQuote
0657:                            && (ch >= 'a' && ch <= 'z' || ch >= 'A'
0658:                                    && ch <= 'Z')) {
0659:                        // ch is a date-time pattern character to be interpreted
0660:                        // by subFormat(); count the number of times it is repeated
0661:                        prevCh = ch;
0662:                        ++count;
0663:                    } else {
0664:                        // Append quoted characters and unquoted non-pattern characters
0665:                        toAppendTo.append(ch);
0666:                    }
0667:                }
0668:                // Format the last item in the pattern, if any
0669:                if (count > 0) {
0670:                    toAppendTo.append(subFormat(prevCh, count, toAppendTo
0671:                            .length(), pos, formatData, cal));
0672:                }
0673:                return toAppendTo;
0674:            }
0675:
0676:            // Map index into pattern character string to Calendar field number
0677:            private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = {
0678:            /*GyM*/Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
0679:            /*dkH*/Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
0680:            /*msS*/Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
0681:            /*EDF*/Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR,
0682:                    Calendar.DAY_OF_WEEK_IN_MONTH,
0683:                    /*wWa*/Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
0684:                    Calendar.AM_PM,
0685:                    /*hKz*/Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
0686:                    /*Yeu*/Calendar.YEAR_WOY, Calendar.DOW_LOCAL,
0687:                    Calendar.EXTENDED_YEAR,
0688:                    /*gAZ*/Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY,
0689:                    Calendar.ZONE_OFFSET,
0690:                    /*v*/Calendar.ZONE_OFFSET,
0691:                    /*c*/Calendar.DAY_OF_WEEK,
0692:                    /*L*/Calendar.MONTH,
0693:                    /*Qq*/Calendar.MONTH, Calendar.MONTH, };
0694:
0695:            // Map index into pattern character string to DateFormat field number
0696:            private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
0697:            /*GyM*/DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD,
0698:                    DateFormat.MONTH_FIELD,
0699:                    /*dkH*/DateFormat.DATE_FIELD,
0700:                    DateFormat.HOUR_OF_DAY1_FIELD,
0701:                    DateFormat.HOUR_OF_DAY0_FIELD,
0702:                    /*msS*/DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD,
0703:                    DateFormat.FRACTIONAL_SECOND_FIELD,
0704:                    /*EDF*/DateFormat.DAY_OF_WEEK_FIELD,
0705:                    DateFormat.DAY_OF_YEAR_FIELD,
0706:                    DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
0707:                    /*wWa*/DateFormat.WEEK_OF_YEAR_FIELD,
0708:                    DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
0709:                    /*hKz*/DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
0710:                    DateFormat.TIMEZONE_FIELD,
0711:                    /*Yeu*/DateFormat.YEAR_WOY_FIELD,
0712:                    DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
0713:                    /*gAZ*/DateFormat.JULIAN_DAY_FIELD,
0714:                    DateFormat.MILLISECONDS_IN_DAY_FIELD,
0715:                    DateFormat.TIMEZONE_RFC_FIELD,
0716:                    /*v*/DateFormat.TIMEZONE_GENERIC_FIELD,
0717:                    /*c*/DateFormat.STANDALONE_DAY_FIELD,
0718:                    /*L*/DateFormat.STANDALONE_MONTH_FIELD,
0719:                    /*Q*/DateFormat.QUARTER_FIELD,
0720:                    /*q*/DateFormat.STANDALONE_QUARTER_FIELD, };
0721:
0722:            /**
0723:             * Format a single field, given its pattern character.  Subclasses may
0724:             * override this method in order to modify or add formatting
0725:             * capabilities.
0726:             * @param ch the pattern character
0727:             * @param count the number of times ch is repeated in the pattern
0728:             * @param beginOffset the offset of the output string at the start of
0729:             * this field; used to set pos when appropriate
0730:             * @param pos receives the position of a field, when appropriate
0731:             * @param formatData the symbols for this formatter
0732:             * @stable ICU 2.0
0733:             */
0734:            protected String subFormat(char ch, int count, int beginOffset,
0735:                    FieldPosition pos, DateFormatSymbols formatData,
0736:                    Calendar cal) throws IllegalArgumentException {
0737:                // Note: formatData is ignored
0738:                StringBuffer buf = new StringBuffer();
0739:                subFormat(buf, ch, count, beginOffset, pos, cal);
0740:                return buf.toString();
0741:            }
0742:
0743:            /**
0744:             * Format a single field; useFastFormat variant.  Reuses a
0745:             * StringBuffer for results instead of creating a String on the
0746:             * heap for each call.
0747:             *
0748:             * NOTE We don't really need the beginOffset parameter, EXCEPT for
0749:             * the need to support the slow subFormat variant (above) which
0750:             * has to pass it in to us.
0751:             *
0752:             * TODO make this API public
0753:             *
0754:             * @internal
0755:             * @deprecated This API is ICU internal only.
0756:             */
0757:            protected void subFormat(StringBuffer buf, char ch, int count,
0758:                    int beginOffset, FieldPosition pos, Calendar cal) {
0759:                final int maxIntCount = Integer.MAX_VALUE;
0760:                final int bufstart = buf.length();
0761:
0762:                final int patternCharIndex = DateFormatSymbols.patternChars
0763:                        .indexOf(ch);
0764:                if (patternCharIndex == -1) {
0765:                    throw new IllegalArgumentException(
0766:                            "Illegal pattern character " + "'" + ch + "' in \""
0767:                                    + new String(pattern) + '"');
0768:                }
0769:
0770:                final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
0771:                int value = cal.get(field);
0772:
0773:                switch (patternCharIndex) {
0774:                case 0: // 'G' - ERA
0775:                    if (count == 5) {
0776:                        buf.append(formatData.narrowEras[value]);
0777:                    } else if (count == 4)
0778:                        buf.append(formatData.eraNames[value]);
0779:                    else
0780:                        buf.append(formatData.eras[value]);
0781:                    break;
0782:                case 1: // 'y' - YEAR
0783:                    /* According to the specification, if the number of pattern letters ('y') is 2,
0784:                     * the year is truncated to 2 digits; otherwise it is interpreted as a number.
0785:                     * But the original code process 'y', 'yy', 'yyy' in the same way. and process
0786:                     * patterns with 4 or more than 4 'y' characters in the same way.
0787:                     * So I change the codes to meet the specification. [Richard/GCl]
0788:                     */
0789:                    if (count == 2)
0790:                        zeroPaddingNumber(buf, value, 2, 2); // clip 1996 to 96
0791:                    else
0792:                        //count = 1 or count > 2
0793:                        zeroPaddingNumber(buf, value, count, maxIntCount);
0794:                    break;
0795:                case 2: // 'M' - MONTH
0796:                    if (count == 5)
0797:                        buf.append(formatData.narrowMonths[value]);
0798:                    else if (count == 4)
0799:                        buf.append(formatData.months[value]);
0800:                    else if (count == 3)
0801:                        buf.append(formatData.shortMonths[value]);
0802:                    else
0803:                        zeroPaddingNumber(buf, value + 1, count, maxIntCount);
0804:                    break;
0805:                case 4: // 'k' - HOUR_OF_DAY (1..24)
0806:                    if (value == 0)
0807:                        zeroPaddingNumber(buf, cal
0808:                                .getMaximum(Calendar.HOUR_OF_DAY) + 1, count,
0809:                                maxIntCount);
0810:                    else
0811:                        zeroPaddingNumber(buf, value, count, maxIntCount);
0812:                    break;
0813:                case 8: // 'S' - FRACTIONAL_SECOND
0814:                    // Fractional seconds left-justify
0815:                {
0816:                    numberFormat.setMinimumIntegerDigits(Math.min(3, count));
0817:                    numberFormat.setMaximumIntegerDigits(maxIntCount);
0818:                    if (count == 1) {
0819:                        value = (value + 50) / 100;
0820:                    } else if (count == 2) {
0821:                        value = (value + 5) / 10;
0822:                    }
0823:                    FieldPosition p = new FieldPosition(-1);
0824:                    numberFormat.format((long) value, buf, p);
0825:                    if (count > 3) {
0826:                        numberFormat.setMinimumIntegerDigits(count - 3);
0827:                        numberFormat.format(0L, buf, p);
0828:                    }
0829:                }
0830:                    break;
0831:                case 9: // 'E' - DAY_OF_WEEK
0832:                    if (count == 5) {
0833:                        buf.append(formatData.narrowWeekdays[value]);
0834:                    } else if (count == 4)
0835:                        buf.append(formatData.weekdays[value]);
0836:                    else
0837:                        // count <= 3, use abbreviated form if exists
0838:                        buf.append(formatData.shortWeekdays[value]);
0839:                    break;
0840:                case 14: // 'a' - AM_PM
0841:                    buf.append(formatData.ampms[value]);
0842:                    break;
0843:                case 15: // 'h' - HOUR (1..12)
0844:                    if (value == 0)
0845:                        zeroPaddingNumber(buf, cal
0846:                                .getLeastMaximum(Calendar.HOUR) + 1, count,
0847:                                maxIntCount);
0848:                    else
0849:                        zeroPaddingNumber(buf, value, count, maxIntCount);
0850:                    break;
0851:                case 17: // 'z' - ZONE_OFFSET
0852:                case 24: // 'v' - TIMEZONE_GENERIC 
0853:                {
0854:
0855:                    String zid;
0856:                    String res = null;
0857:                    zid = ZoneMeta.getCanonicalID(cal.getTimeZone().getID());
0858:                    boolean isGeneric = patternCharIndex == 24;
0859:                    if (zid != null) {
0860:                        if (patternCharIndex == TIMEZONE_GENERIC_FIELD) {
0861:                            if (count < 4) {
0862:                                res = formatData
0863:                                        .getZoneString(
0864:                                                zid,
0865:                                                DateFormatSymbols.TIMEZONE_SHORT_GENERIC);
0866:                            } else {
0867:                                res = formatData
0868:                                        .getZoneString(
0869:                                                zid,
0870:                                                DateFormatSymbols.TIMEZONE_LONG_GENERIC);
0871:                            }
0872:                        } else {
0873:                            if (cal.get(Calendar.DST_OFFSET) != 0) {
0874:                                if (count < 4) {
0875:                                    res = formatData
0876:                                            .getZoneString(
0877:                                                    zid,
0878:                                                    DateFormatSymbols.TIMEZONE_SHORT_DAYLIGHT);
0879:                                } else {
0880:                                    res = formatData
0881:                                            .getZoneString(
0882:                                                    zid,
0883:                                                    DateFormatSymbols.TIMEZONE_LONG_DAYLIGHT);
0884:                                }
0885:                            } else {
0886:                                if (count < 4) {
0887:                                    res = formatData
0888:                                            .getZoneString(
0889:                                                    zid,
0890:                                                    DateFormatSymbols.TIMEZONE_SHORT_STANDARD);
0891:                                } else {
0892:                                    res = formatData
0893:                                            .getZoneString(
0894:                                                    zid,
0895:                                                    DateFormatSymbols.TIMEZONE_LONG_STANDARD);
0896:                                }
0897:                            }
0898:                        }
0899:                    }
0900:                    if (res == null || res.length() == 0) {
0901:                        // note, tr35 does not describe the special case for 'no country' 
0902:                        // implemented below, this is from discussion with Mark
0903:                        if (zid == null || !isGeneric
0904:                                || ZoneMeta.getCanonicalCountry(zid) == null) {
0905:                            long offset = cal.get(Calendar.ZONE_OFFSET)
0906:                                    + cal.get(Calendar.DST_OFFSET);
0907:                            res = ZoneMeta.displayGMT(offset, locale);
0908:                        } else {
0909:                            /*
0910:                            String city = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_EXEMPLAR_CITY);
0911:                            res = ZoneMeta.displayFallback(zid, city, locale);
0912:                             */
0913:                            res = formatData.getZoneString(zid,
0914:                                    DateFormatSymbols.TIMEZONE_EXEMPLAR_CITY);
0915:
0916:                            if (res == null) {
0917:                                res = ZoneMeta.displayFallback(zid, null,
0918:                                        locale);
0919:                            }
0920:                        }
0921:                    }
0922:
0923:                    if (res.length() == 0) {
0924:                        appendGMT(buf, cal);
0925:                    } else {
0926:                        buf.append(res);
0927:                    }
0928:                }
0929:                    break;
0930:                case 23: // 'Z' - TIMEZONE_RFC
0931:                {
0932:                    if (count < 4) {
0933:                        // 'short' (standard Java) form, must use ASCII digits
0934:                        long val = (cal.get(Calendar.ZONE_OFFSET) + cal
0935:                                .get(Calendar.DST_OFFSET))
0936:                                / millisPerMinute;
0937:                        char sign = '+';
0938:                        if (val < 0) {
0939:                            val = -val;
0940:                            sign = '-';
0941:                        }
0942:                        val = (val / 60) * 100 + (val % 60); // minutes => KKmm
0943:                        buf.append(sign);
0944:                        fastZeroPaddingNubmer(buf, (int) val, 4, 4, '0');
0945:                    } else {
0946:                        // long form, localized GMT pattern
0947:                        // not in 3.4 locale data, need to add, so use same default as for general time zone names
0948:                        long val = cal.get(Calendar.ZONE_OFFSET)
0949:                                + cal.get(Calendar.DST_OFFSET);
0950:                        buf.append(ZoneMeta.displayGMT(val, locale));
0951:                    }
0952:                }
0953:                    break;
0954:                case 25: // 'c' - STANDALONE DAY
0955:                    if (count == 5)
0956:                        buf.append(formatData.standaloneNarrowWeekdays[value]);
0957:                    else if (count == 4)
0958:                        buf.append(formatData.standaloneWeekdays[value]);
0959:                    else if (count == 3)
0960:                        buf.append(formatData.standaloneShortWeekdays[value]);
0961:                    else
0962:                        zeroPaddingNumber(buf, value, 1, maxIntCount);
0963:                    break;
0964:                case 26: // 'L' - STANDALONE MONTH
0965:                    if (count == 5)
0966:                        buf.append(formatData.standaloneNarrowMonths[value]);
0967:                    else if (count == 4)
0968:                        buf.append(formatData.standaloneMonths[value]);
0969:                    else if (count == 3)
0970:                        buf.append(formatData.standaloneShortMonths[value]);
0971:                    else
0972:                        zeroPaddingNumber(buf, value + 1, count, maxIntCount);
0973:                    break;
0974:                case 27: // 'Q' - QUARTER
0975:                    if (count >= 4)
0976:                        buf.append(formatData.quarters[value / 3]);
0977:                    else if (count == 3)
0978:                        buf.append(formatData.shortQuarters[value / 3]);
0979:                    else
0980:                        zeroPaddingNumber(buf, (value / 3) + 1, count,
0981:                                maxIntCount);
0982:                    break;
0983:                case 28: // 'q' - STANDALONE QUARTER
0984:                    if (count >= 4)
0985:                        buf.append(formatData.standaloneQuarters[value / 3]);
0986:                    else if (count == 3)
0987:                        buf
0988:                                .append(formatData.standaloneShortQuarters[value / 3]);
0989:                    else
0990:                        zeroPaddingNumber(buf, (value / 3) + 1, count,
0991:                                maxIntCount);
0992:                    break;
0993:                default:
0994:                    // case 3: // 'd' - DATE
0995:                    // case 5: // 'H' - HOUR_OF_DAY (0..23)
0996:                    // case 6: // 'm' - MINUTE
0997:                    // case 7: // 's' - SECOND
0998:                    // case 10: // 'D' - DAY_OF_YEAR
0999:                    // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
1000:                    // case 12: // 'w' - WEEK_OF_YEAR
1001:                    // case 13: // 'W' - WEEK_OF_MONTH
1002:                    // case 16: // 'K' - HOUR (0..11)
1003:                    // case 18: // 'Y' - YEAR_WOY
1004:                    // case 19: // 'e' - DOW_LOCAL
1005:                    // case 20: // 'u' - EXTENDED_YEAR
1006:                    // case 21: // 'g' - JULIAN_DAY
1007:                    // case 22: // 'A' - MILLISECONDS_IN_DAY
1008:
1009:                    zeroPaddingNumber(buf, value, count, maxIntCount);
1010:                    break;
1011:                } // switch (patternCharIndex)
1012:
1013:                // Set the FieldPosition (for the first occurence only)
1014:                if (pos.getBeginIndex() == pos.getEndIndex()
1015:                        && pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
1016:                    pos.setBeginIndex(beginOffset);
1017:                    pos.setEndIndex(beginOffset + buf.length() - bufstart);
1018:                }
1019:            }
1020:
1021:            private void appendGMT(StringBuffer buf, Calendar cal) {
1022:                int value = cal.get(Calendar.ZONE_OFFSET)
1023:                        + cal.get(Calendar.DST_OFFSET);
1024:
1025:                if (value < 0) {
1026:                    buf.append(GMT_MINUS);
1027:                    value = -value; // suppress the '-' sign for text display.
1028:                } else {
1029:                    buf.append(GMT_PLUS);
1030:                }
1031:
1032:                zeroPaddingNumber(buf, (int) (value / millisPerHour), 2, 2);
1033:                buf.append((char) 0x003A) /*':'*/;
1034:                zeroPaddingNumber(buf,
1035:                        (int) ((value % millisPerHour) / millisPerMinute), 2, 2);
1036:            }
1037:
1038:            /**
1039:             * Internal method. Returns null if the value of an array is empty, or if the
1040:             * index is out of bounds
1041:             */
1042:            private String getZoneArrayValue(String[] zs, int ix) {
1043:                if (ix >= 0 && ix < zs.length) {
1044:                    String result = zs[ix];
1045:                    if (result != null && result.length() != 0) {
1046:                        return result;
1047:                    }
1048:                }
1049:                return null;
1050:            }
1051:
1052:            /**
1053:             * Internal high-speed method.  Reuses a StringBuffer for results
1054:             * instead of creating a String on the heap for each call.
1055:             * @internal
1056:             * @deprecated This API is ICU internal only.
1057:             */
1058:            protected void zeroPaddingNumber(StringBuffer buf, int value,
1059:                    int minDigits, int maxDigits) {
1060:                if (useFastZeroPaddingNumber) {
1061:                    fastZeroPaddingNubmer(buf, value, minDigits, maxDigits,
1062:                            zeroDigit);
1063:                    return;
1064:                }
1065:                FieldPosition pos = new FieldPosition(-1);
1066:                numberFormat.setMinimumIntegerDigits(minDigits);
1067:                numberFormat.setMaximumIntegerDigits(maxDigits);
1068:                numberFormat.format(value, buf, pos);
1069:            }
1070:
1071:            /**
1072:             * Internal faster method.  This method does not use NumberFormat
1073:             * to format digits.
1074:             * @internal
1075:             * @deprecated This API is ICU internal only.
1076:             */
1077:            private void fastZeroPaddingNubmer(StringBuffer buf, int value,
1078:                    int minDigits, int maxDigits, char zero) {
1079:                value = value < 0 ? -value : value; //??
1080:                minDigits = minDigits < maxDigits ? minDigits : maxDigits;
1081:                int limit = decimalBuf.length < maxDigits ? decimalBuf.length
1082:                        : maxDigits;
1083:                int index = limit - 1;
1084:                while (true) {
1085:                    decimalBuf[index] = (char) ((value % 10) + zero);
1086:                    value /= 10;
1087:                    if (index == 0 || value == 0) {
1088:                        break;
1089:                    }
1090:                    index--;
1091:                }
1092:                int padding = minDigits - (limit - index);
1093:                for (; padding > 0; padding--) {
1094:                    decimalBuf[--index] = zero;
1095:                }
1096:                buf.append(decimalBuf, index, limit - index);
1097:            }
1098:
1099:            /**
1100:             * Overrides superclass method
1101:             * @stable ICU 2.0
1102:             */
1103:            public void setNumberFormat(NumberFormat newNumberFormat) {
1104:                super .setNumberFormat(newNumberFormat);
1105:                if (newNumberFormat instanceof  DecimalFormat) {
1106:                    zeroDigit = ((DecimalFormat) newNumberFormat)
1107:                            .getDecimalFormatSymbols().getZeroDigit();
1108:                    useFastZeroPaddingNumber = true;
1109:                } else {
1110:                    useFastZeroPaddingNumber = false;
1111:                }
1112:            }
1113:
1114:            /**
1115:             * Formats a number with the specified minimum and maximum number of digits.
1116:             * @stable ICU 2.0
1117:             */
1118:            protected String zeroPaddingNumber(long value, int minDigits,
1119:                    int maxDigits) {
1120:                numberFormat.setMinimumIntegerDigits(minDigits);
1121:                numberFormat.setMaximumIntegerDigits(maxDigits);
1122:                return numberFormat.format(value);
1123:            }
1124:
1125:            /**
1126:             * Format characters that indicate numeric fields.  The character
1127:             * at index 0 is treated specially.
1128:             */
1129:            private static final String NUMERIC_FORMAT_CHARS = "MyudhHmsSDFwWkK";
1130:
1131:            /**
1132:             * Return true if the given format character, occuring count
1133:             * times, represents a numeric field.
1134:             */
1135:            private static final boolean isNumeric(char formatChar, int count) {
1136:                int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
1137:                return (i > 0 || (i == 0 && count < 3));
1138:            }
1139:
1140:            /**
1141:             * Overrides DateFormat
1142:             * @see DateFormat
1143:             * @stable ICU 2.0
1144:             */
1145:            public void parse(String text, Calendar cal, ParsePosition parsePos) {
1146:                int pos = parsePos.getIndex();
1147:                int start = pos;
1148:                boolean[] ambiguousYear = { false };
1149:                int count = 0;
1150:
1151:                // hack, clear parsedTimeZone
1152:                parsedTimeZone = null;
1153:
1154:                // For parsing abutting numeric fields. 'abutPat' is the
1155:                // offset into 'pattern' of the first of 2 or more abutting
1156:                // numeric fields.  'abutStart' is the offset into 'text'
1157:                // where parsing the fields begins. 'abutPass' starts off as 0
1158:                // and increments each time we try to parse the fields.
1159:                int abutPat = -1; // If >=0, we are in a run of abutting numeric fields
1160:                int abutStart = 0;
1161:                int abutPass = 0;
1162:                boolean inQuote = false;
1163:
1164:                for (int i = 0; i < pattern.length(); ++i) {
1165:                    char ch = pattern.charAt(i);
1166:
1167:                    // Handle alphabetic field characters.
1168:                    if (!inQuote
1169:                            && (ch >= 'A' && ch <= 'Z' || ch >= 'a'
1170:                                    && ch <= 'z')) {
1171:                        int fieldPat = i;
1172:
1173:                        // Count the length of this field specifier
1174:                        count = 1;
1175:                        while ((i + 1) < pattern.length()
1176:                                && pattern.charAt(i + 1) == ch) {
1177:                            ++count;
1178:                            ++i;
1179:                        }
1180:
1181:                        if (isNumeric(ch, count)) {
1182:                            if (abutPat < 0) {
1183:                                // Determine if there is an abutting numeric field.  For
1184:                                // most fields we can just look at the next characters,
1185:                                // but the 'm' field is either numeric or text,
1186:                                // depending on the count, so we have to look ahead for
1187:                                // that field.
1188:                                if ((i + 1) < pattern.length()) {
1189:                                    boolean abutting;
1190:                                    char nextCh = pattern.charAt(i + 1);
1191:                                    int k = NUMERIC_FORMAT_CHARS
1192:                                            .indexOf(nextCh);
1193:                                    if (k == 0) {
1194:                                        int j = i + 2;
1195:                                        while (j < pattern.length()
1196:                                                && pattern.charAt(j) == nextCh) {
1197:                                            ++j;
1198:                                        }
1199:                                        abutting = (j - i) < 4; // nextCount < 3
1200:                                    } else {
1201:                                        abutting = k > 0;
1202:                                    }
1203:
1204:                                    // Record the start of a set of abutting numeric
1205:                                    // fields.
1206:                                    if (abutting) {
1207:                                        abutPat = fieldPat;
1208:                                        abutStart = pos;
1209:                                        abutPass = 0;
1210:                                    }
1211:                                }
1212:                            }
1213:                        } else {
1214:                            abutPat = -1; // End of any abutting fields
1215:                        }
1216:
1217:                        // Handle fields within a run of abutting numeric fields.  Take
1218:                        // the pattern "HHmmss" as an example. We will try to parse
1219:                        // 2/2/2 characters of the input text, then if that fails,
1220:                        // 1/2/2.  We only adjust the width of the leftmost field; the
1221:                        // others remain fixed.  This allows "123456" => 12:34:56, but
1222:                        // "12345" => 1:23:45.  Likewise, for the pattern "yyyyMMdd" we
1223:                        // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
1224:                        if (abutPat >= 0) {
1225:                            // If we are at the start of a run of abutting fields, then
1226:                            // shorten this field in each pass.  If we can't shorten
1227:                            // this field any more, then the parse of this set of
1228:                            // abutting numeric fields has failed.
1229:                            if (fieldPat == abutPat) {
1230:                                count -= abutPass++;
1231:                                if (count == 0) {
1232:                                    parsePos.setIndex(start);
1233:                                    parsePos.setErrorIndex(pos);
1234:                                    return;
1235:                                }
1236:                            }
1237:
1238:                            pos = subParse(text, pos, ch, count, true, false,
1239:                                    ambiguousYear, cal);
1240:
1241:                            // If the parse fails anywhere in the run, back up to the
1242:                            // start of the run and retry.
1243:                            if (pos < 0) {
1244:                                i = abutPat - 1;
1245:                                pos = abutStart;
1246:                                continue;
1247:                            }
1248:                        }
1249:
1250:                        // Handle non-numeric fields and non-abutting numeric
1251:                        // fields.
1252:                        else {
1253:                            int s = pos;
1254:                            pos = subParse(text, pos, ch, count, false, true,
1255:                                    ambiguousYear, cal);
1256:
1257:                            if (pos < 0) {
1258:                                parsePos.setErrorIndex(s);
1259:                                parsePos.setIndex(start);
1260:                                return;
1261:                            }
1262:                        }
1263:                    }
1264:
1265:                    // Handle literal pattern characters.  These are any
1266:                    // quoted characters and non-alphabetic unquoted
1267:                    // characters.
1268:                    else {
1269:
1270:                        abutPat = -1; // End of any abutting fields
1271:
1272:                        // Handle quotes.  Two consecutive quotes is a quote
1273:                        // literal, inside or outside of quotes.  Otherwise a
1274:                        // quote indicates entry or exit from a quoted region.
1275:                        if (ch == '\'') {
1276:                            // Match a quote literal '' within OR outside of quotes
1277:                            if ((i + 1) < pattern.length()
1278:                                    && pattern.charAt(i + 1) == ch) {
1279:                                ++i; // Skip over doubled quote
1280:                                // Fall through and treat quote as a literal
1281:                            } else {
1282:                                // Enter or exit quoted region
1283:                                inQuote = !inQuote;
1284:                                continue;
1285:                            }
1286:                        }
1287:
1288:                        // A run of white space in the pattern matches a run
1289:                        // of white space in the input text.
1290:                        if (UCharacterProperty.isRuleWhiteSpace(ch)) {
1291:                            // Advance over run in pattern
1292:                            while ((i + 1) < pattern.length()
1293:                                    && UCharacterProperty
1294:                                            .isRuleWhiteSpace(pattern
1295:                                                    .charAt(i + 1))) {
1296:                                ++i;
1297:                            }
1298:
1299:                            // Advance over run in input text
1300:                            int s = pos;
1301:                            while (pos < text.length()
1302:                                    && UCharacter.isUWhiteSpace(text
1303:                                            .charAt(pos))) {
1304:                                ++pos;
1305:                            }
1306:
1307:                            // Must see at least one white space char in input
1308:                            if (pos > s) {
1309:                                continue;
1310:                            }
1311:                        } else if (pos < text.length()
1312:                                && text.charAt(pos) == ch) {
1313:                            // Match a literal
1314:                            ++pos;
1315:                            continue;
1316:                        }
1317:
1318:                        // We fall through to this point if the match fails
1319:                        parsePos.setIndex(start);
1320:                        parsePos.setErrorIndex(pos);
1321:                        return;
1322:                    }
1323:                }
1324:
1325:                // At this point the fields of Calendar have been set.  Calendar
1326:                // will fill in default values for missing fields when the time
1327:                // is computed.
1328:
1329:                parsePos.setIndex(pos);
1330:
1331:                // This part is a problem:  When we call parsedDate.after, we compute the time.
1332:                // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year
1333:                // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.
1334:                // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am
1335:                // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
1336:                // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we
1337:                // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is
1338:                // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
1339:                /*
1340:                  Date parsedDate = cal.getTime();
1341:                  if( ambiguousYear[0] && !parsedDate.after(defaultCenturyStart) ) {
1342:                  cal.add(Calendar.YEAR, 100);
1343:                  parsedDate = cal.getTime();
1344:                  }
1345:                 */
1346:                // Because of the above condition, save off the fields in case we need to readjust.
1347:                // The procedure we use here is not particularly efficient, but there is no other
1348:                // way to do this given the API restrictions present in Calendar.  We minimize
1349:                // inefficiency by only performing this computation when it might apply, that is,
1350:                // when the two-digit year is equal to the start year, and thus might fall at the
1351:                // front or the back of the default century.  This only works because we adjust
1352:                // the year correctly to start with in other cases -- see subParse().
1353:                try {
1354:                    if (ambiguousYear[0] || parsedTimeZone != null) {
1355:                        // We need a copy of the fields, and we need to avoid triggering a call to
1356:                        // complete(), which will recalculate the fields.  Since we can't access
1357:                        // the fields[] array in Calendar, we clone the entire object.  This will
1358:                        // stop working if Calendar.clone() is ever rewritten to call complete().
1359:                        Calendar copy = (Calendar) cal.clone();
1360:                        if (ambiguousYear[0]) { // the two-digit year == the default start year
1361:                            Date parsedDate = copy.getTime();
1362:                            if (parsedDate.before(defaultCenturyStart)) {
1363:                                // We can't use add here because that does a complete() first.
1364:                                cal.set(Calendar.YEAR,
1365:                                        defaultCenturyStartYear + 100);
1366:                            }
1367:                        }
1368:
1369:                        if (parsedTimeZone != null) {
1370:                            TimeZone tz = parsedTimeZone;
1371:
1372:                            // the calendar represents the parse as gmt time
1373:                            // we need to turn this into local time, so we add the raw offset
1374:                            // then we ask the timezone to handle this local time
1375:                            int[] offsets = new int[2];
1376:                            tz.getOffset(copy.getTimeInMillis()
1377:                                    + tz.getRawOffset(), true, offsets);
1378:
1379:                            cal.set(Calendar.ZONE_OFFSET, offsets[0]);
1380:                            cal.set(Calendar.DST_OFFSET, offsets[1]);
1381:                            cal.setTimeZone(tz);
1382:                        }
1383:                    }
1384:                }
1385:                // An IllegalArgumentException will be thrown by Calendar.getTime()
1386:                // if any fields are out of range, e.g., MONTH == 17.
1387:                catch (IllegalArgumentException e) {
1388:                    parsePos.setErrorIndex(pos);
1389:                    parsePos.setIndex(start);
1390:                }
1391:            }
1392:
1393:            /**
1394:             * Attempt to match the text at a given position against an array of
1395:             * strings.  Since multiple strings in the array may match (for
1396:             * example, if the array contains "a", "ab", and "abc", all will match
1397:             * the input string "abcd") the longest match is returned.  As a side
1398:             * effect, the given field of <code>cal</code> is set to the index
1399:             * of the best match, if there is one.
1400:             * @param text the time text being parsed.
1401:             * @param start where to start parsing.
1402:             * @param field the date field being parsed.
1403:             * @param data the string array to parsed.
1404:             * @return the new start position if matching succeeded; a negative
1405:             * number indicating matching failure, otherwise.  As a side effect,
1406:             * sets the <code>cal</code> field <code>field</code> to the index
1407:             * of the best match, if matching succeeded.
1408:             * @stable ICU 2.0
1409:             */
1410:            protected int matchString(String text, int start, int field,
1411:                    String[] data, Calendar cal) {
1412:                int i = 0;
1413:                int count = data.length;
1414:
1415:                if (field == Calendar.DAY_OF_WEEK)
1416:                    i = 1;
1417:
1418:                // There may be multiple strings in the data[] array which begin with
1419:                // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1420:                // We keep track of the longest match, and return that.  Note that this
1421:                // unfortunately requires us to test all array elements.
1422:                int bestMatchLength = 0, bestMatch = -1;
1423:                for (; i < count; ++i) {
1424:                    int length = data[i].length();
1425:                    // Always compare if we have no match yet; otherwise only compare
1426:                    // against potentially better matches (longer strings).
1427:                    if (length > bestMatchLength
1428:                            && text.regionMatches(true, start, data[i], 0,
1429:                                    length)) {
1430:                        bestMatch = i;
1431:                        bestMatchLength = length;
1432:                    }
1433:                }
1434:                if (bestMatch >= 0) {
1435:                    cal.set(field, bestMatch);
1436:                    return start + bestMatchLength;
1437:                }
1438:                return -start;
1439:            }
1440:
1441:            /**
1442:             * Attempt to match the text at a given position against an array of quarter
1443:             * strings.  Since multiple strings in the array may match (for
1444:             * example, if the array contains "a", "ab", and "abc", all will match
1445:             * the input string "abcd") the longest match is returned.  As a side
1446:             * effect, the given field of <code>cal</code> is set to the index
1447:             * of the best match, if there is one.
1448:             * @param text the time text being parsed.
1449:             * @param start where to start parsing.
1450:             * @param field the date field being parsed.
1451:             * @param data the string array to parsed.
1452:             * @return the new start position if matching succeeded; a negative
1453:             * number indicating matching failure, otherwise.  As a side effect,
1454:             * sets the <code>cal</code> field <code>field</code> to the index
1455:             * of the best match, if matching succeeded.
1456:             * @stable ICU 2.0
1457:             */
1458:            protected int matchQuarterString(String text, int start, int field,
1459:                    String[] data, Calendar cal) {
1460:                int i = 0;
1461:                int count = data.length;
1462:
1463:                // There may be multiple strings in the data[] array which begin with
1464:                // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1465:                // We keep track of the longest match, and return that.  Note that this
1466:                // unfortunately requires us to test all array elements.
1467:                int bestMatchLength = 0, bestMatch = -1;
1468:                for (; i < count; ++i) {
1469:                    int length = data[i].length();
1470:                    // Always compare if we have no match yet; otherwise only compare
1471:                    // against potentially better matches (longer strings).
1472:                    if (length > bestMatchLength
1473:                            && text.regionMatches(true, start, data[i], 0,
1474:                                    length)) {
1475:                        bestMatch = i;
1476:                        bestMatchLength = length;
1477:                    }
1478:                }
1479:
1480:                if (bestMatch >= 0) {
1481:                    cal.set(field, bestMatch * 3);
1482:                    return start + bestMatchLength;
1483:                }
1484:
1485:                return -start;
1486:            }
1487:
1488:            /**
1489:             * find time zone 'text' matched zoneStrings and set cal
1490:             */
1491:            private int subParseZoneString(String text, int start, Calendar cal) {
1492:                // At this point, check for named time zones by looking through
1493:                // the locale data from the DateFormatZoneData strings.
1494:                // Want to be able to parse both short and long forms.
1495:
1496:                // optimize for calendar's current time zone
1497:                TimeZone tz = null;
1498:                String zid = null, value = null;
1499:                int type = -1;
1500:
1501:                DateFormatSymbols.ZoneItem item = formatData
1502:                        .findZoneIDTypeValue(text, start);
1503:                if (item != null) {
1504:                    zid = item.zid;
1505:                    value = item.value;
1506:                    type = item.type;
1507:                }
1508:
1509:                if (zid != null) {
1510:                    tz = TimeZone.getTimeZone(zid);
1511:                }
1512:
1513:                if (tz != null) { // Matched any ?
1514:                    // always set zone offset, needed to get correct hour in wall time
1515:                    // when checking daylight savings
1516:                    cal.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
1517:                    if (type == DateFormatSymbols.TIMEZONE_SHORT_STANDARD
1518:                            || type == DateFormatSymbols.TIMEZONE_LONG_STANDARD) {
1519:                        // standard time
1520:                        cal.set(Calendar.DST_OFFSET, 0);
1521:                        tz = null;
1522:                    } else if (type == DateFormatSymbols.TIMEZONE_SHORT_DAYLIGHT
1523:                            || type == DateFormatSymbols.TIMEZONE_LONG_DAYLIGHT) {
1524:                        // daylight time
1525:                        // use the correct DST SAVINGS for the zone.
1526:                        // cal.set(UCAL_DST_OFFSET, tz->getDSTSavings());
1527:                        cal.set(Calendar.DST_OFFSET, millisPerHour);
1528:                        tz = null;
1529:                    } else {
1530:                        // either standard or daylight
1531:                        // need to finish getting the date, then compute dst offset as
1532:                        // appropriate
1533:                        parsedTimeZone = tz;
1534:                    }
1535:                    if (value != null) {
1536:                        return start + value.length();
1537:                    }
1538:                }
1539:                // complete failure
1540:                return 0;
1541:            }
1542:
1543:            /**
1544:             * Protected method that converts one field of the input string into a
1545:             * numeric field value in <code>cal</code>.  Returns -start (for
1546:             * ParsePosition) if failed.  Subclasses may override this method to
1547:             * modify or add parsing capabilities.
1548:             * @param text the time text to be parsed.
1549:             * @param start where to start parsing.
1550:             * @param ch the pattern character for the date field text to be parsed.
1551:             * @param count the count of a pattern character.
1552:             * @param obeyCount if true, then the next field directly abuts this one,
1553:             * and we should use the count to know when to stop parsing.
1554:             * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
1555:             * is true, then a two-digit year was parsed and may need to be readjusted.
1556:             * @return the new start position if matching succeeded; a negative
1557:             * number indicating matching failure, otherwise.  As a side effect,
1558:             * set the appropriate field of <code>cal</code> with the parsed
1559:             * value.
1560:             * @stable ICU 2.0
1561:             */
1562:            protected int subParse(String text, int start, char ch, int count,
1563:                    boolean obeyCount, boolean allowNegative,
1564:                    boolean[] ambiguousYear, Calendar cal) {
1565:                Number number = null;
1566:                int value = 0;
1567:                int i;
1568:                ParsePosition pos = new ParsePosition(0);
1569:                int patternCharIndex = DateFormatSymbols.patternChars
1570:                        .indexOf(ch);
1571:
1572:                if (patternCharIndex == -1) {
1573:                    return -start;
1574:                }
1575:
1576:                int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1577:
1578:                // If there are any spaces here, skip over them.  If we hit the end
1579:                // of the string, then fail.
1580:                for (;;) {
1581:                    if (start >= text.length()) {
1582:                        return -start;
1583:                    }
1584:                    int c = UTF16.charAt(text, start);
1585:                    if (!UCharacter.isUWhiteSpace(c)) {
1586:                        break;
1587:                    }
1588:                    start += UTF16.getCharCount(c);
1589:                }
1590:                pos.setIndex(start);
1591:
1592:                // We handle a few special cases here where we need to parse
1593:                // a number value.  We handle further, more generic cases below.  We need
1594:                // to handle some of them here because some fields require extra processing on
1595:                // the parsed value.
1596:                if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/
1597:                        || patternCharIndex == 15 /*HOUR1_FIELD*/
1598:                        || (patternCharIndex == 2 /*MONTH_FIELD*/&& count <= 2)
1599:                        || patternCharIndex == 1 || patternCharIndex == 8) {
1600:                    // It would be good to unify this with the obeyCount logic below,
1601:                    // but that's going to be difficult.
1602:                    if (obeyCount) {
1603:                        if ((start + count) > text.length())
1604:                            return -start;
1605:                        number = parseInt(text.substring(0, start + count),
1606:                                pos, allowNegative);
1607:                    } else
1608:                        number = parseInt(text, pos, allowNegative);
1609:                    if (number == null)
1610:                        return -start;
1611:                    value = number.intValue();
1612:                }
1613:
1614:                switch (patternCharIndex) {
1615:                case 0: // 'G' - ERA
1616:                    if (count == 4) {
1617:                        return matchString(text, start, Calendar.ERA,
1618:                                formatData.eraNames, cal);
1619:                    } else {
1620:                        return matchString(text, start, Calendar.ERA,
1621:                                formatData.eras, cal);
1622:                    }
1623:                case 1: // 'y' - YEAR
1624:                    // If there are 3 or more YEAR pattern characters, this indicates
1625:                    // that the year value is to be treated literally, without any
1626:                    // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
1627:                    // we made adjustments to place the 2-digit year in the proper
1628:                    // century, for parsed strings from "00" to "99".  Any other string
1629:                    // is treated literally:  "2250", "-1", "1", "002".
1630:                    /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
1631:                    if (count == 2 && (pos.getIndex() - start) == 2
1632:                            && Character.isDigit(text.charAt(start))
1633:                            && Character.isDigit(text.charAt(start + 1))) {
1634:                        // Assume for example that the defaultCenturyStart is 6/18/1903.
1635:                        // This means that two-digit years will be forced into the range
1636:                        // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
1637:                        // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
1638:                        // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
1639:                        // other fields specify a date before 6/18, or 1903 if they specify a
1640:                        // date afterwards.  As a result, 03 is an ambiguous year.  All other
1641:                        // two-digit years are unambiguous.
1642:                        int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
1643:                        ambiguousYear[0] = value == ambiguousTwoDigitYear;
1644:                        value += (defaultCenturyStartYear / 100) * 100
1645:                                + (value < ambiguousTwoDigitYear ? 100 : 0);
1646:                    }
1647:                    cal.set(Calendar.YEAR, value);
1648:                    return pos.getIndex();
1649:                case 2: // 'M' - MONTH
1650:                    if (count <= 2) // i.e., M or MM.
1651:                    {
1652:                        // Don't want to parse the month if it is a string
1653:                        // while pattern uses numeric style: M or MM.
1654:                        // [We computed 'value' above.]
1655:                        cal.set(Calendar.MONTH, value - 1);
1656:                        return pos.getIndex();
1657:                    } else {
1658:                        // count >= 3 // i.e., MMM or MMMM
1659:                        // Want to be able to parse both short and long forms.
1660:                        // Try count == 4 first:
1661:                        int newStart = matchString(text, start, Calendar.MONTH,
1662:                                formatData.months, cal);
1663:                        if (newStart > 0) {
1664:                            return newStart;
1665:                        } else { // count == 4 failed, now try count == 3
1666:                            return matchString(text, start, Calendar.MONTH,
1667:                                    formatData.shortMonths, cal);
1668:                        }
1669:                    }
1670:                case 26: // 'L' - STAND_ALONE_MONTH
1671:                    if (count <= 2) // i.e., M or MM.
1672:                    {
1673:                        // Don't want to parse the month if it is a string
1674:                        // while pattern uses numeric style: M or MM.
1675:                        // [We computed 'value' above.]
1676:                        cal.set(Calendar.MONTH, value - 1);
1677:                        return pos.getIndex();
1678:                    } else {
1679:                        // count >= 3 // i.e., MMM or MMMM
1680:                        // Want to be able to parse both short and long forms.
1681:                        // Try count == 4 first:
1682:                        int newStart = matchString(text, start, Calendar.MONTH,
1683:                                formatData.standaloneMonths, cal);
1684:                        if (newStart > 0) {
1685:                            return newStart;
1686:                        } else { // count == 4 failed, now try count == 3
1687:                            return matchString(text, start, Calendar.MONTH,
1688:                                    formatData.standaloneShortMonths, cal);
1689:                        }
1690:                    }
1691:                case 4: // 'k' - HOUR_OF_DAY (1..24)
1692:                    // [We computed 'value' above.]
1693:                    if (value == cal.getMaximum(Calendar.HOUR_OF_DAY) + 1)
1694:                        value = 0;
1695:                    cal.set(Calendar.HOUR_OF_DAY, value);
1696:                    return pos.getIndex();
1697:                case 8: // 'S' - FRACTIONAL_SECOND
1698:                    // Fractional seconds left-justify
1699:                    i = pos.getIndex() - start;
1700:                    if (i < 3) {
1701:                        while (i < 3) {
1702:                            value *= 10;
1703:                            i++;
1704:                        }
1705:                    } else {
1706:                        int a = 1;
1707:                        while (i > 3) {
1708:                            a *= 10;
1709:                            i--;
1710:                        }
1711:                        value = (value + (a >> 1)) / a;
1712:                    }
1713:                    cal.set(Calendar.MILLISECOND, value);
1714:                    return pos.getIndex();
1715:                case 9: { // 'E' - DAY_OF_WEEK
1716:                    // Want to be able to parse both short and long forms.
1717:                    // Try count == 4 (EEEE) first:
1718:                    int newStart = matchString(text, start,
1719:                            Calendar.DAY_OF_WEEK, formatData.weekdays, cal);
1720:                    if (newStart > 0) {
1721:                        return newStart;
1722:                    } else { // EEEE failed, now try EEE
1723:                        return matchString(text, start, Calendar.DAY_OF_WEEK,
1724:                                formatData.shortWeekdays, cal);
1725:                    }
1726:                }
1727:                case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
1728:                    // Want to be able to parse both short and long forms.
1729:                    // Try count == 4 (cccc) first:
1730:                    int newStart = matchString(text, start,
1731:                            Calendar.DAY_OF_WEEK,
1732:                            formatData.standaloneWeekdays, cal);
1733:                    if (newStart > 0) {
1734:                        return newStart;
1735:                    } else { // cccc failed, now try ccc
1736:                        return matchString(text, start, Calendar.DAY_OF_WEEK,
1737:                                formatData.standaloneShortWeekdays, cal);
1738:                    }
1739:                }
1740:                case 14: // 'a' - AM_PM
1741:                    return matchString(text, start, Calendar.AM_PM,
1742:                            formatData.ampms, cal);
1743:                case 15: // 'h' - HOUR (1..12)
1744:                    // [We computed 'value' above.]
1745:                    if (value == cal.getLeastMaximum(Calendar.HOUR) + 1)
1746:                        value = 0;
1747:                    cal.set(Calendar.HOUR, value);
1748:                    return pos.getIndex();
1749:                case 17: // 'z' - ZONE_OFFSET
1750:                case 23: // 'Z' - TIMEZONE_RFC
1751:                case 24: // 'v' - TIMEZONE_GENERIC
1752:                    // First try to parse generic forms such as GMT-07:00. Do this first
1753:                    // in case localized DateFormatZoneData contains the string "GMT"
1754:                    // for a zone; in that case, we don't want to match the first three
1755:                    // characters of GMT+/-HH:MM etc.
1756:                {
1757:                    int sign = 0;
1758:                    int offset;
1759:
1760:                    // For time zones that have no known names, look for strings
1761:                    // of the form:
1762:                    //    GMT[+-]hours:minutes or
1763:                    //    GMT[+-]hhmm or
1764:                    //    GMT.
1765:                    if ((text.length() - start) >= GMT.length()
1766:                            && text.regionMatches(true, start, GMT, 0, GMT
1767:                                    .length())) {
1768:                        cal.set(Calendar.DST_OFFSET, 0);
1769:
1770:                        pos.setIndex(start + GMT.length());
1771:
1772:                        try { // try-catch for "GMT" only time zone string
1773:                            switch (text.charAt(pos.getIndex())) {
1774:                            case '+':
1775:                                sign = 1;
1776:                                break;
1777:                            case '-':
1778:                                sign = -1;
1779:                                break;
1780:                            }
1781:                        } catch (StringIndexOutOfBoundsException e) {
1782:                        }
1783:                        if (sign == 0) {
1784:                            cal.set(Calendar.ZONE_OFFSET, 0);
1785:                            return pos.getIndex();
1786:                        }
1787:
1788:                        // Look for hours:minutes or hhmm.
1789:                        pos.setIndex(pos.getIndex() + 1);
1790:                        int st = pos.getIndex();
1791:                        Number tzNumber = numberFormat.parse(text, pos);
1792:                        if (tzNumber == null) {
1793:                            return -start;
1794:                        }
1795:                        if (pos.getIndex() < text.length()
1796:                                && text.charAt(pos.getIndex()) == ':') {
1797:
1798:                            // This is the hours:minutes case
1799:                            offset = tzNumber.intValue() * 60;
1800:                            pos.setIndex(pos.getIndex() + 1);
1801:                            tzNumber = numberFormat.parse(text, pos);
1802:                            if (tzNumber == null) {
1803:                                return -start;
1804:                            }
1805:                            offset += tzNumber.intValue();
1806:                        } else {
1807:                            // This is the hhmm case.
1808:                            offset = tzNumber.intValue();
1809:                            // Assume "-23".."+23" refers to hours.
1810:                            if (offset < 24 && (pos.getIndex() - st) <= 2)
1811:                                offset *= 60;
1812:                            else
1813:                                // todo: this looks questionable, should have more error checking
1814:                                offset = offset % 100 + offset / 100 * 60;
1815:                        }
1816:
1817:                        // Fall through for final processing below of 'offset' and 'sign'.
1818:                    } else {
1819:                        // At this point, check for named time zones by looking through
1820:                        // the locale data from the DateFormatZoneData strings.
1821:                        // Want to be able to parse both short and long forms.
1822:                        i = subParseZoneString(text, start, cal);
1823:                        if (i != 0)
1824:                            return i;
1825:
1826:                        // As a last resort, look for numeric timezones of the form
1827:                        // [+-]hhmm as specified by RFC 822.  This code is actually
1828:                        // a little more permissive than RFC 822.  It will try to do
1829:                        // its best with numbers that aren't strictly 4 digits long.
1830:                        DecimalFormat fmt = new DecimalFormat("+####;-####");
1831:                        fmt.setParseIntegerOnly(true);
1832:                        Number tzNumber = fmt.parse(text, pos);
1833:                        if (tzNumber == null) {
1834:                            return -start; // Wasn't actually a number.
1835:                        }
1836:                        offset = tzNumber.intValue();
1837:                        sign = 1;
1838:                        if (offset < 0) {
1839:                            sign = -1;
1840:                            offset = -offset;
1841:                        }
1842:                        // Assume "-23".."+23" refers to hours. Length includes sign.
1843:                        if (offset < 24 && (pos.getIndex() - start) <= 3)
1844:                            offset = offset * 60;
1845:                        else
1846:                            offset = offset % 100 + offset / 100 * 60;
1847:
1848:                        // Fall through for final processing below of 'offset' and 'sign'.
1849:                    }
1850:
1851:                    // Do the final processing for both of the above cases.  We only
1852:                    // arrive here if the form GMT+/-... or an RFC 822 form was seen.
1853:
1854:                    // assert (sign != 0) : sign; // enable when guaranteed JDK >= 1.4
1855:                    offset *= millisPerMinute * sign;
1856:
1857:                    if (cal.getTimeZone().useDaylightTime()) {
1858:                        cal.set(Calendar.DST_OFFSET, millisPerHour);
1859:                        offset -= millisPerHour;
1860:                    }
1861:                    cal.set(Calendar.ZONE_OFFSET, offset);
1862:
1863:                    return pos.getIndex();
1864:                }
1865:
1866:                case 27: // 'Q' - QUARTER
1867:                    if (count <= 2) // i.e., Q or QQ.
1868:                    {
1869:                        // Don't want to parse the quarter if it is a string
1870:                        // while pattern uses numeric style: Q or QQ.
1871:                        // [We computed 'value' above.]
1872:                        cal.set(Calendar.MONTH, (value - 1) * 3);
1873:                        return pos.getIndex();
1874:                    } else {
1875:                        // count >= 3 // i.e., QQQ or QQQQ
1876:                        // Want to be able to parse both short and long forms.
1877:                        // Try count == 4 first:
1878:                        int newStart = matchQuarterString(text, start,
1879:                                Calendar.MONTH, formatData.quarters, cal);
1880:                        if (newStart > 0) {
1881:                            return newStart;
1882:                        } else { // count == 4 failed, now try count == 3
1883:                            return matchQuarterString(text, start,
1884:                                    Calendar.MONTH, formatData.shortQuarters,
1885:                                    cal);
1886:                        }
1887:                    }
1888:
1889:                case 28: // 'q' - STANDALONE QUARTER
1890:                    if (count <= 2) // i.e., q or qq.
1891:                    {
1892:                        // Don't want to parse the quarter if it is a string
1893:                        // while pattern uses numeric style: q or qq.
1894:                        // [We computed 'value' above.]
1895:                        cal.set(Calendar.MONTH, (value - 1) * 3);
1896:                        return pos.getIndex();
1897:                    } else {
1898:                        // count >= 3 // i.e., qqq or qqqq
1899:                        // Want to be able to parse both short and long forms.
1900:                        // Try count == 4 first:
1901:                        int newStart = matchQuarterString(text, start,
1902:                                Calendar.MONTH, formatData.standaloneQuarters,
1903:                                cal);
1904:                        if (newStart > 0) {
1905:                            return newStart;
1906:                        } else { // count == 4 failed, now try count == 3
1907:                            return matchQuarterString(text, start,
1908:                                    Calendar.MONTH,
1909:                                    formatData.standaloneShortQuarters, cal);
1910:                        }
1911:                    }
1912:
1913:                default:
1914:                    // case 3: // 'd' - DATE
1915:                    // case 5: // 'H' - HOUR_OF_DAY (0..23)
1916:                    // case 6: // 'm' - MINUTE
1917:                    // case 7: // 's' - SECOND
1918:                    // case 10: // 'D' - DAY_OF_YEAR
1919:                    // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
1920:                    // case 12: // 'w' - WEEK_OF_YEAR
1921:                    // case 13: // 'W' - WEEK_OF_MONTH
1922:                    // case 16: // 'K' - HOUR (0..11)
1923:                    // case 18: // 'Y' - YEAR_WOY
1924:                    // case 19: // 'e' - DOW_LOCAL
1925:                    // case 20: // 'u' - EXTENDED_YEAR
1926:                    // case 21: // 'g' - JULIAN_DAY
1927:                    // case 22: // 'A' - MILLISECONDS_IN_DAY
1928:
1929:                    // Handle "generic" fields
1930:                    if (obeyCount) {
1931:                        if ((start + count) > text.length())
1932:                            return -start;
1933:                        number = parseInt(text.substring(0, start + count),
1934:                                pos, allowNegative);
1935:                    } else
1936:                        number = parseInt(text, pos, allowNegative);
1937:                    if (number != null) {
1938:                        cal.set(field, number.intValue());
1939:                        return pos.getIndex();
1940:                    }
1941:                    return -start;
1942:                }
1943:            }
1944:
1945:            /**
1946:             * Parse an integer using fNumberFormat.  This method is semantically
1947:             * const, but actually may modify fNumberFormat.
1948:             */
1949:            private Number parseInt(String text, ParsePosition pos,
1950:                    boolean allowNegative) {
1951:                String oldPrefix = null;
1952:                DecimalFormat df = null;
1953:                if (!allowNegative) {
1954:                    try {
1955:                        df = (DecimalFormat) numberFormat;
1956:                        oldPrefix = df.getNegativePrefix();
1957:                        df.setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
1958:                    } catch (ClassCastException e1) {
1959:                    }
1960:                }
1961:                Number number = numberFormat.parse(text, pos);
1962:                if (df != null) {
1963:                    df.setNegativePrefix(oldPrefix);
1964:                }
1965:                return number;
1966:            }
1967:
1968:            /**
1969:             * Translate a pattern, mapping each character in the from string to the
1970:             * corresponding character in the to string.
1971:             */
1972:            private String translatePattern(String pattern, String from,
1973:                    String to) {
1974:                StringBuffer result = new StringBuffer();
1975:                boolean inQuote = false;
1976:                for (int i = 0; i < pattern.length(); ++i) {
1977:                    char c = pattern.charAt(i);
1978:                    if (inQuote) {
1979:                        if (c == '\'')
1980:                            inQuote = false;
1981:                    } else {
1982:                        if (c == '\'')
1983:                            inQuote = true;
1984:                        else if ((c >= 'a' && c <= 'z')
1985:                                || (c >= 'A' && c <= 'Z')) {
1986:                            int ci = from.indexOf(c);
1987:                            if (ci != -1) {
1988:                                c = to.charAt(ci);
1989:                            }
1990:                            // do not worry on translatepattern if the character is not listed
1991:                            // we do the validity check elsewhere
1992:                        }
1993:                    }
1994:                    result.append(c);
1995:                }
1996:                if (inQuote)
1997:                    throw new IllegalArgumentException(
1998:                            "Unfinished quote in pattern");
1999:                return result.toString();
2000:            }
2001:
2002:            /**
2003:             * Return a pattern string describing this date format.
2004:             * @stable ICU 2.0
2005:             */
2006:            public String toPattern() {
2007:                return pattern;
2008:            }
2009:
2010:            /**
2011:             * Return a localized pattern string describing this date format.
2012:             * @stable ICU 2.0
2013:             */
2014:            public String toLocalizedPattern() {
2015:                return translatePattern(pattern,
2016:                        DateFormatSymbols.patternChars,
2017:                        formatData.localPatternChars);
2018:            }
2019:
2020:            /**
2021:             * Apply the given unlocalized pattern string to this date format.
2022:             * @stable ICU 2.0
2023:             */
2024:            public void applyPattern(String pattern) {
2025:                this .pattern = pattern;
2026:                setLocale(null, null);
2027:            }
2028:
2029:            /**
2030:             * Apply the given localized pattern string to this date format.
2031:             * @stable ICU 2.0
2032:             */
2033:            public void applyLocalizedPattern(String pattern) {
2034:                this .pattern = translatePattern(pattern,
2035:                        formatData.localPatternChars,
2036:                        DateFormatSymbols.patternChars);
2037:                setLocale(null, null);
2038:            }
2039:
2040:            /**
2041:             * Gets the date/time formatting data.
2042:             * @return a copy of the date-time formatting data associated
2043:             * with this date-time formatter.
2044:             * @stable ICU 2.0
2045:             */
2046:            public DateFormatSymbols getDateFormatSymbols() {
2047:                return (DateFormatSymbols) formatData.clone();
2048:            }
2049:
2050:            /**
2051:             * Allows you to set the date/time formatting data.
2052:             * @param newFormatSymbols the new symbols
2053:             * @stable ICU 2.0
2054:             */
2055:            public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
2056:                this .formatData = (DateFormatSymbols) newFormatSymbols.clone();
2057:            }
2058:
2059:            /**
2060:             * Method for subclasses to access the DateFormatSymbols.
2061:             * @stable ICU 2.0
2062:             */
2063:            protected DateFormatSymbols getSymbols() {
2064:                return formatData;
2065:            }
2066:
2067:            /**
2068:             * Overrides Cloneable
2069:             * @stable ICU 2.0
2070:             */
2071:            public Object clone() {
2072:                SimpleDateFormat other = (SimpleDateFormat) super .clone();
2073:                other.formatData = (DateFormatSymbols) formatData.clone();
2074:                return other;
2075:            }
2076:
2077:            /**
2078:             * Override hashCode.
2079:             * Generates the hash code for the SimpleDateFormat object
2080:             * @stable ICU 2.0
2081:             */
2082:            public int hashCode() {
2083:                return pattern.hashCode();
2084:                // just enough fields for a reasonable distribution
2085:            }
2086:
2087:            /**
2088:             * Override equals.
2089:             * @stable ICU 2.0
2090:             */
2091:            public boolean equals(Object obj) {
2092:                if (!super .equals(obj))
2093:                    return false; // super does class check
2094:                SimpleDateFormat that = (SimpleDateFormat) obj;
2095:                return (pattern.equals(that.pattern) && formatData
2096:                        .equals(that.formatData));
2097:            }
2098:
2099:            /**
2100:             * Override readObject.
2101:             */
2102:            private void readObject(ObjectInputStream stream)
2103:                    throws IOException, ClassNotFoundException {
2104:                stream.defaultReadObject();
2105:                ///CLOVER:OFF
2106:                // don't have old serial data to test with
2107:                if (serialVersionOnStream < 1) {
2108:                    // didn't have defaultCenturyStart field
2109:                    initializeDefaultCentury();
2110:                }
2111:                ///CLOVER:ON
2112:                else {
2113:                    // fill in dependent transient field
2114:                    parseAmbiguousDatesAsAfter(defaultCenturyStart);
2115:                }
2116:                serialVersionOnStream = currentSerialVersion;
2117:                locale = getLocale(ULocale.VALID_LOCALE);
2118:            }
2119:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.