Mercurial > hg > blitz_condensed
comparison src/com/go/trove/util/FastDateFormat.java @ 0:3dc0c5604566
Initial checkin of blitz 2.0 fcs - no installer yet.
author | Dan Creswell <dan.creswell@gmail.com> |
---|---|
date | Sat, 21 Mar 2009 11:00:06 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3dc0c5604566 |
---|---|
1 /* ==================================================================== | |
2 * Trove - Copyright (c) 1997-2001 Walt Disney Internet Group | |
3 * ==================================================================== | |
4 * The Tea Software License, Version 1.1 | |
5 * | |
6 * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved. | |
7 * | |
8 * Redistribution and use in source and binary forms, with or without | |
9 * modification, are permitted provided that the following conditions | |
10 * are met: | |
11 * | |
12 * 1. Redistributions of source code must retain the above copyright | |
13 * notice, this list of conditions and the following disclaimer. | |
14 * | |
15 * 2. Redistributions in binary form must reproduce the above copyright | |
16 * notice, this list of conditions and the following disclaimer in | |
17 * the documentation and/or other materials provided with the | |
18 * distribution. | |
19 * | |
20 * 3. The end-user documentation included with the redistribution, | |
21 * if any, must include the following acknowledgment: | |
22 * "This product includes software developed by the | |
23 * Walt Disney Internet Group (http://opensource.go.com/)." | |
24 * Alternately, this acknowledgment may appear in the software itself, | |
25 * if and wherever such third-party acknowledgments normally appear. | |
26 * | |
27 * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must | |
28 * not be used to endorse or promote products derived from this | |
29 * software without prior written permission. For written | |
30 * permission, please contact opensource@dig.com. | |
31 * | |
32 * 5. Products derived from this software may not be called "Tea", | |
33 * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet", | |
34 * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior | |
35 * written permission of the Walt Disney Internet Group. | |
36 * | |
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED | |
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
40 * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS | |
41 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
42 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
43 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
44 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
45 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
47 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
48 * ==================================================================== | |
49 * | |
50 * For more information about Tea, please see http://opensource.go.com/. | |
51 */ | |
52 | |
53 package com.go.trove.util; | |
54 | |
55 import java.util.Date; | |
56 import java.util.Calendar; | |
57 import java.util.GregorianCalendar; | |
58 import java.util.Locale; | |
59 import java.util.TimeZone; | |
60 import java.util.List; | |
61 import java.util.ArrayList; | |
62 import java.util.Map; | |
63 import java.util.HashMap; | |
64 import java.text.DateFormatSymbols; | |
65 import java.text.DateFormat; | |
66 import java.text.SimpleDateFormat; | |
67 | |
68 /****************************************************************************** | |
69 * Similar to {@link java.text.SimpleDateFormat}, but faster and thread-safe. | |
70 * Only formatting is supported, but all patterns are compatible with | |
71 * SimpleDateFormat. | |
72 * | |
73 * @author Brian S O'Neill | |
74 * @version | |
75 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 01/07/03 <!-- $--> | |
76 */ | |
77 public class FastDateFormat { | |
78 /** Style pattern */ | |
79 public static final Object | |
80 FULL = new Integer(SimpleDateFormat.FULL), | |
81 LONG = new Integer(SimpleDateFormat.LONG), | |
82 MEDIUM = new Integer(SimpleDateFormat.MEDIUM), | |
83 SHORT = new Integer(SimpleDateFormat.SHORT); | |
84 | |
85 private static final double LOG_10 = Math.log(10); | |
86 | |
87 private static String cDefaultPattern; | |
88 private static TimeZone cDefaultTimeZone = TimeZone.getDefault(); | |
89 | |
90 private static Map cTimeZoneDisplayCache = new HashMap(); | |
91 | |
92 private static Map cInstanceCache = new HashMap(7); | |
93 private static Map cDateInstanceCache = new HashMap(7); | |
94 private static Map cTimeInstanceCache = new HashMap(7); | |
95 private static Map cDateTimeInstanceCache = new HashMap(7); | |
96 | |
97 public static FastDateFormat getInstance() { | |
98 return getInstance(getDefaultPattern(), null, null, null); | |
99 } | |
100 | |
101 /** | |
102 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
103 */ | |
104 public static FastDateFormat getInstance(String pattern) | |
105 throws IllegalArgumentException | |
106 { | |
107 return getInstance(pattern, null, null, null); | |
108 } | |
109 | |
110 /** | |
111 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
112 * @param timeZone optional time zone, overrides time zone of formatted | |
113 * date | |
114 */ | |
115 public static FastDateFormat getInstance | |
116 (String pattern, TimeZone timeZone) throws IllegalArgumentException | |
117 { | |
118 return getInstance(pattern, timeZone, null, null); | |
119 } | |
120 | |
121 /** | |
122 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
123 * @param locale optional locale, overrides system locale | |
124 */ | |
125 public static FastDateFormat getInstance | |
126 (String pattern, Locale locale) throws IllegalArgumentException | |
127 { | |
128 return getInstance(pattern, null, locale, null); | |
129 } | |
130 | |
131 /** | |
132 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
133 * @param symbols optional date format symbols, overrides symbols for | |
134 * system locale | |
135 */ | |
136 public static FastDateFormat getInstance | |
137 (String pattern, DateFormatSymbols symbols) | |
138 throws IllegalArgumentException | |
139 { | |
140 return getInstance(pattern, null, null, symbols); | |
141 } | |
142 | |
143 /** | |
144 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
145 * @param timeZone optional time zone, overrides time zone of formatted | |
146 * date | |
147 * @param locale optional locale, overrides system locale | |
148 */ | |
149 public static FastDateFormat getInstance | |
150 (String pattern, TimeZone timeZone, Locale locale) | |
151 throws IllegalArgumentException | |
152 { | |
153 return getInstance(pattern, timeZone, locale, null); | |
154 } | |
155 | |
156 /** | |
157 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
158 * @param timeZone optional time zone, overrides time zone of formatted | |
159 * date | |
160 * @param locale optional locale, overrides system locale | |
161 * @param symbols optional date format symbols, overrides symbols for | |
162 * provided locale | |
163 */ | |
164 public static synchronized FastDateFormat getInstance | |
165 (String pattern, TimeZone timeZone, Locale locale, | |
166 DateFormatSymbols symbols) | |
167 throws IllegalArgumentException | |
168 { | |
169 Object key = pattern; | |
170 | |
171 if (timeZone != null) { | |
172 key = new Pair(key, timeZone); | |
173 } | |
174 if (locale != null) { | |
175 key = new Pair(key, locale); | |
176 } | |
177 if (symbols != null) { | |
178 key = new Pair(key, symbols); | |
179 } | |
180 | |
181 FastDateFormat format = (FastDateFormat)cInstanceCache.get(key); | |
182 if (format == null) { | |
183 if (locale == null) { | |
184 locale = Locale.getDefault(); | |
185 } | |
186 if (symbols == null) { | |
187 symbols = new DateFormatSymbols(locale); | |
188 } | |
189 format = new FastDateFormat(pattern, timeZone, locale, symbols); | |
190 cInstanceCache.put(key, format); | |
191 } | |
192 return format; | |
193 } | |
194 | |
195 /** | |
196 * @param style date style: FULL, LONG, MEDIUM, or SHORT | |
197 * @param timeZone optional time zone, overrides time zone of formatted | |
198 * date | |
199 * @param locale optional locale, overrides system locale | |
200 */ | |
201 public static synchronized FastDateFormat getDateInstance | |
202 (Object style, TimeZone timeZone, Locale locale) | |
203 throws IllegalArgumentException | |
204 { | |
205 Object key = style; | |
206 | |
207 if (timeZone != null) { | |
208 key = new Pair(key, timeZone); | |
209 } | |
210 if (locale == null) { | |
211 key = new Pair(key, locale); | |
212 } | |
213 | |
214 FastDateFormat format = (FastDateFormat)cDateInstanceCache.get(key); | |
215 | |
216 if (format == null) { | |
217 int ds; | |
218 try { | |
219 ds = ((Integer)style).intValue(); | |
220 } | |
221 catch (ClassCastException e) { | |
222 throw new IllegalArgumentException | |
223 ("Illegal date style: " + style); | |
224 } | |
225 | |
226 if (locale == null) { | |
227 locale = Locale.getDefault(); | |
228 } | |
229 | |
230 try { | |
231 String pattern = ((SimpleDateFormat)DateFormat.getDateInstance(ds, locale)).toPattern(); | |
232 format = getInstance(pattern, timeZone, locale); | |
233 cDateInstanceCache.put(key, format); | |
234 } | |
235 catch (ClassCastException e) { | |
236 throw new IllegalArgumentException | |
237 ("No date pattern for locale: " + locale); | |
238 } | |
239 } | |
240 | |
241 return format; | |
242 } | |
243 | |
244 /** | |
245 * @param style time style: FULL, LONG, MEDIUM, or SHORT | |
246 * @param timeZone optional time zone, overrides time zone of formatted | |
247 * date | |
248 * @param locale optional locale, overrides system locale | |
249 */ | |
250 public static synchronized FastDateFormat getTimeInstance | |
251 (Object style, TimeZone timeZone, Locale locale) | |
252 throws IllegalArgumentException | |
253 { | |
254 Object key = style; | |
255 | |
256 if (timeZone != null) { | |
257 key = new Pair(key, timeZone); | |
258 } | |
259 if (locale != null) { | |
260 key = new Pair(key, locale); | |
261 } | |
262 | |
263 FastDateFormat format = (FastDateFormat)cTimeInstanceCache.get(key); | |
264 | |
265 if (format == null) { | |
266 int ts; | |
267 try { | |
268 ts = ((Integer)style).intValue(); | |
269 } | |
270 catch (ClassCastException e) { | |
271 throw new IllegalArgumentException | |
272 ("Illegal time style: " + style); | |
273 } | |
274 | |
275 if (locale == null) { | |
276 locale = Locale.getDefault(); | |
277 } | |
278 | |
279 try { | |
280 String pattern = ((SimpleDateFormat)DateFormat.getTimeInstance(ts, locale)).toPattern(); | |
281 format = getInstance(pattern, timeZone, locale); | |
282 cTimeInstanceCache.put(key, format); | |
283 } | |
284 catch (ClassCastException e) { | |
285 throw new IllegalArgumentException | |
286 ("No date pattern for locale: " + locale); | |
287 } | |
288 } | |
289 | |
290 return format; | |
291 } | |
292 | |
293 /** | |
294 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT | |
295 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT | |
296 * @param timeZone optional time zone, overrides time zone of formatted | |
297 * date | |
298 * @param locale optional locale, overrides system locale | |
299 */ | |
300 public static synchronized FastDateFormat getDateTimeInstance | |
301 (Object dateStyle, Object timeStyle, TimeZone timeZone, Locale locale) | |
302 throws IllegalArgumentException | |
303 { | |
304 Object key = new Pair(dateStyle, timeStyle); | |
305 | |
306 if (timeZone != null) { | |
307 key = new Pair(key, timeZone); | |
308 } | |
309 if (locale != null) { | |
310 key = new Pair(key, locale); | |
311 } | |
312 | |
313 FastDateFormat format = | |
314 (FastDateFormat)cDateTimeInstanceCache.get(key); | |
315 | |
316 if (format == null) { | |
317 int ds; | |
318 try { | |
319 ds = ((Integer)dateStyle).intValue(); | |
320 } | |
321 catch (ClassCastException e) { | |
322 throw new IllegalArgumentException | |
323 ("Illegal date style: " + dateStyle); | |
324 } | |
325 | |
326 int ts; | |
327 try { | |
328 ts = ((Integer)timeStyle).intValue(); | |
329 } | |
330 catch (ClassCastException e) { | |
331 throw new IllegalArgumentException | |
332 ("Illegal time style: " + timeStyle); | |
333 } | |
334 | |
335 if (locale == null) { | |
336 locale = Locale.getDefault(); | |
337 } | |
338 | |
339 try { | |
340 String pattern = ((SimpleDateFormat)DateFormat.getDateTimeInstance(ds, ts, locale)).toPattern(); | |
341 format = getInstance(pattern, timeZone, locale); | |
342 cDateTimeInstanceCache.put(key, format); | |
343 } | |
344 catch (ClassCastException e) { | |
345 throw new IllegalArgumentException | |
346 ("No date time pattern for locale: " + locale); | |
347 } | |
348 } | |
349 | |
350 return format; | |
351 } | |
352 | |
353 static synchronized String getTimeZoneDisplay(TimeZone tz, | |
354 boolean daylight, | |
355 int style, | |
356 Locale locale) { | |
357 Object key = new TimeZoneDisplayKey(tz, daylight, style, locale); | |
358 String value = (String)cTimeZoneDisplayCache.get(key); | |
359 if (value == null) { | |
360 // This is a very slow call, so cache the results. | |
361 value = tz.getDisplayName(daylight, style, locale); | |
362 cTimeZoneDisplayCache.put(key, value); | |
363 } | |
364 return value; | |
365 } | |
366 | |
367 private static synchronized String getDefaultPattern() { | |
368 if (cDefaultPattern == null) { | |
369 cDefaultPattern = new SimpleDateFormat().toPattern(); | |
370 } | |
371 return cDefaultPattern; | |
372 } | |
373 | |
374 /** | |
375 * Returns a list of Rules. | |
376 */ | |
377 private static List parse(String pattern, TimeZone timeZone, Locale locale, | |
378 DateFormatSymbols symbols) { | |
379 List rules = new ArrayList(); | |
380 | |
381 String[] ERAs = symbols.getEras(); | |
382 String[] months = symbols.getMonths(); | |
383 String[] shortMonths = symbols.getShortMonths(); | |
384 String[] weekdays = symbols.getWeekdays(); | |
385 String[] shortWeekdays = symbols.getShortWeekdays(); | |
386 String[] AmPmStrings = symbols.getAmPmStrings(); | |
387 | |
388 int length = pattern.length(); | |
389 int[] indexRef = new int[1]; | |
390 | |
391 for (int i=0; i<length; i++) { | |
392 indexRef[0] = i; | |
393 String token = parseToken(pattern, indexRef); | |
394 i = indexRef[0]; | |
395 | |
396 int tokenLen = token.length(); | |
397 if (tokenLen == 0) { | |
398 break; | |
399 } | |
400 | |
401 Rule rule; | |
402 char c = token.charAt(0); | |
403 | |
404 switch (c) { | |
405 case 'G': // era designator (text) | |
406 rule = new TextField(Calendar.ERA, ERAs); | |
407 break; | |
408 case 'y': // year (number) | |
409 if (tokenLen >= 4) { | |
410 rule = new UnpaddedNumberField(Calendar.YEAR); | |
411 } | |
412 else { | |
413 rule = new TwoDigitYearField(); | |
414 } | |
415 break; | |
416 case 'M': // month in year (text and number) | |
417 if (tokenLen >= 4) { | |
418 rule = new TextField(Calendar.MONTH, months); | |
419 } | |
420 else if (tokenLen == 3) { | |
421 rule = new TextField(Calendar.MONTH, shortMonths); | |
422 } | |
423 else if (tokenLen == 2) { | |
424 rule = new TwoDigitMonthField(); | |
425 } | |
426 else { | |
427 rule = new UnpaddedMonthField(); | |
428 } | |
429 break; | |
430 case 'd': // day in month (number) | |
431 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen); | |
432 break; | |
433 case 'h': // hour in am/pm (number, 1..12) | |
434 rule = new TwelveHourField | |
435 (selectNumberRule(Calendar.HOUR, tokenLen)); | |
436 break; | |
437 case 'H': // hour in day (number, 0..23) | |
438 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen); | |
439 break; | |
440 case 'm': // minute in hour (number) | |
441 rule = selectNumberRule(Calendar.MINUTE, tokenLen); | |
442 break; | |
443 case 's': // second in minute (number) | |
444 rule = selectNumberRule(Calendar.SECOND, tokenLen); | |
445 break; | |
446 case 'S': // millisecond (number) | |
447 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen); | |
448 break; | |
449 case 'E': // day in week (text) | |
450 rule = new TextField | |
451 (Calendar.DAY_OF_WEEK, | |
452 tokenLen < 4 ? shortWeekdays : weekdays); | |
453 break; | |
454 case 'D': // day in year (number) | |
455 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen); | |
456 break; | |
457 case 'F': // day of week in month (number) | |
458 rule = selectNumberRule | |
459 (Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen); | |
460 break; | |
461 case 'w': // week in year (number) | |
462 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen); | |
463 break; | |
464 case 'W': // week in month (number) | |
465 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen); | |
466 break; | |
467 case 'a': // am/pm marker (text) | |
468 rule = new TextField(Calendar.AM_PM, AmPmStrings); | |
469 break; | |
470 case 'k': // hour in day (1..24) | |
471 rule = new TwentyFourHourField | |
472 (selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen)); | |
473 break; | |
474 case 'K': // hour in am/pm (0..11) | |
475 rule = selectNumberRule(Calendar.HOUR, tokenLen); | |
476 break; | |
477 case 'z': // time zone (text) | |
478 if (tokenLen >= 4) { | |
479 rule = new TimeZoneRule(timeZone, locale, TimeZone.LONG); | |
480 } | |
481 else { | |
482 rule = new TimeZoneRule(timeZone, locale, TimeZone.SHORT); | |
483 } | |
484 break; | |
485 case '\'': // literal text | |
486 String sub = token.substring(1); | |
487 if (sub.length() == 1) { | |
488 rule = new CharacterLiteral(sub.charAt(0)); | |
489 } | |
490 else { | |
491 rule = new StringLiteral(new String(sub)); | |
492 } | |
493 break; | |
494 default: | |
495 throw new IllegalArgumentException | |
496 ("Illegal pattern component: " + token); | |
497 } | |
498 | |
499 rules.add(rule); | |
500 } | |
501 | |
502 return rules; | |
503 } | |
504 | |
505 private static String parseToken(String pattern, int[] indexRef) { | |
506 StringBuffer buf = new StringBuffer(); | |
507 | |
508 int i = indexRef[0]; | |
509 int length = pattern.length(); | |
510 | |
511 char c = pattern.charAt(i); | |
512 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { | |
513 // Scan a run of the same character, which indicates a time | |
514 // pattern. | |
515 buf.append(c); | |
516 | |
517 while (i + 1 < length) { | |
518 char peek = pattern.charAt(i + 1); | |
519 if (peek == c) { | |
520 buf.append(c); | |
521 i++; | |
522 } | |
523 else { | |
524 break; | |
525 } | |
526 } | |
527 } | |
528 else { | |
529 // This will identify token as text. | |
530 buf.append('\''); | |
531 | |
532 boolean inLiteral = false; | |
533 | |
534 for (; i < length; i++) { | |
535 c = pattern.charAt(i); | |
536 | |
537 if (c == '\'') { | |
538 if (i + 1 < length && pattern.charAt(i + 1) == '\'') { | |
539 // '' is treated as escaped ' | |
540 i++; | |
541 buf.append(c); | |
542 } | |
543 else { | |
544 inLiteral = !inLiteral; | |
545 } | |
546 } | |
547 else if (!inLiteral && | |
548 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { | |
549 i--; | |
550 break; | |
551 } | |
552 else { | |
553 buf.append(c); | |
554 } | |
555 } | |
556 } | |
557 | |
558 indexRef[0] = i; | |
559 return buf.toString(); | |
560 } | |
561 | |
562 private static NumberRule selectNumberRule(int field, int padding) { | |
563 switch (padding) { | |
564 case 1: | |
565 return new UnpaddedNumberField(field); | |
566 case 2: | |
567 return new TwoDigitNumberField(field); | |
568 default: | |
569 return new PaddedNumberField(field, padding); | |
570 } | |
571 } | |
572 | |
573 private final String mPattern; | |
574 private final TimeZone mTimeZone; | |
575 private final Locale mLocale; | |
576 private final Rule[] mRules; | |
577 private final int mMaxLengthEstimate; | |
578 | |
579 private FastDateFormat() { | |
580 this(getDefaultPattern(), null, null, null); | |
581 } | |
582 | |
583 /** | |
584 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
585 */ | |
586 private FastDateFormat(String pattern) throws IllegalArgumentException { | |
587 this(pattern, null, null, null); | |
588 } | |
589 | |
590 /** | |
591 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
592 * @param timeZone optional time zone, overrides time zone of formatted | |
593 * date | |
594 */ | |
595 private FastDateFormat(String pattern, TimeZone timeZone) | |
596 throws IllegalArgumentException | |
597 { | |
598 this(pattern, timeZone, null, null); | |
599 } | |
600 | |
601 /** | |
602 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
603 * @param locale optional locale, overrides system locale | |
604 */ | |
605 private FastDateFormat(String pattern, Locale locale) | |
606 throws IllegalArgumentException | |
607 { | |
608 this(pattern, null, locale, null); | |
609 } | |
610 | |
611 /** | |
612 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
613 * @param symbols optional date format symbols, overrides symbols for | |
614 * system locale | |
615 */ | |
616 private FastDateFormat(String pattern, DateFormatSymbols symbols) | |
617 throws IllegalArgumentException | |
618 { | |
619 this(pattern, null, null, symbols); | |
620 } | |
621 | |
622 /** | |
623 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
624 * @param timeZone optional time zone, overrides time zone of formatted | |
625 * date | |
626 * @param locale optional locale, overrides system locale | |
627 */ | |
628 private FastDateFormat(String pattern, TimeZone timeZone, Locale locale) | |
629 throws IllegalArgumentException | |
630 { | |
631 this(pattern, timeZone, locale, null); | |
632 } | |
633 | |
634 /** | |
635 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern | |
636 * @param timeZone optional time zone, overrides time zone of formatted | |
637 * date | |
638 * @param locale optional locale, overrides system locale | |
639 * @param symbols optional date format symbols, overrides symbols for | |
640 * provided locale | |
641 */ | |
642 private FastDateFormat(String pattern, TimeZone timeZone, Locale locale, | |
643 DateFormatSymbols symbols) | |
644 throws IllegalArgumentException | |
645 { | |
646 if (locale == null) { | |
647 locale = Locale.getDefault(); | |
648 } | |
649 | |
650 mPattern = pattern; | |
651 mTimeZone = timeZone; | |
652 mLocale = locale; | |
653 | |
654 if (symbols == null) { | |
655 symbols = new DateFormatSymbols(locale); | |
656 } | |
657 | |
658 List rulesList = parse(pattern, timeZone, locale, symbols); | |
659 mRules = (Rule[])rulesList.toArray(new Rule[rulesList.size()]); | |
660 | |
661 int len = 0; | |
662 for (int i=mRules.length; --i >= 0; ) { | |
663 len += mRules[i].estimateLength(); | |
664 } | |
665 | |
666 mMaxLengthEstimate = len; | |
667 } | |
668 | |
669 public String format(Date date) { | |
670 Calendar c = new GregorianCalendar(cDefaultTimeZone); | |
671 c.setTime(date); | |
672 if (mTimeZone != null) { | |
673 c.setTimeZone(mTimeZone); | |
674 } | |
675 return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString(); | |
676 } | |
677 | |
678 public String format(Calendar calendar) { | |
679 return format(calendar, new StringBuffer(mMaxLengthEstimate)) | |
680 .toString(); | |
681 } | |
682 | |
683 public StringBuffer format(Date date, StringBuffer buf) { | |
684 Calendar c = new GregorianCalendar(cDefaultTimeZone); | |
685 c.setTime(date); | |
686 if (mTimeZone != null) { | |
687 c.setTimeZone(mTimeZone); | |
688 } | |
689 return applyRules(c, buf); | |
690 } | |
691 | |
692 public StringBuffer format(Calendar calendar, StringBuffer buf) { | |
693 if (mTimeZone != null) { | |
694 calendar = (Calendar)calendar.clone(); | |
695 calendar.setTimeZone(mTimeZone); | |
696 } | |
697 return applyRules(calendar, buf); | |
698 } | |
699 | |
700 private StringBuffer applyRules(Calendar calendar, StringBuffer buf) { | |
701 Rule[] rules = mRules; | |
702 int len = mRules.length; | |
703 for (int i=0; i<len; i++) { | |
704 rules[i].appendTo(buf, calendar); | |
705 } | |
706 return buf; | |
707 } | |
708 | |
709 public String getPattern() { | |
710 return mPattern; | |
711 } | |
712 | |
713 /** | |
714 * Returns the time zone used by this formatter, or null if time zone of | |
715 * formatted dates is used instead. | |
716 */ | |
717 public TimeZone getTimeZone() { | |
718 return mTimeZone; | |
719 } | |
720 | |
721 public Locale getLocale() { | |
722 return mLocale; | |
723 } | |
724 | |
725 /** | |
726 * Returns an estimate for the maximum length date that this date | |
727 * formatter will produce. The actual formatted length will almost always | |
728 * be less than or equal to this amount. | |
729 */ | |
730 public int getMaxLengthEstimate() { | |
731 return mMaxLengthEstimate; | |
732 } | |
733 | |
734 private interface Rule { | |
735 int estimateLength(); | |
736 | |
737 void appendTo(StringBuffer buffer, Calendar calendar); | |
738 } | |
739 | |
740 private interface NumberRule extends Rule { | |
741 void appendTo(StringBuffer buffer, int value); | |
742 } | |
743 | |
744 private static class CharacterLiteral implements Rule { | |
745 private final char mValue; | |
746 | |
747 CharacterLiteral(char value) { | |
748 mValue = value; | |
749 } | |
750 | |
751 public int estimateLength() { | |
752 return 1; | |
753 } | |
754 | |
755 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
756 buffer.append(mValue); | |
757 } | |
758 } | |
759 | |
760 private static class StringLiteral implements Rule { | |
761 private final String mValue; | |
762 | |
763 StringLiteral(String value) { | |
764 mValue = value; | |
765 } | |
766 | |
767 public int estimateLength() { | |
768 return mValue.length(); | |
769 } | |
770 | |
771 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
772 buffer.append(mValue); | |
773 } | |
774 } | |
775 | |
776 private static class TextField implements Rule { | |
777 private final int mField; | |
778 private final String[] mValues; | |
779 | |
780 TextField(int field, String[] values) { | |
781 mField = field; | |
782 mValues = values; | |
783 } | |
784 | |
785 public int estimateLength() { | |
786 int max = 0; | |
787 for (int i=mValues.length; --i >= 0; ) { | |
788 int len = mValues[i].length(); | |
789 if (len > max) { | |
790 max = len; | |
791 } | |
792 } | |
793 return max; | |
794 } | |
795 | |
796 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
797 buffer.append(mValues[calendar.get(mField)]); | |
798 } | |
799 } | |
800 | |
801 private static class UnpaddedNumberField implements NumberRule { | |
802 private final int mField; | |
803 | |
804 UnpaddedNumberField(int field) { | |
805 mField = field; | |
806 } | |
807 | |
808 public int estimateLength() { | |
809 return 4; | |
810 } | |
811 | |
812 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
813 appendTo(buffer, calendar.get(mField)); | |
814 } | |
815 | |
816 public final void appendTo(StringBuffer buffer, int value) { | |
817 if (value < 10) { | |
818 buffer.append((char)(value + '0')); | |
819 } | |
820 else if (value < 100) { | |
821 buffer.append((char)(value / 10 + '0')); | |
822 buffer.append((char)(value % 10 + '0')); | |
823 } | |
824 else { | |
825 buffer.append(Integer.toString(value)); | |
826 } | |
827 } | |
828 } | |
829 | |
830 private static class UnpaddedMonthField implements NumberRule { | |
831 UnpaddedMonthField() { | |
832 } | |
833 | |
834 public int estimateLength() { | |
835 return 2; | |
836 } | |
837 | |
838 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
839 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); | |
840 } | |
841 | |
842 public final void appendTo(StringBuffer buffer, int value) { | |
843 if (value < 10) { | |
844 buffer.append((char)(value + '0')); | |
845 } | |
846 else { | |
847 buffer.append((char)(value / 10 + '0')); | |
848 buffer.append((char)(value % 10 + '0')); | |
849 } | |
850 } | |
851 } | |
852 | |
853 private static class PaddedNumberField implements NumberRule { | |
854 private final int mField; | |
855 private final int mSize; | |
856 | |
857 PaddedNumberField(int field, int size) { | |
858 if (size < 3) { | |
859 // Should use UnpaddedNumberField or TwoDigitNumberField. | |
860 throw new IllegalArgumentException(); | |
861 } | |
862 mField = field; | |
863 mSize = size; | |
864 } | |
865 | |
866 public int estimateLength() { | |
867 return 4; | |
868 } | |
869 | |
870 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
871 appendTo(buffer, calendar.get(mField)); | |
872 } | |
873 | |
874 public final void appendTo(StringBuffer buffer, int value) { | |
875 if (value < 100) { | |
876 for (int i = mSize; --i >= 2; ) { | |
877 buffer.append('0'); | |
878 } | |
879 buffer.append((char)(value / 10 + '0')); | |
880 buffer.append((char)(value % 10 + '0')); | |
881 } | |
882 else { | |
883 int digits; | |
884 if (value < 1000) { | |
885 digits = 3; | |
886 } | |
887 else { | |
888 digits = (int)(Math.log(value) / LOG_10) + 1; | |
889 } | |
890 for (int i = mSize; --i >= digits; ) { | |
891 buffer.append('0'); | |
892 } | |
893 buffer.append(Integer.toString(value)); | |
894 } | |
895 } | |
896 } | |
897 | |
898 private static class TwoDigitNumberField implements NumberRule { | |
899 private final int mField; | |
900 | |
901 TwoDigitNumberField(int field) { | |
902 mField = field; | |
903 } | |
904 | |
905 public int estimateLength() { | |
906 return 2; | |
907 } | |
908 | |
909 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
910 appendTo(buffer, calendar.get(mField)); | |
911 } | |
912 | |
913 public final void appendTo(StringBuffer buffer, int value) { | |
914 if (value < 100) { | |
915 buffer.append((char)(value / 10 + '0')); | |
916 buffer.append((char)(value % 10 + '0')); | |
917 } | |
918 else { | |
919 buffer.append(Integer.toString(value)); | |
920 } | |
921 } | |
922 } | |
923 | |
924 private static class TwoDigitYearField implements NumberRule { | |
925 TwoDigitYearField() { | |
926 } | |
927 | |
928 public int estimateLength() { | |
929 return 2; | |
930 } | |
931 | |
932 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
933 appendTo(buffer, calendar.get(Calendar.YEAR) % 100); | |
934 } | |
935 | |
936 public final void appendTo(StringBuffer buffer, int value) { | |
937 buffer.append((char)(value / 10 + '0')); | |
938 buffer.append((char)(value % 10 + '0')); | |
939 } | |
940 } | |
941 | |
942 private static class TwoDigitMonthField implements NumberRule { | |
943 TwoDigitMonthField() { | |
944 } | |
945 | |
946 public int estimateLength() { | |
947 return 2; | |
948 } | |
949 | |
950 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
951 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); | |
952 } | |
953 | |
954 public final void appendTo(StringBuffer buffer, int value) { | |
955 buffer.append((char)(value / 10 + '0')); | |
956 buffer.append((char)(value % 10 + '0')); | |
957 } | |
958 } | |
959 | |
960 private static class TwelveHourField implements NumberRule { | |
961 private final NumberRule mRule; | |
962 | |
963 TwelveHourField(NumberRule rule) { | |
964 mRule = rule; | |
965 } | |
966 | |
967 public int estimateLength() { | |
968 return mRule.estimateLength(); | |
969 } | |
970 | |
971 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
972 int value = calendar.get(Calendar.HOUR); | |
973 if (value == 0) { | |
974 value = calendar.getLeastMaximum(Calendar.HOUR) + 1; | |
975 } | |
976 mRule.appendTo(buffer, value); | |
977 } | |
978 | |
979 public void appendTo(StringBuffer buffer, int value) { | |
980 mRule.appendTo(buffer, value); | |
981 } | |
982 } | |
983 | |
984 private static class TwentyFourHourField implements NumberRule { | |
985 private final NumberRule mRule; | |
986 | |
987 TwentyFourHourField(NumberRule rule) { | |
988 mRule = rule; | |
989 } | |
990 | |
991 public int estimateLength() { | |
992 return mRule.estimateLength(); | |
993 } | |
994 | |
995 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
996 int value = calendar.get(Calendar.HOUR_OF_DAY); | |
997 if (value == 0) { | |
998 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; | |
999 } | |
1000 mRule.appendTo(buffer, value); | |
1001 } | |
1002 | |
1003 public void appendTo(StringBuffer buffer, int value) { | |
1004 mRule.appendTo(buffer, value); | |
1005 } | |
1006 } | |
1007 | |
1008 private static class TimeZoneRule implements Rule { | |
1009 private final TimeZone mTimeZone; | |
1010 private final Locale mLocale; | |
1011 private final int mStyle; | |
1012 private final String mStandard; | |
1013 private final String mDaylight; | |
1014 | |
1015 TimeZoneRule(TimeZone timeZone, Locale locale, int style) { | |
1016 mTimeZone = timeZone; | |
1017 mLocale = locale; | |
1018 mStyle = style; | |
1019 | |
1020 if (timeZone != null) { | |
1021 mStandard = getTimeZoneDisplay(timeZone, false, style, locale); | |
1022 mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); | |
1023 } | |
1024 else { | |
1025 mStandard = null; | |
1026 mDaylight = null; | |
1027 } | |
1028 } | |
1029 | |
1030 public int estimateLength() { | |
1031 if (mTimeZone != null) { | |
1032 return Math.max(mStandard.length(), mDaylight.length()); | |
1033 } | |
1034 else if (mStyle == TimeZone.SHORT) { | |
1035 return 4; | |
1036 } | |
1037 else { | |
1038 return 40; | |
1039 } | |
1040 } | |
1041 | |
1042 public void appendTo(StringBuffer buffer, Calendar calendar) { | |
1043 TimeZone timeZone; | |
1044 if ((timeZone = mTimeZone) != null) { | |
1045 if (timeZone.useDaylightTime() && | |
1046 calendar.get(Calendar.DST_OFFSET) != 0) { | |
1047 | |
1048 buffer.append(mDaylight); | |
1049 } | |
1050 else { | |
1051 buffer.append(mStandard); | |
1052 } | |
1053 } | |
1054 else { | |
1055 timeZone = calendar.getTimeZone(); | |
1056 if (timeZone.useDaylightTime() && | |
1057 calendar.get(Calendar.DST_OFFSET) != 0) { | |
1058 | |
1059 buffer.append(getTimeZoneDisplay | |
1060 (timeZone, true, mStyle, mLocale)); | |
1061 } | |
1062 else { | |
1063 buffer.append(getTimeZoneDisplay | |
1064 (timeZone, false, mStyle, mLocale)); | |
1065 } | |
1066 } | |
1067 } | |
1068 } | |
1069 | |
1070 private static class TimeZoneDisplayKey { | |
1071 private final TimeZone mTimeZone; | |
1072 private final int mStyle; | |
1073 private final Locale mLocale; | |
1074 | |
1075 TimeZoneDisplayKey(TimeZone timeZone, | |
1076 boolean daylight, int style, Locale locale) { | |
1077 mTimeZone = timeZone; | |
1078 if (daylight) { | |
1079 style |= 0x80000000; | |
1080 } | |
1081 mStyle = style; | |
1082 mLocale = locale; | |
1083 } | |
1084 | |
1085 public int hashCode() { | |
1086 return mStyle * 31 + mLocale.hashCode(); | |
1087 } | |
1088 | |
1089 public boolean equals(Object obj) { | |
1090 if (this == obj) { | |
1091 return true; | |
1092 } | |
1093 if (obj instanceof TimeZoneDisplayKey) { | |
1094 TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj; | |
1095 return | |
1096 mTimeZone.equals(other.mTimeZone) && | |
1097 mStyle == other.mStyle && | |
1098 mLocale.equals(other.mLocale); | |
1099 } | |
1100 return false; | |
1101 } | |
1102 } | |
1103 } |