1 /*
2 * Copyright 2001-2014 Stephen Colebourne
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.joda.time.format;
17
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ConcurrentHashMap;
25
26 import org.joda.time.Chronology;
27 import org.joda.time.DateTimeConstants;
28 import org.joda.time.DateTimeField;
29 import org.joda.time.DateTimeFieldType;
30 import org.joda.time.DateTimeUtils;
31 import org.joda.time.DateTimeZone;
32 import org.joda.time.MutableDateTime;
33 import org.joda.time.MutableDateTime.Property;
34 import org.joda.time.ReadablePartial;
35 import org.joda.time.field.MillisDurationField;
36 import org.joda.time.field.PreciseDateTimeField;
37
38 /**
39 * Factory that creates complex instances of DateTimeFormatter via method calls.
40 * <p>
41 * Datetime formatting is performed by the {@link DateTimeFormatter} class.
42 * Three classes provide factory methods to create formatters, and this is one.
43 * The others are {@link DateTimeFormat} and {@link ISODateTimeFormat}.
44 * <p>
45 * DateTimeFormatterBuilder is used for constructing formatters which are then
46 * used to print or parse. The formatters are built by appending specific fields
47 * or other formatters to an instance of this builder.
48 * <p>
49 * For example, a formatter that prints month and year, like "January 1970",
50 * can be constructed as follows:
51 * <p>
52 * <pre>
53 * DateTimeFormatter monthAndYear = new DateTimeFormatterBuilder()
54 * .appendMonthOfYearText()
55 * .appendLiteral(' ')
56 * .appendYear(4, 4)
57 * .toFormatter();
58 * </pre>
59 * <p>
60 * DateTimeFormatterBuilder itself is mutable and not thread-safe, but the
61 * formatters that it builds are thread-safe and immutable.
62 *
63 * @author Brian S O'Neill
64 * @author Stephen Colebourne
65 * @author Fredrik Borgh
66 * @since 1.0
67 * @see DateTimeFormat
68 * @see ISODateTimeFormat
69 */
70 public class DateTimeFormatterBuilder {
71
72 /** Array of printers and parsers (alternating). */
73 private ArrayList<Object> iElementPairs;
74 /** Cache of the last returned formatter. */
75 private Object iFormatter;
76
77 //-----------------------------------------------------------------------
78 /**
79 * Creates a DateTimeFormatterBuilder.
80 */
81 public DateTimeFormatterBuilder() {
82 super();
83 iElementPairs = new ArrayList<Object>();
84 }
85
86 //-----------------------------------------------------------------------
87 /**
88 * Constructs a DateTimeFormatter using all the appended elements.
89 * <p>
90 * This is the main method used by applications at the end of the build
91 * process to create a usable formatter.
92 * <p>
93 * Subsequent changes to this builder do not affect the returned formatter.
94 * <p>
95 * The returned formatter may not support both printing and parsing.
96 * The methods {@link DateTimeFormatter#isPrinter()} and
97 * {@link DateTimeFormatter#isParser()} will help you determine the state
98 * of the formatter.
99 *
100 * @throws UnsupportedOperationException if neither printing nor parsing is supported
101 */
102 public DateTimeFormatter toFormatter() {
103 Object f = getFormatter();
104 InternalPrinter printer = null;
105 if (isPrinter(f)) {
106 printer = (InternalPrinter) f;
107 }
108 InternalParser parser = null;
109 if (isParser(f)) {
110 parser = (InternalParser) f;
111 }
112 if (printer != null || parser != null) {
113 return new DateTimeFormatter(printer, parser);
114 }
115 throw new UnsupportedOperationException("Both printing and parsing not supported");
116 }
117
118 /**
119 * Internal method to create a DateTimePrinter instance using all the
120 * appended elements.
121 * <p>
122 * Most applications will not use this method.
123 * If you want a printer in an application, call {@link #toFormatter()}
124 * and just use the printing API.
125 * <p>
126 * Subsequent changes to this builder do not affect the returned printer.
127 *
128 * @throws UnsupportedOperationException if printing is not supported
129 */
130 public DateTimePrinter toPrinter() {
131 Object f = getFormatter();
132 if (isPrinter(f)) {
133 InternalPrinter ip = (InternalPrinter) f;
134 return InternalPrinterDateTimePrinter.of(ip);
135 }
136 throw new UnsupportedOperationException("Printing is not supported");
137 }
138
139 /**
140 * Internal method to create a DateTimeParser instance using all the
141 * appended elements.
142 * <p>
143 * Most applications will not use this method.
144 * If you want a parser in an application, call {@link #toFormatter()}
145 * and just use the parsing API.
146 * <p>
147 * Subsequent changes to this builder do not affect the returned parser.
148 *
149 * @throws UnsupportedOperationException if parsing is not supported
150 */
151 public DateTimeParser toParser() {
152 Object f = getFormatter();
153 if (isParser(f)) {
154 InternalParser ip = (InternalParser) f;
155 return InternalParserDateTimeParser.of(ip);
156 }
157 throw new UnsupportedOperationException("Parsing is not supported");
158 }
159
160 //-----------------------------------------------------------------------
161 /**
162 * Returns true if toFormatter can be called without throwing an
163 * UnsupportedOperationException.
164 *
165 * @return true if a formatter can be built
166 */
167 public boolean canBuildFormatter() {
168 return isFormatter(getFormatter());
169 }
170
171 /**
172 * Returns true if toPrinter can be called without throwing an
173 * UnsupportedOperationException.
174 *
175 * @return true if a printer can be built
176 */
177 public boolean canBuildPrinter() {
178 return isPrinter(getFormatter());
179 }
180
181 /**
182 * Returns true if toParser can be called without throwing an
183 * UnsupportedOperationException.
184 *
185 * @return true if a parser can be built
186 */
187 public boolean canBuildParser() {
188 return isParser(getFormatter());
189 }
190
191 //-----------------------------------------------------------------------
192 /**
193 * Clears out all the appended elements, allowing this builder to be
194 * reused.
195 */
196 public void clear() {
197 iFormatter = null;
198 iElementPairs.clear();
199 }
200
201 //-----------------------------------------------------------------------
202 /**
203 * Appends another formatter.
204 * <p>
205 * This extracts the underlying printer and parser and appends them
206 * The printer and parser interfaces are the low-level part of the formatting API.
207 * Normally, instances are extracted from another formatter.
208 * Note however that any formatter specific information, such as the locale,
209 * time-zone, chronology, offset parsing or pivot/default year, will not be
210 * extracted by this method.
211 *
212 * @param formatter the formatter to add
213 * @return this DateTimeFormatterBuilder, for chaining
214 * @throws IllegalArgumentException if formatter is null or of an invalid type
215 */
216 public DateTimeFormatterBuilder append(DateTimeFormatter formatter) {
217 if (formatter == null) {
218 throw new IllegalArgumentException("No formatter supplied");
219 }
220 return append0(formatter.getPrinter0(), formatter.getParser0());
221 }
222
223 /**
224 * Appends just a printer. With no matching parser, a parser cannot be
225 * built from this DateTimeFormatterBuilder.
226 * <p>
227 * The printer interface is part of the low-level part of the formatting API.
228 * Normally, instances are extracted from another formatter.
229 * Note however that any formatter specific information, such as the locale,
230 * time-zone, chronology, offset parsing or pivot/default year, will not be
231 * extracted by this method.
232 *
233 * @param printer the printer to add, not null
234 * @return this DateTimeFormatterBuilder, for chaining
235 * @throws IllegalArgumentException if printer is null or of an invalid type
236 */
237 public DateTimeFormatterBuilder append(DateTimePrinter printer) {
238 checkPrinter(printer);
239 return append0(DateTimePrinterInternalPrinter.of(printer), null);
240 }
241
242 /**
243 * Appends just a parser. With no matching printer, a printer cannot be
244 * built from this builder.
245 * <p>
246 * The parser interface is part of the low-level part of the formatting API.
247 * Normally, instances are extracted from another formatter.
248 * Note however that any formatter specific information, such as the locale,
249 * time-zone, chronology, offset parsing or pivot/default year, will not be
250 * extracted by this method.
251 *
252 * @param parser the parser to add, not null
253 * @return this DateTimeFormatterBuilder, for chaining
254 * @throws IllegalArgumentException if parser is null or of an invalid type
255 */
256 public DateTimeFormatterBuilder append(DateTimeParser parser) {
257 checkParser(parser);
258 return append0(null, DateTimeParserInternalParser.of(parser));
259 }
260
261 /**
262 * Appends a printer/parser pair.
263 * <p>
264 * The printer and parser interfaces are the low-level part of the formatting API.
265 * Normally, instances are extracted from another formatter.
266 * Note however that any formatter specific information, such as the locale,
267 * time-zone, chronology, offset parsing or pivot/default year, will not be
268 * extracted by this method.
269 *
270 * @param printer the printer to add, not null
271 * @param parser the parser to add, not null
272 * @return this DateTimeFormatterBuilder, for chaining
273 * @throws IllegalArgumentException if printer or parser is null or of an invalid type
274 */
275 public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser parser) {
276 checkPrinter(printer);
277 checkParser(parser);
278 return append0(DateTimePrinterInternalPrinter.of(printer), DateTimeParserInternalParser.of(parser));
279 }
280
281 /**
282 * Appends a printer and a set of matching parsers. When parsing, the first
283 * parser in the list is selected for parsing. If it fails, the next is
284 * chosen, and so on. If none of these parsers succeeds, then the failed
285 * position of the parser that made the greatest progress is returned.
286 * <p>
287 * Only the printer is optional. In addition, it is illegal for any but the
288 * last of the parser array elements to be null. If the last element is
289 * null, this represents the empty parser. The presence of an empty parser
290 * indicates that the entire array of parse formats is optional.
291 * <p>
292 * The printer and parser interfaces are the low-level part of the formatting API.
293 * Normally, instances are extracted from another formatter.
294 * Note however that any formatter specific information, such as the locale,
295 * time-zone, chronology, offset parsing or pivot/default year, will not be
296 * extracted by this method.
297 *
298 * @param printer the printer to add
299 * @param parsers the parsers to add
300 * @return this DateTimeFormatterBuilder, for chaining
301 * @throws IllegalArgumentException if any printer or parser is of an invalid type
302 * @throws IllegalArgumentException if any parser element but the last is null
303 */
304 public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser[] parsers) {
305 if (printer != null) {
306 checkPrinter(printer);
307 }
308 if (parsers == null) {
309 throw new IllegalArgumentException("No parsers supplied");
310 }
311 int length = parsers.length;
312 if (length == 1) {
313 if (parsers[0] == null) {
314 throw new IllegalArgumentException("No parser supplied");
315 }
316 return append0(DateTimePrinterInternalPrinter.of(printer), DateTimeParserInternalParser.of(parsers[0]));
317 }
318
319 InternalParser[] copyOfParsers = new InternalParser[length];
320 int i;
321 for (i = 0; i < length - 1; i++) {
322 if ((copyOfParsers[i] = DateTimeParserInternalParser.of(parsers[i])) == null) {
323 throw new IllegalArgumentException("Incomplete parser array");
324 }
325 }
326 copyOfParsers[i] = DateTimeParserInternalParser.of(parsers[i]);
327
328 return append0(DateTimePrinterInternalPrinter.of(printer), new MatchingParser(copyOfParsers));
329 }
330
331 /**
332 * Appends just a parser element which is optional. With no matching
333 * printer, a printer cannot be built from this DateTimeFormatterBuilder.
334 * <p>
335 * The parser interface is part of the low-level part of the formatting API.
336 * Normally, instances are extracted from another formatter.
337 * Note however that any formatter specific information, such as the locale,
338 * time-zone, chronology, offset parsing or pivot/default year, will not be
339 * extracted by this method.
340 *
341 * @return this DateTimeFormatterBuilder, for chaining
342 * @throws IllegalArgumentException if parser is null or of an invalid type
343 */
344 public DateTimeFormatterBuilder appendOptional(DateTimeParser parser) {
345 checkParser(parser);
346 InternalParser[] parsers = new InternalParser[] {DateTimeParserInternalParser.of(parser), null};
347 return append0(null, new MatchingParser(parsers));
348 }
349
350 //-----------------------------------------------------------------------
351 /**
352 * Checks if the parser is non null and a provider.
353 *
354 * @param parser the parser to check
355 */
356 private void checkParser(DateTimeParser parser) {
357 if (parser == null) {
358 throw new IllegalArgumentException("No parser supplied");
359 }
360 }
361
362 /**
363 * Checks if the printer is non null and a provider.
364 *
365 * @param printer the printer to check
366 */
367 private void checkPrinter(DateTimePrinter printer) {
368 if (printer == null) {
369 throw new IllegalArgumentException("No printer supplied");
370 }
371 }
372
373 private DateTimeFormatterBuilder append0(Object element) {
374 iFormatter = null;
375 // Add the element as both a printer and parser.
376 iElementPairs.add(element);
377 iElementPairs.add(element);
378 return this;
379 }
380
381 private DateTimeFormatterBuilder append0(
382 InternalPrinter printer, InternalParser parser) {
383 iFormatter = null;
384 iElementPairs.add(printer);
385 iElementPairs.add(parser);
386 return this;
387 }
388
389 //-----------------------------------------------------------------------
390 /**
391 * Instructs the printer to emit a specific character, and the parser to
392 * expect it. The parser is case-insensitive.
393 *
394 * @return this DateTimeFormatterBuilder, for chaining
395 */
396 public DateTimeFormatterBuilder appendLiteral(char c) {
397 return append0(new CharacterLiteral(c));
398 }
399
400 /**
401 * Instructs the printer to emit specific text, and the parser to expect
402 * it. The parser is case-insensitive.
403 *
404 * @return this DateTimeFormatterBuilder, for chaining
405 * @throws IllegalArgumentException if text is null
406 */
407 public DateTimeFormatterBuilder appendLiteral(String text) {
408 if (text == null) {
409 throw new IllegalArgumentException("Literal must not be null");
410 }
411 switch (text.length()) {
412 case 0:
413 return this;
414 case 1:
415 return append0(new CharacterLiteral(text.charAt(0)));
416 default:
417 return append0(new StringLiteral(text));
418 }
419 }
420
421 /**
422 * Instructs the printer to emit a field value as a decimal number, and the
423 * parser to expect an unsigned decimal number.
424 *
425 * @param fieldType type of field to append
426 * @param minDigits minimum number of digits to <i>print</i>
427 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
428 * maximum number of digits to print
429 * @return this DateTimeFormatterBuilder, for chaining
430 * @throws IllegalArgumentException if field type is null
431 */
432 public DateTimeFormatterBuilder appendDecimal(
433 DateTimeFieldType fieldType, int minDigits, int maxDigits) {
434 if (fieldType == null) {
435 throw new IllegalArgumentException("Field type must not be null");
436 }
437 if (maxDigits < minDigits) {
438 maxDigits = minDigits;
439 }
440 if (minDigits < 0 || maxDigits <= 0) {
441 throw new IllegalArgumentException();
442 }
443 if (minDigits <= 1) {
444 return append0(new UnpaddedNumber(fieldType, maxDigits, false));
445 } else {
446 return append0(new PaddedNumber(fieldType, maxDigits, false, minDigits));
447 }
448 }
449
450 /**
451 * Instructs the printer to emit a field value as a fixed-width decimal
452 * number (smaller numbers will be left-padded with zeros), and the parser
453 * to expect an unsigned decimal number with the same fixed width.
454 *
455 * @param fieldType type of field to append
456 * @param numDigits the exact number of digits to parse or print, except if
457 * printed value requires more digits
458 * @return this DateTimeFormatterBuilder, for chaining
459 * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
460 * @since 1.5
461 */
462 public DateTimeFormatterBuilder appendFixedDecimal(
463 DateTimeFieldType fieldType, int numDigits) {
464 if (fieldType == null) {
465 throw new IllegalArgumentException("Field type must not be null");
466 }
467 if (numDigits <= 0) {
468 throw new IllegalArgumentException("Illegal number of digits: " + numDigits);
469 }
470 return append0(new FixedNumber(fieldType, numDigits, false));
471 }
472
473 /**
474 * Instructs the printer to emit a field value as a decimal number, and the
475 * parser to expect a signed decimal number.
476 *
477 * @param fieldType type of field to append
478 * @param minDigits minimum number of digits to <i>print</i>
479 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
480 * maximum number of digits to print
481 * @return this DateTimeFormatterBuilder, for chaining
482 * @throws IllegalArgumentException if field type is null
483 */
484 public DateTimeFormatterBuilder appendSignedDecimal(
485 DateTimeFieldType fieldType, int minDigits, int maxDigits) {
486 if (fieldType == null) {
487 throw new IllegalArgumentException("Field type must not be null");
488 }
489 if (maxDigits < minDigits) {
490 maxDigits = minDigits;
491 }
492 if (minDigits < 0 || maxDigits <= 0) {
493 throw new IllegalArgumentException();
494 }
495 if (minDigits <= 1) {
496 return append0(new UnpaddedNumber(fieldType, maxDigits, true));
497 } else {
498 return append0(new PaddedNumber(fieldType, maxDigits, true, minDigits));
499 }
500 }
501
502 /**
503 * Instructs the printer to emit a field value as a fixed-width decimal
504 * number (smaller numbers will be left-padded with zeros), and the parser
505 * to expect an signed decimal number with the same fixed width.
506 *
507 * @param fieldType type of field to append
508 * @param numDigits the exact number of digits to parse or print, except if
509 * printed value requires more digits
510 * @return this DateTimeFormatterBuilder, for chaining
511 * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
512 * @since 1.5
513 */
514 public DateTimeFormatterBuilder appendFixedSignedDecimal(
515 DateTimeFieldType fieldType, int numDigits) {
516 if (fieldType == null) {
517 throw new IllegalArgumentException("Field type must not be null");
518 }
519 if (numDigits <= 0) {
520 throw new IllegalArgumentException("Illegal number of digits: " + numDigits);
521 }
522 return append0(new FixedNumber(fieldType, numDigits, true));
523 }
524
525 /**
526 * Instructs the printer to emit a field value as text, and the
527 * parser to expect text.
528 *
529 * @param fieldType type of field to append
530 * @return this DateTimeFormatterBuilder, for chaining
531 * @throws IllegalArgumentException if field type is null
532 */
533 public DateTimeFormatterBuilder appendText(DateTimeFieldType fieldType) {
534 if (fieldType == null) {
535 throw new IllegalArgumentException("Field type must not be null");
536 }
537 return append0(new TextField(fieldType, false));
538 }
539
540 /**
541 * Instructs the printer to emit a field value as short text, and the
542 * parser to expect text.
543 *
544 * @param fieldType type of field to append
545 * @return this DateTimeFormatterBuilder, for chaining
546 * @throws IllegalArgumentException if field type is null
547 */
548 public DateTimeFormatterBuilder appendShortText(DateTimeFieldType fieldType) {
549 if (fieldType == null) {
550 throw new IllegalArgumentException("Field type must not be null");
551 }
552 return append0(new TextField(fieldType, true));
553 }
554
555 /**
556 * Instructs the printer to emit a remainder of time as a decimal fraction,
557 * without decimal point. For example, if the field is specified as
558 * minuteOfHour and the time is 12:30:45, the value printed is 75. A
559 * decimal point is implied, so the fraction is 0.75, or three-quarters of
560 * a minute.
561 *
562 * @param fieldType type of field to append
563 * @param minDigits minimum number of digits to print.
564 * @param maxDigits maximum number of digits to print or parse.
565 * @return this DateTimeFormatterBuilder, for chaining
566 * @throws IllegalArgumentException if field type is null
567 */
568 public DateTimeFormatterBuilder appendFraction(
569 DateTimeFieldType fieldType, int minDigits, int maxDigits) {
570 if (fieldType == null) {
571 throw new IllegalArgumentException("Field type must not be null");
572 }
573 if (maxDigits < minDigits) {
574 maxDigits = minDigits;
575 }
576 if (minDigits < 0 || maxDigits <= 0) {
577 throw new IllegalArgumentException();
578 }
579 return append0(new Fraction(fieldType, minDigits, maxDigits));
580 }
581
582 /**
583 * Appends the print/parse of a fractional second.
584 * <p>
585 * This reliably handles the case where fractional digits are being handled
586 * beyond a visible decimal point. The digits parsed will always be treated
587 * as the most significant (numerically largest) digits.
588 * Thus '23' will be parsed as 230 milliseconds.
589 * Contrast this behaviour to {@link #appendMillisOfSecond}.
590 * This method does not print or parse the decimal point itself.
591 *
592 * @param minDigits minimum number of digits to print
593 * @param maxDigits maximum number of digits to print or parse
594 * @return this DateTimeFormatterBuilder, for chaining
595 */
596 public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) {
597 return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits);
598 }
599
600 /**
601 * Appends the print/parse of a fractional minute.
602 * <p>
603 * This reliably handles the case where fractional digits are being handled
604 * beyond a visible decimal point. The digits parsed will always be treated
605 * as the most significant (numerically largest) digits.
606 * Thus '23' will be parsed as 0.23 minutes (converted to milliseconds).
607 * This method does not print or parse the decimal point itself.
608 *
609 * @param minDigits minimum number of digits to print
610 * @param maxDigits maximum number of digits to print or parse
611 * @return this DateTimeFormatterBuilder, for chaining
612 */
613 public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) {
614 return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits);
615 }
616
617 /**
618 * Appends the print/parse of a fractional hour.
619 * <p>
620 * This reliably handles the case where fractional digits are being handled
621 * beyond a visible decimal point. The digits parsed will always be treated
622 * as the most significant (numerically largest) digits.
623 * Thus '23' will be parsed as 0.23 hours (converted to milliseconds).
624 * This method does not print or parse the decimal point itself.
625 *
626 * @param minDigits minimum number of digits to print
627 * @param maxDigits maximum number of digits to print or parse
628 * @return this DateTimeFormatterBuilder, for chaining
629 */
630 public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) {
631 return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits);
632 }
633
634 /**
635 * Appends the print/parse of a fractional day.
636 * <p>
637 * This reliably handles the case where fractional digits are being handled
638 * beyond a visible decimal point. The digits parsed will always be treated
639 * as the most significant (numerically largest) digits.
640 * Thus '23' will be parsed as 0.23 days (converted to milliseconds).
641 * This method does not print or parse the decimal point itself.
642 *
643 * @param minDigits minimum number of digits to print
644 * @param maxDigits maximum number of digits to print or parse
645 * @return this DateTimeFormatterBuilder, for chaining
646 */
647 public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) {
648 return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits);
649 }
650
651 /**
652 * Instructs the printer to emit a numeric millisOfSecond field.
653 * <p>
654 * This method will append a field that prints a three digit value.
655 * During parsing the value that is parsed is assumed to be three digits.
656 * If less than three digits are present then they will be counted as the
657 * smallest parts of the millisecond. This is probably not what you want
658 * if you are using the field as a fraction. Instead, a fractional
659 * millisecond should be produced using {@link #appendFractionOfSecond}.
660 *
661 * @param minDigits minimum number of digits to print
662 * @return this DateTimeFormatterBuilder, for chaining
663 */
664 public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) {
665 return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3);
666 }
667
668 /**
669 * Instructs the printer to emit a numeric millisOfDay field.
670 *
671 * @param minDigits minimum number of digits to print
672 * @return this DateTimeFormatterBuilder, for chaining
673 */
674 public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) {
675 return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8);
676 }
677
678 /**
679 * Instructs the printer to emit a numeric secondOfMinute field.
680 *
681 * @param minDigits minimum number of digits to print
682 * @return this DateTimeFormatterBuilder, for chaining
683 */
684 public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) {
685 return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2);
686 }
687
688 /**
689 * Instructs the printer to emit a numeric secondOfDay field.
690 *
691 * @param minDigits minimum number of digits to print
692 * @return this DateTimeFormatterBuilder, for chaining
693 */
694 public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) {
695 return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5);
696 }
697
698 /**
699 * Instructs the printer to emit a numeric minuteOfHour field.
700 *
701 * @param minDigits minimum number of digits to print
702 * @return this DateTimeFormatterBuilder, for chaining
703 */
704 public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) {
705 return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2);
706 }
707
708 /**
709 * Instructs the printer to emit a numeric minuteOfDay field.
710 *
711 * @param minDigits minimum number of digits to print
712 * @return this DateTimeFormatterBuilder, for chaining
713 */
714 public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) {
715 return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4);
716 }
717
718 /**
719 * Instructs the printer to emit a numeric hourOfDay field.
720 *
721 * @param minDigits minimum number of digits to print
722 * @return this DateTimeFormatterBuilder, for chaining
723 */
724 public DateTimeFormatterBuilder appendHourOfDay(int minDigits) {
725 return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2);
726 }
727
728 /**
729 * Instructs the printer to emit a numeric clockhourOfDay field.
730 *
731 * @param minDigits minimum number of digits to print
732 * @return this DateTimeFormatterBuilder, for chaining
733 */
734 public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) {
735 return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2);
736 }
737
738 /**
739 * Instructs the printer to emit a numeric hourOfHalfday field.
740 *
741 * @param minDigits minimum number of digits to print
742 * @return this DateTimeFormatterBuilder, for chaining
743 */
744 public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) {
745 return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2);
746 }
747
748 /**
749 * Instructs the printer to emit a numeric clockhourOfHalfday field.
750 *
751 * @param minDigits minimum number of digits to print
752 * @return this DateTimeFormatterBuilder, for chaining
753 */
754 public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) {
755 return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2);
756 }
757
758 /**
759 * Instructs the printer to emit a numeric dayOfWeek field.
760 *
761 * @param minDigits minimum number of digits to print
762 * @return this DateTimeFormatterBuilder, for chaining
763 */
764 public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) {
765 return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1);
766 }
767
768 /**
769 * Instructs the printer to emit a numeric dayOfMonth field.
770 *
771 * @param minDigits minimum number of digits to print
772 * @return this DateTimeFormatterBuilder, for chaining
773 */
774 public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) {
775 return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2);
776 }
777
778 /**
779 * Instructs the printer to emit a numeric dayOfYear field.
780 *
781 * @param minDigits minimum number of digits to print
782 * @return this DateTimeFormatterBuilder, for chaining
783 */
784 public DateTimeFormatterBuilder appendDayOfYear(int minDigits) {
785 return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3);
786 }
787
788 /**
789 * Instructs the printer to emit a numeric weekOfWeekyear field.
790 *
791 * @param minDigits minimum number of digits to print
792 * @return this DateTimeFormatterBuilder, for chaining
793 */
794 public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) {
795 return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2);
796 }
797
798 /**
799 * Instructs the printer to emit a numeric weekyear field.
800 *
801 * @param minDigits minimum number of digits to <i>print</i>
802 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
803 * maximum number of digits to print
804 * @return this DateTimeFormatterBuilder, for chaining
805 */
806 public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) {
807 return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits);
808 }
809
810 /**
811 * Instructs the printer to emit a numeric monthOfYear field.
812 *
813 * @param minDigits minimum number of digits to print
814 * @return this DateTimeFormatterBuilder, for chaining
815 */
816 public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) {
817 return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2);
818 }
819
820 /**
821 * Instructs the printer to emit a numeric year field.
822 *
823 * @param minDigits minimum number of digits to <i>print</i>
824 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
825 * maximum number of digits to print
826 * @return this DateTimeFormatterBuilder, for chaining
827 */
828 public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) {
829 return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits);
830 }
831
832 /**
833 * Instructs the printer to emit a numeric year field which always prints
834 * and parses two digits. A pivot year is used during parsing to determine
835 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
836 *
837 * <pre>
838 * pivot supported range 00 is 20 is 40 is 60 is 80 is
839 * ---------------------------------------------------------------
840 * 1950 1900..1999 1900 1920 1940 1960 1980
841 * 1975 1925..2024 2000 2020 1940 1960 1980
842 * 2000 1950..2049 2000 2020 2040 1960 1980
843 * 2025 1975..2074 2000 2020 2040 2060 1980
844 * 2050 2000..2099 2000 2020 2040 2060 2080
845 * </pre>
846 *
847 * @param pivot pivot year to use when parsing
848 * @return this DateTimeFormatterBuilder, for chaining
849 */
850 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) {
851 return appendTwoDigitYear(pivot, false);
852 }
853
854 /**
855 * Instructs the printer to emit a numeric year field which always prints
856 * two digits. A pivot year is used during parsing to determine the range
857 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
858 * parse is instructed to be lenient and the digit count is not two, it is
859 * treated as an absolute year. With lenient parsing, specifying a positive
860 * or negative sign before the year also makes it absolute.
861 *
862 * @param pivot pivot year to use when parsing
863 * @param lenientParse when true, if digit count is not two, it is treated
864 * as an absolute year
865 * @return this DateTimeFormatterBuilder, for chaining
866 * @since 1.1
867 */
868 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) {
869 return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse));
870 }
871
872 /**
873 * Instructs the printer to emit a numeric weekyear field which always prints
874 * and parses two digits. A pivot year is used during parsing to determine
875 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
876 *
877 * <pre>
878 * pivot supported range 00 is 20 is 40 is 60 is 80 is
879 * ---------------------------------------------------------------
880 * 1950 1900..1999 1900 1920 1940 1960 1980
881 * 1975 1925..2024 2000 2020 1940 1960 1980
882 * 2000 1950..2049 2000 2020 2040 1960 1980
883 * 2025 1975..2074 2000 2020 2040 2060 1980
884 * 2050 2000..2099 2000 2020 2040 2060 2080
885 * </pre>
886 *
887 * @param pivot pivot weekyear to use when parsing
888 * @return this DateTimeFormatterBuilder, for chaining
889 */
890 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) {
891 return appendTwoDigitWeekyear(pivot, false);
892 }
893
894 /**
895 * Instructs the printer to emit a numeric weekyear field which always prints
896 * two digits. A pivot year is used during parsing to determine the range
897 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
898 * parse is instructed to be lenient and the digit count is not two, it is
899 * treated as an absolute weekyear. With lenient parsing, specifying a positive
900 * or negative sign before the weekyear also makes it absolute.
901 *
902 * @param pivot pivot weekyear to use when parsing
903 * @param lenientParse when true, if digit count is not two, it is treated
904 * as an absolute weekyear
905 * @return this DateTimeFormatterBuilder, for chaining
906 * @since 1.1
907 */
908 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) {
909 return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse));
910 }
911
912 /**
913 * Instructs the printer to emit a numeric yearOfEra field.
914 *
915 * @param minDigits minimum number of digits to <i>print</i>
916 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
917 * maximum number of digits to print
918 * @return this DateTimeFormatterBuilder, for chaining
919 */
920 public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) {
921 return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits);
922 }
923
924 /**
925 * Instructs the printer to emit a numeric year of century field.
926 *
927 * @param minDigits minimum number of digits to print
928 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
929 * maximum number of digits to print
930 * @return this DateTimeFormatterBuilder, for chaining
931 */
932 public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) {
933 return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits);
934 }
935
936 /**
937 * Instructs the printer to emit a numeric century of era field.
938 *
939 * @param minDigits minimum number of digits to print
940 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
941 * maximum number of digits to print
942 * @return this DateTimeFormatterBuilder, for chaining
943 */
944 public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) {
945 return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits);
946 }
947
948 /**
949 * Instructs the printer to emit a locale-specific AM/PM text, and the
950 * parser to expect it. The parser is case-insensitive.
951 *
952 * @return this DateTimeFormatterBuilder, for chaining
953 */
954 public DateTimeFormatterBuilder appendHalfdayOfDayText() {
955 return appendText(DateTimeFieldType.halfdayOfDay());
956 }
957
958 /**
959 * Instructs the printer to emit a locale-specific dayOfWeek text. The
960 * parser will accept a long or short dayOfWeek text, case-insensitive.
961 *
962 * @return this DateTimeFormatterBuilder, for chaining
963 */
964 public DateTimeFormatterBuilder appendDayOfWeekText() {
965 return appendText(DateTimeFieldType.dayOfWeek());
966 }
967
968 /**
969 * Instructs the printer to emit a short locale-specific dayOfWeek
970 * text. The parser will accept a long or short dayOfWeek text,
971 * case-insensitive.
972 *
973 * @return this DateTimeFormatterBuilder, for chaining
974 */
975 public DateTimeFormatterBuilder appendDayOfWeekShortText() {
976 return appendShortText(DateTimeFieldType.dayOfWeek());
977 }
978
979 /**
980 * Instructs the printer to emit a short locale-specific monthOfYear
981 * text. The parser will accept a long or short monthOfYear text,
982 * case-insensitive.
983 *
984 * @return this DateTimeFormatterBuilder, for chaining
985 */
986 public DateTimeFormatterBuilder appendMonthOfYearText() {
987 return appendText(DateTimeFieldType.monthOfYear());
988 }
989
990 /**
991 * Instructs the printer to emit a locale-specific monthOfYear text. The
992 * parser will accept a long or short monthOfYear text, case-insensitive.
993 *
994 * @return this DateTimeFormatterBuilder, for chaining
995 */
996 public DateTimeFormatterBuilder appendMonthOfYearShortText() {
997 return appendShortText(DateTimeFieldType.monthOfYear());
998 }
999
1000 /**
1001 * Instructs the printer to emit a locale-specific era text (BC/AD), and
1002 * the parser to expect it. The parser is case-insensitive.
1003 *
1004 * @return this DateTimeFormatterBuilder, for chaining
1005 */
1006 public DateTimeFormatterBuilder appendEraText() {
1007 return appendText(DateTimeFieldType.era());
1008 }
1009
1010 /**
1011 * Instructs the printer to emit a locale-specific time zone name.
1012 * Using this method prevents parsing, because time zone names are not unique.
1013 * See {@link #appendTimeZoneName(Map)}.
1014 *
1015 * @return this DateTimeFormatterBuilder, for chaining
1016 */
1017 public DateTimeFormatterBuilder appendTimeZoneName() {
1018 return append0(new TimeZoneName(TimeZoneName.LONG_NAME, null), null);
1019 }
1020
1021 /**
1022 * Instructs the printer to emit a locale-specific time zone name, providing a lookup for parsing.
1023 * Time zone names are not unique, thus the API forces you to supply the lookup.
1024 * The names are searched in the order of the map, thus it is strongly recommended
1025 * to use a {@code LinkedHashMap} or similar.
1026 *
1027 * @param parseLookup the table of names, not null
1028 * @return this DateTimeFormatterBuilder, for chaining
1029 */
1030 public DateTimeFormatterBuilder appendTimeZoneName(Map<String, DateTimeZone> parseLookup) {
1031 TimeZoneName pp = new TimeZoneName(TimeZoneName.LONG_NAME, parseLookup);
1032 return append0(pp, pp);
1033 }
1034
1035 /**
1036 * Instructs the printer to emit a short locale-specific time zone name.
1037 * Using this method prevents parsing, because time zone names are not unique.
1038 * See {@link #appendTimeZoneShortName(Map)}.
1039 *
1040 * @return this DateTimeFormatterBuilder, for chaining
1041 */
1042 public DateTimeFormatterBuilder appendTimeZoneShortName() {
1043 return append0(new TimeZoneName(TimeZoneName.SHORT_NAME, null), null);
1044 }
1045
1046 /**
1047 * Instructs the printer to emit a short locale-specific time zone
1048 * name, providing a lookup for parsing.
1049 * Time zone names are not unique, thus the API forces you to supply the lookup.
1050 * The names are searched in the order of the map, thus it is strongly recommended
1051 * to use a {@code LinkedHashMap} or similar.
1052 *
1053 * @param parseLookup the table of names, null to use the {@link DateTimeUtils#getDefaultTimeZoneNames() default names}
1054 * @return this DateTimeFormatterBuilder, for chaining
1055 */
1056 public DateTimeFormatterBuilder appendTimeZoneShortName(Map<String, DateTimeZone> parseLookup) {
1057 TimeZoneName pp = new TimeZoneName(TimeZoneName.SHORT_NAME, parseLookup);
1058 return append0(pp, pp);
1059 }
1060
1061 /**
1062 * Instructs the printer to emit the identifier of the time zone.
1063 * From version 2.0, this field can be parsed.
1064 *
1065 * @return this DateTimeFormatterBuilder, for chaining
1066 */
1067 public DateTimeFormatterBuilder appendTimeZoneId() {
1068 return append0(TimeZoneId.INSTANCE, TimeZoneId.INSTANCE);
1069 }
1070
1071 /**
1072 * Instructs the printer to emit text and numbers to display time zone
1073 * offset from UTC. A parser will use the parsed time zone offset to adjust
1074 * the datetime.
1075 * <p>
1076 * If zero offset text is supplied, then it will be printed when the zone is zero.
1077 * During parsing, either the zero offset text, or the offset will be parsed.
1078 *
1079 * @param zeroOffsetText the text to use if time zone offset is zero. If
1080 * null, offset is always shown.
1081 * @param showSeparators if true, prints ':' separator before minute and
1082 * second field and prints '.' separator before fraction field.
1083 * @param minFields minimum number of fields to print, stopping when no
1084 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1085 * @param maxFields maximum number of fields to print
1086 * @return this DateTimeFormatterBuilder, for chaining
1087 */
1088 public DateTimeFormatterBuilder appendTimeZoneOffset(
1089 String zeroOffsetText, boolean showSeparators,
1090 int minFields, int maxFields) {
1091 return append0(new TimeZoneOffset
1092 (zeroOffsetText, zeroOffsetText, showSeparators, minFields, maxFields));
1093 }
1094
1095 /**
1096 * Instructs the printer to emit text and numbers to display time zone
1097 * offset from UTC. A parser will use the parsed time zone offset to adjust
1098 * the datetime.
1099 * <p>
1100 * If zero offset print text is supplied, then it will be printed when the zone is zero.
1101 * If zero offset parse text is supplied, then either it or the offset will be parsed.
1102 *
1103 * @param zeroOffsetPrintText the text to print if time zone offset is zero. If
1104 * null, offset is always shown.
1105 * @param zeroOffsetParseText the text to optionally parse to indicate that the time
1106 * zone offset is zero. If null, then always use the offset.
1107 * @param showSeparators if true, prints ':' separator before minute and
1108 * second field and prints '.' separator before fraction field.
1109 * @param minFields minimum number of fields to print, stopping when no
1110 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1111 * @param maxFields maximum number of fields to print
1112 * @return this DateTimeFormatterBuilder, for chaining
1113 * @since 2.0
1114 */
1115 public DateTimeFormatterBuilder appendTimeZoneOffset(
1116 String zeroOffsetPrintText, String zeroOffsetParseText, boolean showSeparators,
1117 int minFields, int maxFields) {
1118 return append0(new TimeZoneOffset
1119 (zeroOffsetPrintText, zeroOffsetParseText, showSeparators, minFields, maxFields));
1120 }
1121
1122 //-----------------------------------------------------------------------
1123 /**
1124 * Calls upon {@link DateTimeFormat} to parse the pattern and append the
1125 * results into this builder.
1126 *
1127 * @param pattern pattern specification
1128 * @throws IllegalArgumentException if the pattern is invalid
1129 * @see DateTimeFormat
1130 */
1131 public DateTimeFormatterBuilder appendPattern(String pattern) {
1132 DateTimeFormat.appendPatternTo(this, pattern);
1133 return this;
1134 }
1135
1136 //-----------------------------------------------------------------------
1137 private Object getFormatter() {
1138 Object f = iFormatter;
1139
1140 if (f == null) {
1141 if (iElementPairs.size() == 2) {
1142 Object printer = iElementPairs.get(0);
1143 Object parser = iElementPairs.get(1);
1144
1145 if (printer != null) {
1146 if (printer == parser || parser == null) {
1147 f = printer;
1148 }
1149 } else {
1150 f = parser;
1151 }
1152 }
1153
1154 if (f == null) {
1155 f = new Composite(iElementPairs);
1156 }
1157
1158 iFormatter = f;
1159 }
1160
1161 return f;
1162 }
1163
1164 private boolean isPrinter(Object f) {
1165 if (f instanceof InternalPrinter) {
1166 if (f instanceof Composite) {
1167 return ((Composite)f).isPrinter();
1168 }
1169 return true;
1170 }
1171 return false;
1172 }
1173
1174 private boolean isParser(Object f) {
1175 if (f instanceof InternalParser) {
1176 if (f instanceof Composite) {
1177 return ((Composite)f).isParser();
1178 }
1179 return true;
1180 }
1181 return false;
1182 }
1183
1184 private boolean isFormatter(Object f) {
1185 return (isPrinter(f) || isParser(f));
1186 }
1187
1188 static void appendUnknownString(Appendable appendable, int len) throws IOException {
1189 for (int i = len; --i >= 0;) {
1190 appendable.append('\ufffd');
1191 }
1192 }
1193
1194 //-----------------------------------------------------------------------
1195 static class CharacterLiteral
1196 implements InternalPrinter, InternalParser {
1197
1198 private final char iValue;
1199
1200 CharacterLiteral(char value) {
1201 super();
1202 iValue = value;
1203 }
1204
1205 public int estimatePrintedLength() {
1206 return 1;
1207 }
1208
1209 public void printTo(
1210 Appendable appendable, long instant, Chronology chrono,
1211 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1212 appendable.append(iValue);
1213 }
1214
1215 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1216 appendable.append(iValue);
1217 }
1218
1219 public int estimateParsedLength() {
1220 return 1;
1221 }
1222
1223 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1224 if (position >= text.length()) {
1225 return ~position;
1226 }
1227
1228 char a = text.charAt(position);
1229 char b = iValue;
1230
1231 if (a != b) {
1232 a = Character.toUpperCase(a);
1233 b = Character.toUpperCase(b);
1234 if (a != b) {
1235 a = Character.toLowerCase(a);
1236 b = Character.toLowerCase(b);
1237 if (a != b) {
1238 return ~position;
1239 }
1240 }
1241 }
1242
1243 return position + 1;
1244 }
1245 }
1246
1247 //-----------------------------------------------------------------------
1248 static class StringLiteral
1249 implements InternalPrinter, InternalParser {
1250
1251 private final String iValue;
1252
1253 StringLiteral(String value) {
1254 super();
1255 iValue = value;
1256 }
1257
1258 public int estimatePrintedLength() {
1259 return iValue.length();
1260 }
1261
1262 public void printTo(
1263 Appendable appendable, long instant, Chronology chrono,
1264 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1265 appendable.append(iValue);
1266 }
1267
1268 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1269 appendable.append(iValue);
1270 }
1271
1272 public int estimateParsedLength() {
1273 return iValue.length();
1274 }
1275
1276 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1277 if (csStartsWithIgnoreCase(text, position, iValue)) {
1278 return position + iValue.length();
1279 }
1280 return ~position;
1281 }
1282 }
1283
1284 //-----------------------------------------------------------------------
1285 static abstract class NumberFormatter
1286 implements InternalPrinter, InternalParser {
1287 protected final DateTimeFieldType iFieldType;
1288 protected final int iMaxParsedDigits;
1289 protected final boolean iSigned;
1290
1291 NumberFormatter(DateTimeFieldType fieldType,
1292 int maxParsedDigits, boolean signed) {
1293 super();
1294 iFieldType = fieldType;
1295 iMaxParsedDigits = maxParsedDigits;
1296 iSigned = signed;
1297 }
1298
1299 public int estimateParsedLength() {
1300 return iMaxParsedDigits;
1301 }
1302
1303 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1304 int limit = Math.min(iMaxParsedDigits, text.length() - position);
1305
1306 boolean negative = false;
1307 int length = 0;
1308 while (length < limit) {
1309 char c = text.charAt(position + length);
1310 if (length == 0 && (c == '-' || c == '+') && iSigned) {
1311 negative = c == '-';
1312
1313 // Next character must be a digit.
1314 if (length + 1 >= limit ||
1315 (c = text.charAt(position + length + 1)) < '0' || c > '9')
1316 {
1317 break;
1318 }
1319
1320 if (negative) {
1321 length++;
1322 } else {
1323 // Skip the '+' for parseInt to succeed.
1324 position++;
1325 }
1326 // Expand the limit to disregard the sign character.
1327 limit = Math.min(limit + 1, text.length() - position);
1328 continue;
1329 }
1330 if (c < '0' || c > '9') {
1331 break;
1332 }
1333 length++;
1334 }
1335
1336 if (length == 0) {
1337 return ~position;
1338 }
1339
1340 int value;
1341 if (length >= 9) {
1342 // Since value may exceed integer limits, use stock parser
1343 // which checks for this.
1344 value = Integer.parseInt(text.subSequence(position, position += length).toString());
1345 } else {
1346 int i = position;
1347 if (negative) {
1348 i++;
1349 }
1350 try {
1351 value = text.charAt(i++) - '0';
1352 } catch (StringIndexOutOfBoundsException e) {
1353 return ~position;
1354 }
1355 position += length;
1356 while (i < position) {
1357 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1358 }
1359 if (negative) {
1360 value = -value;
1361 }
1362 }
1363
1364 bucket.saveField(iFieldType, value);
1365 return position;
1366 }
1367 }
1368
1369 //-----------------------------------------------------------------------
1370 static class UnpaddedNumber extends NumberFormatter {
1371
1372 protected UnpaddedNumber(DateTimeFieldType fieldType,
1373 int maxParsedDigits, boolean signed)
1374 {
1375 super(fieldType, maxParsedDigits, signed);
1376 }
1377
1378 public int estimatePrintedLength() {
1379 return iMaxParsedDigits;
1380 }
1381
1382 public void printTo(
1383 Appendable appendable, long instant, Chronology chrono,
1384 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1385 try {
1386 DateTimeField field = iFieldType.getField(chrono);
1387 FormatUtils.appendUnpaddedInteger(appendable, field.get(instant));
1388 } catch (RuntimeException e) {
1389 appendable.append('\ufffd');
1390 }
1391 }
1392
1393 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1394 if (partial.isSupported(iFieldType)) {
1395 try {
1396 FormatUtils.appendUnpaddedInteger(appendable, partial.get(iFieldType));
1397 } catch (RuntimeException e) {
1398 appendable.append('\ufffd');
1399 }
1400 } else {
1401 appendable.append('\ufffd');
1402 }
1403 }
1404 }
1405
1406 //-----------------------------------------------------------------------
1407 static class PaddedNumber extends NumberFormatter {
1408
1409 protected final int iMinPrintedDigits;
1410
1411 protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits,
1412 boolean signed, int minPrintedDigits)
1413 {
1414 super(fieldType, maxParsedDigits, signed);
1415 iMinPrintedDigits = minPrintedDigits;
1416 }
1417
1418 public int estimatePrintedLength() {
1419 return iMaxParsedDigits;
1420 }
1421
1422 public void printTo(
1423 Appendable appendable, long instant, Chronology chrono,
1424 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1425 try {
1426 DateTimeField field = iFieldType.getField(chrono);
1427 FormatUtils.appendPaddedInteger(appendable, field.get(instant), iMinPrintedDigits);
1428 } catch (RuntimeException e) {
1429 appendUnknownString(appendable, iMinPrintedDigits);
1430 }
1431 }
1432
1433 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1434 if (partial.isSupported(iFieldType)) {
1435 try {
1436 FormatUtils.appendPaddedInteger(appendable, partial.get(iFieldType), iMinPrintedDigits);
1437 } catch (RuntimeException e) {
1438 appendUnknownString(appendable, iMinPrintedDigits);
1439 }
1440 } else {
1441 appendUnknownString(appendable, iMinPrintedDigits);
1442 }
1443 }
1444 }
1445
1446 //-----------------------------------------------------------------------
1447 static class FixedNumber extends PaddedNumber {
1448
1449 protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) {
1450 super(fieldType, numDigits, signed, numDigits);
1451 }
1452
1453 @Override
1454 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1455 int newPos = super.parseInto(bucket, text, position);
1456 if (newPos < 0) {
1457 return newPos;
1458 }
1459 int expectedPos = position + iMaxParsedDigits;
1460 if (newPos != expectedPos) {
1461 if (iSigned) {
1462 char c = text.charAt(position);
1463 if (c == '-' || c == '+') {
1464 expectedPos++;
1465 }
1466 }
1467 if (newPos > expectedPos) {
1468 // The failure is at the position of the first extra digit.
1469 return ~(expectedPos + 1);
1470 } else if (newPos < expectedPos) {
1471 // The failure is at the position where the next digit should be.
1472 return ~newPos;
1473 }
1474 }
1475 return newPos;
1476 }
1477 }
1478
1479 //-----------------------------------------------------------------------
1480 static class TwoDigitYear
1481 implements InternalPrinter, InternalParser {
1482
1483 /** The field to print/parse. */
1484 private final DateTimeFieldType iType;
1485 /** The pivot year. */
1486 private final int iPivot;
1487 private final boolean iLenientParse;
1488
1489 TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) {
1490 super();
1491 iType = type;
1492 iPivot = pivot;
1493 iLenientParse = lenientParse;
1494 }
1495
1496 public int estimateParsedLength() {
1497 return iLenientParse ? 4 : 2;
1498 }
1499
1500 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1501 int limit = text.length() - position;
1502
1503 if (!iLenientParse) {
1504 limit = Math.min(2, limit);
1505 if (limit < 2) {
1506 return ~position;
1507 }
1508 } else {
1509 boolean hasSignChar = false;
1510 boolean negative = false;
1511 int length = 0;
1512 while (length < limit) {
1513 char c = text.charAt(position + length);
1514 if (length == 0 && (c == '-' || c == '+')) {
1515 hasSignChar = true;
1516 negative = c == '-';
1517 if (negative) {
1518 length++;
1519 } else {
1520 // Skip the '+' for parseInt to succeed.
1521 position++;
1522 limit--;
1523 }
1524 continue;
1525 }
1526 if (c < '0' || c > '9') {
1527 break;
1528 }
1529 length++;
1530 }
1531
1532 if (length == 0) {
1533 return ~position;
1534 }
1535
1536 if (hasSignChar || length != 2) {
1537 int value;
1538 if (length >= 9) {
1539 // Since value may exceed integer limits, use stock
1540 // parser which checks for this.
1541 value = Integer.parseInt(text.subSequence(position, position += length).toString());
1542 } else {
1543 int i = position;
1544 if (negative) {
1545 i++;
1546 }
1547 try {
1548 value = text.charAt(i++) - '0';
1549 } catch (StringIndexOutOfBoundsException e) {
1550 return ~position;
1551 }
1552 position += length;
1553 while (i < position) {
1554 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1555 }
1556 if (negative) {
1557 value = -value;
1558 }
1559 }
1560
1561 bucket.saveField(iType, value);
1562 return position;
1563 }
1564 }
1565
1566 int year;
1567 char c = text.charAt(position);
1568 if (c < '0' || c > '9') {
1569 return ~position;
1570 }
1571 year = c - '0';
1572 c = text.charAt(position + 1);
1573 if (c < '0' || c > '9') {
1574 return ~position;
1575 }
1576 year = ((year << 3) + (year << 1)) + c - '0';
1577
1578 int pivot = iPivot;
1579 // If the bucket pivot year is non-null, use that when parsing
1580 if (bucket.getPivotYear() != null) {
1581 pivot = bucket.getPivotYear().intValue();
1582 }
1583
1584 int low = pivot - 50;
1585
1586 int t;
1587 if (low >= 0) {
1588 t = low % 100;
1589 } else {
1590 t = 99 + ((low + 1) % 100);
1591 }
1592
1593 year += low + ((year < t) ? 100 : 0) - t;
1594
1595 bucket.saveField(iType, year);
1596 return position + 2;
1597 }
1598
1599 public int estimatePrintedLength() {
1600 return 2;
1601 }
1602
1603 public void printTo(
1604 Appendable appendable, long instant, Chronology chrono,
1605 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1606 int year = getTwoDigitYear(instant, chrono);
1607 if (year < 0) {
1608 appendable.append('\ufffd');
1609 appendable.append('\ufffd');
1610 } else {
1611 FormatUtils.appendPaddedInteger(appendable, year, 2);
1612 }
1613 }
1614
1615 private int getTwoDigitYear(long instant, Chronology chrono) {
1616 try {
1617 int year = iType.getField(chrono).get(instant);
1618 if (year < 0) {
1619 year = -year;
1620 }
1621 return year % 100;
1622 } catch (RuntimeException e) {
1623 return -1;
1624 }
1625 }
1626
1627 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1628 int year = getTwoDigitYear(partial);
1629 if (year < 0) {
1630 appendable.append('\ufffd');
1631 appendable.append('\ufffd');
1632 } else {
1633 FormatUtils.appendPaddedInteger(appendable, year, 2);
1634 }
1635 }
1636
1637 private int getTwoDigitYear(ReadablePartial partial) {
1638 if (partial.isSupported(iType)) {
1639 try {
1640 int year = partial.get(iType);
1641 if (year < 0) {
1642 year = -year;
1643 }
1644 return year % 100;
1645 } catch (RuntimeException e) {}
1646 }
1647 return -1;
1648 }
1649 }
1650
1651 //-----------------------------------------------------------------------
1652 static class TextField
1653 implements InternalPrinter, InternalParser {
1654
1655 private static Map<Locale, Map<DateTimeFieldType, Object[]>> cParseCache =
1656 new ConcurrentHashMap<Locale, Map<DateTimeFieldType, Object[]>>();
1657 private final DateTimeFieldType iFieldType;
1658 private final boolean iShort;
1659
1660 TextField(DateTimeFieldType fieldType, boolean isShort) {
1661 super();
1662 iFieldType = fieldType;
1663 iShort = isShort;
1664 }
1665
1666 public int estimatePrintedLength() {
1667 return iShort ? 6 : 20;
1668 }
1669
1670 public void printTo(
1671 Appendable appendable, long instant, Chronology chrono,
1672 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1673 try {
1674 appendable.append(print(instant, chrono, locale));
1675 } catch (RuntimeException e) {
1676 appendable.append('\ufffd');
1677 }
1678 }
1679
1680 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1681 try {
1682 appendable.append(print(partial, locale));
1683 } catch (RuntimeException e) {
1684 appendable.append('\ufffd');
1685 }
1686 }
1687
1688 private String print(long instant, Chronology chrono, Locale locale) {
1689 DateTimeField field = iFieldType.getField(chrono);
1690 if (iShort) {
1691 return field.getAsShortText(instant, locale);
1692 } else {
1693 return field.getAsText(instant, locale);
1694 }
1695 }
1696
1697 private String print(ReadablePartial partial, Locale locale) {
1698 if (partial.isSupported(iFieldType)) {
1699 DateTimeField field = iFieldType.getField(partial.getChronology());
1700 if (iShort) {
1701 return field.getAsShortText(partial, locale);
1702 } else {
1703 return field.getAsText(partial, locale);
1704 }
1705 } else {
1706 return "\ufffd";
1707 }
1708 }
1709
1710 public int estimateParsedLength() {
1711 return estimatePrintedLength();
1712 }
1713
1714 @SuppressWarnings("unchecked")
1715 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1716 Locale locale = bucket.getLocale();
1717 // handle languages which might have non ASCII A-Z or punctuation
1718 // bug 1788282
1719 Map<String, Boolean> validValues = null;
1720 int maxLength = 0;
1721 Map<DateTimeFieldType, Object[]> innerMap = cParseCache.get(locale);
1722 if (innerMap == null) {
1723 innerMap = new ConcurrentHashMap<DateTimeFieldType, Object[]>();
1724 cParseCache.put(locale, innerMap);
1725 }
1726 Object[] array = innerMap.get(iFieldType);
1727 if (array == null) {
1728 validValues = new ConcurrentHashMap<String, Boolean>(32); // use map as no concurrent Set
1729 MutableDateTime dt = new MutableDateTime(0L, DateTimeZone.UTC);
1730 Property property = dt.property(iFieldType);
1731 int min = property.getMinimumValueOverall();
1732 int max = property.getMaximumValueOverall();
1733 if (max - min > 32) { // protect against invalid fields
1734 return ~position;
1735 }
1736 maxLength = property.getMaximumTextLength(locale);
1737 for (int i = min; i <= max; i++) {
1738 property.set(i);
1739 validValues.put(property.getAsShortText(locale), Boolean.TRUE);
1740 validValues.put(property.getAsShortText(locale).toLowerCase(locale), Boolean.TRUE);
1741 validValues.put(property.getAsShortText(locale).toUpperCase(locale), Boolean.TRUE);
1742 validValues.put(property.getAsText(locale), Boolean.TRUE);
1743 validValues.put(property.getAsText(locale).toLowerCase(locale), Boolean.TRUE);
1744 validValues.put(property.getAsText(locale).toUpperCase(locale), Boolean.TRUE);
1745 }
1746 if ("en".equals(locale.getLanguage()) && iFieldType == DateTimeFieldType.era()) {
1747 // hack to support for parsing "BCE" and "CE" if the language is English
1748 validValues.put("BCE", Boolean.TRUE);
1749 validValues.put("bce", Boolean.TRUE);
1750 validValues.put("CE", Boolean.TRUE);
1751 validValues.put("ce", Boolean.TRUE);
1752 maxLength = 3;
1753 }
1754 array = new Object[] {validValues, Integer.valueOf(maxLength)};
1755 innerMap.put(iFieldType, array);
1756 } else {
1757 validValues = (Map<String, Boolean>) array[0];
1758 maxLength = ((Integer) array[1]).intValue();
1759 }
1760 // match the longest string first using our knowledge of the max length
1761 int limit = Math.min(text.length(), position + maxLength);
1762 for (int i = limit; i > position; i--) {
1763 String match = text.subSequence(position, i).toString();
1764 if (validValues.containsKey(match)) {
1765 bucket.saveField(iFieldType, match, locale);
1766 return i;
1767 }
1768 }
1769 return ~position;
1770 }
1771 }
1772
1773 //-----------------------------------------------------------------------
1774 static class Fraction
1775 implements InternalPrinter, InternalParser {
1776
1777 private final DateTimeFieldType iFieldType;
1778 protected int iMinDigits;
1779 protected int iMaxDigits;
1780
1781 protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) {
1782 super();
1783 iFieldType = fieldType;
1784 // Limit the precision requirements.
1785 if (maxDigits > 18) {
1786 maxDigits = 18;
1787 }
1788 iMinDigits = minDigits;
1789 iMaxDigits = maxDigits;
1790 }
1791
1792 public int estimatePrintedLength() {
1793 return iMaxDigits;
1794 }
1795
1796 public void printTo(
1797 Appendable appendable, long instant, Chronology chrono,
1798 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1799 printTo(appendable, instant, chrono);
1800 }
1801
1802 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1803 // removed check whether field is supported, as input field is typically
1804 // secondOfDay which is unsupported by TimeOfDay
1805 long millis = partial.getChronology().set(partial, 0L);
1806 printTo(appendable, millis, partial.getChronology());
1807 }
1808
1809 protected void printTo(Appendable appendable, long instant, Chronology chrono)
1810 throws IOException
1811 {
1812 DateTimeField field = iFieldType.getField(chrono);
1813 int minDigits = iMinDigits;
1814
1815 long fraction;
1816 try {
1817 fraction = field.remainder(instant);
1818 } catch (RuntimeException e) {
1819 appendUnknownString(appendable, minDigits);
1820 return;
1821 }
1822
1823 if (fraction == 0) {
1824 while (--minDigits >= 0) {
1825 appendable.append('0');
1826 }
1827 return;
1828 }
1829
1830 String str;
1831 long[] fractionData = getFractionData(fraction, field);
1832 long scaled = fractionData[0];
1833 int maxDigits = (int) fractionData[1];
1834
1835 if ((scaled & 0x7fffffff) == scaled) {
1836 str = Integer.toString((int) scaled);
1837 } else {
1838 str = Long.toString(scaled);
1839 }
1840
1841 int length = str.length();
1842 int digits = maxDigits;
1843 while (length < digits) {
1844 appendable.append('0');
1845 minDigits--;
1846 digits--;
1847 }
1848
1849 if (minDigits < digits) {
1850 // Chop off as many trailing zero digits as necessary.
1851 while (minDigits < digits) {
1852 if (length <= 1 || str.charAt(length - 1) != '0') {
1853 break;
1854 }
1855 digits--;
1856 length--;
1857 }
1858 if (length < str.length()) {
1859 for (int i=0; i<length; i++) {
1860 appendable.append(str.charAt(i));
1861 }
1862 return;
1863 }
1864 }
1865
1866 appendable.append(str);
1867 }
1868
1869 private long[] getFractionData(long fraction, DateTimeField field) {
1870 long rangeMillis = field.getDurationField().getUnitMillis();
1871 long scalar;
1872 int maxDigits = iMaxDigits;
1873 while (true) {
1874 switch (maxDigits) {
1875 default: scalar = 1L; break;
1876 case 1: scalar = 10L; break;
1877 case 2: scalar = 100L; break;
1878 case 3: scalar = 1000L; break;
1879 case 4: scalar = 10000L; break;
1880 case 5: scalar = 100000L; break;
1881 case 6: scalar = 1000000L; break;
1882 case 7: scalar = 10000000L; break;
1883 case 8: scalar = 100000000L; break;
1884 case 9: scalar = 1000000000L; break;
1885 case 10: scalar = 10000000000L; break;
1886 case 11: scalar = 100000000000L; break;
1887 case 12: scalar = 1000000000000L; break;
1888 case 13: scalar = 10000000000000L; break;
1889 case 14: scalar = 100000000000000L; break;
1890 case 15: scalar = 1000000000000000L; break;
1891 case 16: scalar = 10000000000000000L; break;
1892 case 17: scalar = 100000000000000000L; break;
1893 case 18: scalar = 1000000000000000000L; break;
1894 }
1895 if (((rangeMillis * scalar) / scalar) == rangeMillis) {
1896 break;
1897 }
1898 // Overflowed: scale down.
1899 maxDigits--;
1900 }
1901
1902 return new long[] {fraction * scalar / rangeMillis, maxDigits};
1903 }
1904
1905 public int estimateParsedLength() {
1906 return iMaxDigits;
1907 }
1908
1909 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1910 DateTimeField field = iFieldType.getField(bucket.getChronology());
1911
1912 int limit = Math.min(iMaxDigits, text.length() - position);
1913
1914 long value = 0;
1915 long n = field.getDurationField().getUnitMillis() * 10;
1916 int length = 0;
1917 while (length < limit) {
1918 char c = text.charAt(position + length);
1919 if (c < '0' || c > '9') {
1920 break;
1921 }
1922 length++;
1923 long nn = n / 10;
1924 value += (c - '0') * nn;
1925 n = nn;
1926 }
1927
1928 value /= 10;
1929
1930 if (length == 0) {
1931 return ~position;
1932 }
1933
1934 if (value > Integer.MAX_VALUE) {
1935 return ~position;
1936 }
1937
1938 DateTimeField parseField = new PreciseDateTimeField(
1939 DateTimeFieldType.millisOfSecond(),
1940 MillisDurationField.INSTANCE,
1941 field.getDurationField());
1942
1943 bucket.saveField(parseField, (int) value);
1944
1945 return position + length;
1946 }
1947 }
1948
1949 //-----------------------------------------------------------------------
1950 static class TimeZoneOffset
1951 implements InternalPrinter, InternalParser {
1952
1953 private final String iZeroOffsetPrintText;
1954 private final String iZeroOffsetParseText;
1955 private final boolean iShowSeparators;
1956 private final int iMinFields;
1957 private final int iMaxFields;
1958
1959 TimeZoneOffset(String zeroOffsetPrintText, String zeroOffsetParseText,
1960 boolean showSeparators,
1961 int minFields, int maxFields)
1962 {
1963 super();
1964 iZeroOffsetPrintText = zeroOffsetPrintText;
1965 iZeroOffsetParseText = zeroOffsetParseText;
1966 iShowSeparators = showSeparators;
1967 if (minFields <= 0 || maxFields < minFields) {
1968 throw new IllegalArgumentException();
1969 }
1970 if (minFields > 4) {
1971 minFields = 4;
1972 maxFields = 4;
1973 }
1974 iMinFields = minFields;
1975 iMaxFields = maxFields;
1976 }
1977
1978 public int estimatePrintedLength() {
1979 int est = 1 + iMinFields << 1;
1980 if (iShowSeparators) {
1981 est += iMinFields - 1;
1982 }
1983 if (iZeroOffsetPrintText != null && iZeroOffsetPrintText.length() > est) {
1984 est = iZeroOffsetPrintText.length();
1985 }
1986 return est;
1987 }
1988
1989 public void printTo(
1990 Appendable buf, long instant, Chronology chrono,
1991 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1992 if (displayZone == null) {
1993 return; // no zone
1994 }
1995 if (displayOffset == 0 && iZeroOffsetPrintText != null) {
1996 buf.append(iZeroOffsetPrintText);
1997 return;
1998 }
1999 if (displayOffset >= 0) {
2000 buf.append('+');
2001 } else {
2002 buf.append('-');
2003 displayOffset = -displayOffset;
2004 }
2005
2006 int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2007 FormatUtils.appendPaddedInteger(buf, hours, 2);
2008 if (iMaxFields == 1) {
2009 return;
2010 }
2011 displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2012 if (displayOffset == 0 && iMinFields <= 1) {
2013 return;
2014 }
2015
2016 int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2017 if (iShowSeparators) {
2018 buf.append(':');
2019 }
2020 FormatUtils.appendPaddedInteger(buf, minutes, 2);
2021 if (iMaxFields == 2) {
2022 return;
2023 }
2024 displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2025 if (displayOffset == 0 && iMinFields <= 2) {
2026 return;
2027 }
2028
2029 int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2030 if (iShowSeparators) {
2031 buf.append(':');
2032 }
2033 FormatUtils.appendPaddedInteger(buf, seconds, 2);
2034 if (iMaxFields == 3) {
2035 return;
2036 }
2037 displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2038 if (displayOffset == 0 && iMinFields <= 3) {
2039 return;
2040 }
2041
2042 if (iShowSeparators) {
2043 buf.append('.');
2044 }
2045 FormatUtils.appendPaddedInteger(buf, displayOffset, 3);
2046 }
2047
2048 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2049 // no zone info
2050 }
2051
2052 public int estimateParsedLength() {
2053 return estimatePrintedLength();
2054 }
2055
2056 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2057 int limit = text.length() - position;
2058
2059 zeroOffset:
2060 if (iZeroOffsetParseText != null) {
2061 if (iZeroOffsetParseText.length() == 0) {
2062 // Peek ahead, looking for sign character.
2063 if (limit > 0) {
2064 char c = text.charAt(position);
2065 if (c == '-' || c == '+') {
2066 break zeroOffset;
2067 }
2068 }
2069 bucket.setOffset(Integer.valueOf(0));
2070 return position;
2071 }
2072 if (csStartsWithIgnoreCase(text, position, iZeroOffsetParseText)) {
2073 bucket.setOffset(Integer.valueOf(0));
2074 return position + iZeroOffsetParseText.length();
2075 }
2076 }
2077
2078 // Format to expect is sign character followed by at least one digit.
2079
2080 if (limit <= 1) {
2081 return ~position;
2082 }
2083
2084 boolean negative;
2085 char c = text.charAt(position);
2086 if (c == '-') {
2087 negative = true;
2088 } else if (c == '+') {
2089 negative = false;
2090 } else {
2091 return ~position;
2092 }
2093
2094 limit--;
2095 position++;
2096
2097 // Format following sign is one of:
2098 //
2099 // hh
2100 // hhmm
2101 // hhmmss
2102 // hhmmssSSS
2103 // hh:mm
2104 // hh:mm:ss
2105 // hh:mm:ss.SSS
2106
2107 // First parse hours.
2108
2109 if (digitCount(text, position, 2) < 2) {
2110 // Need two digits for hour.
2111 return ~position;
2112 }
2113
2114 int offset;
2115
2116 int hours = FormatUtils.parseTwoDigits(text, position);
2117 if (hours > 23) {
2118 return ~position;
2119 }
2120 offset = hours * DateTimeConstants.MILLIS_PER_HOUR;
2121 limit -= 2;
2122 position += 2;
2123
2124 parse: {
2125 // Need to decide now if separators are expected or parsing
2126 // stops at hour field.
2127
2128 if (limit <= 0) {
2129 break parse;
2130 }
2131
2132 boolean expectSeparators;
2133 c = text.charAt(position);
2134 if (c == ':') {
2135 expectSeparators = true;
2136 limit--;
2137 position++;
2138 } else if (c >= '0' && c <= '9') {
2139 expectSeparators = false;
2140 } else {
2141 break parse;
2142 }
2143
2144 // Proceed to parse minutes.
2145
2146 int count = digitCount(text, position, 2);
2147 if (count == 0 && !expectSeparators) {
2148 break parse;
2149 } else if (count < 2) {
2150 // Need two digits for minute.
2151 return ~position;
2152 }
2153
2154 int minutes = FormatUtils.parseTwoDigits(text, position);
2155 if (minutes > 59) {
2156 return ~position;
2157 }
2158 offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2159 limit -= 2;
2160 position += 2;
2161
2162 // Proceed to parse seconds.
2163
2164 if (limit <= 0) {
2165 break parse;
2166 }
2167
2168 if (expectSeparators) {
2169 if (text.charAt(position) != ':') {
2170 break parse;
2171 }
2172 limit--;
2173 position++;
2174 }
2175
2176 count = digitCount(text, position, 2);
2177 if (count == 0 && !expectSeparators) {
2178 break parse;
2179 } else if (count < 2) {
2180 // Need two digits for second.
2181 return ~position;
2182 }
2183
2184 int seconds = FormatUtils.parseTwoDigits(text, position);
2185 if (seconds > 59) {
2186 return ~position;
2187 }
2188 offset += seconds * DateTimeConstants.MILLIS_PER_SECOND;
2189 limit -= 2;
2190 position += 2;
2191
2192 // Proceed to parse fraction of second.
2193
2194 if (limit <= 0) {
2195 break parse;
2196 }
2197
2198 if (expectSeparators) {
2199 if (text.charAt(position) != '.' && text.charAt(position) != ',') {
2200 break parse;
2201 }
2202 limit--;
2203 position++;
2204 }
2205
2206 count = digitCount(text, position, 3);
2207 if (count == 0 && !expectSeparators) {
2208 break parse;
2209 } else if (count < 1) {
2210 // Need at least one digit for fraction of second.
2211 return ~position;
2212 }
2213
2214 offset += (text.charAt(position++) - '0') * 100;
2215 if (count > 1) {
2216 offset += (text.charAt(position++) - '0') * 10;
2217 if (count > 2) {
2218 offset += text.charAt(position++) - '0';
2219 }
2220 }
2221 }
2222
2223 bucket.setOffset(Integer.valueOf(negative ? -offset : offset));
2224 return position;
2225 }
2226
2227 /**
2228 * Returns actual amount of digits to parse, but no more than original
2229 * 'amount' parameter.
2230 */
2231 private int digitCount(CharSequence text, int position, int amount) {
2232 int limit = Math.min(text.length() - position, amount);
2233 amount = 0;
2234 for (; limit > 0; limit--) {
2235 char c = text.charAt(position + amount);
2236 if (c < '0' || c > '9') {
2237 break;
2238 }
2239 amount++;
2240 }
2241 return amount;
2242 }
2243 }
2244
2245 //-----------------------------------------------------------------------
2246 static class TimeZoneName
2247 implements InternalPrinter, InternalParser {
2248
2249 static final int LONG_NAME = 0;
2250 static final int SHORT_NAME = 1;
2251
2252 private final Map<String, DateTimeZone> iParseLookup;
2253 private final int iType;
2254
2255 TimeZoneName(int type, Map<String, DateTimeZone> parseLookup) {
2256 super();
2257 iType = type;
2258 iParseLookup = parseLookup;
2259 }
2260
2261 public int estimatePrintedLength() {
2262 return (iType == SHORT_NAME ? 4 : 20);
2263 }
2264
2265 public void printTo(
2266 Appendable appendable, long instant, Chronology chrono,
2267 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2268 appendable.append(print(instant - displayOffset, displayZone, locale));
2269 }
2270
2271 private String print(long instant, DateTimeZone displayZone, Locale locale) {
2272 if (displayZone == null) {
2273 return ""; // no zone
2274 }
2275 switch (iType) {
2276 case LONG_NAME:
2277 return displayZone.getName(instant, locale);
2278 case SHORT_NAME:
2279 return displayZone.getShortName(instant, locale);
2280 }
2281 return "";
2282 }
2283
2284 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2285 // no zone info
2286 }
2287
2288 public int estimateParsedLength() {
2289 return (iType == SHORT_NAME ? 4 : 20);
2290 }
2291
2292 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2293 Map<String, DateTimeZone> parseLookup = iParseLookup;
2294 parseLookup = (parseLookup != null ? parseLookup : DateTimeUtils.getDefaultTimeZoneNames());
2295 String matched = null;
2296 for (String name : parseLookup.keySet()) {
2297 if (csStartsWith(text, position, name)) {
2298 if (matched == null || name.length() > matched.length()) {
2299 matched = name;
2300 }
2301 }
2302 }
2303 if (matched != null) {
2304 bucket.setZone(parseLookup.get(matched));
2305 return position + matched.length();
2306 }
2307 return ~position;
2308 }
2309 }
2310
2311 //-----------------------------------------------------------------------
2312 static enum TimeZoneId
2313 implements InternalPrinter, InternalParser {
2314
2315 INSTANCE;
2316 static final Set<String> ALL_IDS = DateTimeZone.getAvailableIDs();
2317 static final int MAX_LENGTH;
2318 static {
2319 int max = 0;
2320 for (String id : ALL_IDS) {
2321 max = Math.max(max, id.length());
2322 }
2323 MAX_LENGTH = max;
2324 }
2325
2326 public int estimatePrintedLength() {
2327 return MAX_LENGTH;
2328 }
2329
2330 public void printTo(
2331 Appendable appendable, long instant, Chronology chrono,
2332 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2333 appendable.append(displayZone != null ? displayZone.getID() : "");
2334 }
2335
2336 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2337 // no zone info
2338 }
2339
2340 public int estimateParsedLength() {
2341 return MAX_LENGTH;
2342 }
2343
2344 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2345 String best = null;
2346 for (String id : ALL_IDS) {
2347 if (csStartsWith(text, position, id)) {
2348 if (best == null || id.length() > best.length()) {
2349 best = id;
2350 }
2351 }
2352 }
2353 if (best != null) {
2354 bucket.setZone(DateTimeZone.forID(best));
2355 return position + best.length();
2356 }
2357 return ~position;
2358 }
2359 }
2360
2361 //-----------------------------------------------------------------------
2362 static class Composite
2363 implements InternalPrinter, InternalParser {
2364
2365 private final InternalPrinter[] iPrinters;
2366 private final InternalParser[] iParsers;
2367
2368 private final int iPrintedLengthEstimate;
2369 private final int iParsedLengthEstimate;
2370
2371 Composite(List<Object> elementPairs) {
2372 super();
2373
2374 List<Object> printerList = new ArrayList<Object>();
2375 List<Object> parserList = new ArrayList<Object>();
2376
2377 decompose(elementPairs, printerList, parserList);
2378
2379 if (printerList.contains(null) || printerList.isEmpty()) {
2380 iPrinters = null;
2381 iPrintedLengthEstimate = 0;
2382 } else {
2383 int size = printerList.size();
2384 iPrinters = new InternalPrinter[size];
2385 int printEst = 0;
2386 for (int i=0; i<size; i++) {
2387 InternalPrinter printer = (InternalPrinter) printerList.get(i);
2388 printEst += printer.estimatePrintedLength();
2389 iPrinters[i] = printer;
2390 }
2391 iPrintedLengthEstimate = printEst;
2392 }
2393
2394 if (parserList.contains(null) || parserList.isEmpty()) {
2395 iParsers = null;
2396 iParsedLengthEstimate = 0;
2397 } else {
2398 int size = parserList.size();
2399 iParsers = new InternalParser[size];
2400 int parseEst = 0;
2401 for (int i=0; i<size; i++) {
2402 InternalParser parser = (InternalParser) parserList.get(i);
2403 parseEst += parser.estimateParsedLength();
2404 iParsers[i] = parser;
2405 }
2406 iParsedLengthEstimate = parseEst;
2407 }
2408 }
2409
2410 public int estimatePrintedLength() {
2411 return iPrintedLengthEstimate;
2412 }
2413
2414 public void printTo(
2415 Appendable appendable, long instant, Chronology chrono,
2416 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2417 InternalPrinter[] elements = iPrinters;
2418 if (elements == null) {
2419 throw new UnsupportedOperationException();
2420 }
2421
2422 if (locale == null) {
2423 // Guard against default locale changing concurrently.
2424 locale = Locale.getDefault();
2425 }
2426
2427 int len = elements.length;
2428 for (int i = 0; i < len; i++) {
2429 elements[i].printTo(appendable, instant, chrono, displayOffset, displayZone, locale);
2430 }
2431 }
2432
2433 public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2434 InternalPrinter[] elements = iPrinters;
2435 if (elements == null) {
2436 throw new UnsupportedOperationException();
2437 }
2438
2439 if (locale == null) {
2440 // Guard against default locale changing concurrently.
2441 locale = Locale.getDefault();
2442 }
2443
2444 int len = elements.length;
2445 for (int i=0; i<len; i++) {
2446 elements[i].printTo(appendable, partial, locale);
2447 }
2448 }
2449
2450 public int estimateParsedLength() {
2451 return iParsedLengthEstimate;
2452 }
2453
2454 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2455 InternalParser[] elements = iParsers;
2456 if (elements == null) {
2457 throw new UnsupportedOperationException();
2458 }
2459
2460 int len = elements.length;
2461 for (int i=0; i<len && position >= 0; i++) {
2462 position = elements[i].parseInto(bucket, text, position);
2463 }
2464 return position;
2465 }
2466
2467 boolean isPrinter() {
2468 return iPrinters != null;
2469 }
2470
2471 boolean isParser() {
2472 return iParsers != null;
2473 }
2474
2475 /**
2476 * Processes the element pairs, putting results into the given printer
2477 * and parser lists.
2478 */
2479 private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) {
2480 int size = elementPairs.size();
2481 for (int i=0; i<size; i+=2) {
2482 Object element = elementPairs.get(i);
2483 if (element instanceof Composite) {
2484 addArrayToList(printerList, ((Composite)element).iPrinters);
2485 } else {
2486 printerList.add(element);
2487 }
2488
2489 element = elementPairs.get(i + 1);
2490 if (element instanceof Composite) {
2491 addArrayToList(parserList, ((Composite)element).iParsers);
2492 } else {
2493 parserList.add(element);
2494 }
2495 }
2496 }
2497
2498 private void addArrayToList(List<Object> list, Object[] array) {
2499 if (array != null) {
2500 for (int i=0; i<array.length; i++) {
2501 list.add(array[i]);
2502 }
2503 }
2504 }
2505 }
2506
2507 //-----------------------------------------------------------------------
2508 static class MatchingParser
2509 implements InternalParser {
2510
2511 private final InternalParser[] iParsers;
2512 private final int iParsedLengthEstimate;
2513
2514 MatchingParser(InternalParser[] parsers) {
2515 super();
2516 iParsers = parsers;
2517 int est = 0;
2518 for (int i=parsers.length; --i>=0 ;) {
2519 InternalParser parser = parsers[i];
2520 if (parser != null) {
2521 int len = parser.estimateParsedLength();
2522 if (len > est) {
2523 est = len;
2524 }
2525 }
2526 }
2527 iParsedLengthEstimate = est;
2528 }
2529
2530 public int estimateParsedLength() {
2531 return iParsedLengthEstimate;
2532 }
2533
2534 public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2535 InternalParser[] parsers = iParsers;
2536 int length = parsers.length;
2537
2538 final Object originalState = bucket.saveState();
2539 boolean isOptional = false;
2540
2541 int bestValidPos = position;
2542 Object bestValidState = null;
2543
2544 int bestInvalidPos = position;
2545
2546 for (int i=0; i<length; i++) {
2547 InternalParser parser = parsers[i];
2548 if (parser == null) {
2549 // The empty parser wins only if nothing is better.
2550 if (bestValidPos <= position) {
2551 return position;
2552 }
2553 isOptional = true;
2554 break;
2555 }
2556 int parsePos = parser.parseInto(bucket, text, position);
2557 if (parsePos >= position) {
2558 if (parsePos > bestValidPos) {
2559 if (parsePos >= text.length() ||
2560 (i + 1) >= length || parsers[i + 1] == null) {
2561
2562 // Completely parsed text or no more parsers to
2563 // check. Skip the rest.
2564 return parsePos;
2565 }
2566 bestValidPos = parsePos;
2567 bestValidState = bucket.saveState();
2568 }
2569 } else {
2570 if (parsePos < 0) {
2571 parsePos = ~parsePos;
2572 if (parsePos > bestInvalidPos) {
2573 bestInvalidPos = parsePos;
2574 }
2575 }
2576 }
2577 bucket.restoreState(originalState);
2578 }
2579
2580 if (bestValidPos > position || (bestValidPos == position && isOptional)) {
2581 // Restore the state to the best valid parse.
2582 if (bestValidState != null) {
2583 bucket.restoreState(bestValidState);
2584 }
2585 return bestValidPos;
2586 }
2587
2588 return ~bestInvalidPos;
2589 }
2590 }
2591
2592 static boolean csStartsWith(CharSequence text, int position, String search) {
2593 int searchLen = search.length();
2594 if ((text.length() - position) < searchLen) {
2595 return false;
2596 }
2597 for (int i = 0; i < searchLen; i++) {
2598 if (text.charAt(position + i) != search.charAt(i)) {
2599 return false;
2600 }
2601 }
2602 return true;
2603 }
2604
2605 static boolean csStartsWithIgnoreCase(CharSequence text, int position, String search) {
2606 int searchLen = search.length();
2607 if ((text.length() - position) < searchLen) {
2608 return false;
2609 }
2610 for (int i = 0; i < searchLen; i++) {
2611 char ch1 = text.charAt(position + i);
2612 char ch2 = search.charAt(i);
2613 if (ch1 != ch2) {
2614 char u1 = Character.toUpperCase(ch1);
2615 char u2 = Character.toUpperCase(ch2);
2616 if (u1 != u2 && Character.toLowerCase(u1) != Character.toLowerCase(u2)) {
2617 return false;
2618 }
2619 }
2620 }
2621 return true;
2622 }
2623
2624 }
2625