1. /* ====================================================================
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowledgement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowledgement may appear in the software itself,
  24. * if and wherever such third-party acknowledgements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Commons", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Software Foundation.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.commons.lang.time;
  55. import java.text.DateFormat;
  56. import java.text.DateFormatSymbols;
  57. import java.text.FieldPosition;
  58. import java.text.Format;
  59. import java.text.ParsePosition;
  60. import java.text.SimpleDateFormat;
  61. import java.util.ArrayList;
  62. import java.util.Calendar;
  63. import java.util.Date;
  64. import java.util.GregorianCalendar;
  65. import java.util.HashMap;
  66. import java.util.List;
  67. import java.util.Locale;
  68. import java.util.Map;
  69. import java.util.TimeZone;
  70. /**
  71. * <p>FastDateFormat is a fast and thread-safe version of
  72. * {@link java.text.SimpleDateFormat}.</p>
  73. *
  74. * <p>This class can be used as a direct replacement to
  75. * <code>SimpleDateFormat</code> in most formatting situations.
  76. * This class is especially useful in multi-threaded server environments.
  77. * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
  78. * nor will it be as Sun have closed the bug/RFE.
  79. * </p>
  80. *
  81. * <p>Only formatting is supported, but all patterns are compatible with
  82. * SimpleDateFormat (except time zones - see below).</p>
  83. *
  84. * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent
  85. * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>).
  86. * This pattern letter can be used here (on all JDK versions).</p>
  87. *
  88. * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent
  89. * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>).
  90. * This introduces a minor incompatability with Java 1.4, but at a gain of
  91. * useful functionality.</p>
  92. *
  93. * @author TeaTrove project
  94. * @author Brian S O'Neill
  95. * @author Sean Schofield
  96. * @author Gary Gregory
  97. * @author Stephen Colebourne
  98. * @since 2.0
  99. * @version $Id: FastDateFormat.java,v 1.15 2003/08/18 02:22:25 bayard Exp $
  100. */
  101. public class FastDateFormat extends Format {
  102. // A lot of the speed in this class comes from caching, but some comes
  103. // from the special int to StringBuffer conversion.
  104. //
  105. // The following produces a padded 2 digit number:
  106. // buffer.append((char)(value / 10 + '0'));
  107. // buffer.append((char)(value % 10 + '0'));
  108. //
  109. // Note that the fastest append to StringBuffer is a single char (used here).
  110. // Note that Integer.toString() is not called, the conversion is simply
  111. // taking the value and adding (mathematically) the ASCII value for '0'.
  112. // So, don't change this code! It works and is very fast.
  113. /**
  114. * FULL locale dependent date or time style.
  115. */
  116. public static final int FULL = DateFormat.FULL;
  117. /**
  118. * LONG locale dependent date or time style.
  119. */
  120. public static final int LONG = DateFormat.LONG;
  121. /**
  122. * MEDIUM locale dependent date or time style.
  123. */
  124. public static final int MEDIUM = DateFormat.MEDIUM;
  125. /**
  126. * SHORT locale dependent date or time style.
  127. */
  128. public static final int SHORT = DateFormat.SHORT;
  129. // package scoped as used by inner class
  130. static final double LOG_10 = Math.log(10);
  131. private static String cDefaultPattern;
  132. private static Map cInstanceCache = new HashMap(7);
  133. private static Map cDateInstanceCache = new HashMap(7);
  134. private static Map cTimeInstanceCache = new HashMap(7);
  135. private static Map cDateTimeInstanceCache = new HashMap(7);
  136. private static Map cTimeZoneDisplayCache = new HashMap(7);
  137. /**
  138. * The pattern.
  139. */
  140. private final String mPattern;
  141. /**
  142. * The time zone.
  143. */
  144. private final TimeZone mTimeZone;
  145. /**
  146. * Whether the time zone overrides any on Calendars.
  147. */
  148. private final boolean mTimeZoneForced;
  149. /**
  150. * The locale.
  151. */
  152. private final Locale mLocale;
  153. /**
  154. * Whether the locale overrides the default.
  155. */
  156. private final boolean mLocaleForced;
  157. /**
  158. * The parsed rules.
  159. */
  160. private Rule[] mRules;
  161. /**
  162. * The estimated maximum length.
  163. */
  164. private int mMaxLengthEstimate;
  165. //-----------------------------------------------------------------------
  166. /**
  167. * <p>Gets a formatter instance using the default pattern in the
  168. * default locale.</p>
  169. *
  170. * @return a date/time formatter
  171. */
  172. public static FastDateFormat getInstance() {
  173. return getInstance(getDefaultPattern(), null, null);
  174. }
  175. /**
  176. * <p>Gets a formatter instance using the specified pattern in the
  177. * default locale.</p>
  178. *
  179. * @param pattern {@link java.text.SimpleDateFormat} compatible
  180. * pattern
  181. * @return a pattern based date/time formatter
  182. * @throws IllegalArgumentException if pattern is invalid
  183. */
  184. public static FastDateFormat getInstance(String pattern) {
  185. return getInstance(pattern, null, null);
  186. }
  187. /**
  188. * <p>Gets a formatter instance using the specified pattern and
  189. * time zone.</p>
  190. *
  191. * @param pattern {@link java.text.SimpleDateFormat} compatible
  192. * pattern
  193. * @param timeZone optional time zone, overrides time zone of
  194. * formatted date
  195. * @return a pattern based date/time formatter
  196. * @throws IllegalArgumentException if pattern is invalid
  197. */
  198. public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
  199. return getInstance(pattern, timeZone, null);
  200. }
  201. /**
  202. * <p>Gets a formatter instance using the specified pattern and
  203. * locale.</p>
  204. *
  205. * @param pattern {@link java.text.SimpleDateFormat} compatible
  206. * pattern
  207. * @param locale optional locale, overrides system locale
  208. * @return a pattern based date/time formatter
  209. * @throws IllegalArgumentException if pattern is invalid
  210. */
  211. public static FastDateFormat getInstance(String pattern, Locale locale) {
  212. return getInstance(pattern, null, locale);
  213. }
  214. /**
  215. * <p>Gets a formatter instance using the specified pattern, time zone
  216. * and locale.</p>
  217. *
  218. * @param pattern {@link java.text.SimpleDateFormat} compatible
  219. * pattern
  220. * @param timeZone optional time zone, overrides time zone of
  221. * formatted date
  222. * @param locale optional locale, overrides system locale
  223. * @return a pattern based date/time formatter
  224. * @throws IllegalArgumentException if pattern is invalid
  225. * or <code>null</code>
  226. */
  227. public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
  228. FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
  229. FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
  230. if (format == null) {
  231. format = emptyFormat;
  232. format.init(); // convert shell format into usable one
  233. cInstanceCache.put(format, format); // this is OK!
  234. }
  235. return format;
  236. }
  237. /**
  238. * <p>Gets a date formatter instance using the specified style, time
  239. * zone and locale.</p>
  240. *
  241. * @param style date style: FULL, LONG, MEDIUM, or SHORT
  242. * @param timeZone optional time zone, overrides time zone of
  243. * formatted date
  244. * @param locale optional locale, overrides system locale
  245. * @return a localized standard date formatter
  246. * @throws IllegalArgumentException if the Locale has no date
  247. * pattern defined
  248. */
  249. public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
  250. Object key = new Integer(style);
  251. if (timeZone != null) {
  252. key = new Pair(key, timeZone);
  253. }
  254. if (locale == null) {
  255. key = new Pair(key, locale);
  256. }
  257. FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key);
  258. if (format == null) {
  259. if (locale == null) {
  260. locale = Locale.getDefault();
  261. }
  262. try {
  263. SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale);
  264. String pattern = formatter.toPattern();
  265. format = getInstance(pattern, timeZone, locale);
  266. cDateInstanceCache.put(key, format);
  267. } catch (ClassCastException ex) {
  268. throw new IllegalArgumentException("No date pattern for locale: " + locale);
  269. }
  270. }
  271. return format;
  272. }
  273. /**
  274. * <p>Gets a time formatter instance using the specified style, time
  275. * zone and locale.</p>
  276. *
  277. * @param style time style: FULL, LONG, MEDIUM, or SHORT
  278. * @param timeZone optional time zone, overrides time zone of
  279. * formatted time
  280. * @param locale optional locale, overrides system locale
  281. * @return a localized standard time formatter
  282. * @throws IllegalArgumentException if the Locale has no time
  283. * pattern defined
  284. */
  285. public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
  286. Object key = new Integer(style);
  287. if (timeZone != null) {
  288. key = new Pair(key, timeZone);
  289. }
  290. if (locale != null) {
  291. key = new Pair(key, locale);
  292. }
  293. FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key);
  294. if (format == null) {
  295. if (locale == null) {
  296. locale = Locale.getDefault();
  297. }
  298. try {
  299. SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale);
  300. String pattern = formatter.toPattern();
  301. format = getInstance(pattern, timeZone, locale);
  302. cTimeInstanceCache.put(key, format);
  303. } catch (ClassCastException ex) {
  304. throw new IllegalArgumentException("No date pattern for locale: " + locale);
  305. }
  306. }
  307. return format;
  308. }
  309. /**
  310. * <p>Gets a date/time formatter instance using the specified style,
  311. * time zone and locale.</p>
  312. *
  313. * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
  314. * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
  315. * @param timeZone optional time zone, overrides time zone of
  316. * formatted date
  317. * @param locale optional locale, overrides system locale
  318. * @return a localized standard date/time formatter
  319. * @throws IllegalArgumentException if the Locale has no date/time
  320. * pattern defined
  321. */
  322. public static synchronized FastDateFormat getDateTimeInstance(
  323. int dateStyle, int timeStyle, TimeZone timeZone, Locale locale) {
  324. Object key = new Pair(new Integer(dateStyle), new Integer(timeStyle));
  325. if (timeZone != null) {
  326. key = new Pair(key, timeZone);
  327. }
  328. if (locale != null) {
  329. key = new Pair(key, locale);
  330. }
  331. FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key);
  332. if (format == null) {
  333. if (locale == null) {
  334. locale = Locale.getDefault();
  335. }
  336. try {
  337. SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
  338. String pattern = formatter.toPattern();
  339. format = getInstance(pattern, timeZone, locale);
  340. cDateTimeInstanceCache.put(key, format);
  341. } catch (ClassCastException ex) {
  342. throw new IllegalArgumentException("No date time pattern for locale: " + locale);
  343. }
  344. }
  345. return format;
  346. }
  347. //-----------------------------------------------------------------------
  348. /**
  349. * <p>Gets the time zone display name, using a cache for performance.</p>
  350. *
  351. * @param tz the zone to query
  352. * @param daylight true if daylight savings
  353. * @param style the style to use <code>TimeZone.LONG</code>
  354. * or <code>TimeZone.SHORT</code>
  355. * @param locale the locale to use
  356. * @return the textual name of the time zone
  357. */
  358. static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
  359. Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
  360. String value = (String) cTimeZoneDisplayCache.get(key);
  361. if (value == null) {
  362. // This is a very slow call, so cache the results.
  363. value = tz.getDisplayName(daylight, style, locale);
  364. cTimeZoneDisplayCache.put(key, value);
  365. }
  366. return value;
  367. }
  368. /**
  369. * <p>Gets the default pattern.</p>
  370. *
  371. * @return the default pattern
  372. */
  373. private static synchronized String getDefaultPattern() {
  374. if (cDefaultPattern == null) {
  375. cDefaultPattern = new SimpleDateFormat().toPattern();
  376. }
  377. return cDefaultPattern;
  378. }
  379. // Constructor
  380. //-----------------------------------------------------------------------
  381. /**
  382. * <p>Constructs a new FastDateFormat.</p>
  383. *
  384. * @param pattern {@link java.text.SimpleDateFormat} compatible
  385. * pattern
  386. * @param timeZone time zone to use, <code>null</code> means use
  387. * default for <code>Date</code> and value within for
  388. * <code>Calendar</code>
  389. * @param locale locale, <code>null</code> means use system
  390. * default
  391. * @throws IllegalArgumentException if pattern is invalid or
  392. * <code>null</code>
  393. */
  394. protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
  395. super();
  396. if (pattern == null) {
  397. throw new IllegalArgumentException("The pattern must not be null");
  398. }
  399. mPattern = pattern;
  400. mTimeZoneForced = (timeZone != null);
  401. if (timeZone == null) {
  402. timeZone = TimeZone.getDefault();
  403. }
  404. mTimeZone = timeZone;
  405. mLocaleForced = (locale != null);
  406. if (locale == null) {
  407. locale = Locale.getDefault();
  408. }
  409. mLocale = locale;
  410. }
  411. /**
  412. * <p>Initialise the instance for first use.</p>
  413. */
  414. protected void init() {
  415. List rulesList = parsePattern();
  416. mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);
  417. int len = 0;
  418. for (int i=mRules.length; --i >= 0; ) {
  419. len += mRules[i].estimateLength();
  420. }
  421. mMaxLengthEstimate = len;
  422. }
  423. // Parse the pattern
  424. //-----------------------------------------------------------------------
  425. /**
  426. * <p>Returns a list of Rules given a pattern.</p>
  427. *
  428. * @return a <code>List</code> of Rule objects
  429. * @throws IllegalArgumentException if pattern is invalid
  430. */
  431. protected List parsePattern() {
  432. DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
  433. List rules = new ArrayList();
  434. String[] ERAs = symbols.getEras();
  435. String[] months = symbols.getMonths();
  436. String[] shortMonths = symbols.getShortMonths();
  437. String[] weekdays = symbols.getWeekdays();
  438. String[] shortWeekdays = symbols.getShortWeekdays();
  439. String[] AmPmStrings = symbols.getAmPmStrings();
  440. int length = mPattern.length();
  441. int[] indexRef = new int[1];
  442. for (int i = 0; i < length; i++) {
  443. indexRef[0] = i;
  444. String token = parseToken(mPattern, indexRef);
  445. i = indexRef[0];
  446. int tokenLen = token.length();
  447. if (tokenLen == 0) {
  448. break;
  449. }
  450. Rule rule;
  451. char c = token.charAt(0);
  452. switch (c) {
  453. case 'G': // era designator (text)
  454. rule = new TextField(Calendar.ERA, ERAs);
  455. break;
  456. case 'y': // year (number)
  457. if (tokenLen >= 4) {
  458. rule = UnpaddedNumberField.INSTANCE_YEAR;
  459. } else {
  460. rule = TwoDigitYearField.INSTANCE;
  461. }
  462. break;
  463. case 'M': // month in year (text and number)
  464. if (tokenLen >= 4) {
  465. rule = new TextField(Calendar.MONTH, months);
  466. } else if (tokenLen == 3) {
  467. rule = new TextField(Calendar.MONTH, shortMonths);
  468. } else if (tokenLen == 2) {
  469. rule = TwoDigitMonthField.INSTANCE;
  470. } else {
  471. rule = UnpaddedMonthField.INSTANCE;
  472. }
  473. break;
  474. case 'd': // day in month (number)
  475. rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
  476. break;
  477. case 'h': // hour in am/pm (number, 1..12)
  478. rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
  479. break;
  480. case 'H': // hour in day (number, 0..23)
  481. rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
  482. break;
  483. case 'm': // minute in hour (number)
  484. rule = selectNumberRule(Calendar.MINUTE, tokenLen);
  485. break;
  486. case 's': // second in minute (number)
  487. rule = selectNumberRule(Calendar.SECOND, tokenLen);
  488. break;
  489. case 'S': // millisecond (number)
  490. rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
  491. break;
  492. case 'E': // day in week (text)
  493. rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
  494. break;
  495. case 'D': // day in year (number)
  496. rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
  497. break;
  498. case 'F': // day of week in month (number)
  499. rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
  500. break;
  501. case 'w': // week in year (number)
  502. rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
  503. break;
  504. case 'W': // week in month (number)
  505. rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
  506. break;
  507. case 'a': // am/pm marker (text)
  508. rule = new TextField(Calendar.AM_PM, AmPmStrings);
  509. break;
  510. case 'k': // hour in day (1..24)
  511. rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
  512. break;
  513. case 'K': // hour in am/pm (0..11)
  514. rule = selectNumberRule(Calendar.HOUR, tokenLen);
  515. break;
  516. case 'z': // time zone (text)
  517. if (tokenLen >= 4) {
  518. rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
  519. } else {
  520. rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT);
  521. }
  522. break;
  523. case 'Z': // time zone (value)
  524. if (tokenLen == 1) {
  525. rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
  526. } else {
  527. rule = TimeZoneNumberRule.INSTANCE_COLON;
  528. }
  529. break;
  530. case '\'': // literal text
  531. String sub = token.substring(1);
  532. if (sub.length() == 1) {
  533. rule = new CharacterLiteral(sub.charAt(0));
  534. } else {
  535. rule = new StringLiteral(sub);
  536. }
  537. break;
  538. default:
  539. throw new IllegalArgumentException("Illegal pattern component: " + token);
  540. }
  541. rules.add(rule);
  542. }
  543. return rules;
  544. }
  545. /**
  546. * <p>Performs the parsing of tokens.</p>
  547. *
  548. * @param pattern the pattern
  549. * @param indexRef index references
  550. * @return parsed token
  551. */
  552. protected String parseToken(String pattern, int[] indexRef) {
  553. StringBuffer buf = new StringBuffer();
  554. int i = indexRef[0];
  555. int length = pattern.length();
  556. char c = pattern.charAt(i);
  557. if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
  558. // Scan a run of the same character, which indicates a time
  559. // pattern.
  560. buf.append(c);
  561. while (i + 1 < length) {
  562. char peek = pattern.charAt(i + 1);
  563. if (peek == c) {
  564. buf.append(c);
  565. i++;
  566. } else {
  567. break;
  568. }
  569. }
  570. } else {
  571. // This will identify token as text.
  572. buf.append('\'');
  573. boolean inLiteral = false;
  574. for (; i < length; i++) {
  575. c = pattern.charAt(i);
  576. if (c == '\'') {
  577. if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
  578. // '' is treated as escaped '
  579. i++;
  580. buf.append(c);
  581. } else {
  582. inLiteral = !inLiteral;
  583. }
  584. } else if (!inLiteral &&
  585. (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
  586. i--;
  587. break;
  588. } else {
  589. buf.append(c);
  590. }
  591. }
  592. }
  593. indexRef[0] = i;
  594. return buf.toString();
  595. }
  596. /**
  597. * <p>Gets an appropriate rule for the padding required.</p>
  598. *
  599. * @param field the field to get a rule for
  600. * @param padding the padding required
  601. * @return a new rule with the correct padding
  602. */
  603. protected NumberRule selectNumberRule(int field, int padding) {
  604. switch (padding) {
  605. case 1:
  606. return new UnpaddedNumberField(field);
  607. case 2:
  608. return new TwoDigitNumberField(field);
  609. default:
  610. return new PaddedNumberField(field, padding);
  611. }
  612. }
  613. // Format methods
  614. //-----------------------------------------------------------------------
  615. /**
  616. * <p>Format either a <code>Date</code> or a
  617. * <code>Calendar</code> object.</p>
  618. *
  619. * @param obj the object to format
  620. * @param toAppendTo the buffer to append to
  621. * @param pos the position - ignored
  622. * @return the buffer passed in
  623. */
  624. public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
  625. if (obj instanceof Date) {
  626. return format((Date) obj, toAppendTo);
  627. } else if (obj instanceof Calendar) {
  628. return format((Calendar) obj, toAppendTo);
  629. } else {
  630. throw new IllegalArgumentException("Unknown class: " +
  631. (obj == null ? "<null>" : obj.getClass().getName()));
  632. }
  633. }
  634. /**
  635. * <p>Formats a <code>Date</code> object.</p>
  636. *
  637. * @param date the date to format
  638. * @return the formatted string
  639. */
  640. public String format(Date date) {
  641. Calendar c = new GregorianCalendar(mTimeZone);
  642. c.setTime(date);
  643. return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
  644. }
  645. /**
  646. * <p>Formats a <code>Calendar</code> object.</p>
  647. *
  648. * @param calendar the calendar to format
  649. * @return the formatted string
  650. */
  651. public String format(Calendar calendar) {
  652. return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
  653. }
  654. /**
  655. * <p>Formats a <code>Date</code> object into the
  656. * supplied <code>StringBuffer</code>.</p>
  657. *
  658. * @param date the date to format
  659. * @param buf the buffer to format into
  660. * @return the specified string buffer
  661. */
  662. public StringBuffer format(Date date, StringBuffer buf) {
  663. Calendar c = new GregorianCalendar(mTimeZone);
  664. c.setTime(date);
  665. return applyRules(c, buf);
  666. }
  667. /**
  668. * <p>Formats a <code>Calendar</code> object into the
  669. * supplied <code>StringBuffer</code>.</p>
  670. *
  671. * @param calendar the calendar to format
  672. * @param buf the buffer to format into
  673. * @return the specified string buffer
  674. */
  675. public StringBuffer format(Calendar calendar, StringBuffer buf) {
  676. if (mTimeZoneForced) {
  677. calendar = (Calendar) calendar.clone();
  678. calendar.setTimeZone(mTimeZone);
  679. }
  680. return applyRules(calendar, buf);
  681. }
  682. /**
  683. * <p>Performs the formatting by applying the rules to the
  684. * specified calendar.</p>
  685. *
  686. * @param calendar the calendar to format
  687. * @param buf the buffer to format into
  688. * @return the specified string buffer
  689. */
  690. protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
  691. Rule[] rules = mRules;
  692. int len = mRules.length;
  693. for (int i = 0; i < len; i++) {
  694. rules[i].appendTo(buf, calendar);
  695. }
  696. return buf;
  697. }
  698. // Parsing
  699. //-----------------------------------------------------------------------
  700. /**
  701. * <p>Parsing not supported.</p>
  702. *
  703. * @param source the string to parse
  704. * @param pos the parsing position
  705. * @return <code>null</code> as not supported
  706. */
  707. public Object parseObject(String source, ParsePosition pos) {
  708. pos.setIndex(0);
  709. pos.setErrorIndex(0);
  710. return null;
  711. }
  712. // Accessors
  713. //-----------------------------------------------------------------------
  714. /**
  715. * <p>Gets the pattern used by this formatter.</p>
  716. *
  717. * @return the pattern, {@link java.text.SimpleDateFormat} compatible
  718. */
  719. public String getPattern() {
  720. return mPattern;
  721. }
  722. /**
  723. * <p>Gets the time zone used by this formatter.</p>
  724. *
  725. * <p>This zone is always used for <code>Date</code> formatting.
  726. * If a <code>Calendar</code> is passed in to be formatted, the
  727. * time zone on that may be used depending on
  728. * {@link #getTimeZoneOverridesCalendar()}.</p>
  729. *
  730. * @return the time zone
  731. */
  732. public TimeZone getTimeZone() {
  733. return mTimeZone;
  734. }
  735. /**
  736. * <p>Returns <code>true</code> if the time zone of the
  737. * calendar overrides the time zone of the formatter.</p>
  738. *
  739. * @return <code>true</code> if time zone of formatter
  740. * overridden for calendars
  741. */
  742. public boolean getTimeZoneOverridesCalendar() {
  743. return mTimeZoneForced;
  744. }
  745. /**
  746. * <p>Gets the locale used by this formatter.</p>
  747. *
  748. * @return the locale
  749. */
  750. public Locale getLocale() {
  751. return mLocale;
  752. }
  753. /**
  754. * <p>Gets an estimate for the maximum string length that the
  755. * formatter will produce.</p>
  756. *
  757. * <p>The actual formatted length will almost always be less than or
  758. * equal to this amount.</p>
  759. *
  760. * @return the maximum formatted length
  761. */
  762. public int getMaxLengthEstimate() {
  763. return mMaxLengthEstimate;
  764. }
  765. // Basics
  766. //-----------------------------------------------------------------------
  767. /**
  768. * <p>Compare two objects for equality.</p>
  769. *
  770. * @param obj the object to compare to
  771. * @return <code>true</code> if equal
  772. */
  773. public boolean equals(Object obj) {
  774. if (obj instanceof FastDateFormat == false) {
  775. return false;
  776. }
  777. FastDateFormat other = (FastDateFormat) obj;
  778. if (
  779. (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
  780. (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
  781. (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
  782. (mTimeZoneForced == other.mTimeZoneForced) &&
  783. (mLocaleForced == other.mLocaleForced)
  784. ) {
  785. return true;
  786. }
  787. return false;
  788. }
  789. /**
  790. * <p>A suitable hashcode.</p>
  791. *
  792. * @return a hashcode compatable with equals
  793. */
  794. public int hashCode() {
  795. int total = 0;
  796. total += mPattern.hashCode();
  797. total += mTimeZone.hashCode();
  798. total += (mTimeZoneForced ? 1 : 0);
  799. total += mLocale.hashCode();
  800. total += (mLocaleForced ? 1 : 0);
  801. return total;
  802. }
  803. /**
  804. * <p>Gets a debugging string version of this formatter.</p>
  805. *
  806. * @return a debugging string
  807. */
  808. public String toString() {
  809. return "FastDateFormat[" + mPattern + "]";
  810. }
  811. // Rules
  812. //-----------------------------------------------------------------------
  813. /**
  814. * <p>Inner class defining a rule.</p>
  815. */
  816. private interface Rule {
  817. int estimateLength();
  818. void appendTo(StringBuffer buffer, Calendar calendar);
  819. }
  820. /**
  821. * <p>Inner class defining a numeric rule.</p>
  822. */
  823. private interface NumberRule extends Rule {
  824. void appendTo(StringBuffer buffer, int value);
  825. }
  826. /**
  827. * <p>Inner class to output a constant single character.</p>
  828. */
  829. private static class CharacterLiteral implements Rule {
  830. private final char mValue;
  831. CharacterLiteral(char value) {
  832. mValue = value;
  833. }
  834. public int estimateLength() {
  835. return 1;
  836. }
  837. public void appendTo(StringBuffer buffer, Calendar calendar) {
  838. buffer.append(mValue);
  839. }
  840. }
  841. /**
  842. * <p>Inner class to output a constant string.</p>
  843. */
  844. private static class StringLiteral implements Rule {
  845. private final String mValue;
  846. StringLiteral(String value) {
  847. mValue = value;
  848. }
  849. public int estimateLength() {
  850. return mValue.length();
  851. }
  852. public void appendTo(StringBuffer buffer, Calendar calendar) {
  853. buffer.append(mValue);
  854. }
  855. }
  856. /**
  857. * <p>Inner class to output one of a set of values.</p>
  858. */
  859. private static class TextField implements Rule {
  860. private final int mField;
  861. private final String[] mValues;
  862. TextField(int field, String[] values) {
  863. mField = field;
  864. mValues = values;
  865. }
  866. public int estimateLength() {
  867. int max = 0;
  868. for (int i=mValues.length; --i >= 0; ) {
  869. int len = mValues[i].length();
  870. if (len > max) {
  871. max = len;
  872. }
  873. }
  874. return max;
  875. }
  876. public void appendTo(StringBuffer buffer, Calendar calendar) {
  877. buffer.append(mValues[calendar.get(mField)]);
  878. }
  879. }
  880. /**
  881. * <p>Inner class to output an unpadded number.</p>
  882. */
  883. private static class UnpaddedNumberField implements NumberRule {
  884. static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(Calendar.YEAR);
  885. private final int mField;
  886. UnpaddedNumberField(int field) {
  887. mField = field;
  888. }
  889. public int estimateLength() {
  890. return 4;
  891. }
  892. public void appendTo(StringBuffer buffer, Calendar calendar) {
  893. appendTo(buffer, calendar.get(mField));
  894. }
  895. public final void appendTo(StringBuffer buffer, int value) {
  896. if (value < 10) {
  897. buffer.append((char)(value + '0'));
  898. } else if (value < 100) {
  899. buffer.append((char)(value / 10 + '0'));
  900. buffer.append((char)(value % 10 + '0'));
  901. } else {
  902. buffer.append(Integer.toString(value));
  903. }
  904. }
  905. }
  906. /**
  907. * <p>Inner class to output an unpadded month.</p>
  908. */
  909. private static class UnpaddedMonthField implements NumberRule {
  910. static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
  911. UnpaddedMonthField() {
  912. }
  913. public int estimateLength() {
  914. return 2;
  915. }
  916. public void appendTo(StringBuffer buffer, Calendar calendar) {
  917. appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
  918. }
  919. public final void appendTo(StringBuffer buffer, int value) {
  920. if (value < 10) {
  921. buffer.append((char)(value + '0'));
  922. } else {
  923. buffer.append((char)(value / 10 + '0'));
  924. buffer.append((char)(value % 10 + '0'));
  925. }
  926. }
  927. }
  928. /**
  929. * <p>Inner class to output a padded number.</p>
  930. */
  931. private static class PaddedNumberField implements NumberRule {
  932. private final int mField;
  933. private final int mSize;
  934. PaddedNumberField(int field, int size) {
  935. if (size < 3) {
  936. // Should use UnpaddedNumberField or TwoDigitNumberField.
  937. throw new IllegalArgumentException();
  938. }
  939. mField = field;
  940. mSize = size;
  941. }
  942. public int estimateLength() {
  943. return 4;
  944. }
  945. public void appendTo(StringBuffer buffer, Calendar calendar) {
  946. appendTo(buffer, calendar.get(mField));
  947. }
  948. public final void appendTo(StringBuffer buffer, int value) {
  949. if (value < 100) {
  950. for (int i = mSize; --i >= 2; ) {
  951. buffer.append('0');
  952. }
  953. buffer.append((char)(value / 10 + '0'));
  954. buffer.append((char)(value % 10 + '0'));
  955. } else {
  956. int digits;
  957. if (value < 1000) {
  958. digits = 3;
  959. } else {
  960. digits = (int)(Math.log(value) / LOG_10) + 1;
  961. }
  962. for (int i = mSize; --i >= digits; ) {
  963. buffer.append('0');
  964. }
  965. buffer.append(Integer.toString(value));
  966. }
  967. }
  968. }
  969. /**
  970. * <p>Inner class to output a two digit number.</p>
  971. */
  972. private static class TwoDigitNumberField implements NumberRule {
  973. private final int mField;
  974. TwoDigitNumberField(int field) {
  975. mField = field;
  976. }
  977. public int estimateLength() {
  978. return 2;
  979. }
  980. public void appendTo(StringBuffer buffer, Calendar calendar) {
  981. appendTo(buffer, calendar.get(mField));
  982. }
  983. public final void appendTo(StringBuffer buffer, int value) {
  984. if (value < 100) {
  985. buffer.append((char)(value / 10 + '0'));
  986. buffer.append((char)(value % 10 + '0'));
  987. } else {
  988. buffer.append(Integer.toString(value));
  989. }
  990. }
  991. }
  992. /**
  993. * <p>Inner class to output a two digit year.</p>
  994. */
  995. private static class TwoDigitYearField implements NumberRule {
  996. static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
  997. TwoDigitYearField() {
  998. }
  999. public int estimateLength() {
  1000. return 2;
  1001. }
  1002. public void appendTo(StringBuffer buffer, Calendar calendar) {
  1003. appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
  1004. }
  1005. public final void appendTo(StringBuffer buffer, int value) {
  1006. buffer.append((char)(value / 10 + '0'));
  1007. buffer.append((char)(value % 10 + '0'));
  1008. }
  1009. }
  1010. /**
  1011. * <p>Inner class to output a two digit month.</p>
  1012. */
  1013. private static class TwoDigitMonthField implements NumberRule {
  1014. static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
  1015. TwoDigitMonthField() {
  1016. }
  1017. public int estimateLength() {
  1018. return 2;
  1019. }
  1020. public void appendTo(StringBuffer buffer, Calendar calendar) {
  1021. appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
  1022. }
  1023. public final void appendTo(StringBuffer buffer, int value) {
  1024. buffer.append((char)(value / 10 + '0'));
  1025. buffer.append((char)(value % 10 + '0'));
  1026. }
  1027. }
  1028. /**
  1029. * <p>Inner class to output the twelve hour field.</p>
  1030. */
  1031. private static class TwelveHourField implements NumberRule {
  1032. private final NumberRule mRule;
  1033. TwelveHourField(NumberRule rule) {
  1034. mRule = rule;
  1035. }
  1036. public int estimateLength() {
  1037. return mRule.estimateLength();
  1038. }
  1039. public void appendTo(StringBuffer buffer, Calendar calendar) {
  1040. int value = calendar.get(Calendar.HOUR);
  1041. if (value == 0) {
  1042. value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
  1043. }
  1044. mRule.appendTo(buffer, value);
  1045. }
  1046. public void appendTo(StringBuffer buffer, int value) {
  1047. mRule.appendTo(buffer, value);
  1048. }
  1049. }
  1050. /**
  1051. * <p>Inner class to output the twenty four hour field.</p>
  1052. */
  1053. private static class TwentyFourHourField implements NumberRule {
  1054. private final NumberRule mRule;
  1055. TwentyFourHourField(NumberRule rule) {
  1056. mRule = rule;
  1057. }
  1058. public int estimateLength() {
  1059. return mRule.estimateLength();
  1060. }
  1061. public void appendTo(StringBuffer buffer, Calendar calendar) {
  1062. int value = calendar.get(Calendar.HOUR_OF_DAY);
  1063. if (value == 0) {
  1064. value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
  1065. }
  1066. mRule.appendTo(buffer, value);
  1067. }
  1068. public void appendTo(StringBuffer buffer, int value) {
  1069. mRule.appendTo(buffer, value);
  1070. }
  1071. }
  1072. /**
  1073. * <p>Inner class to output a time zone name.</p>
  1074. */
  1075. private static class TimeZoneNameRule implements Rule {
  1076. private final TimeZone mTimeZone;
  1077. private final boolean mTimeZoneForced;
  1078. private final Locale mLocale;
  1079. private final int mStyle;
  1080. private final String mStandard;
  1081. private final String mDaylight;
  1082. TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) {
  1083. mTimeZone = timeZone;
  1084. mTimeZoneForced = timeZoneForced;
  1085. mLocale = locale;
  1086. mStyle = style;
  1087. if (timeZoneForced) {
  1088. mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
  1089. mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
  1090. } else {
  1091. mStandard = null;
  1092. mDaylight = null;
  1093. }
  1094. }
  1095. public int estimateLength() {
  1096. if (mTimeZoneForced) {
  1097. return Math.max(mStandard.length(), mDaylight.length());
  1098. } else if (mStyle == TimeZone.SHORT) {
  1099. return 4;
  1100. } else {
  1101. return 40;
  1102. }
  1103. }
  1104. public void appendTo(StringBuffer buffer, Calendar calendar) {
  1105. if (mTimeZoneForced) {
  1106. if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
  1107. buffer.append(mDaylight);
  1108. } else {
  1109. buffer.append(mStandard);
  1110. }
  1111. } else {
  1112. TimeZone timeZone = calendar.getTimeZone();
  1113. if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
  1114. buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
  1115. } else {
  1116. buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
  1117. }
  1118. }
  1119. }
  1120. }
  1121. /**
  1122. * <p>Inner class to output a time zone as a number <code>+/-HHMM</code>
  1123. * or <code>+/-HH:MM</code>.</p>
  1124. */
  1125. private static class TimeZoneNumberRule implements Rule {
  1126. static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
  1127. static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
  1128. final boolean mColon;
  1129. TimeZoneNumberRule(boolean colon) {
  1130. mColon = colon;
  1131. }
  1132. public int estimateLength() {
  1133. return 5;
  1134. }
  1135. public void appendTo(StringBuffer buffer, Calendar calendar) {
  1136. int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
  1137. if (offset < 0) {
  1138. buffer.append('-');
  1139. offset = -offset;
  1140. } else {
  1141. buffer.append('+');
  1142. }
  1143. int hours = offset / (60 * 60 * 1000);
  1144. buffer.append((char)(hours / 10 + '0'));
  1145. buffer.append((char)(hours % 10 + '0'));
  1146. if (mColon) {
  1147. buffer.append(':');
  1148. }
  1149. int minutes = offset / (60 * 1000) - 60 * hours;
  1150. buffer.append((char)(minutes / 10 + '0'));
  1151. buffer.append((char)(minutes % 10 + '0'));
  1152. }
  1153. }
  1154. // ----------------------------------------------------------------------
  1155. /**
  1156. * <p>Inner class that acts as a compound key for time zone names.</p>
  1157. */
  1158. private static class TimeZoneDisplayKey {
  1159. private final TimeZone mTimeZone;
  1160. private final int mStyle;
  1161. private final Locale mLocale;
  1162. TimeZoneDisplayKey(TimeZone timeZone,
  1163. boolean daylight, int style, Locale locale) {
  1164. mTimeZone = timeZone;
  1165. if (daylight) {
  1166. style |= 0x80000000;
  1167. }
  1168. mStyle = style;
  1169. mLocale = locale;
  1170. }
  1171. public int hashCode() {
  1172. return mStyle * 31 + mLocale.hashCode();
  1173. }
  1174. public boolean equals(Object obj) {
  1175. if (this == obj) {
  1176. return true;
  1177. }
  1178. if (obj instanceof TimeZoneDisplayKey) {
  1179. TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
  1180. return
  1181. mTimeZone.equals(other.mTimeZone) &&
  1182. mStyle == other.mStyle &&
  1183. mLocale.equals(other.mLocale);
  1184. }
  1185. return false;
  1186. }
  1187. }
  1188. // ----------------------------------------------------------------------
  1189. /**
  1190. * <p>Helper class for creating compound objects.</p>
  1191. *
  1192. * <p>One use for this class is to create a hashtable key
  1193. * out of multiple objects.</p>
  1194. */
  1195. private static class Pair {
  1196. private final Object mObj1;
  1197. private final Object mObj2;
  1198. public Pair(Object obj1, Object obj2) {
  1199. mObj1 = obj1;
  1200. mObj2 = obj2;
  1201. }
  1202. public boolean equals(Object obj) {
  1203. if (this == obj) {
  1204. return true;
  1205. }
  1206. if (!(obj instanceof Pair)) {
  1207. return false;
  1208. }
  1209. Pair key = (Pair)obj;
  1210. return
  1211. (mObj1 == null ?
  1212. key.mObj1 == null : mObj1.equals(key.mObj1)) &&
  1213. (mObj2 == null ?
  1214. key.mObj2 == null : mObj2.equals(key.mObj2));
  1215. }
  1216. public int hashCode() {
  1217. return
  1218. (mObj1 == null ? 0 : mObj1.hashCode()) +
  1219. (mObj2 == null ? 0 : mObj2.hashCode());
  1220. }
  1221. public String toString() {
  1222. return "[" + mObj1 + ':' + mObj2 + ']';
  1223. }
  1224. }
  1225. }