1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.joda.time.format;
17
18 import java.io.IOException;
19 import java.io.Writer;
20 import java.text.DateFormat;
21 import java.text.SimpleDateFormat;
22 import java.util.HashMap;
23 import java.util.Locale;
24 import java.util.Map;
25
26 import org.joda.time.Chronology;
27 import org.joda.time.DateTime;
28 import org.joda.time.DateTimeZone;
29 import org.joda.time.ReadablePartial;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 public class DateTimeFormat {
127
128
129 static final int FULL = 0;
130
131 static final int LONG = 1;
132
133 static final int MEDIUM = 2;
134
135 static final int SHORT = 3;
136
137 static final int NONE = 4;
138
139
140 static final int DATE = 0;
141
142 static final int TIME = 1;
143
144 static final int DATETIME = 2;
145
146
147 private static final Map<String, DateTimeFormatter> cPatternedCache = new HashMap<String, DateTimeFormatter>(7);
148
149 private static final DateTimeFormatter[] cStyleCache = new DateTimeFormatter[25];
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 public static DateTimeFormatter forPattern(String pattern) {
170 return createFormatterForPattern(pattern);
171 }
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193 public static DateTimeFormatter forStyle(String style) {
194 return createFormatterForStyle(style);
195 }
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 public static String patternForStyle(String style, Locale locale) {
212 DateTimeFormatter formatter = createFormatterForStyle(style);
213 if (locale == null) {
214 locale = Locale.getDefault();
215 }
216
217 return ((StyleFormatter) formatter.getPrinter()).getPattern(locale);
218 }
219
220
221
222
223
224
225
226
227
228
229 public static DateTimeFormatter shortDate() {
230 return createFormatterForStyleIndex(SHORT, NONE);
231 }
232
233
234
235
236
237
238
239
240
241 public static DateTimeFormatter shortTime() {
242 return createFormatterForStyleIndex(NONE, SHORT);
243 }
244
245
246
247
248
249
250
251
252
253 public static DateTimeFormatter shortDateTime() {
254 return createFormatterForStyleIndex(SHORT, SHORT);
255 }
256
257
258
259
260
261
262
263
264
265
266 public static DateTimeFormatter mediumDate() {
267 return createFormatterForStyleIndex(MEDIUM, NONE);
268 }
269
270
271
272
273
274
275
276
277
278 public static DateTimeFormatter mediumTime() {
279 return createFormatterForStyleIndex(NONE, MEDIUM);
280 }
281
282
283
284
285
286
287
288
289
290 public static DateTimeFormatter mediumDateTime() {
291 return createFormatterForStyleIndex(MEDIUM, MEDIUM);
292 }
293
294
295
296
297
298
299
300
301
302
303 public static DateTimeFormatter longDate() {
304 return createFormatterForStyleIndex(LONG, NONE);
305 }
306
307
308
309
310
311
312
313
314
315 public static DateTimeFormatter longTime() {
316 return createFormatterForStyleIndex(NONE, LONG);
317 }
318
319
320
321
322
323
324
325
326
327 public static DateTimeFormatter longDateTime() {
328 return createFormatterForStyleIndex(LONG, LONG);
329 }
330
331
332
333
334
335
336
337
338
339
340 public static DateTimeFormatter fullDate() {
341 return createFormatterForStyleIndex(FULL, NONE);
342 }
343
344
345
346
347
348
349
350
351
352 public static DateTimeFormatter fullTime() {
353 return createFormatterForStyleIndex(NONE, FULL);
354 }
355
356
357
358
359
360
361
362
363
364 public static DateTimeFormatter fullDateTime() {
365 return createFormatterForStyleIndex(FULL, FULL);
366 }
367
368
369
370
371
372
373
374
375
376 static void appendPatternTo(DateTimeFormatterBuilder builder, String pattern) {
377 parsePatternTo(builder, pattern);
378 }
379
380
381
382
383
384
385
386 protected DateTimeFormat() {
387 super();
388 }
389
390
391
392
393
394
395
396
397
398
399 private static void parsePatternTo(DateTimeFormatterBuilder builder, String pattern) {
400 int length = pattern.length();
401 int[] indexRef = new int[1];
402
403 for (int i=0; i<length; i++) {
404 indexRef[0] = i;
405 String token = parseToken(pattern, indexRef);
406 i = indexRef[0];
407
408 int tokenLen = token.length();
409 if (tokenLen == 0) {
410 break;
411 }
412 char c = token.charAt(0);
413
414 switch (c) {
415 case 'G':
416 builder.appendEraText();
417 break;
418 case 'C':
419 builder.appendCenturyOfEra(tokenLen, tokenLen);
420 break;
421 case 'x':
422 case 'y':
423 case 'Y':
424 if (tokenLen == 2) {
425 boolean lenientParse = true;
426
427
428 if (i + 1 < length) {
429 indexRef[0]++;
430 if (isNumericToken(parseToken(pattern, indexRef))) {
431
432
433
434 lenientParse = false;
435 }
436 indexRef[0]--;
437 }
438
439
440 switch (c) {
441 case 'x':
442 builder.appendTwoDigitWeekyear
443 (new DateTime().getWeekyear() - 30, lenientParse);
444 break;
445 case 'y':
446 case 'Y':
447 default:
448 builder.appendTwoDigitYear(new DateTime().getYear() - 30, lenientParse);
449 break;
450 }
451 } else {
452
453 int maxDigits = 9;
454
455
456 if (i + 1 < length) {
457 indexRef[0]++;
458 if (isNumericToken(parseToken(pattern, indexRef))) {
459
460 maxDigits = tokenLen;
461 }
462 indexRef[0]--;
463 }
464
465 switch (c) {
466 case 'x':
467 builder.appendWeekyear(tokenLen, maxDigits);
468 break;
469 case 'y':
470 builder.appendYear(tokenLen, maxDigits);
471 break;
472 case 'Y':
473 builder.appendYearOfEra(tokenLen, maxDigits);
474 break;
475 }
476 }
477 break;
478 case 'M':
479 if (tokenLen >= 3) {
480 if (tokenLen >= 4) {
481 builder.appendMonthOfYearText();
482 } else {
483 builder.appendMonthOfYearShortText();
484 }
485 } else {
486 builder.appendMonthOfYear(tokenLen);
487 }
488 break;
489 case 'd':
490 builder.appendDayOfMonth(tokenLen);
491 break;
492 case 'a':
493 builder.appendHalfdayOfDayText();
494 break;
495 case 'h':
496 builder.appendClockhourOfHalfday(tokenLen);
497 break;
498 case 'H':
499 builder.appendHourOfDay(tokenLen);
500 break;
501 case 'k':
502 builder.appendClockhourOfDay(tokenLen);
503 break;
504 case 'K':
505 builder.appendHourOfHalfday(tokenLen);
506 break;
507 case 'm':
508 builder.appendMinuteOfHour(tokenLen);
509 break;
510 case 's':
511 builder.appendSecondOfMinute(tokenLen);
512 break;
513 case 'S':
514 builder.appendFractionOfSecond(tokenLen, tokenLen);
515 break;
516 case 'e':
517 builder.appendDayOfWeek(tokenLen);
518 break;
519 case 'E':
520 if (tokenLen >= 4) {
521 builder.appendDayOfWeekText();
522 } else {
523 builder.appendDayOfWeekShortText();
524 }
525 break;
526 case 'D':
527 builder.appendDayOfYear(tokenLen);
528 break;
529 case 'w':
530 builder.appendWeekOfWeekyear(tokenLen);
531 break;
532 case 'z':
533 if (tokenLen >= 4) {
534 builder.appendTimeZoneName();
535 } else {
536 builder.appendTimeZoneShortName(null);
537 }
538 break;
539 case 'Z':
540 if (tokenLen == 1) {
541 builder.appendTimeZoneOffset(null, "Z", false, 2, 2);
542 } else if (tokenLen == 2) {
543 builder.appendTimeZoneOffset(null, "Z", true, 2, 2);
544 } else {
545 builder.appendTimeZoneId();
546 }
547 break;
548 case '\'':
549 String sub = token.substring(1);
550 if (sub.length() == 1) {
551 builder.appendLiteral(sub.charAt(0));
552 } else {
553
554
555 builder.appendLiteral(new String(sub));
556 }
557 break;
558 default:
559 throw new IllegalArgumentException
560 ("Illegal pattern component: " + token);
561 }
562 }
563 }
564
565
566
567
568
569
570
571
572
573 private static String parseToken(String pattern, int[] indexRef) {
574 StringBuilder buf = new StringBuilder();
575
576 int i = indexRef[0];
577 int length = pattern.length();
578
579 char c = pattern.charAt(i);
580 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
581
582
583 buf.append(c);
584
585 while (i + 1 < length) {
586 char peek = pattern.charAt(i + 1);
587 if (peek == c) {
588 buf.append(c);
589 i++;
590 } else {
591 break;
592 }
593 }
594 } else {
595
596 buf.append('\'');
597
598 boolean inLiteral = false;
599
600 for (; i < length; i++) {
601 c = pattern.charAt(i);
602
603 if (c == '\'') {
604 if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
605
606 i++;
607 buf.append(c);
608 } else {
609 inLiteral = !inLiteral;
610 }
611 } else if (!inLiteral &&
612 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
613 i--;
614 break;
615 } else {
616 buf.append(c);
617 }
618 }
619 }
620
621 indexRef[0] = i;
622 return buf.toString();
623 }
624
625
626
627
628
629
630
631 private static boolean isNumericToken(String token) {
632 int tokenLen = token.length();
633 if (tokenLen > 0) {
634 char c = token.charAt(0);
635 switch (c) {
636 case 'c':
637 case 'C':
638 case 'x':
639 case 'y':
640 case 'Y':
641 case 'd':
642 case 'h':
643 case 'H':
644 case 'm':
645 case 's':
646 case 'S':
647 case 'e':
648 case 'D':
649 case 'F':
650 case 'w':
651 case 'W':
652 case 'k':
653 case 'K':
654 return true;
655 case 'M':
656 if (tokenLen <= 2) {
657 return true;
658 }
659 }
660 }
661
662 return false;
663 }
664
665
666
667
668
669
670
671
672
673 private static DateTimeFormatter createFormatterForPattern(String pattern) {
674 if (pattern == null || pattern.length() == 0) {
675 throw new IllegalArgumentException("Invalid pattern specification");
676 }
677 DateTimeFormatter formatter = null;
678 synchronized (cPatternedCache) {
679 formatter = cPatternedCache.get(pattern);
680 if (formatter == null) {
681 DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
682 parsePatternTo(builder, pattern);
683 formatter = builder.toFormatter();
684
685 cPatternedCache.put(pattern, formatter);
686 }
687 }
688 return formatter;
689 }
690
691
692
693
694
695
696
697
698
699
700 private static DateTimeFormatter createFormatterForStyle(String style) {
701 if (style == null || style.length() != 2) {
702 throw new IllegalArgumentException("Invalid style specification: " + style);
703 }
704 int dateStyle = selectStyle(style.charAt(0));
705 int timeStyle = selectStyle(style.charAt(1));
706 if (dateStyle == NONE && timeStyle == NONE) {
707 throw new IllegalArgumentException("Style '--' is invalid");
708 }
709 return createFormatterForStyleIndex(dateStyle, timeStyle);
710 }
711
712
713
714
715
716
717
718
719 private static DateTimeFormatter createFormatterForStyleIndex(int dateStyle, int timeStyle) {
720 int index = ((dateStyle << 2) + dateStyle) + timeStyle;
721 DateTimeFormatter f = null;
722 synchronized (cStyleCache) {
723 f = cStyleCache[index];
724 if (f == null) {
725 int type = DATETIME;
726 if (dateStyle == NONE) {
727 type = TIME;
728 } else if (timeStyle == NONE) {
729 type = DATE;
730 }
731 StyleFormatter llf = new StyleFormatter(
732 dateStyle, timeStyle, type);
733 f = new DateTimeFormatter(llf, llf);
734 cStyleCache[index] = f;
735 }
736 }
737 return f;
738 }
739
740
741
742
743
744
745
746 private static int selectStyle(char ch) {
747 switch (ch) {
748 case 'S':
749 return SHORT;
750 case 'M':
751 return MEDIUM;
752 case 'L':
753 return LONG;
754 case 'F':
755 return FULL;
756 case '-':
757 return NONE;
758 default:
759 throw new IllegalArgumentException("Invalid style character: " + ch);
760 }
761 }
762
763
764 static class StyleFormatter
765 implements DateTimePrinter, DateTimeParser {
766
767 private static final Map<String, DateTimeFormatter> cCache = new HashMap<String, DateTimeFormatter>();
768
769 private final int iDateStyle;
770 private final int iTimeStyle;
771 private final int iType;
772
773 StyleFormatter(int dateStyle, int timeStyle, int type) {
774 super();
775 iDateStyle = dateStyle;
776 iTimeStyle = timeStyle;
777 iType = type;
778 }
779
780 public int estimatePrintedLength() {
781 return 40;
782 }
783
784 public void printTo(
785 StringBuffer buf, long instant, Chronology chrono,
786 int displayOffset, DateTimeZone displayZone, Locale locale) {
787 DateTimePrinter p = getFormatter(locale).getPrinter();
788 p.printTo(buf, instant, chrono, displayOffset, displayZone, locale);
789 }
790
791 public void printTo(
792 Writer out, long instant, Chronology chrono,
793 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
794 DateTimePrinter p = getFormatter(locale).getPrinter();
795 p.printTo(out, instant, chrono, displayOffset, displayZone, locale);
796 }
797
798 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
799 DateTimePrinter p = getFormatter(locale).getPrinter();
800 p.printTo(buf, partial, locale);
801 }
802
803 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
804 DateTimePrinter p = getFormatter(locale).getPrinter();
805 p.printTo(out, partial, locale);
806 }
807
808 public int estimateParsedLength() {
809 return 40;
810 }
811
812 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
813 DateTimeParser p = getFormatter(bucket.getLocale()).getParser();
814 return p.parseInto(bucket, text, position);
815 }
816
817 private DateTimeFormatter getFormatter(Locale locale) {
818 locale = (locale == null ? Locale.getDefault() : locale);
819 String key = Integer.toString(iType + (iDateStyle << 4) + (iTimeStyle << 8)) + locale.toString();
820 DateTimeFormatter f = null;
821 synchronized (cCache) {
822 f = cCache.get(key);
823 if (f == null) {
824 String pattern = getPattern(locale);
825 f = DateTimeFormat.forPattern(pattern);
826 cCache.put(key, f);
827 }
828 }
829 return f;
830 }
831
832 String getPattern(Locale locale) {
833 DateFormat f = null;
834 switch (iType) {
835 case DATE:
836 f = DateFormat.getDateInstance(iDateStyle, locale);
837 break;
838 case TIME:
839 f = DateFormat.getTimeInstance(iTimeStyle, locale);
840 break;
841 case DATETIME:
842 f = DateFormat.getDateTimeInstance(iDateStyle, iTimeStyle, locale);
843 break;
844 }
845 if (f instanceof SimpleDateFormat == false) {
846 throw new IllegalArgumentException("No datetime pattern for locale: " + locale);
847 }
848 return ((SimpleDateFormat) f).toPattern();
849 }
850 }
851
852 }