1. /*
  2. * @(#)SimpleDateFormat.java 1.52 00/01/19
  3. *
  4. * Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. /*
  11. * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
  12. * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved
  13. *
  14. * The original version of this source code and documentation is copyrighted
  15. * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  16. * materials are provided under terms of a License Agreement between Taligent
  17. * and Sun. This technology is protected by multiple US and International
  18. * patents. This notice and attribution to Taligent may not be removed.
  19. * Taligent is a registered trademark of Taligent, Inc.
  20. *
  21. */
  22. package java.text;
  23. import java.util.TimeZone;
  24. import java.util.Calendar;
  25. import java.util.Date;
  26. import java.util.Locale;
  27. import java.util.ResourceBundle;
  28. import java.util.SimpleTimeZone;
  29. import java.util.GregorianCalendar;
  30. import java.io.ObjectInputStream;
  31. import java.io.IOException;
  32. import java.lang.ClassNotFoundException;
  33. import java.util.Hashtable;
  34. import java.lang.StringIndexOutOfBoundsException;
  35. /**
  36. * <code>SimpleDateFormat</code> is a concrete class for formatting and
  37. * parsing dates in a locale-sensitive manner. It allows for formatting
  38. * (date -> text), parsing (text -> date), and normalization.
  39. *
  40. * <p>
  41. * <code>SimpleDateFormat</code> allows you to start by choosing
  42. * any user-defined patterns for date-time formatting. However, you
  43. * are encouraged to create a date-time formatter with either
  44. * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
  45. * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
  46. * of these class methods can return a date/time formatter initialized
  47. * with a default format pattern. You may modify the format pattern
  48. * using the <code>applyPattern</code> methods as desired.
  49. * For more information on using these methods, see
  50. * {@link DateFormat}.
  51. *
  52. * <p>
  53. * <strong>Time Format Syntax:</strong>
  54. * <p>
  55. * To specify the time format use a <em>time pattern</em> string.
  56. * In this pattern, all ASCII letters are reserved as pattern letters,
  57. * which are defined as the following:
  58. * <blockquote>
  59. * <pre>
  60. * Symbol Meaning Presentation Example
  61. * ------ ------- ------------ -------
  62. * G era designator (Text) AD
  63. * y year (Number) 1996
  64. * M month in year (Text & Number) July & 07
  65. * d day in month (Number) 10
  66. * h hour in am/pm (1~12) (Number) 12
  67. * H hour in day (0~23) (Number) 0
  68. * m minute in hour (Number) 30
  69. * s second in minute (Number) 55
  70. * S millisecond (Number) 978
  71. * E day in week (Text) Tuesday
  72. * D day in year (Number) 189
  73. * F day of week in month (Number) 2 (2nd Wed in July)
  74. * w week in year (Number) 27
  75. * W week in month (Number) 2
  76. * a am/pm marker (Text) PM
  77. * k hour in day (1~24) (Number) 24
  78. * K hour in am/pm (0~11) (Number) 0
  79. * z time zone (Text) Pacific Standard Time
  80. * ' escape for text (Delimiter)
  81. * '' single quote (Literal) '
  82. * </pre>
  83. * </blockquote>
  84. * The count of pattern letters determine the format.
  85. * <p>
  86. * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
  87. * < 4--use short or abbreviated form if one exists.
  88. * <p>
  89. * <strong>(Number)</strong>: the minimum number of digits. Shorter
  90. * numbers are zero-padded to this amount. Year is handled specially;
  91. * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
  92. * <p>
  93. * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
  94. * <p>
  95. * Any characters in the pattern that are not in the ranges of ['a'..'z']
  96. * and ['A'..'Z'] will be treated as quoted text. For instance, characters
  97. * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
  98. * even they are not embraced within single quotes.
  99. * <p>
  100. * A pattern containing any invalid pattern letter will result in a thrown
  101. * exception during formatting or parsing.
  102. *
  103. * <p>
  104. * <strong>Examples Using the US Locale:</strong>
  105. * <blockquote>
  106. * <pre>
  107. * Format Pattern Result
  108. * -------------- -------
  109. * "yyyy.MM.dd G 'at' hh:mm:ss z" ->> 1996.07.10 AD at 15:08:56 PDT
  110. * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
  111. * "h:mm a" ->> 12:08 PM
  112. * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
  113. * "K:mm a, z" ->> 0:00 PM, PST
  114. * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 1996.July.10 AD 12:08 PM
  115. * </pre>
  116. * </blockquote>
  117. * <strong>Code Sample:</strong>
  118. * <blockquote>
  119. * <pre>
  120. * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
  121. * pdt.setStartRule(DateFields.APRIL, 1, DateFields.SUNDAY, 2*60*60*1000);
  122. * pdt.setEndRule(DateFields.OCTOBER, -1, DateFields.SUNDAY, 2*60*60*1000);
  123. * <br>
  124. * // Format the current time.
  125. * SimpleDateFormat formatter
  126. * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
  127. * Date currentTime_1 = new Date();
  128. * String dateString = formatter.format(currentTime_1);
  129. * <br>
  130. * // Parse the previous string back into a Date.
  131. * ParsePosition pos = new ParsePosition(0);
  132. * Date currentTime_2 = formatter.parse(dateString, pos);
  133. * </pre>
  134. * </blockquote>
  135. * In the example, the time value <code>currentTime_2</code> obtained from
  136. * parsing will be equal to <code>currentTime_1</code>. However, they may not be
  137. * equal if the am/pm marker 'a' is left out from the format pattern while
  138. * the "hour in am/pm" pattern symbol is used. This information loss can
  139. * happen when formatting the time in PM.
  140. *
  141. * <p>
  142. * When parsing a date string using the abbreviated year pattern ("y" or "yy"),
  143. * SimpleDateFormat must interpret the abbreviated year
  144. * relative to some century. It does this by adjusting dates to be
  145. * within 80 years before and 20 years after the time the SimpleDateFormat
  146. * instance is created. For example, using a pattern of "MM/dd/yy" and a
  147. * SimpleDateFormat instance created on Jan 1, 1997, the string
  148. * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
  149. * would be interpreted as May 4, 1964.
  150. * During parsing, only strings consisting of exactly two digits, as defined by
  151. * {@link Character#isDigit(char)}, will be parsed into the default century.
  152. * Any other numeric string, such as a one digit string, a three or more digit
  153. * string, or a two digit string that isn't all digits (for example, "-1"), is
  154. * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
  155. * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
  156. *
  157. * <p>
  158. * If the year pattern has more than two 'y' characters, the year is
  159. * interpreted literally, regardless of the number of digits. So using the
  160. * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
  161. *
  162. * <p>
  163. * For time zones that have no names, use strings GMT+hours:minutes or
  164. * GMT-hours:minutes.
  165. *
  166. * <p>
  167. * The calendar defines what is the first day of the week, the first week
  168. * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
  169. * time zone. There is one common decimal format to handle all the numbers;
  170. * the digit count is handled programmatically according to the pattern.
  171. *
  172. * @see java.util.Calendar
  173. * @see java.util.GregorianCalendar
  174. * @see java.util.TimeZone
  175. * @see DateFormat
  176. * @see DateFormatSymbols
  177. * @see DecimalFormat
  178. * @version 1.52, 01/19/00
  179. * @author Mark Davis, Chen-Lieh Huang, Alan Liu
  180. */
  181. public class SimpleDateFormat extends DateFormat {
  182. // the official serial version ID which says cryptically
  183. // which version we're compatible with
  184. static final long serialVersionUID = 4774881970558875024L;
  185. // the internal serial version which says which version was written
  186. // - 0 (default) for version up to JDK 1.1.3
  187. // - 1 for version from JDK 1.1.4, which includes a new field
  188. static final int currentSerialVersion = 1;
  189. /**
  190. * The version of the serialized data on the stream. Possible values:
  191. * <ul>
  192. * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
  193. * has no <code>defaultCenturyStart</code> on stream.
  194. * <li><b>1</b> JDK 1.1.4 or later. This version adds
  195. * <code>defaultCenturyStart</code>.
  196. * </ul>
  197. * When streaming out this class, the most recent format
  198. * and the highest allowable <code>serialVersionOnStream</code>
  199. * is written.
  200. * @serial
  201. * @since JDK1.1.4
  202. */
  203. private int serialVersionOnStream = currentSerialVersion;
  204. /**
  205. * The pattern string of this formatter. This is always a non-localized
  206. * pattern. May not be null. See class documentation for details.
  207. * @serial
  208. */
  209. private String pattern;
  210. /**
  211. * The symbols used by this formatter for week names, month names,
  212. * etc. May not be null.
  213. * @serial
  214. * @see java.text.DateFormatSymbols
  215. */
  216. private DateFormatSymbols formatData;
  217. /**
  218. * We map dates with two-digit years into the century starting at
  219. * <code>defaultCenturyStart</code>, which may be any date. May
  220. * not be null.
  221. * @serial
  222. * @since JDK1.1.4
  223. */
  224. private Date defaultCenturyStart;
  225. transient private int defaultCenturyStartYear;
  226. private static final int millisPerHour = 60 * 60 * 1000;
  227. private static final int millisPerMinute = 60 * 1000;
  228. // For time zones that have no names, use strings GMT+minutes and
  229. // GMT-minutes. For instance, in France the time zone is GMT+60.
  230. private static final String GMT_PLUS = "GMT+";
  231. private static final String GMT_MINUS = "GMT-";
  232. private static final String GMT = "GMT";
  233. /**
  234. * Cache to hold the DateTimePatterns of a Locale.
  235. */
  236. private static Hashtable cachedLocaleData = new Hashtable(3);
  237. /**
  238. * Construct a SimpleDateFormat using the default pattern for the default
  239. * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
  240. * generality, use the factory methods in the DateFormat class.
  241. *
  242. * @see java.text.DateFormat
  243. */
  244. public SimpleDateFormat() {
  245. this(SHORT, SHORT, Locale.getDefault());
  246. }
  247. /**
  248. * Construct a SimpleDateFormat using the given pattern in the default
  249. * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
  250. * generality, use the factory methods in the DateFormat class.
  251. */
  252. public SimpleDateFormat(String pattern)
  253. {
  254. this(pattern, Locale.getDefault());
  255. }
  256. /**
  257. * Construct a SimpleDateFormat using the given pattern and locale.
  258. * <b>Note:</b> Not all locales support SimpleDateFormat; for full
  259. * generality, use the factory methods in the DateFormat class.
  260. */
  261. public SimpleDateFormat(String pattern, Locale loc)
  262. {
  263. this.pattern = pattern;
  264. this.formatData = new DateFormatSymbols(loc);
  265. initialize(loc);
  266. }
  267. /**
  268. * Construct a SimpleDateFormat using the given pattern and
  269. * locale-specific symbol data.
  270. */
  271. public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
  272. {
  273. this.pattern = pattern;
  274. this.formatData = (DateFormatSymbols) formatData.clone();
  275. initialize(Locale.getDefault());
  276. }
  277. /* Package-private, called by DateFormat factory methods */
  278. SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) {
  279. /* try the cache first */
  280. String[] dateTimePatterns = (String[]) cachedLocaleData.get(loc);
  281. if (dateTimePatterns == null) { /* cache miss */
  282. ResourceBundle r = ResourceBundle.getBundle
  283. ("java.text.resources.LocaleElements", loc);
  284. dateTimePatterns = r.getStringArray("DateTimePatterns");
  285. /* update cache */
  286. cachedLocaleData.put(loc, dateTimePatterns);
  287. }
  288. formatData = new DateFormatSymbols(loc);
  289. if ((timeStyle >= 0) && (dateStyle >= 0)) {
  290. Object[] dateTimeArgs = {dateTimePatterns[timeStyle],
  291. dateTimePatterns[dateStyle + 4]};
  292. pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
  293. }
  294. else if (timeStyle >= 0) {
  295. pattern = dateTimePatterns[timeStyle];
  296. }
  297. else if (dateStyle >= 0) {
  298. pattern = dateTimePatterns[dateStyle + 4];
  299. }
  300. else {
  301. throw new IllegalArgumentException("No date or time style specified");
  302. }
  303. initialize(loc);
  304. }
  305. /* Initialize calendar and numberFormat fields */
  306. private void initialize(Locale loc) {
  307. // The format object must be constructed using the symbols for this zone.
  308. // However, the calendar should use the current default TimeZone.
  309. // If this is not contained in the locale zone strings, then the zone
  310. // will be formatted using generic GMT+/-H:MM nomenclature.
  311. calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
  312. numberFormat = NumberFormat.getInstance(loc);
  313. numberFormat.setGroupingUsed(false);
  314. if (numberFormat instanceof DecimalFormat)
  315. ((DecimalFormat)numberFormat).setDecimalSeparatorAlwaysShown(false);
  316. numberFormat.setParseIntegerOnly(true); /* So that dd.MM.yy can be parsed */
  317. numberFormat.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
  318. initializeDefaultCentury();
  319. }
  320. /* Initialize the fields we use to disambiguate ambiguous years. Separate
  321. * so we can call it from readObject().
  322. */
  323. private void initializeDefaultCentury() {
  324. calendar.setTime( new Date() );
  325. calendar.add( Calendar.YEAR, -80 );
  326. parseAmbiguousDatesAsAfter(calendar.getTime());
  327. }
  328. /* Define one-century window into which to disambiguate dates using
  329. * two-digit years.
  330. */
  331. private void parseAmbiguousDatesAsAfter(Date startDate) {
  332. defaultCenturyStart = startDate;
  333. calendar.setTime(startDate);
  334. defaultCenturyStartYear = calendar.get(Calendar.YEAR);
  335. }
  336. /**
  337. * Sets the 100-year period 2-digit years will be interpreted as being in
  338. * to begin on the date the user specifies.
  339. * @param startDate During parsing, two digit years will be placed in the range
  340. * <code>startDate</code> to <code>startDate + 100 years</code>.
  341. */
  342. public void set2DigitYearStart(Date startDate) {
  343. parseAmbiguousDatesAsAfter(startDate);
  344. }
  345. /**
  346. * Returns the beginning date of the 100-year period 2-digit years are interpreted
  347. * as being within.
  348. * @return the start of the 100-year period into which two digit years are
  349. * parsed
  350. */
  351. public Date get2DigitYearStart() {
  352. return defaultCenturyStart;
  353. }
  354. /**
  355. * Overrides DateFormat
  356. * <p>Formats a date or time, which is the standard millis
  357. * since January 1, 1970, 00:00:00 GMT.
  358. * <p>Example: using the US locale:
  359. * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
  360. * @param date the date-time value to be formatted into a date-time string.
  361. * @param toAppendTo where the new date-time text is to be appended.
  362. * @param pos the formatting position. On input: an alignment field,
  363. * if desired. On output: the offsets of the alignment field.
  364. * @return the formatted date-time string.
  365. * @see java.text.DateFormat
  366. */
  367. public StringBuffer format(Date date, StringBuffer toAppendTo,
  368. FieldPosition pos)
  369. {
  370. // Initialize
  371. pos.beginIndex = pos.endIndex = 0;
  372. // Convert input date to time field list
  373. calendar.setTime(date);
  374. boolean inQuote = false; // true when between single quotes
  375. char prevCh = 0; // previous pattern character
  376. int count = 0; // number of time prevCh repeated
  377. for (int i=0; i<pattern.length(); ++i) {
  378. char ch = pattern.charAt(i);
  379. // Use subFormat() to format a repeated pattern character
  380. // when a different pattern or non-pattern character is seen
  381. if (ch != prevCh && count > 0) {
  382. toAppendTo.append(
  383. subFormat(prevCh, count, toAppendTo.length(), pos));
  384. count = 0;
  385. }
  386. if (ch == '\'') {
  387. // Consecutive single quotes are a single quote literal,
  388. // either outside of quotes or between quotes
  389. if ((i+1)<pattern.length() && pattern.charAt(i+1) == '\'') {
  390. toAppendTo.append('\'');
  391. ++i;
  392. } else {
  393. inQuote = !inQuote;
  394. }
  395. } else if (!inQuote
  396. && (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')) {
  397. // ch is a date-time pattern character to be interpreted
  398. // by subFormat(); count the number of times it is repeated
  399. prevCh = ch;
  400. ++count;
  401. }
  402. else {
  403. // Append quoted characters and unquoted non-pattern characters
  404. toAppendTo.append(ch);
  405. }
  406. }
  407. // Format the last item in the pattern, if any
  408. if (count > 0) {
  409. toAppendTo.append(
  410. subFormat(prevCh, count, toAppendTo.length(), pos));
  411. }
  412. return toAppendTo;
  413. }
  414. // Map index into pattern character string to Calendar field number
  415. private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
  416. {
  417. Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
  418. Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
  419. Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
  420. Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
  421. Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
  422. Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET
  423. };
  424. // Map index into pattern character string to DateFormat field number
  425. private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
  426. DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
  427. DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD,
  428. DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD,
  429. DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD,
  430. DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD,
  431. DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD,
  432. DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
  433. DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
  434. DateFormat.TIMEZONE_FIELD,
  435. };
  436. // Private member function that does the real date/time formatting.
  437. private String subFormat(char ch, int count, int beginOffset,
  438. FieldPosition pos)
  439. throws IllegalArgumentException
  440. {
  441. int patternCharIndex = -1;
  442. int maxIntCount = Integer.MAX_VALUE;
  443. String current = "";
  444. if ((patternCharIndex=formatData.patternChars.indexOf(ch)) == -1)
  445. throw new IllegalArgumentException("Illegal pattern character " +
  446. "'" + ch + "'");
  447. int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  448. int value = calendar.get(field);
  449. switch (patternCharIndex) {
  450. case 0: // 'G' - ERA
  451. current = formatData.eras[value];
  452. break;
  453. case 1: // 'y' - YEAR
  454. if (count >= 4)
  455. current = zeroPaddingNumber(value, 4, maxIntCount);
  456. else // count < 4
  457. current = zeroPaddingNumber(value, 2, 2); // clip 1996 to 96
  458. break;
  459. case 2: // 'M' - MONTH
  460. if (count >= 4)
  461. current = formatData.months[value];
  462. else if (count == 3)
  463. current = formatData.shortMonths[value];
  464. else
  465. current = zeroPaddingNumber(value+1, count, maxIntCount);
  466. break;
  467. case 4: // 'k' - HOUR_OF_DAY: 1-based. eg, 23:59 + 1 hour =>> 24:59
  468. if (value == 0)
  469. current = zeroPaddingNumber(
  470. calendar.getMaximum(Calendar.HOUR_OF_DAY)+1,
  471. count, maxIntCount);
  472. else
  473. current = zeroPaddingNumber(value, count, maxIntCount);
  474. break;
  475. case 9: // 'E' - DAY_OF_WEEK
  476. if (count >= 4)
  477. current = formatData.weekdays[value];
  478. else // count < 4, use abbreviated form if exists
  479. current = formatData.shortWeekdays[value];
  480. break;
  481. case 14: // 'a' - AM_PM
  482. current = formatData.ampms[value];
  483. break;
  484. case 15: // 'h' - HOUR:1-based. eg, 11PM + 1 hour =>> 12 AM
  485. if (value == 0)
  486. current = zeroPaddingNumber(
  487. calendar.getLeastMaximum(Calendar.HOUR)+1,
  488. count, maxIntCount);
  489. else
  490. current = zeroPaddingNumber(value, count, maxIntCount);
  491. break;
  492. case 17: // 'z' - ZONE_OFFSET
  493. int zoneIndex
  494. = formatData.getZoneIndex (calendar.getTimeZone().getID());
  495. if (zoneIndex == -1)
  496. {
  497. // For time zones that have no names, use strings
  498. // GMT+hours:minutes and GMT-hours:minutes.
  499. // For instance, France time zone uses GMT+01:00.
  500. StringBuffer zoneString = new StringBuffer();
  501. value = calendar.get(Calendar.ZONE_OFFSET) +
  502. calendar.get(Calendar.DST_OFFSET);
  503. if (value < 0)
  504. {
  505. zoneString.append(GMT_MINUS);
  506. value = -value; // suppress the '-' sign for text display.
  507. }
  508. else
  509. zoneString.append(GMT_PLUS);
  510. zoneString.append(
  511. zeroPaddingNumber((int)(valuemillisPerHour), 2, 2));
  512. zoneString.append(':');
  513. zoneString.append(
  514. zeroPaddingNumber(
  515. (int)((value%millisPerHour)/millisPerMinute), 2, 2));
  516. current = zoneString.toString();
  517. }
  518. else if (calendar.get(Calendar.DST_OFFSET) != 0)
  519. {
  520. if (count >= 4)
  521. current = formatData.zoneStrings[zoneIndex][3];
  522. else
  523. // count < 4, use abbreviated form if exists
  524. current = formatData.zoneStrings[zoneIndex][4];
  525. }
  526. else
  527. {
  528. if (count >= 4)
  529. current = formatData.zoneStrings[zoneIndex][1];
  530. else
  531. current = formatData.zoneStrings[zoneIndex][2];
  532. }
  533. break;
  534. default:
  535. // case 3: // 'd' - DATE
  536. // case 5: // 'H' - HOUR_OF_DAY:0-based. eg, 23:59 + 1 hour =>> 00:59
  537. // case 6: // 'm' - MINUTE
  538. // case 7: // 's' - SECOND
  539. // case 8: // 'S' - MILLISECOND
  540. // case 10: // 'D' - DAY_OF_YEAR
  541. // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
  542. // case 12: // 'w' - WEEK_OF_YEAR
  543. // case 13: // 'W' - WEEK_OF_MONTH
  544. // case 16: // 'K' - HOUR: 0-based. eg, 11PM + 1 hour =>> 0 AM
  545. current = zeroPaddingNumber(value, count, maxIntCount);
  546. break;
  547. } // switch (patternCharIndex)
  548. if (pos.field == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
  549. // set for the first occurence only.
  550. if (pos.beginIndex == 0 && pos.endIndex == 0) {
  551. pos.beginIndex = beginOffset;
  552. pos.endIndex = beginOffset + current.length();
  553. }
  554. }
  555. return current;
  556. }
  557. /**
  558. * Formats a number with the specified minimum and maximum number of digits.
  559. */
  560. private String zeroPaddingNumber(long value, int minDigits, int maxDigits)
  561. {
  562. numberFormat.setMinimumIntegerDigits(minDigits);
  563. numberFormat.setMaximumIntegerDigits(maxDigits);
  564. return numberFormat.format(value);
  565. }
  566. /**
  567. * Overrides DateFormat
  568. * @see java.text.DateFormat
  569. */
  570. public Date parse(String text, ParsePosition pos)
  571. {
  572. int start = pos.index;
  573. int oldStart = start;
  574. boolean[] ambiguousYear = {false};
  575. calendar.clear(); // Clears all the time fields
  576. boolean inQuote = false; // inQuote set true when hits 1st single quote
  577. char prevCh = 0;
  578. int count = 0;
  579. int interQuoteCount = 1; // Number of chars between quotes
  580. for (int i=0; i<pattern.length(); ++i)
  581. {
  582. char ch = pattern.charAt(i);
  583. if (inQuote)
  584. {
  585. if (ch == '\'')
  586. {
  587. // ends with 2nd single quote
  588. inQuote = false;
  589. // two consecutive quotes outside a quote means we have
  590. // a quote literal we need to match.
  591. if (count == 0)
  592. {
  593. if (start >= text.length() || ch != text.charAt(start))
  594. {
  595. pos.index = oldStart;
  596. pos.errorIndex = start;
  597. return null;
  598. }
  599. ++start;
  600. }
  601. count = 0;
  602. interQuoteCount = 0;
  603. }
  604. else
  605. {
  606. // pattern uses text following from 1st single quote.
  607. if (start >= text.length() || ch != text.charAt(start)) {
  608. // Check for cases like: 'at' in pattern vs "xt"
  609. // in time text, where 'a' doesn't match with 'x'.
  610. // If fail to match, return null.
  611. pos.index = oldStart; // left unchanged
  612. pos.errorIndex = start;
  613. return null;
  614. }
  615. ++count;
  616. ++start;
  617. }
  618. }
  619. else // !inQuote
  620. {
  621. if (ch == '\'')
  622. {
  623. inQuote = true;
  624. if (count > 0) // handle cases like: e'at'
  625. {
  626. int startOffset = start;
  627. start=subParse(text, start, prevCh, count,
  628. false, ambiguousYear);
  629. if ( start<0 ) {
  630. pos.errorIndex = startOffset;
  631. pos.index = oldStart;
  632. return null;
  633. }
  634. count = 0;
  635. }
  636. if (interQuoteCount == 0)
  637. {
  638. // This indicates two consecutive quotes inside a quote,
  639. // for example, 'o''clock'. We need to parse this as
  640. // representing a single quote within the quote.
  641. int startOffset = start;
  642. if (start >= text.length() || ch != text.charAt(start))
  643. {
  644. pos.errorIndex = startOffset;
  645. pos.index = oldStart;
  646. return null;
  647. }
  648. ++start;
  649. count = 1; // Make it look like we never left
  650. }
  651. }
  652. else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
  653. {
  654. // ch is a date-time pattern
  655. if (ch != prevCh && count > 0) // e.g., yyyyMMdd
  656. {
  657. int startOffset = start;
  658. // This is the only case where we pass in 'true' for
  659. // obeyCount. That's because the next field directly
  660. // abuts this one, so we have to use the count to know when
  661. // to stop parsing. [LIU]
  662. start = subParse(text, start, prevCh, count, true,
  663. ambiguousYear);
  664. if (start < 0) {
  665. pos.errorIndex = startOffset;
  666. pos.index = oldStart;
  667. return null;
  668. }
  669. prevCh = ch;
  670. count = 1;
  671. }
  672. else
  673. {
  674. if (ch != prevCh)
  675. prevCh = ch;
  676. count++;
  677. }
  678. }
  679. else if (count > 0)
  680. {
  681. // handle cases like: MM-dd-yy, HH:mm:ss, or yyyy MM dd,
  682. // where ch = '-', ':', or ' ', repectively.
  683. int startOffset = start;
  684. start=subParse(text, start, prevCh, count,
  685. false, ambiguousYear);
  686. if ( start < 0 ) {
  687. pos.errorIndex = startOffset;
  688. pos.index = oldStart;
  689. return null;
  690. }
  691. if (start >= text.length() || ch != text.charAt(start)) {
  692. // handle cases like: 'MMMM dd' in pattern vs. "janx20"
  693. // in time text, where ' ' doesn't match with 'x'.
  694. pos.errorIndex = start;
  695. pos.index = oldStart;
  696. return null;
  697. }
  698. start++;
  699. count = 0;
  700. prevCh = 0;
  701. }
  702. else // any other unquoted characters
  703. {
  704. if (start >= text.length() || ch != text.charAt(start)) {
  705. // handle cases like: 'MMMM dd' in pattern vs.
  706. // "jan,,,20" in time text, where " " doesn't
  707. // match with ",,,".
  708. pos.errorIndex = start;
  709. pos.index = oldStart;
  710. return null;
  711. }
  712. start++;
  713. }
  714. ++interQuoteCount;
  715. }
  716. }
  717. // Parse the last item in the pattern
  718. if (count > 0)
  719. {
  720. int startOffset = start;
  721. start=subParse(text, start, prevCh, count,
  722. false, ambiguousYear);
  723. if ( start < 0 ) {
  724. pos.index = oldStart;
  725. pos.errorIndex = startOffset;
  726. return null;
  727. }
  728. }
  729. // At this point the fields of Calendar have been set. Calendar
  730. // will fill in default values for missing fields when the time
  731. // is computed.
  732. pos.index = start;
  733. // This part is a problem: When we call parsedDate.after, we compute the time.
  734. // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
  735. // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
  736. // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
  737. // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
  738. // on that day. It is therefore parsed out to fields as 3:30 am. Then we
  739. // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
  740. // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
  741. /*
  742. Date parsedDate = calendar.getTime();
  743. if( ambiguousYear[0] && !parsedDate.after(defaultCenturyStart) ) {
  744. calendar.add(Calendar.YEAR, 100);
  745. parsedDate = calendar.getTime();
  746. }
  747. */
  748. // Because of the above condition, save off the fields in case we need to readjust.
  749. // The procedure we use here is not particularly efficient, but there is no other
  750. // way to do this given the API restrictions present in Calendar. We minimize
  751. // inefficiency by only performing this computation when it might apply, that is,
  752. // when the two-digit year is equal to the start year, and thus might fall at the
  753. // front or the back of the default century. This only works because we adjust
  754. // the year correctly to start with in other cases -- see subParse().
  755. Date parsedDate;
  756. try {
  757. if (ambiguousYear[0]) // If this is true then the two-digit year == the default start year
  758. {
  759. // We need a copy of the fields, and we need to avoid triggering a call to
  760. // complete(), which will recalculate the fields. Since we can't access
  761. // the fields[] array in Calendar, we clone the entire object. This will
  762. // stop working if Calendar.clone() is ever rewritten to call complete().
  763. Calendar savedCalendar = (Calendar)calendar.clone();
  764. parsedDate = calendar.getTime();
  765. if (parsedDate.before(defaultCenturyStart))
  766. {
  767. // We can't use add here because that does a complete() first.
  768. savedCalendar.set(Calendar.YEAR, defaultCenturyStartYear + 100);
  769. parsedDate = savedCalendar.getTime();
  770. }
  771. }
  772. else parsedDate = calendar.getTime();
  773. }
  774. // An IllegalArgumentException will be thrown by Calendar.getTime()
  775. // if any fields are out of range, e.g., MONTH == 17.
  776. catch (IllegalArgumentException e) {
  777. pos.errorIndex = start;
  778. pos.index = oldStart;
  779. return null;
  780. }
  781. return parsedDate;
  782. }
  783. /**
  784. * Private code-size reduction function used by subParse.
  785. * @param text the time text being parsed.
  786. * @param start where to start parsing.
  787. * @param field the date field being parsed.
  788. * @param data the string array to parsed.
  789. * @return the new start position if matching succeeded; a negative number
  790. * indicating matching failure, otherwise.
  791. */
  792. private int matchString(String text, int start, int field, String[] data)
  793. {
  794. int i = 0;
  795. int count = data.length;
  796. if (field == Calendar.DAY_OF_WEEK) i = 1;
  797. // There may be multiple strings in the data[] array which begin with
  798. // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
  799. // We keep track of the longest match, and return that. Note that this
  800. // unfortunately requires us to test all array elements.
  801. int bestMatchLength = 0, bestMatch = -1;
  802. for (; i<count; ++i)
  803. {
  804. int length = data[i].length();
  805. // Always compare if we have no match yet; otherwise only compare
  806. // against potentially better matches (longer strings).
  807. if (length > bestMatchLength &&
  808. text.regionMatches(true, start, data[i], 0, length))
  809. {
  810. bestMatch = i;
  811. bestMatchLength = length;
  812. }
  813. }
  814. if (bestMatch >= 0)
  815. {
  816. calendar.set(field, bestMatch);
  817. return start + bestMatchLength;
  818. }
  819. return -start;
  820. }
  821. private int matchZoneString(String text, int start, int zoneIndex) {
  822. int j;
  823. for (j = 1; j <= 4; ++j) {
  824. // Checking long and short zones [1 & 2],
  825. // and long and short daylight [3 & 4].
  826. if (text.regionMatches(true, start,
  827. formatData.zoneStrings[zoneIndex][j], 0,
  828. formatData.zoneStrings[zoneIndex][j].length())) {
  829. break;
  830. }
  831. }
  832. return (j > 4) ? -1 : j;
  833. }
  834. /**
  835. * find time zone 'text' matched zoneStrings and set to internal
  836. * calendar.
  837. */
  838. private int subParseZoneString(String text, int start) {
  839. // At this point, check for named time zones by looking through
  840. // the locale data from the DateFormatZoneData strings.
  841. // Want to be able to parse both short and long forms.
  842. int zoneIndex =
  843. formatData.getZoneIndex (getTimeZone().getID());
  844. TimeZone tz = null;
  845. int j = 0, i = 0;
  846. if ((zoneIndex != -1) && ((j = matchZoneString(text, start, zoneIndex)) > 0)) {
  847. tz = TimeZone.getTimeZone(formatData.zoneStrings[zoneIndex][0]);
  848. i = zoneIndex;
  849. }
  850. if (tz == null) {
  851. zoneIndex =
  852. formatData.getZoneIndex (TimeZone.getDefault().getID());
  853. if ((zoneIndex != -1) && ((j = matchZoneString(text, start, zoneIndex)) > 0)) {
  854. tz = TimeZone.getTimeZone(formatData.zoneStrings[zoneIndex][0]);
  855. i = zoneIndex;
  856. }
  857. }
  858. if (tz == null) {
  859. for (i = 0; i < formatData.zoneStrings.length; i++) {
  860. if ((j = matchZoneString(text, start, i)) > 0) {
  861. tz = TimeZone.getTimeZone(formatData.zoneStrings[i][0]);
  862. break;
  863. }
  864. }
  865. }
  866. if (tz != null) { // Matched any ?
  867. calendar.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
  868. // The code below time zone is assumed to be instance of
  869. // SimpleTimeZone.
  870. calendar.set(Calendar.DST_OFFSET,
  871. j >= 3 ? ((SimpleTimeZone)tz).getDSTSavings() : 0);
  872. return (start + formatData.zoneStrings[i][j].length());
  873. }
  874. return 0;
  875. }
  876. /**
  877. * Private member function that converts the parsed date strings into
  878. * timeFields. Returns -start (for ParsePosition) if failed.
  879. * @param text the time text to be parsed.
  880. * @param start where to start parsing.
  881. * @param ch the pattern character for the date field text to be parsed.
  882. * @param count the count of a pattern character.
  883. * @param obeyCount if true, then the next field directly abuts this one,
  884. * and we should use the count to know when to stop parsing.
  885. * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
  886. * is true, then a two-digit year was parsed and may need to be readjusted.
  887. * @return the new start position if matching succeeded; a negative number
  888. * indicating matching failure, otherwise.
  889. */
  890. private int subParse(String text, int start, char ch, int count,
  891. boolean obeyCount, boolean[] ambiguousYear)
  892. {
  893. Number number = null;
  894. int value = 0;
  895. int i;
  896. ParsePosition pos = new ParsePosition(0);
  897. int patternCharIndex = -1;
  898. if ((patternCharIndex=formatData.patternChars.indexOf(ch)) == -1)
  899. return -start;
  900. pos.index = start;
  901. int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  902. // If there are any spaces here, skip over them. If we hit the end
  903. // of the string, then fail.
  904. for (;;) {
  905. if (pos.index >= text.length()) return -start;
  906. char c = text.charAt(pos.index);
  907. if (c != ' ' && c != '\t') break;
  908. ++pos.index;
  909. }
  910. // We handle a few special cases here where we need to parse
  911. // a number value. We handle further, more generic cases below. We need
  912. // to handle some of them here because some fields require extra processing on
  913. // the parsed value.
  914. if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
  915. patternCharIndex == 15 /*HOUR1_FIELD*/ ||
  916. (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
  917. patternCharIndex == 1)
  918. {
  919. // It would be good to unify this with the obeyCount logic below,
  920. // but that's going to be difficult.
  921. if (obeyCount)
  922. {
  923. if ((start+count) > text.length()) return -start;
  924. number = numberFormat.parse(text.substring(0, start+count), pos);
  925. }
  926. else number = numberFormat.parse(text, pos);
  927. if (number == null)
  928. return -start;
  929. value = number.intValue();
  930. }
  931. switch (patternCharIndex)
  932. {
  933. case 0: // 'G' - ERA
  934. return matchString(text, start, Calendar.ERA, formatData.eras);
  935. case 1: // 'y' - YEAR
  936. // If there are 3 or more YEAR pattern characters, this indicates
  937. // that the year value is to be treated literally, without any
  938. // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
  939. // we made adjustments to place the 2-digit year in the proper
  940. // century, for parsed strings from "00" to "99". Any other string
  941. // is treated literally: "2250", "-1", "1", "002".
  942. if (count <= 2 && (pos.index - start) == 2
  943. && Character.isDigit(text.charAt(start))
  944. && Character.isDigit(text.charAt(start+1)))
  945. {
  946. // Assume for example that the defaultCenturyStart is 6/18/1903.
  947. // This means that two-digit years will be forced into the range
  948. // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
  949. // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
  950. // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
  951. // other fields specify a date before 6/18, or 1903 if they specify a
  952. // date afterwards. As a result, 03 is an ambiguous year. All other
  953. // two-digit years are unambiguous.
  954. int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
  955. ambiguousYear[0] = value == ambiguousTwoDigitYear;
  956. value += (defaultCenturyStartYear100)*100 +
  957. (value < ambiguousTwoDigitYear ? 100 : 0);
  958. }
  959. calendar.set(Calendar.YEAR, value);
  960. return pos.index;
  961. case 2: // 'M' - MONTH
  962. if (count <= 2) // i.e., M or MM.
  963. {
  964. // Don't want to parse the month if it is a string
  965. // while pattern uses numeric style: M or MM.
  966. // [We computed 'value' above.]
  967. calendar.set(Calendar.MONTH, value - 1);
  968. return pos.index;
  969. }
  970. else
  971. {
  972. // count >= 3 // i.e., MMM or MMMM
  973. // Want to be able to parse both short and long forms.
  974. // Try count == 4 first:
  975. int newStart = 0;
  976. if ((newStart=matchString(text, start, Calendar.MONTH,
  977. formatData.months)) > 0)
  978. return newStart;
  979. else // count == 4 failed, now try count == 3
  980. return matchString(text, start, Calendar.MONTH,
  981. formatData.shortMonths);
  982. }
  983. case 4: // 'k' - HOUR_OF_DAY: 1-based. eg, 23:59 + 1 hour =>> 24:59
  984. // [We computed 'value' above.]
  985. if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
  986. calendar.set(Calendar.HOUR_OF_DAY, value);
  987. return pos.index;
  988. case 9: { // 'E' - DAY_OF_WEEK
  989. // Want to be able to parse both short and long forms.
  990. // Try count == 4 (DDDD) first:
  991. int newStart = 0;
  992. if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
  993. formatData.weekdays)) > 0)
  994. return newStart;
  995. else // DDDD failed, now try DDD
  996. return matchString(text, start, Calendar.DAY_OF_WEEK,
  997. formatData.shortWeekdays);
  998. }
  999. case 14: // 'a' - AM_PM
  1000. return matchString(text, start, Calendar.AM_PM, formatData.ampms);
  1001. case 15: // 'h' - HOUR:1-based. eg, 11PM + 1 hour =>> 12 AM
  1002. // [We computed 'value' above.]
  1003. if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) value = 0;
  1004. calendar.set(Calendar.HOUR, value);
  1005. return pos.index;
  1006. case 17: // 'z' - ZONE_OFFSET
  1007. // First try to parse generic forms such as GMT-07:00. Do this first
  1008. // in case localized DateFormatZoneData contains the string "GMT"
  1009. // for a zone; in that case, we don't want to match the first three
  1010. // characters of GMT+/-HH:MM etc.
  1011. {
  1012. int sign = 0;
  1013. int offset;
  1014. // For time zones that have no known names, look for strings
  1015. // of the form:
  1016. // GMT[+-]hours:minutes or
  1017. // GMT[+-]hhmm or
  1018. // GMT.
  1019. if ((text.length() - start) >= GMT.length() &&
  1020. text.regionMatches(true, start, GMT, 0, GMT.length()))
  1021. {
  1022. calendar.set(Calendar.DST_OFFSET, 0);
  1023. pos.index = start + GMT.length();
  1024. try { // try-catch for "GMT" only time zone string
  1025. if( text.charAt(pos.index) == '+' ) {
  1026. sign = 1;
  1027. } else if( text.charAt(pos.index) == '-' ) {
  1028. sign = -1;
  1029. }
  1030. } catch(StringIndexOutOfBoundsException e) {
  1031. }
  1032. if (sign == 0) {
  1033. calendar.set(Calendar.ZONE_OFFSET, 0 );
  1034. return pos.index;
  1035. }
  1036. // Look for hours:minutes or hhmm.
  1037. pos.index++;
  1038. Number tzNumber = numberFormat.parse(text, pos);
  1039. if( tzNumber == null) {
  1040. return -start;
  1041. }
  1042. if( text.charAt(pos.index) == ':' ) {
  1043. // This is the hours:minutes case
  1044. offset = tzNumber.intValue() * 60;
  1045. pos.index++;
  1046. tzNumber = numberFormat.parse(text, pos);
  1047. if( tzNumber == null) {
  1048. return -start;
  1049. }
  1050. offset += tzNumber.intValue();
  1051. }
  1052. else {
  1053. // This is the hhmm case.
  1054. offset = tzNumber.intValue();
  1055. if( offset < 24 )
  1056. offset *= 60;
  1057. else
  1058. offset = offset % 100 + offset / 100 * 60;
  1059. }
  1060. // Fall through for final processing below of 'offset' and 'sign'.
  1061. }
  1062. else {
  1063. // At this point, check for named time zones by looking through
  1064. // the locale data from the DateFormatZoneData strings.
  1065. // Want to be able to parse both short and long forms.
  1066. i = subParseZoneString(text, start);
  1067. if (i != 0)
  1068. return i;
  1069. // As a last resort, look for numeric timezones of the form
  1070. // [+-]hhmm as specified by RFC 822. This code is actually
  1071. // a little more permissive than RFC 822. It will try to do
  1072. // its best with numbers that aren't strictly 4 digits long.
  1073. DecimalFormat fmt = new DecimalFormat("+####;-####");
  1074. fmt.setParseIntegerOnly(true);
  1075. Number tzNumber = fmt.parse( text, pos );
  1076. if( tzNumber == null) {
  1077. return -start; // Wasn't actually a number.
  1078. }
  1079. offset = tzNumber.intValue();
  1080. sign = 1;
  1081. if( offset < 0 ) {
  1082. sign = -1;
  1083. offset = -offset;
  1084. }
  1085. if( offset < 24 )
  1086. offset = offset * 60;
  1087. else
  1088. offset = offset % 100 + offset / 100 * 60;
  1089. // Fall through for final processing below of 'offset' and 'sign'.
  1090. }
  1091. // Do the final processing for both of the above cases. We only
  1092. // arrive here if the form GMT+/-... or an RFC 822 form was seen.
  1093. if (sign != 0)
  1094. {
  1095. offset *= millisPerMinute * sign;
  1096. if (calendar.getTimeZone().useDaylightTime())
  1097. {
  1098. calendar.set(Calendar.DST_OFFSET, millisPerHour);
  1099. offset -= millisPerHour;
  1100. }
  1101. calendar.set(Calendar.ZONE_OFFSET, offset);
  1102. return pos.index;
  1103. }
  1104. }
  1105. // All efforts to parse a zone failed.
  1106. return -start;
  1107. default:
  1108. // case 3: // 'd' - DATE
  1109. // case 5: // 'H' - HOUR_OF_DAY:0-based. eg, 23:59 + 1 hour =>> 00:59
  1110. // case 6: // 'm' - MINUTE
  1111. // case 7: // 's' - SECOND
  1112. // case 8: // 'S' - MILLISECOND
  1113. // case 10: // 'D' - DAY_OF_YEAR
  1114. // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
  1115. // case 12: // 'w' - WEEK_OF_YEAR
  1116. // case 13: // 'W' - WEEK_OF_MONTH
  1117. // case 16: // 'K' - HOUR: 0-based. eg, 11PM + 1 hour =>> 0 AM
  1118. // Handle "generic" fields
  1119. if (obeyCount)
  1120. {
  1121. if ((start+count) > text.length()) return -start;
  1122. number = numberFormat.parse(text.substring(0, start+count), pos);
  1123. }
  1124. else number = numberFormat.parse(text, pos);
  1125. if (number != null) {
  1126. calendar.set(field, number.intValue());
  1127. return pos.index;
  1128. }
  1129. return -start;
  1130. }
  1131. }
  1132. /**
  1133. * Translate a pattern, mapping each character in the from string to the
  1134. * corresponding character in the to string.
  1135. */
  1136. private String translatePattern(String pattern, String from, String to) {
  1137. StringBuffer result = new StringBuffer();
  1138. boolean inQuote = false;
  1139. for (int i = 0; i < pattern.length(); ++i) {
  1140. char c = pattern.charAt(i);
  1141. if (inQuote) {
  1142. if (c == '\'')
  1143. inQuote = false;
  1144. }
  1145. else {
  1146. if (c == '\'')
  1147. inQuote = true;
  1148. else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
  1149. int ci = from.indexOf(c);
  1150. if (ci == -1)
  1151. throw new IllegalArgumentException("Illegal pattern " +
  1152. " character '" +
  1153. c + "'");
  1154. c = to.charAt(ci);
  1155. }
  1156. }
  1157. result.append(c);
  1158. }
  1159. if (inQuote)
  1160. throw new IllegalArgumentException("Unfinished quote in pattern");
  1161. return result.toString();
  1162. }
  1163. /**
  1164. * Return a pattern string describing this date format.
  1165. */
  1166. public String toPattern() {
  1167. return pattern;
  1168. }
  1169. /**
  1170. * Return a localized pattern string describing this date format.
  1171. */
  1172. public String toLocalizedPattern() {
  1173. return translatePattern(pattern,
  1174. formatData.patternChars,
  1175. formatData.localPatternChars);
  1176. }
  1177. /**
  1178. * Apply the given unlocalized pattern string to this date format.
  1179. */
  1180. public void applyPattern (String pattern)
  1181. {
  1182. this.pattern = pattern;
  1183. }
  1184. /**
  1185. * Apply the given localized pattern string to this date format.
  1186. */
  1187. public void applyLocalizedPattern(String pattern) {
  1188. this.pattern = translatePattern(pattern,
  1189. formatData.localPatternChars,
  1190. formatData.patternChars);
  1191. }
  1192. /**
  1193. * Gets the date/time formatting data.
  1194. * @return a copy of the date-time formatting data associated
  1195. * with this date-time formatter.
  1196. */
  1197. public DateFormatSymbols getDateFormatSymbols()
  1198. {
  1199. return (DateFormatSymbols)formatData.clone();
  1200. }
  1201. /**
  1202. * Allows you to set the date/time formatting data.
  1203. * @param newFormatData the given date-time formatting data.
  1204. */
  1205. public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
  1206. {
  1207. this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
  1208. }
  1209. /**
  1210. * Overrides Cloneable
  1211. */
  1212. public Object clone() {
  1213. SimpleDateFormat other = (SimpleDateFormat) super.clone();
  1214. other.formatData = (DateFormatSymbols) formatData.clone();
  1215. return other;
  1216. }
  1217. /**
  1218. * Override hashCode.
  1219. * Generates the hash code for the SimpleDateFormat object
  1220. */
  1221. public int hashCode()
  1222. {
  1223. return pattern.hashCode();
  1224. // just enough fields for a reasonable distribution
  1225. }
  1226. /**
  1227. * Override equals.
  1228. */
  1229. public boolean equals(Object obj)
  1230. {
  1231. if (!super.equals(obj)) return false; // super does class check
  1232. SimpleDateFormat that = (SimpleDateFormat) obj;
  1233. return (pattern.equals(that.pattern)
  1234. && formatData.equals(that.formatData));
  1235. }
  1236. /**
  1237. * Override readObject.
  1238. */
  1239. private void readObject(ObjectInputStream stream)
  1240. throws IOException, ClassNotFoundException {
  1241. stream.defaultReadObject();
  1242. if (serialVersionOnStream < 1) {
  1243. // didn't have defaultCenturyStart field
  1244. initializeDefaultCentury();
  1245. }
  1246. else {
  1247. // fill in dependent transient field
  1248. parseAmbiguousDatesAsAfter(defaultCenturyStart);
  1249. }
  1250. serialVersionOnStream = currentSerialVersion;
  1251. }
  1252. }