SimpleDateFormat.java 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324
  1. /* SimpleDateFormat.java -- A class for parsing/formating simple
  2. date constructs
  3. Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005
  4. Free Software Foundation, Inc.
  5. This file is part of GNU Classpath.
  6. GNU Classpath is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2, or (at your option)
  9. any later version.
  10. GNU Classpath is distributed in the hope that it will be useful, but
  11. WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with GNU Classpath; see the file COPYING. If not, write to the
  16. Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. 02110-1301 USA.
  18. Linking this library statically or dynamically with other modules is
  19. making a combined work based on this library. Thus, the terms and
  20. conditions of the GNU General Public License cover the whole
  21. combination.
  22. As a special exception, the copyright holders of this library give you
  23. permission to link this library with independent modules to produce an
  24. executable, regardless of the license terms of these independent
  25. modules, and to copy and distribute the resulting executable under
  26. terms of your choice, provided that you also meet, for each linked
  27. independent module, the terms and conditions of the license of that
  28. module. An independent module is a module which is not derived from
  29. or based on this library. If you modify this library, you may extend
  30. this exception to your version of the library, but you are not
  31. obligated to do so. If you do not wish to do so, delete this
  32. exception statement from your version. */
  33. package java.text;
  34. import gnu.java.lang.CPStringBuilder;
  35. import gnu.java.text.AttributedFormatBuffer;
  36. import gnu.java.text.FormatBuffer;
  37. import gnu.java.text.FormatCharacterIterator;
  38. import gnu.java.text.StringFormatBuffer;
  39. import java.io.IOException;
  40. import java.io.InvalidObjectException;
  41. import java.io.ObjectInputStream;
  42. import java.util.ArrayList;
  43. import java.util.Calendar;
  44. import java.util.Date;
  45. import java.util.GregorianCalendar;
  46. import java.util.Iterator;
  47. import java.util.Locale;
  48. import java.util.TimeZone;
  49. import java.util.regex.Matcher;
  50. import java.util.regex.Pattern;
  51. /**
  52. * SimpleDateFormat provides convenient methods for parsing and formatting
  53. * dates using Gregorian calendars (see java.util.GregorianCalendar).
  54. * This class is not thread-safe; external synchronisation should be applied
  55. * if an instance is to be accessed from multiple threads.
  56. */
  57. public class SimpleDateFormat extends DateFormat
  58. {
  59. /**
  60. * This class is used by <code>SimpleDateFormat</code> as a
  61. * compiled representation of a format string. The field
  62. * ID, size, and character used are stored for each sequence
  63. * of pattern characters.
  64. */
  65. private class CompiledField
  66. {
  67. /**
  68. * The ID of the field within the local pattern characters.
  69. * Package private for use in out class.
  70. */
  71. int field;
  72. /**
  73. * The size of the character sequence.
  74. * Package private for use in out class.
  75. */
  76. int size;
  77. /**
  78. * The character used.
  79. */
  80. private char character;
  81. /**
  82. * Constructs a compiled field using the
  83. * the given field ID, size and character
  84. * values.
  85. *
  86. * @param f the field ID.
  87. * @param s the size of the field.
  88. * @param c the character used.
  89. */
  90. public CompiledField(int f, int s, char c)
  91. {
  92. field = f;
  93. size = s;
  94. character = c;
  95. }
  96. /**
  97. * Retrieves the ID of the field relative to
  98. * the local pattern characters.
  99. */
  100. public int getField()
  101. {
  102. return field;
  103. }
  104. /**
  105. * Retrieves the size of the character sequence.
  106. */
  107. public int getSize()
  108. {
  109. return size;
  110. }
  111. /**
  112. * Retrieves the character used in the sequence.
  113. */
  114. public char getCharacter()
  115. {
  116. return character;
  117. }
  118. /**
  119. * Returns a <code>String</code> representation
  120. * of the compiled field, primarily for debugging
  121. * purposes.
  122. *
  123. * @return a <code>String</code> representation.
  124. */
  125. public String toString()
  126. {
  127. CPStringBuilder builder;
  128. builder = new CPStringBuilder(getClass().getName());
  129. builder.append("[field=");
  130. builder.append(field);
  131. builder.append(", size=");
  132. builder.append(size);
  133. builder.append(", character=");
  134. builder.append(character);
  135. builder.append("]");
  136. return builder.toString();
  137. }
  138. }
  139. /**
  140. * A list of <code>CompiledField</code>s and {@code String}s
  141. * representing the compiled version of the pattern.
  142. *
  143. * @see CompiledField
  144. * @serial Ignored.
  145. */
  146. private transient ArrayList<Object> tokens;
  147. /**
  148. * The localised data used in formatting,
  149. * such as the day and month names in the local
  150. * language, and the localized pattern characters.
  151. *
  152. * @see DateFormatSymbols
  153. * @serial The localisation data. May not be null.
  154. */
  155. private DateFormatSymbols formatData;
  156. /**
  157. * The date representing the start of the century
  158. * used for interpreting two digit years. For
  159. * example, 24/10/2004 would cause two digit
  160. * years to be interpreted as representing
  161. * the years between 2004 and 2104.
  162. *
  163. * @see #get2DigitYearStart()
  164. * @see #set2DigitYearStart(java.util.Date)
  165. * @see Date
  166. * @serial The start date of the century for parsing two digit years.
  167. * May not be null.
  168. */
  169. private Date defaultCenturyStart;
  170. /**
  171. * The year at which interpretation of two
  172. * digit years starts.
  173. *
  174. * @see #get2DigitYearStart()
  175. * @see #set2DigitYearStart(java.util.Date)
  176. * @serial Ignored.
  177. */
  178. private transient int defaultCentury;
  179. /**
  180. * The non-localized pattern string. This
  181. * only ever contains the pattern characters
  182. * stored in standardChars. Localized patterns
  183. * are translated to this form.
  184. *
  185. * @see #applyPattern(String)
  186. * @see #applyLocalizedPattern(String)
  187. * @see #toPattern()
  188. * @see #toLocalizedPattern()
  189. * @serial The non-localized pattern string. May not be null.
  190. */
  191. private String pattern;
  192. /**
  193. * The version of serialized data used by this class.
  194. * Version 0 only includes the pattern and formatting
  195. * data. Version 1 adds the start date for interpreting
  196. * two digit years.
  197. *
  198. * @serial This specifies the version of the data being serialized.
  199. * Version 0 (or no version) specifies just <code>pattern</code>
  200. * and <code>formatData</code>. Version 1 adds
  201. * the <code>defaultCenturyStart</code>. This implementation
  202. * always writes out version 1 data.
  203. */
  204. private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier
  205. /**
  206. * For compatability.
  207. */
  208. private static final long serialVersionUID = 4774881970558875024L;
  209. // This string is specified in the root of the CLDR.
  210. private static final String standardChars = "GyMdkHmsSEDFwWahKzYeugAZvcL";
  211. /**
  212. * Represents the position of the RFC822 timezone pattern character
  213. * in the array of localized pattern characters. In the
  214. * U.S. locale, this is 'Z'. The value is the offset of the current
  215. * time from GMT e.g. -0500 would be five hours prior to GMT.
  216. */
  217. private static final int RFC822_TIMEZONE_FIELD = 23;
  218. /**
  219. * Reads the serialized version of this object.
  220. * If the serialized data is only version 0,
  221. * then the date for the start of the century
  222. * for interpreting two digit years is computed.
  223. * The pattern is parsed and compiled following the process
  224. * of reading in the serialized data.
  225. *
  226. * @param stream the object stream to read the data from.
  227. * @throws IOException if an I/O error occurs.
  228. * @throws ClassNotFoundException if the class of the serialized data
  229. * could not be found.
  230. * @throws InvalidObjectException if the pattern is invalid.
  231. */
  232. private void readObject(ObjectInputStream stream)
  233. throws IOException, ClassNotFoundException
  234. {
  235. stream.defaultReadObject();
  236. if (serialVersionOnStream < 1)
  237. {
  238. computeCenturyStart ();
  239. serialVersionOnStream = 1;
  240. }
  241. else
  242. // Ensure that defaultCentury gets set.
  243. set2DigitYearStart(defaultCenturyStart);
  244. // Set up items normally taken care of by the constructor.
  245. tokens = new ArrayList<Object>();
  246. try
  247. {
  248. compileFormat(pattern);
  249. }
  250. catch (IllegalArgumentException e)
  251. {
  252. throw new InvalidObjectException("The stream pattern was invalid.");
  253. }
  254. }
  255. /**
  256. * Compiles the supplied non-localized pattern into a form
  257. * from which formatting and parsing can be performed.
  258. * This also detects errors in the pattern, which will
  259. * be raised on later use of the compiled data.
  260. *
  261. * @param pattern the non-localized pattern to compile.
  262. * @throws IllegalArgumentException if the pattern is invalid.
  263. */
  264. private void compileFormat(String pattern)
  265. {
  266. // Any alphabetical characters are treated as pattern characters
  267. // unless enclosed in single quotes.
  268. char thisChar;
  269. int pos;
  270. int field;
  271. CompiledField current = null;
  272. for (int i = 0; i < pattern.length(); i++)
  273. {
  274. thisChar = pattern.charAt(i);
  275. field = standardChars.indexOf(thisChar);
  276. if (field == -1)
  277. {
  278. current = null;
  279. if ((thisChar >= 'A' && thisChar <= 'Z')
  280. || (thisChar >= 'a' && thisChar <= 'z'))
  281. {
  282. // Not a valid letter
  283. throw new IllegalArgumentException("Invalid letter "
  284. + thisChar +
  285. " encountered at character "
  286. + i + ".");
  287. }
  288. else if (thisChar == '\'')
  289. {
  290. // Quoted text section; skip to next single quote
  291. pos = pattern.indexOf('\'', i + 1);
  292. // First look for '' -- meaning a single quote.
  293. if (pos == i + 1)
  294. tokens.add("'");
  295. else
  296. {
  297. // Look for the terminating quote. However, if we
  298. // see a '', that represents a literal quote and
  299. // we must iterate.
  300. CPStringBuilder buf = new CPStringBuilder();
  301. int oldPos = i + 1;
  302. do
  303. {
  304. if (pos == -1)
  305. throw new IllegalArgumentException("Quotes starting at character "
  306. + i +
  307. " not closed.");
  308. buf.append(pattern.substring(oldPos, pos));
  309. if (pos + 1 >= pattern.length()
  310. || pattern.charAt(pos + 1) != '\'')
  311. break;
  312. buf.append('\'');
  313. oldPos = pos + 2;
  314. pos = pattern.indexOf('\'', pos + 2);
  315. }
  316. while (true);
  317. tokens.add(buf.toString());
  318. }
  319. i = pos;
  320. }
  321. else
  322. {
  323. // A special character
  324. tokens.add(Character.valueOf(thisChar));
  325. }
  326. }
  327. else
  328. {
  329. // A valid field
  330. if ((current != null) && (field == current.field))
  331. current.size++;
  332. else
  333. {
  334. current = new CompiledField(field, 1, thisChar);
  335. tokens.add(current);
  336. }
  337. }
  338. }
  339. }
  340. /**
  341. * Returns a string representation of this
  342. * class.
  343. *
  344. * @return a string representation of the <code>SimpleDateFormat</code>
  345. * instance.
  346. */
  347. public String toString()
  348. {
  349. CPStringBuilder output = new CPStringBuilder(getClass().getName());
  350. output.append("[tokens=");
  351. output.append(tokens);
  352. output.append(", formatData=");
  353. output.append(formatData);
  354. output.append(", defaultCenturyStart=");
  355. output.append(defaultCenturyStart);
  356. output.append(", defaultCentury=");
  357. output.append(defaultCentury);
  358. output.append(", pattern=");
  359. output.append(pattern);
  360. output.append(", serialVersionOnStream=");
  361. output.append(serialVersionOnStream);
  362. output.append(", standardChars=");
  363. output.append(standardChars);
  364. output.append("]");
  365. return output.toString();
  366. }
  367. /**
  368. * Constructs a SimpleDateFormat using the default pattern for
  369. * the default locale.
  370. */
  371. public SimpleDateFormat()
  372. {
  373. /*
  374. * There does not appear to be a standard API for determining
  375. * what the default pattern for a locale is, so use package-scope
  376. * variables in DateFormatSymbols to encapsulate this.
  377. */
  378. super();
  379. Locale locale = Locale.getDefault();
  380. calendar = new GregorianCalendar(locale);
  381. computeCenturyStart();
  382. tokens = new ArrayList<Object>();
  383. formatData = new DateFormatSymbols(locale);
  384. pattern = (formatData.dateFormats[DEFAULT] + ' '
  385. + formatData.timeFormats[DEFAULT]);
  386. compileFormat(pattern);
  387. numberFormat = NumberFormat.getInstance(locale);
  388. numberFormat.setGroupingUsed (false);
  389. numberFormat.setParseIntegerOnly (true);
  390. numberFormat.setMaximumFractionDigits (0);
  391. }
  392. /**
  393. * Creates a date formatter using the specified non-localized pattern,
  394. * with the default DateFormatSymbols for the default locale.
  395. *
  396. * @param pattern the pattern to use.
  397. * @throws NullPointerException if the pattern is null.
  398. * @throws IllegalArgumentException if the pattern is invalid.
  399. */
  400. public SimpleDateFormat(String pattern)
  401. {
  402. this(pattern, Locale.getDefault());
  403. }
  404. /**
  405. * Creates a date formatter using the specified non-localized pattern,
  406. * with the default DateFormatSymbols for the given locale.
  407. *
  408. * @param pattern the non-localized pattern to use.
  409. * @param locale the locale to use for the formatting symbols.
  410. * @throws NullPointerException if the pattern is null.
  411. * @throws IllegalArgumentException if the pattern is invalid.
  412. */
  413. public SimpleDateFormat(String pattern, Locale locale)
  414. {
  415. super();
  416. calendar = new GregorianCalendar(locale);
  417. computeCenturyStart();
  418. tokens = new ArrayList<Object>();
  419. formatData = new DateFormatSymbols(locale);
  420. compileFormat(pattern);
  421. this.pattern = pattern;
  422. numberFormat = NumberFormat.getInstance(locale);
  423. numberFormat.setGroupingUsed (false);
  424. numberFormat.setParseIntegerOnly (true);
  425. numberFormat.setMaximumFractionDigits (0);
  426. }
  427. /**
  428. * Creates a date formatter using the specified non-localized
  429. * pattern. The specified DateFormatSymbols will be used when
  430. * formatting.
  431. *
  432. * @param pattern the non-localized pattern to use.
  433. * @param formatData the formatting symbols to use.
  434. * @throws NullPointerException if the pattern or formatData is null.
  435. * @throws IllegalArgumentException if the pattern is invalid.
  436. */
  437. public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
  438. {
  439. super();
  440. calendar = new GregorianCalendar();
  441. computeCenturyStart ();
  442. tokens = new ArrayList<Object>();
  443. if (formatData == null)
  444. throw new NullPointerException("formatData");
  445. this.formatData = formatData;
  446. compileFormat(pattern);
  447. this.pattern = pattern;
  448. numberFormat = NumberFormat.getInstance();
  449. numberFormat.setGroupingUsed (false);
  450. numberFormat.setParseIntegerOnly (true);
  451. numberFormat.setMaximumFractionDigits (0);
  452. }
  453. /**
  454. * This method returns a string with the formatting pattern being used
  455. * by this object. This string is unlocalized.
  456. *
  457. * @return The format string.
  458. */
  459. public String toPattern()
  460. {
  461. return pattern;
  462. }
  463. /**
  464. * This method returns a string with the formatting pattern being used
  465. * by this object. This string is localized.
  466. *
  467. * @return The format string.
  468. */
  469. public String toLocalizedPattern()
  470. {
  471. String localChars = formatData.getLocalPatternChars();
  472. return translateLocalizedPattern(pattern, standardChars, localChars);
  473. }
  474. /**
  475. * This method sets the formatting pattern that should be used by this
  476. * object. This string is not localized.
  477. *
  478. * @param pattern The new format pattern.
  479. * @throws NullPointerException if the pattern is null.
  480. * @throws IllegalArgumentException if the pattern is invalid.
  481. */
  482. public void applyPattern(String pattern)
  483. {
  484. tokens.clear();
  485. compileFormat(pattern);
  486. this.pattern = pattern;
  487. }
  488. /**
  489. * This method sets the formatting pattern that should be used by this
  490. * object. This string is localized.
  491. *
  492. * @param pattern The new format pattern.
  493. * @throws NullPointerException if the pattern is null.
  494. * @throws IllegalArgumentException if the pattern is invalid.
  495. */
  496. public void applyLocalizedPattern(String pattern)
  497. {
  498. String localChars = formatData.getLocalPatternChars();
  499. pattern = translateLocalizedPattern(pattern, localChars, standardChars);
  500. applyPattern(pattern);
  501. }
  502. /**
  503. * Translates either from or to a localized variant of the pattern
  504. * string. For example, in the German locale, 't' (for 'tag') is
  505. * used instead of 'd' (for 'date'). This method translates
  506. * a localized pattern (such as 'ttt') to a non-localized pattern
  507. * (such as 'ddd'), or vice versa. Non-localized patterns use
  508. * a standard set of characters, which match those of the U.S. English
  509. * locale.
  510. *
  511. * @param pattern the pattern to translate.
  512. * @param oldChars the old set of characters (used in the pattern).
  513. * @param newChars the new set of characters (which will be used in the
  514. * pattern).
  515. * @return a version of the pattern using the characters in
  516. * <code>newChars</code>.
  517. */
  518. private String translateLocalizedPattern(String pattern,
  519. String oldChars, String newChars)
  520. {
  521. int len = pattern.length();
  522. CPStringBuilder buf = new CPStringBuilder(len);
  523. boolean quoted = false;
  524. for (int i = 0; i < len; i++)
  525. {
  526. char ch = pattern.charAt(i);
  527. if (ch == '\'')
  528. quoted = ! quoted;
  529. if (! quoted)
  530. {
  531. int j = oldChars.indexOf(ch);
  532. if (j >= 0)
  533. ch = newChars.charAt(j);
  534. }
  535. buf.append(ch);
  536. }
  537. return buf.toString();
  538. }
  539. /**
  540. * Returns the start of the century used for two digit years.
  541. *
  542. * @return A <code>Date</code> representing the start of the century
  543. * for two digit years.
  544. */
  545. public Date get2DigitYearStart()
  546. {
  547. return defaultCenturyStart;
  548. }
  549. /**
  550. * Sets the start of the century used for two digit years.
  551. *
  552. * @param date A <code>Date</code> representing the start of the century for
  553. * two digit years.
  554. */
  555. public void set2DigitYearStart(Date date)
  556. {
  557. defaultCenturyStart = date;
  558. calendar.clear();
  559. calendar.setTime(date);
  560. int year = calendar.get(Calendar.YEAR);
  561. defaultCentury = year - (year % 100);
  562. }
  563. /**
  564. * This method returns a copy of the format symbol information used
  565. * for parsing and formatting dates.
  566. *
  567. * @return a copy of the date format symbols.
  568. */
  569. public DateFormatSymbols getDateFormatSymbols()
  570. {
  571. return (DateFormatSymbols) formatData.clone();
  572. }
  573. /**
  574. * This method sets the format symbols information used for parsing
  575. * and formatting dates.
  576. *
  577. * @param formatData The date format symbols.
  578. * @throws NullPointerException if <code>formatData</code> is null.
  579. */
  580. public void setDateFormatSymbols(DateFormatSymbols formatData)
  581. {
  582. if (formatData == null)
  583. {
  584. throw new
  585. NullPointerException("The supplied format data was null.");
  586. }
  587. this.formatData = formatData;
  588. }
  589. /**
  590. * This methods tests whether the specified object is equal to this
  591. * object. This will be true if and only if the specified object:
  592. * <p>
  593. * <ul>
  594. * <li>Is not <code>null</code>.</li>
  595. * <li>Is an instance of <code>SimpleDateFormat</code>.</li>
  596. * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>)
  597. * level.</li>
  598. * <li>Has the same formatting pattern.</li>
  599. * <li>Is using the same formatting symbols.</li>
  600. * <li>Is using the same century for two digit years.</li>
  601. * </ul>
  602. *
  603. * @param o The object to compare for equality against.
  604. *
  605. * @return <code>true</code> if the specified object is equal to this object,
  606. * <code>false</code> otherwise.
  607. */
  608. public boolean equals(Object o)
  609. {
  610. if (!super.equals(o))
  611. return false;
  612. if (!(o instanceof SimpleDateFormat))
  613. return false;
  614. SimpleDateFormat sdf = (SimpleDateFormat)o;
  615. if (defaultCentury != sdf.defaultCentury)
  616. return false;
  617. if (!toPattern().equals(sdf.toPattern()))
  618. return false;
  619. if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
  620. return false;
  621. return true;
  622. }
  623. /**
  624. * This method returns a hash value for this object.
  625. *
  626. * @return A hash value for this object.
  627. */
  628. public int hashCode()
  629. {
  630. return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^
  631. getDateFormatSymbols().hashCode();
  632. }
  633. /**
  634. * Formats the date input according to the format string in use,
  635. * appending to the specified StringBuffer. The input StringBuffer
  636. * is returned as output for convenience.
  637. */
  638. private void formatWithAttribute(Date date, FormatBuffer buffer, FieldPosition pos)
  639. {
  640. String temp;
  641. calendar.setTime(date);
  642. // go through vector, filling in fields where applicable, else toString
  643. Iterator<Object> iter = tokens.iterator();
  644. while (iter.hasNext())
  645. {
  646. Object o = iter.next();
  647. if (o instanceof CompiledField)
  648. {
  649. CompiledField cf = (CompiledField) o;
  650. int beginIndex = buffer.length();
  651. switch (cf.getField())
  652. {
  653. case ERA_FIELD:
  654. buffer.append (formatData.eras[calendar.get (Calendar.ERA)], DateFormat.Field.ERA);
  655. break;
  656. case YEAR_FIELD:
  657. // If we have two digits, then we truncate. Otherwise, we
  658. // use the size of the pattern, and zero pad.
  659. buffer.setDefaultAttribute (DateFormat.Field.YEAR);
  660. if (cf.getSize() == 2)
  661. {
  662. temp = "00"+String.valueOf (calendar.get (Calendar.YEAR));
  663. buffer.append (temp.substring (temp.length() - 2));
  664. }
  665. else
  666. withLeadingZeros (calendar.get (Calendar.YEAR), cf.getSize(), buffer);
  667. break;
  668. case MONTH_FIELD:
  669. buffer.setDefaultAttribute (DateFormat.Field.MONTH);
  670. if (cf.getSize() < 3)
  671. withLeadingZeros (calendar.get (Calendar.MONTH) + 1, cf.getSize(), buffer);
  672. else if (cf.getSize() < 4)
  673. buffer.append (formatData.shortMonths[calendar.get (Calendar.MONTH)]);
  674. else
  675. buffer.append (formatData.months[calendar.get (Calendar.MONTH)]);
  676. break;
  677. case DATE_FIELD:
  678. buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_MONTH);
  679. withLeadingZeros (calendar.get (Calendar.DATE), cf.getSize(), buffer);
  680. break;
  681. case HOUR_OF_DAY1_FIELD: // 1-24
  682. buffer.setDefaultAttribute(DateFormat.Field.HOUR_OF_DAY1);
  683. withLeadingZeros ( ((calendar.get (Calendar.HOUR_OF_DAY) + 23) % 24) + 1,
  684. cf.getSize(), buffer);
  685. break;
  686. case HOUR_OF_DAY0_FIELD: // 0-23
  687. buffer.setDefaultAttribute (DateFormat.Field.HOUR_OF_DAY0);
  688. withLeadingZeros (calendar.get (Calendar.HOUR_OF_DAY), cf.getSize(), buffer);
  689. break;
  690. case MINUTE_FIELD:
  691. buffer.setDefaultAttribute (DateFormat.Field.MINUTE);
  692. withLeadingZeros (calendar.get (Calendar.MINUTE),
  693. cf.getSize(), buffer);
  694. break;
  695. case SECOND_FIELD:
  696. buffer.setDefaultAttribute (DateFormat.Field.SECOND);
  697. withLeadingZeros(calendar.get (Calendar.SECOND),
  698. cf.getSize(), buffer);
  699. break;
  700. case MILLISECOND_FIELD:
  701. buffer.setDefaultAttribute (DateFormat.Field.MILLISECOND);
  702. withLeadingZeros (calendar.get (Calendar.MILLISECOND), cf.getSize(), buffer);
  703. break;
  704. case DAY_OF_WEEK_FIELD:
  705. buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK);
  706. if (cf.getSize() < 4)
  707. buffer.append (formatData.shortWeekdays[calendar.get (Calendar.DAY_OF_WEEK)]);
  708. else
  709. buffer.append (formatData.weekdays[calendar.get (Calendar.DAY_OF_WEEK)]);
  710. break;
  711. case DAY_OF_YEAR_FIELD:
  712. buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_YEAR);
  713. withLeadingZeros (calendar.get (Calendar.DAY_OF_YEAR), cf.getSize(), buffer);
  714. break;
  715. case DAY_OF_WEEK_IN_MONTH_FIELD:
  716. buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK_IN_MONTH);
  717. withLeadingZeros (calendar.get (Calendar.DAY_OF_WEEK_IN_MONTH),
  718. cf.getSize(), buffer);
  719. break;
  720. case WEEK_OF_YEAR_FIELD:
  721. buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_YEAR);
  722. withLeadingZeros (calendar.get (Calendar.WEEK_OF_YEAR),
  723. cf.getSize(), buffer);
  724. break;
  725. case WEEK_OF_MONTH_FIELD:
  726. buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_MONTH);
  727. withLeadingZeros (calendar.get (Calendar.WEEK_OF_MONTH),
  728. cf.getSize(), buffer);
  729. break;
  730. case AM_PM_FIELD:
  731. buffer.setDefaultAttribute (DateFormat.Field.AM_PM);
  732. buffer.append (formatData.ampms[calendar.get (Calendar.AM_PM)]);
  733. break;
  734. case HOUR1_FIELD: // 1-12
  735. buffer.setDefaultAttribute (DateFormat.Field.HOUR1);
  736. withLeadingZeros (((calendar.get (Calendar.HOUR) + 11) % 12) + 1,
  737. cf.getSize(), buffer);
  738. break;
  739. case HOUR0_FIELD: // 0-11
  740. buffer.setDefaultAttribute (DateFormat.Field.HOUR0);
  741. withLeadingZeros (calendar.get (Calendar.HOUR), cf.getSize(), buffer);
  742. break;
  743. case TIMEZONE_FIELD:
  744. buffer.setDefaultAttribute (DateFormat.Field.TIME_ZONE);
  745. TimeZone zone = calendar.getTimeZone();
  746. boolean isDST = calendar.get (Calendar.DST_OFFSET) != 0;
  747. // FIXME: XXX: This should be a localized time zone.
  748. String zoneID = zone.getDisplayName
  749. (isDST, cf.getSize() > 3 ? TimeZone.LONG : TimeZone.SHORT);
  750. buffer.append (zoneID);
  751. break;
  752. case RFC822_TIMEZONE_FIELD:
  753. buffer.setDefaultAttribute(DateFormat.Field.TIME_ZONE);
  754. int pureMinutes = (calendar.get(Calendar.ZONE_OFFSET) +
  755. calendar.get(Calendar.DST_OFFSET)) / (1000 * 60);
  756. String sign = (pureMinutes < 0) ? "-" : "+";
  757. pureMinutes = Math.abs(pureMinutes);
  758. int hours = pureMinutes / 60;
  759. int minutes = pureMinutes % 60;
  760. buffer.append(sign);
  761. withLeadingZeros(hours, 2, buffer);
  762. withLeadingZeros(minutes, 2, buffer);
  763. break;
  764. default:
  765. throw new IllegalArgumentException ("Illegal pattern character " +
  766. cf.getCharacter());
  767. }
  768. if (pos != null && (buffer.getDefaultAttribute() == pos.getFieldAttribute()
  769. || cf.getField() == pos.getField()))
  770. {
  771. pos.setBeginIndex(beginIndex);
  772. pos.setEndIndex(buffer.length());
  773. }
  774. }
  775. else
  776. {
  777. buffer.append(o.toString(), null);
  778. }
  779. }
  780. }
  781. public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
  782. {
  783. formatWithAttribute(date, new StringFormatBuffer (buffer), pos);
  784. return buffer;
  785. }
  786. public AttributedCharacterIterator formatToCharacterIterator(Object date)
  787. throws IllegalArgumentException
  788. {
  789. if (date == null)
  790. throw new NullPointerException("null argument");
  791. if (!(date instanceof Date))
  792. throw new IllegalArgumentException("argument should be an instance of java.util.Date");
  793. AttributedFormatBuffer buf = new AttributedFormatBuffer();
  794. formatWithAttribute((Date)date, buf,
  795. null);
  796. buf.sync();
  797. return new FormatCharacterIterator(buf.getBuffer().toString(),
  798. buf.getRanges(),
  799. buf.getAttributes());
  800. }
  801. private void withLeadingZeros(int value, int length, FormatBuffer buffer)
  802. {
  803. String valStr = String.valueOf(value);
  804. for (length -= valStr.length(); length > 0; length--)
  805. buffer.append('0');
  806. buffer.append(valStr);
  807. }
  808. private boolean expect(String source, ParsePosition pos, char ch)
  809. {
  810. int x = pos.getIndex();
  811. boolean r = x < source.length() && source.charAt(x) == ch;
  812. if (r)
  813. pos.setIndex(x + 1);
  814. else
  815. pos.setErrorIndex(x);
  816. return r;
  817. }
  818. /**
  819. * This method parses the specified string into a date.
  820. *
  821. * @param dateStr The date string to parse.
  822. * @param pos The input and output parse position
  823. *
  824. * @return The parsed date, or <code>null</code> if the string cannot be
  825. * parsed.
  826. */
  827. public Date parse (String dateStr, ParsePosition pos)
  828. {
  829. int fmt_index = 0;
  830. int fmt_max = pattern.length();
  831. calendar.clear();
  832. boolean saw_timezone = false;
  833. int quote_start = -1;
  834. boolean is2DigitYear = false;
  835. try
  836. {
  837. for (; fmt_index < fmt_max; ++fmt_index)
  838. {
  839. char ch = pattern.charAt(fmt_index);
  840. if (ch == '\'')
  841. {
  842. if (fmt_index < fmt_max - 1
  843. && pattern.charAt(fmt_index + 1) == '\'')
  844. {
  845. if (! expect (dateStr, pos, ch))
  846. return null;
  847. ++fmt_index;
  848. }
  849. else
  850. quote_start = quote_start < 0 ? fmt_index : -1;
  851. continue;
  852. }
  853. if (quote_start != -1
  854. || ((ch < 'a' || ch > 'z')
  855. && (ch < 'A' || ch > 'Z')))
  856. {
  857. if (quote_start == -1 && ch == ' ')
  858. {
  859. // A single unquoted space in the pattern may match
  860. // any number of spaces in the input.
  861. int index = pos.getIndex();
  862. int save = index;
  863. while (index < dateStr.length()
  864. && Character.isWhitespace(dateStr.charAt(index)))
  865. ++index;
  866. if (index > save)
  867. pos.setIndex(index);
  868. else
  869. {
  870. // Didn't see any whitespace.
  871. pos.setErrorIndex(index);
  872. return null;
  873. }
  874. }
  875. else if (! expect (dateStr, pos, ch))
  876. return null;
  877. continue;
  878. }
  879. // We've arrived at a potential pattern character in the
  880. // pattern.
  881. int fmt_count = 1;
  882. while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
  883. {
  884. ++fmt_count;
  885. }
  886. // We might need to limit the number of digits to parse in
  887. // some cases. We look to the next pattern character to
  888. // decide.
  889. boolean limit_digits = false;
  890. if (fmt_index < fmt_max
  891. && standardChars.indexOf(pattern.charAt(fmt_index)) >= 0)
  892. limit_digits = true;
  893. --fmt_index;
  894. // We can handle most fields automatically: most either are
  895. // numeric or are looked up in a string vector. In some cases
  896. // we need an offset. When numeric, `offset' is added to the
  897. // resulting value. When doing a string lookup, offset is the
  898. // initial index into the string array.
  899. int calendar_field;
  900. boolean is_numeric = true;
  901. int offset = 0;
  902. boolean maybe2DigitYear = false;
  903. boolean oneBasedHour = false;
  904. boolean oneBasedHourOfDay = false;
  905. Integer simpleOffset;
  906. String[] set1 = null;
  907. String[] set2 = null;
  908. switch (ch)
  909. {
  910. case 'd':
  911. calendar_field = Calendar.DATE;
  912. break;
  913. case 'D':
  914. calendar_field = Calendar.DAY_OF_YEAR;
  915. break;
  916. case 'F':
  917. calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
  918. break;
  919. case 'E':
  920. is_numeric = false;
  921. offset = 1;
  922. calendar_field = Calendar.DAY_OF_WEEK;
  923. set1 = formatData.getWeekdays();
  924. set2 = formatData.getShortWeekdays();
  925. break;
  926. case 'w':
  927. calendar_field = Calendar.WEEK_OF_YEAR;
  928. break;
  929. case 'W':
  930. calendar_field = Calendar.WEEK_OF_MONTH;
  931. break;
  932. case 'M':
  933. calendar_field = Calendar.MONTH;
  934. if (fmt_count <= 2)
  935. offset = -1;
  936. else
  937. {
  938. is_numeric = false;
  939. set1 = formatData.getMonths();
  940. set2 = formatData.getShortMonths();
  941. }
  942. break;
  943. case 'y':
  944. calendar_field = Calendar.YEAR;
  945. if (fmt_count <= 2)
  946. maybe2DigitYear = true;
  947. break;
  948. case 'K':
  949. calendar_field = Calendar.HOUR;
  950. break;
  951. case 'h':
  952. calendar_field = Calendar.HOUR;
  953. oneBasedHour = true;
  954. break;
  955. case 'H':
  956. calendar_field = Calendar.HOUR_OF_DAY;
  957. break;
  958. case 'k':
  959. calendar_field = Calendar.HOUR_OF_DAY;
  960. oneBasedHourOfDay = true;
  961. break;
  962. case 'm':
  963. calendar_field = Calendar.MINUTE;
  964. break;
  965. case 's':
  966. calendar_field = Calendar.SECOND;
  967. break;
  968. case 'S':
  969. calendar_field = Calendar.MILLISECOND;
  970. break;
  971. case 'a':
  972. is_numeric = false;
  973. calendar_field = Calendar.AM_PM;
  974. set1 = formatData.getAmPmStrings();
  975. break;
  976. case 'z':
  977. case 'Z':
  978. // We need a special case for the timezone, because it
  979. // uses a different data structure than the other cases.
  980. is_numeric = false;
  981. calendar_field = Calendar.ZONE_OFFSET;
  982. String[][] zoneStrings = formatData.getZoneStrings();
  983. int zoneCount = zoneStrings.length;
  984. int index = pos.getIndex();
  985. boolean found_zone = false;
  986. simpleOffset = computeOffset(dateStr.substring(index), pos);
  987. if (simpleOffset != null)
  988. {
  989. found_zone = true;
  990. saw_timezone = true;
  991. calendar.set(Calendar.DST_OFFSET, 0);
  992. offset = simpleOffset.intValue();
  993. }
  994. else
  995. {
  996. for (int j = 0; j < zoneCount; j++)
  997. {
  998. String[] strings = zoneStrings[j];
  999. int k;
  1000. for (k = 0; k < strings.length; ++k)
  1001. {
  1002. if (dateStr.startsWith(strings[k], index))
  1003. break;
  1004. }
  1005. if (k != strings.length)
  1006. {
  1007. found_zone = true;
  1008. saw_timezone = true;
  1009. TimeZone tz = TimeZone.getTimeZone (strings[0]);
  1010. // Check if it's a DST zone or ordinary
  1011. if(k == 3 || k == 4)
  1012. calendar.set (Calendar.DST_OFFSET, tz.getDSTSavings());
  1013. else
  1014. calendar.set (Calendar.DST_OFFSET, 0);
  1015. offset = tz.getRawOffset ();
  1016. pos.setIndex(index + strings[k].length());
  1017. break;
  1018. }
  1019. }
  1020. }
  1021. if (! found_zone)
  1022. {
  1023. pos.setErrorIndex(pos.getIndex());
  1024. return null;
  1025. }
  1026. break;
  1027. default:
  1028. pos.setErrorIndex(pos.getIndex());
  1029. return null;
  1030. }
  1031. // Compute the value we should assign to the field.
  1032. int value;
  1033. int index = -1;
  1034. if (is_numeric)
  1035. {
  1036. numberFormat.setMinimumIntegerDigits(fmt_count);
  1037. if (maybe2DigitYear)
  1038. index = pos.getIndex();
  1039. Number n = null;
  1040. if (limit_digits)
  1041. {
  1042. // numberFormat.setMaximumIntegerDigits(fmt_count) may
  1043. // not work as expected. So we explicitly use substring
  1044. // of dateStr.
  1045. int origPos = pos.getIndex();
  1046. pos.setIndex(0);
  1047. n = numberFormat.parse(dateStr.substring(origPos, origPos + fmt_count), pos);
  1048. pos.setIndex(origPos + pos.getIndex());
  1049. }
  1050. else
  1051. n = numberFormat.parse(dateStr, pos);
  1052. if (pos == null || ! (n instanceof Long))
  1053. return null;
  1054. value = n.intValue() + offset;
  1055. }
  1056. else if (set1 != null)
  1057. {
  1058. index = pos.getIndex();
  1059. int i;
  1060. boolean found = false;
  1061. for (i = offset; i < set1.length; ++i)
  1062. {
  1063. if (set1[i] != null)
  1064. if (dateStr.toUpperCase().startsWith(set1[i].toUpperCase(),
  1065. index))
  1066. {
  1067. found = true;
  1068. pos.setIndex(index + set1[i].length());
  1069. break;
  1070. }
  1071. }
  1072. if (!found && set2 != null)
  1073. {
  1074. for (i = offset; i < set2.length; ++i)
  1075. {
  1076. if (set2[i] != null)
  1077. if (dateStr.toUpperCase().startsWith(set2[i].toUpperCase(),
  1078. index))
  1079. {
  1080. found = true;
  1081. pos.setIndex(index + set2[i].length());
  1082. break;
  1083. }
  1084. }
  1085. }
  1086. if (!found)
  1087. {
  1088. pos.setErrorIndex(index);
  1089. return null;
  1090. }
  1091. value = i;
  1092. }
  1093. else
  1094. value = offset;
  1095. if (maybe2DigitYear)
  1096. {
  1097. // Parse into default century if the numeric year string has
  1098. // exactly 2 digits.
  1099. int digit_count = pos.getIndex() - index;
  1100. if (digit_count == 2)
  1101. {
  1102. is2DigitYear = true;
  1103. value += defaultCentury;
  1104. }
  1105. }
  1106. // Calendar uses 0-based hours.
  1107. // I.e. 00:00 AM is midnight, not 12 AM or 24:00
  1108. if (oneBasedHour && value == 12)
  1109. value = 0;
  1110. if (oneBasedHourOfDay && value == 24)
  1111. value = 0;
  1112. // Assign the value and move on.
  1113. calendar.set(calendar_field, value);
  1114. }
  1115. if (is2DigitYear)
  1116. {
  1117. // Apply the 80-20 heuristic to dermine the full year based on
  1118. // defaultCenturyStart.
  1119. int year = calendar.get(Calendar.YEAR);
  1120. if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
  1121. calendar.set(Calendar.YEAR, year + 100);
  1122. }
  1123. if (! saw_timezone)
  1124. {
  1125. // Use the real rules to determine whether or not this
  1126. // particular time is in daylight savings.
  1127. calendar.clear (Calendar.DST_OFFSET);
  1128. calendar.clear (Calendar.ZONE_OFFSET);
  1129. }
  1130. return calendar.getTime();
  1131. }
  1132. catch (IllegalArgumentException x)
  1133. {
  1134. pos.setErrorIndex(pos.getIndex());
  1135. return null;
  1136. }
  1137. }
  1138. /**
  1139. * <p>
  1140. * Computes the time zone offset in milliseconds
  1141. * relative to GMT, based on the supplied
  1142. * <code>String</code> representation.
  1143. * </p>
  1144. * <p>
  1145. * The supplied <code>String</code> must be a three
  1146. * or four digit signed number, with an optional 'GMT'
  1147. * prefix. The first one or two digits represents the hours,
  1148. * while the last two represent the minutes. The
  1149. * two sets of digits can optionally be separated by
  1150. * ':'. The mandatory sign prefix (either '+' or '-')
  1151. * indicates the direction of the offset from GMT.
  1152. * </p>
  1153. * <p>
  1154. * For example, 'GMT+0200' specifies 2 hours after
  1155. * GMT, while '-05:00' specifies 5 hours prior to
  1156. * GMT. The special case of 'GMT' alone can be used
  1157. * to represent the offset, 0.
  1158. * </p>
  1159. * <p>
  1160. * If the <code>String</code> can not be parsed,
  1161. * the result will be null. The resulting offset
  1162. * is wrapped in an <code>Integer</code> object, in
  1163. * order to allow such failure to be represented.
  1164. * </p>
  1165. *
  1166. * @param zoneString a string in the form
  1167. * (GMT)? sign hours : minutes
  1168. * where sign = '+' or '-', hours
  1169. * is a one or two digits representing
  1170. * a number between 0 and 23, and
  1171. * minutes is two digits representing
  1172. * a number between 0 and 59.
  1173. * @return the parsed offset, or null if parsing
  1174. * failed.
  1175. */
  1176. private Integer computeOffset(String zoneString, ParsePosition pos)
  1177. {
  1178. Pattern pattern =
  1179. Pattern.compile("(GMT)?([+-])([012])?([0-9]):?([0-9]{2})");
  1180. Matcher matcher = pattern.matcher(zoneString);
  1181. // Match from start, but ignore trailing parts
  1182. boolean hasAll = matcher.lookingAt();
  1183. try
  1184. {
  1185. // Do we have at least the sign, hour and minute?
  1186. matcher.group(2);
  1187. matcher.group(4);
  1188. matcher.group(5);
  1189. }
  1190. catch (IllegalStateException ise)
  1191. {
  1192. hasAll = false;
  1193. }
  1194. if (hasAll)
  1195. {
  1196. int sign = matcher.group(2).equals("+") ? 1 : -1;
  1197. int hour = Integer.parseInt(matcher.group(4));
  1198. if (!matcher.group(3).equals(""))
  1199. hour += (Integer.parseInt(matcher.group(3)) * 10);
  1200. int minutes = Integer.parseInt(matcher.group(5));
  1201. if (hour > 23)
  1202. return null;
  1203. int offset = sign * ((hour * 60) + minutes) * 60000;
  1204. // advance the index
  1205. pos.setIndex(pos.getIndex() + matcher.end());
  1206. return Integer.valueOf(offset);
  1207. }
  1208. else if (zoneString.startsWith("GMT"))
  1209. {
  1210. pos.setIndex(pos.getIndex() + 3);
  1211. return Integer.valueOf(0);
  1212. }
  1213. return null;
  1214. }
  1215. // Compute the start of the current century as defined by
  1216. // get2DigitYearStart.
  1217. private void computeCenturyStart()
  1218. {
  1219. int year = calendar.get(Calendar.YEAR);
  1220. calendar.set(Calendar.YEAR, year - 80);
  1221. set2DigitYearStart(calendar.getTime());
  1222. }
  1223. /**
  1224. * Returns a copy of this instance of
  1225. * <code>SimpleDateFormat</code>. The copy contains
  1226. * clones of the formatting symbols and the 2-digit
  1227. * year century start date.
  1228. */
  1229. public Object clone()
  1230. {
  1231. SimpleDateFormat clone = (SimpleDateFormat) super.clone();
  1232. clone.setDateFormatSymbols((DateFormatSymbols) formatData.clone());
  1233. clone.set2DigitYearStart((Date) defaultCenturyStart.clone());
  1234. return clone;
  1235. }
  1236. }