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