001 /*
002 * Copyright 2001-2011 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<Object> 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<Object>();
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, for chaining
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, for chaining
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, for chaining
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, for chaining
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 addition, 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, for chaining
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, for chaining
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, for chaining
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, for chaining
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 minimum 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, for chaining
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, for chaining
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 minimum 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, for chaining
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, for chaining
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, for chaining
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, for chaining
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 * without 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 minimum number of digits to print.
526 * @param maxDigits maximum number of digits to print or parse.
527 * @return this DateTimeFormatterBuilder, for chaining
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 * Appends the print/parse of a fractional second.
546 * <p>
547 * This reliably handles the case where fractional digits are being handled
548 * beyond a visible decimal point. The digits parsed will always be treated
549 * as the most significant (numerically largest) digits.
550 * Thus '23' will be parsed as 230 milliseconds.
551 * Contrast this behaviour to {@link #appendMillisOfSecond}.
552 * This method does not print or parse the decimal point itself.
553 *
554 * @param minDigits minimum number of digits to print
555 * @param maxDigits maximum number of digits to print or parse
556 * @return this DateTimeFormatterBuilder, for chaining
557 */
558 public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) {
559 return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits);
560 }
561
562 /**
563 * Appends the print/parse of a fractional minute.
564 * <p>
565 * This reliably handles the case where fractional digits are being handled
566 * beyond a visible decimal point. The digits parsed will always be treated
567 * as the most significant (numerically largest) digits.
568 * Thus '23' will be parsed as 0.23 minutes (converted to milliseconds).
569 * This method does not print or parse the decimal point itself.
570 *
571 * @param minDigits minimum number of digits to print
572 * @param maxDigits maximum number of digits to print or parse
573 * @return this DateTimeFormatterBuilder, for chaining
574 */
575 public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) {
576 return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits);
577 }
578
579 /**
580 * Appends the print/parse of a fractional hour.
581 * <p>
582 * This reliably handles the case where fractional digits are being handled
583 * beyond a visible decimal point. The digits parsed will always be treated
584 * as the most significant (numerically largest) digits.
585 * Thus '23' will be parsed as 0.23 hours (converted to milliseconds).
586 * This method does not print or parse the decimal point itself.
587 *
588 * @param minDigits minimum number of digits to print
589 * @param maxDigits maximum number of digits to print or parse
590 * @return this DateTimeFormatterBuilder, for chaining
591 */
592 public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) {
593 return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits);
594 }
595
596 /**
597 * Appends the print/parse of a fractional day.
598 * <p>
599 * This reliably handles the case where fractional digits are being handled
600 * beyond a visible decimal point. The digits parsed will always be treated
601 * as the most significant (numerically largest) digits.
602 * Thus '23' will be parsed as 0.23 days (converted to milliseconds).
603 * This method does not print or parse the decimal point itself.
604 *
605 * @param minDigits minimum number of digits to print
606 * @param maxDigits maximum number of digits to print or parse
607 * @return this DateTimeFormatterBuilder, for chaining
608 */
609 public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) {
610 return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits);
611 }
612
613 /**
614 * Instructs the printer to emit a numeric millisOfSecond field.
615 * <p>
616 * This method will append a field that prints a three digit value.
617 * During parsing the value that is parsed is assumed to be three digits.
618 * If less than three digits are present then they will be counted as the
619 * smallest parts of the millisecond. This is probably not what you want
620 * if you are using the field as a fraction. Instead, a fractional
621 * millisecond should be produced using {@link #appendFractionOfSecond}.
622 *
623 * @param minDigits minimum number of digits to print
624 * @return this DateTimeFormatterBuilder, for chaining
625 */
626 public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) {
627 return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3);
628 }
629
630 /**
631 * Instructs the printer to emit a numeric millisOfDay field.
632 *
633 * @param minDigits minimum number of digits to print
634 * @return this DateTimeFormatterBuilder, for chaining
635 */
636 public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) {
637 return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8);
638 }
639
640 /**
641 * Instructs the printer to emit a numeric secondOfMinute field.
642 *
643 * @param minDigits minimum number of digits to print
644 * @return this DateTimeFormatterBuilder, for chaining
645 */
646 public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) {
647 return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2);
648 }
649
650 /**
651 * Instructs the printer to emit a numeric secondOfDay field.
652 *
653 * @param minDigits minimum number of digits to print
654 * @return this DateTimeFormatterBuilder, for chaining
655 */
656 public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) {
657 return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5);
658 }
659
660 /**
661 * Instructs the printer to emit a numeric minuteOfHour field.
662 *
663 * @param minDigits minimum number of digits to print
664 * @return this DateTimeFormatterBuilder, for chaining
665 */
666 public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) {
667 return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2);
668 }
669
670 /**
671 * Instructs the printer to emit a numeric minuteOfDay field.
672 *
673 * @param minDigits minimum number of digits to print
674 * @return this DateTimeFormatterBuilder, for chaining
675 */
676 public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) {
677 return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4);
678 }
679
680 /**
681 * Instructs the printer to emit a numeric hourOfDay field.
682 *
683 * @param minDigits minimum number of digits to print
684 * @return this DateTimeFormatterBuilder, for chaining
685 */
686 public DateTimeFormatterBuilder appendHourOfDay(int minDigits) {
687 return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2);
688 }
689
690 /**
691 * Instructs the printer to emit a numeric clockhourOfDay field.
692 *
693 * @param minDigits minimum number of digits to print
694 * @return this DateTimeFormatterBuilder, for chaining
695 */
696 public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) {
697 return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2);
698 }
699
700 /**
701 * Instructs the printer to emit a numeric hourOfHalfday field.
702 *
703 * @param minDigits minimum number of digits to print
704 * @return this DateTimeFormatterBuilder, for chaining
705 */
706 public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) {
707 return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2);
708 }
709
710 /**
711 * Instructs the printer to emit a numeric clockhourOfHalfday field.
712 *
713 * @param minDigits minimum number of digits to print
714 * @return this DateTimeFormatterBuilder, for chaining
715 */
716 public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) {
717 return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2);
718 }
719
720 /**
721 * Instructs the printer to emit a numeric dayOfWeek field.
722 *
723 * @param minDigits minimum number of digits to print
724 * @return this DateTimeFormatterBuilder, for chaining
725 */
726 public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) {
727 return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1);
728 }
729
730 /**
731 * Instructs the printer to emit a numeric dayOfMonth field.
732 *
733 * @param minDigits minimum number of digits to print
734 * @return this DateTimeFormatterBuilder, for chaining
735 */
736 public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) {
737 return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2);
738 }
739
740 /**
741 * Instructs the printer to emit a numeric dayOfYear field.
742 *
743 * @param minDigits minimum number of digits to print
744 * @return this DateTimeFormatterBuilder, for chaining
745 */
746 public DateTimeFormatterBuilder appendDayOfYear(int minDigits) {
747 return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3);
748 }
749
750 /**
751 * Instructs the printer to emit a numeric weekOfWeekyear field.
752 *
753 * @param minDigits minimum number of digits to print
754 * @return this DateTimeFormatterBuilder, for chaining
755 */
756 public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) {
757 return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2);
758 }
759
760 /**
761 * Instructs the printer to emit a numeric weekyear field.
762 *
763 * @param minDigits minimum number of digits to <i>print</i>
764 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
765 * maximum number of digits to print
766 * @return this DateTimeFormatterBuilder, for chaining
767 */
768 public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) {
769 return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits);
770 }
771
772 /**
773 * Instructs the printer to emit a numeric monthOfYear field.
774 *
775 * @param minDigits minimum number of digits to print
776 * @return this DateTimeFormatterBuilder, for chaining
777 */
778 public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) {
779 return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2);
780 }
781
782 /**
783 * Instructs the printer to emit a numeric year field.
784 *
785 * @param minDigits minimum number of digits to <i>print</i>
786 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
787 * maximum number of digits to print
788 * @return this DateTimeFormatterBuilder, for chaining
789 */
790 public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) {
791 return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits);
792 }
793
794 /**
795 * Instructs the printer to emit a numeric year field which always prints
796 * and parses two digits. A pivot year is used during parsing to determine
797 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
798 *
799 * <pre>
800 * pivot supported range 00 is 20 is 40 is 60 is 80 is
801 * ---------------------------------------------------------------
802 * 1950 1900..1999 1900 1920 1940 1960 1980
803 * 1975 1925..2024 2000 2020 1940 1960 1980
804 * 2000 1950..2049 2000 2020 2040 1960 1980
805 * 2025 1975..2074 2000 2020 2040 2060 1980
806 * 2050 2000..2099 2000 2020 2040 2060 2080
807 * </pre>
808 *
809 * @param pivot pivot year to use when parsing
810 * @return this DateTimeFormatterBuilder, for chaining
811 */
812 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) {
813 return appendTwoDigitYear(pivot, false);
814 }
815
816 /**
817 * Instructs the printer to emit a numeric year field which always prints
818 * two digits. A pivot year is used during parsing to determine the range
819 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
820 * parse is instructed to be lenient and the digit count is not two, it is
821 * treated as an absolute year. With lenient parsing, specifying a positive
822 * or negative sign before the year also makes it absolute.
823 *
824 * @param pivot pivot year to use when parsing
825 * @param lenientParse when true, if digit count is not two, it is treated
826 * as an absolute year
827 * @return this DateTimeFormatterBuilder, for chaining
828 * @since 1.1
829 */
830 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) {
831 return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse));
832 }
833
834 /**
835 * Instructs the printer to emit a numeric weekyear field which always prints
836 * and parses two digits. A pivot year is used during parsing to determine
837 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
838 *
839 * <pre>
840 * pivot supported range 00 is 20 is 40 is 60 is 80 is
841 * ---------------------------------------------------------------
842 * 1950 1900..1999 1900 1920 1940 1960 1980
843 * 1975 1925..2024 2000 2020 1940 1960 1980
844 * 2000 1950..2049 2000 2020 2040 1960 1980
845 * 2025 1975..2074 2000 2020 2040 2060 1980
846 * 2050 2000..2099 2000 2020 2040 2060 2080
847 * </pre>
848 *
849 * @param pivot pivot weekyear to use when parsing
850 * @return this DateTimeFormatterBuilder, for chaining
851 */
852 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) {
853 return appendTwoDigitWeekyear(pivot, false);
854 }
855
856 /**
857 * Instructs the printer to emit a numeric weekyear field which always prints
858 * two digits. A pivot year is used during parsing to determine the range
859 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
860 * parse is instructed to be lenient and the digit count is not two, it is
861 * treated as an absolute weekyear. With lenient parsing, specifying a positive
862 * or negative sign before the weekyear also makes it absolute.
863 *
864 * @param pivot pivot weekyear to use when parsing
865 * @param lenientParse when true, if digit count is not two, it is treated
866 * as an absolute weekyear
867 * @return this DateTimeFormatterBuilder, for chaining
868 * @since 1.1
869 */
870 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) {
871 return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse));
872 }
873
874 /**
875 * Instructs the printer to emit a numeric yearOfEra field.
876 *
877 * @param minDigits minimum number of digits to <i>print</i>
878 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
879 * maximum number of digits to print
880 * @return this DateTimeFormatterBuilder, for chaining
881 */
882 public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) {
883 return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits);
884 }
885
886 /**
887 * Instructs the printer to emit a numeric year of century field.
888 *
889 * @param minDigits minimum number of digits to print
890 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
891 * maximum number of digits to print
892 * @return this DateTimeFormatterBuilder, for chaining
893 */
894 public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) {
895 return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits);
896 }
897
898 /**
899 * Instructs the printer to emit a numeric century of era field.
900 *
901 * @param minDigits minimum number of digits to print
902 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
903 * maximum number of digits to print
904 * @return this DateTimeFormatterBuilder, for chaining
905 */
906 public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) {
907 return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits);
908 }
909
910 /**
911 * Instructs the printer to emit a locale-specific AM/PM text, and the
912 * parser to expect it. The parser is case-insensitive.
913 *
914 * @return this DateTimeFormatterBuilder, for chaining
915 */
916 public DateTimeFormatterBuilder appendHalfdayOfDayText() {
917 return appendText(DateTimeFieldType.halfdayOfDay());
918 }
919
920 /**
921 * Instructs the printer to emit a locale-specific dayOfWeek text. The
922 * parser will accept a long or short dayOfWeek text, case-insensitive.
923 *
924 * @return this DateTimeFormatterBuilder, for chaining
925 */
926 public DateTimeFormatterBuilder appendDayOfWeekText() {
927 return appendText(DateTimeFieldType.dayOfWeek());
928 }
929
930 /**
931 * Instructs the printer to emit a short locale-specific dayOfWeek
932 * text. The parser will accept a long or short dayOfWeek text,
933 * case-insensitive.
934 *
935 * @return this DateTimeFormatterBuilder, for chaining
936 */
937 public DateTimeFormatterBuilder appendDayOfWeekShortText() {
938 return appendShortText(DateTimeFieldType.dayOfWeek());
939 }
940
941 /**
942 * Instructs the printer to emit a short locale-specific monthOfYear
943 * text. The parser will accept a long or short monthOfYear text,
944 * case-insensitive.
945 *
946 * @return this DateTimeFormatterBuilder, for chaining
947 */
948 public DateTimeFormatterBuilder appendMonthOfYearText() {
949 return appendText(DateTimeFieldType.monthOfYear());
950 }
951
952 /**
953 * Instructs the printer to emit a locale-specific monthOfYear text. The
954 * parser will accept a long or short monthOfYear text, case-insensitive.
955 *
956 * @return this DateTimeFormatterBuilder, for chaining
957 */
958 public DateTimeFormatterBuilder appendMonthOfYearShortText() {
959 return appendShortText(DateTimeFieldType.monthOfYear());
960 }
961
962 /**
963 * Instructs the printer to emit a locale-specific era text (BC/AD), and
964 * the parser to expect it. The parser is case-insensitive.
965 *
966 * @return this DateTimeFormatterBuilder, for chaining
967 */
968 public DateTimeFormatterBuilder appendEraText() {
969 return appendText(DateTimeFieldType.era());
970 }
971
972 /**
973 * Instructs the printer to emit a locale-specific time zone name.
974 * Using this method prevents parsing, because time zone names are not unique.
975 * See {@link #appendTimeZoneName(Map)}.
976 *
977 * @return this DateTimeFormatterBuilder, for chaining
978 */
979 public DateTimeFormatterBuilder appendTimeZoneName() {
980 return append0(new TimeZoneName(TimeZoneName.LONG_NAME, null), null);
981 }
982
983 /**
984 * Instructs the printer to emit a locale-specific time zone name, providing a lookup for parsing.
985 * Time zone names are not unique, thus the API forces you to supply the lookup.
986 * The names are searched in the order of the map, thus it is strongly recommended
987 * to use a {@code LinkedHashMap} or similar.
988 *
989 * @param parseLookup the table of names, not null
990 * @return this DateTimeFormatterBuilder, for chaining
991 */
992 public DateTimeFormatterBuilder appendTimeZoneName(Map<String, DateTimeZone> parseLookup) {
993 TimeZoneName pp = new TimeZoneName(TimeZoneName.LONG_NAME, parseLookup);
994 return append0(pp, pp);
995 }
996
997 /**
998 * Instructs the printer to emit a short locale-specific time zone name.
999 * Using this method prevents parsing, because time zone names are not unique.
1000 * See {@link #appendTimeZoneShortName(Map)}.
1001 *
1002 * @return this DateTimeFormatterBuilder, for chaining
1003 */
1004 public DateTimeFormatterBuilder appendTimeZoneShortName() {
1005 return append0(new TimeZoneName(TimeZoneName.SHORT_NAME, null), null);
1006 }
1007
1008 /**
1009 * Instructs the printer to emit a short locale-specific time zone
1010 * name, providing a lookup for parsing.
1011 * Time zone names are not unique, thus the API forces you to supply the lookup.
1012 * The names are searched in the order of the map, thus it is strongly recommended
1013 * to use a {@code LinkedHashMap} or similar.
1014 *
1015 * @param parseLookup the table of names, not null
1016 * @return this DateTimeFormatterBuilder, for chaining
1017 */
1018 public DateTimeFormatterBuilder appendTimeZoneShortName(Map<String, DateTimeZone> parseLookup) {
1019 TimeZoneName pp = new TimeZoneName(TimeZoneName.SHORT_NAME, parseLookup);
1020 return append0(pp, pp);
1021 }
1022
1023 /**
1024 * Instructs the printer to emit the identifier of the time zone.
1025 * From version 2.0, this field can be parsed.
1026 *
1027 * @return this DateTimeFormatterBuilder, for chaining
1028 */
1029 public DateTimeFormatterBuilder appendTimeZoneId() {
1030 return append0(TimeZoneId.INSTANCE, TimeZoneId.INSTANCE);
1031 }
1032
1033 /**
1034 * Instructs the printer to emit text and numbers to display time zone
1035 * offset from UTC. A parser will use the parsed time zone offset to adjust
1036 * the datetime.
1037 * <p>
1038 * If zero offset text is supplied, then it will be printed when the zone is zero.
1039 * During parsing, either the zero offset text, or the offset will be parsed.
1040 *
1041 * @param zeroOffsetText the text to use if time zone offset is zero. If
1042 * null, offset is always shown.
1043 * @param showSeparators if true, prints ':' separator before minute and
1044 * second field and prints '.' separator before fraction field.
1045 * @param minFields minimum number of fields to print, stopping when no
1046 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1047 * @param maxFields maximum number of fields to print
1048 * @return this DateTimeFormatterBuilder, for chaining
1049 */
1050 public DateTimeFormatterBuilder appendTimeZoneOffset(
1051 String zeroOffsetText, boolean showSeparators,
1052 int minFields, int maxFields) {
1053 return append0(new TimeZoneOffset
1054 (zeroOffsetText, zeroOffsetText, showSeparators, minFields, maxFields));
1055 }
1056
1057 /**
1058 * Instructs the printer to emit text and numbers to display time zone
1059 * offset from UTC. A parser will use the parsed time zone offset to adjust
1060 * the datetime.
1061 * <p>
1062 * If zero offset print text is supplied, then it will be printed when the zone is zero.
1063 * If zero offset parse text is supplied, then either it or the offset will be parsed.
1064 *
1065 * @param zeroOffsetPrintText the text to print if time zone offset is zero. If
1066 * null, offset is always shown.
1067 * @param zeroOffsetParseText the text to optionally parse to indicate that the time
1068 * zone offset is zero. If null, then always use the offset.
1069 * @param showSeparators if true, prints ':' separator before minute and
1070 * second field and prints '.' separator before fraction field.
1071 * @param minFields minimum number of fields to print, stopping when no
1072 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1073 * @param maxFields maximum number of fields to print
1074 * @return this DateTimeFormatterBuilder, for chaining
1075 * @since 2.0
1076 */
1077 public DateTimeFormatterBuilder appendTimeZoneOffset(
1078 String zeroOffsetPrintText, String zeroOffsetParseText, boolean showSeparators,
1079 int minFields, int maxFields) {
1080 return append0(new TimeZoneOffset
1081 (zeroOffsetPrintText, zeroOffsetParseText, showSeparators, minFields, maxFields));
1082 }
1083
1084 //-----------------------------------------------------------------------
1085 /**
1086 * Calls upon {@link DateTimeFormat} to parse the pattern and append the
1087 * results into this builder.
1088 *
1089 * @param pattern pattern specification
1090 * @throws IllegalArgumentException if the pattern is invalid
1091 * @see DateTimeFormat
1092 */
1093 public DateTimeFormatterBuilder appendPattern(String pattern) {
1094 DateTimeFormat.appendPatternTo(this, pattern);
1095 return this;
1096 }
1097
1098 //-----------------------------------------------------------------------
1099 private Object getFormatter() {
1100 Object f = iFormatter;
1101
1102 if (f == null) {
1103 if (iElementPairs.size() == 2) {
1104 Object printer = iElementPairs.get(0);
1105 Object parser = iElementPairs.get(1);
1106
1107 if (printer != null) {
1108 if (printer == parser || parser == null) {
1109 f = printer;
1110 }
1111 } else {
1112 f = parser;
1113 }
1114 }
1115
1116 if (f == null) {
1117 f = new Composite(iElementPairs);
1118 }
1119
1120 iFormatter = f;
1121 }
1122
1123 return f;
1124 }
1125
1126 private boolean isPrinter(Object f) {
1127 if (f instanceof DateTimePrinter) {
1128 if (f instanceof Composite) {
1129 return ((Composite)f).isPrinter();
1130 }
1131 return true;
1132 }
1133 return false;
1134 }
1135
1136 private boolean isParser(Object f) {
1137 if (f instanceof DateTimeParser) {
1138 if (f instanceof Composite) {
1139 return ((Composite)f).isParser();
1140 }
1141 return true;
1142 }
1143 return false;
1144 }
1145
1146 private boolean isFormatter(Object f) {
1147 return (isPrinter(f) || isParser(f));
1148 }
1149
1150 static void appendUnknownString(StringBuffer buf, int len) {
1151 for (int i = len; --i >= 0;) {
1152 buf.append('\ufffd');
1153 }
1154 }
1155
1156 static void printUnknownString(Writer out, int len) throws IOException {
1157 for (int i = len; --i >= 0;) {
1158 out.write('\ufffd');
1159 }
1160 }
1161
1162 //-----------------------------------------------------------------------
1163 static class CharacterLiteral
1164 implements DateTimePrinter, DateTimeParser {
1165
1166 private final char iValue;
1167
1168 CharacterLiteral(char value) {
1169 super();
1170 iValue = value;
1171 }
1172
1173 public int estimatePrintedLength() {
1174 return 1;
1175 }
1176
1177 public void printTo(
1178 StringBuffer buf, long instant, Chronology chrono,
1179 int displayOffset, DateTimeZone displayZone, Locale locale) {
1180 buf.append(iValue);
1181 }
1182
1183 public void printTo(
1184 Writer out, long instant, Chronology chrono,
1185 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1186 out.write(iValue);
1187 }
1188
1189 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1190 buf.append(iValue);
1191 }
1192
1193 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1194 out.write(iValue);
1195 }
1196
1197 public int estimateParsedLength() {
1198 return 1;
1199 }
1200
1201 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1202 if (position >= text.length()) {
1203 return ~position;
1204 }
1205
1206 char a = text.charAt(position);
1207 char b = iValue;
1208
1209 if (a != b) {
1210 a = Character.toUpperCase(a);
1211 b = Character.toUpperCase(b);
1212 if (a != b) {
1213 a = Character.toLowerCase(a);
1214 b = Character.toLowerCase(b);
1215 if (a != b) {
1216 return ~position;
1217 }
1218 }
1219 }
1220
1221 return position + 1;
1222 }
1223 }
1224
1225 //-----------------------------------------------------------------------
1226 static class StringLiteral
1227 implements DateTimePrinter, DateTimeParser {
1228
1229 private final String iValue;
1230
1231 StringLiteral(String value) {
1232 super();
1233 iValue = value;
1234 }
1235
1236 public int estimatePrintedLength() {
1237 return iValue.length();
1238 }
1239
1240 public void printTo(
1241 StringBuffer buf, long instant, Chronology chrono,
1242 int displayOffset, DateTimeZone displayZone, Locale locale) {
1243 buf.append(iValue);
1244 }
1245
1246 public void printTo(
1247 Writer out, long instant, Chronology chrono,
1248 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1249 out.write(iValue);
1250 }
1251
1252 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1253 buf.append(iValue);
1254 }
1255
1256 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1257 out.write(iValue);
1258 }
1259
1260 public int estimateParsedLength() {
1261 return iValue.length();
1262 }
1263
1264 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1265 if (text.regionMatches(true, position, iValue, 0, iValue.length())) {
1266 return position + iValue.length();
1267 }
1268 return ~position;
1269 }
1270 }
1271
1272 //-----------------------------------------------------------------------
1273 static abstract class NumberFormatter
1274 implements DateTimePrinter, DateTimeParser {
1275 protected final DateTimeFieldType iFieldType;
1276 protected final int iMaxParsedDigits;
1277 protected final boolean iSigned;
1278
1279 NumberFormatter(DateTimeFieldType fieldType,
1280 int maxParsedDigits, boolean signed) {
1281 super();
1282 iFieldType = fieldType;
1283 iMaxParsedDigits = maxParsedDigits;
1284 iSigned = signed;
1285 }
1286
1287 public int estimateParsedLength() {
1288 return iMaxParsedDigits;
1289 }
1290
1291 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1292 int limit = Math.min(iMaxParsedDigits, text.length() - position);
1293
1294 boolean negative = false;
1295 int length = 0;
1296 while (length < limit) {
1297 char c = text.charAt(position + length);
1298 if (length == 0 && (c == '-' || c == '+') && iSigned) {
1299 negative = c == '-';
1300
1301 // Next character must be a digit.
1302 if (length + 1 >= limit ||
1303 (c = text.charAt(position + length + 1)) < '0' || c > '9')
1304 {
1305 break;
1306 }
1307
1308 if (negative) {
1309 length++;
1310 } else {
1311 // Skip the '+' for parseInt to succeed.
1312 position++;
1313 }
1314 // Expand the limit to disregard the sign character.
1315 limit = Math.min(limit + 1, text.length() - position);
1316 continue;
1317 }
1318 if (c < '0' || c > '9') {
1319 break;
1320 }
1321 length++;
1322 }
1323
1324 if (length == 0) {
1325 return ~position;
1326 }
1327
1328 int value;
1329 if (length >= 9) {
1330 // Since value may exceed integer limits, use stock parser
1331 // which checks for this.
1332 value = Integer.parseInt(text.substring(position, position += length));
1333 } else {
1334 int i = position;
1335 if (negative) {
1336 i++;
1337 }
1338 try {
1339 value = text.charAt(i++) - '0';
1340 } catch (StringIndexOutOfBoundsException e) {
1341 return ~position;
1342 }
1343 position += length;
1344 while (i < position) {
1345 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1346 }
1347 if (negative) {
1348 value = -value;
1349 }
1350 }
1351
1352 bucket.saveField(iFieldType, value);
1353 return position;
1354 }
1355 }
1356
1357 //-----------------------------------------------------------------------
1358 static class UnpaddedNumber extends NumberFormatter {
1359
1360 protected UnpaddedNumber(DateTimeFieldType fieldType,
1361 int maxParsedDigits, boolean signed)
1362 {
1363 super(fieldType, maxParsedDigits, signed);
1364 }
1365
1366 public int estimatePrintedLength() {
1367 return iMaxParsedDigits;
1368 }
1369
1370 public void printTo(
1371 StringBuffer buf, long instant, Chronology chrono,
1372 int displayOffset, DateTimeZone displayZone, Locale locale) {
1373 try {
1374 DateTimeField field = iFieldType.getField(chrono);
1375 FormatUtils.appendUnpaddedInteger(buf, field.get(instant));
1376 } catch (RuntimeException e) {
1377 buf.append('\ufffd');
1378 }
1379 }
1380
1381 public void printTo(
1382 Writer out, long instant, Chronology chrono,
1383 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1384 try {
1385 DateTimeField field = iFieldType.getField(chrono);
1386 FormatUtils.writeUnpaddedInteger(out, field.get(instant));
1387 } catch (RuntimeException e) {
1388 out.write('\ufffd');
1389 }
1390 }
1391
1392 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1393 if (partial.isSupported(iFieldType)) {
1394 try {
1395 FormatUtils.appendUnpaddedInteger(buf, partial.get(iFieldType));
1396 } catch (RuntimeException e) {
1397 buf.append('\ufffd');
1398 }
1399 } else {
1400 buf.append('\ufffd');
1401 }
1402 }
1403
1404 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1405 if (partial.isSupported(iFieldType)) {
1406 try {
1407 FormatUtils.writeUnpaddedInteger(out, partial.get(iFieldType));
1408 } catch (RuntimeException e) {
1409 out.write('\ufffd');
1410 }
1411 } else {
1412 out.write('\ufffd');
1413 }
1414 }
1415 }
1416
1417 //-----------------------------------------------------------------------
1418 static class PaddedNumber extends NumberFormatter {
1419
1420 protected final int iMinPrintedDigits;
1421
1422 protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits,
1423 boolean signed, int minPrintedDigits)
1424 {
1425 super(fieldType, maxParsedDigits, signed);
1426 iMinPrintedDigits = minPrintedDigits;
1427 }
1428
1429 public int estimatePrintedLength() {
1430 return iMaxParsedDigits;
1431 }
1432
1433 public void printTo(
1434 StringBuffer buf, long instant, Chronology chrono,
1435 int displayOffset, DateTimeZone displayZone, Locale locale) {
1436 try {
1437 DateTimeField field = iFieldType.getField(chrono);
1438 FormatUtils.appendPaddedInteger(buf, field.get(instant), iMinPrintedDigits);
1439 } catch (RuntimeException e) {
1440 appendUnknownString(buf, iMinPrintedDigits);
1441 }
1442 }
1443
1444 public void printTo(
1445 Writer out, long instant, Chronology chrono,
1446 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1447 try {
1448 DateTimeField field = iFieldType.getField(chrono);
1449 FormatUtils.writePaddedInteger(out, field.get(instant), iMinPrintedDigits);
1450 } catch (RuntimeException e) {
1451 printUnknownString(out, iMinPrintedDigits);
1452 }
1453 }
1454
1455 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1456 if (partial.isSupported(iFieldType)) {
1457 try {
1458 FormatUtils.appendPaddedInteger(buf, partial.get(iFieldType), iMinPrintedDigits);
1459 } catch (RuntimeException e) {
1460 appendUnknownString(buf, iMinPrintedDigits);
1461 }
1462 } else {
1463 appendUnknownString(buf, iMinPrintedDigits);
1464 }
1465 }
1466
1467 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1468 if (partial.isSupported(iFieldType)) {
1469 try {
1470 FormatUtils.writePaddedInteger(out, partial.get(iFieldType), iMinPrintedDigits);
1471 } catch (RuntimeException e) {
1472 printUnknownString(out, iMinPrintedDigits);
1473 }
1474 } else {
1475 printUnknownString(out, iMinPrintedDigits);
1476 }
1477 }
1478 }
1479
1480 //-----------------------------------------------------------------------
1481 static class FixedNumber extends PaddedNumber {
1482
1483 protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) {
1484 super(fieldType, numDigits, signed, numDigits);
1485 }
1486
1487 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1488 int newPos = super.parseInto(bucket, text, position);
1489 if (newPos < 0) {
1490 return newPos;
1491 }
1492 int expectedPos = position + iMaxParsedDigits;
1493 if (newPos != expectedPos) {
1494 if (iSigned) {
1495 char c = text.charAt(position);
1496 if (c == '-' || c == '+') {
1497 expectedPos++;
1498 }
1499 }
1500 if (newPos > expectedPos) {
1501 // The failure is at the position of the first extra digit.
1502 return ~(expectedPos + 1);
1503 } else if (newPos < expectedPos) {
1504 // The failure is at the position where the next digit should be.
1505 return ~newPos;
1506 }
1507 }
1508 return newPos;
1509 }
1510 }
1511
1512 //-----------------------------------------------------------------------
1513 static class TwoDigitYear
1514 implements DateTimePrinter, DateTimeParser {
1515
1516 /** The field to print/parse. */
1517 private final DateTimeFieldType iType;
1518 /** The pivot year. */
1519 private final int iPivot;
1520 private final boolean iLenientParse;
1521
1522 TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) {
1523 super();
1524 iType = type;
1525 iPivot = pivot;
1526 iLenientParse = lenientParse;
1527 }
1528
1529 public int estimateParsedLength() {
1530 return iLenientParse ? 4 : 2;
1531 }
1532
1533 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1534 int limit = text.length() - position;
1535
1536 if (!iLenientParse) {
1537 limit = Math.min(2, limit);
1538 if (limit < 2) {
1539 return ~position;
1540 }
1541 } else {
1542 boolean hasSignChar = false;
1543 boolean negative = false;
1544 int length = 0;
1545 while (length < limit) {
1546 char c = text.charAt(position + length);
1547 if (length == 0 && (c == '-' || c == '+')) {
1548 hasSignChar = true;
1549 negative = c == '-';
1550 if (negative) {
1551 length++;
1552 } else {
1553 // Skip the '+' for parseInt to succeed.
1554 position++;
1555 limit--;
1556 }
1557 continue;
1558 }
1559 if (c < '0' || c > '9') {
1560 break;
1561 }
1562 length++;
1563 }
1564
1565 if (length == 0) {
1566 return ~position;
1567 }
1568
1569 if (hasSignChar || length != 2) {
1570 int value;
1571 if (length >= 9) {
1572 // Since value may exceed integer limits, use stock
1573 // parser which checks for this.
1574 value = Integer.parseInt(text.substring(position, position += length));
1575 } else {
1576 int i = position;
1577 if (negative) {
1578 i++;
1579 }
1580 try {
1581 value = text.charAt(i++) - '0';
1582 } catch (StringIndexOutOfBoundsException e) {
1583 return ~position;
1584 }
1585 position += length;
1586 while (i < position) {
1587 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1588 }
1589 if (negative) {
1590 value = -value;
1591 }
1592 }
1593
1594 bucket.saveField(iType, value);
1595 return position;
1596 }
1597 }
1598
1599 int year;
1600 char c = text.charAt(position);
1601 if (c < '0' || c > '9') {
1602 return ~position;
1603 }
1604 year = c - '0';
1605 c = text.charAt(position + 1);
1606 if (c < '0' || c > '9') {
1607 return ~position;
1608 }
1609 year = ((year << 3) + (year << 1)) + c - '0';
1610
1611 int pivot = iPivot;
1612 // If the bucket pivot year is non-null, use that when parsing
1613 if (bucket.getPivotYear() != null) {
1614 pivot = bucket.getPivotYear().intValue();
1615 }
1616
1617 int low = pivot - 50;
1618
1619 int t;
1620 if (low >= 0) {
1621 t = low % 100;
1622 } else {
1623 t = 99 + ((low + 1) % 100);
1624 }
1625
1626 year += low + ((year < t) ? 100 : 0) - t;
1627
1628 bucket.saveField(iType, year);
1629 return position + 2;
1630 }
1631
1632 public int estimatePrintedLength() {
1633 return 2;
1634 }
1635
1636 public void printTo(
1637 StringBuffer buf, long instant, Chronology chrono,
1638 int displayOffset, DateTimeZone displayZone, Locale locale) {
1639 int year = getTwoDigitYear(instant, chrono);
1640 if (year < 0) {
1641 buf.append('\ufffd');
1642 buf.append('\ufffd');
1643 } else {
1644 FormatUtils.appendPaddedInteger(buf, year, 2);
1645 }
1646 }
1647
1648 public void printTo(
1649 Writer out, long instant, Chronology chrono,
1650 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1651 int year = getTwoDigitYear(instant, chrono);
1652 if (year < 0) {
1653 out.write('\ufffd');
1654 out.write('\ufffd');
1655 } else {
1656 FormatUtils.writePaddedInteger(out, year, 2);
1657 }
1658 }
1659
1660 private int getTwoDigitYear(long instant, Chronology chrono) {
1661 try {
1662 int year = iType.getField(chrono).get(instant);
1663 if (year < 0) {
1664 year = -year;
1665 }
1666 return year % 100;
1667 } catch (RuntimeException e) {
1668 return -1;
1669 }
1670 }
1671
1672 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1673 int year = getTwoDigitYear(partial);
1674 if (year < 0) {
1675 buf.append('\ufffd');
1676 buf.append('\ufffd');
1677 } else {
1678 FormatUtils.appendPaddedInteger(buf, year, 2);
1679 }
1680 }
1681
1682 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1683 int year = getTwoDigitYear(partial);
1684 if (year < 0) {
1685 out.write('\ufffd');
1686 out.write('\ufffd');
1687 } else {
1688 FormatUtils.writePaddedInteger(out, year, 2);
1689 }
1690 }
1691
1692 private int getTwoDigitYear(ReadablePartial partial) {
1693 if (partial.isSupported(iType)) {
1694 try {
1695 int year = partial.get(iType);
1696 if (year < 0) {
1697 year = -year;
1698 }
1699 return year % 100;
1700 } catch (RuntimeException e) {}
1701 }
1702 return -1;
1703 }
1704 }
1705
1706 //-----------------------------------------------------------------------
1707 static class TextField
1708 implements DateTimePrinter, DateTimeParser {
1709
1710 private static Map<Locale, Map<DateTimeFieldType, Object[]>> cParseCache =
1711 new HashMap<Locale, Map<DateTimeFieldType, Object[]>>();
1712 private final DateTimeFieldType iFieldType;
1713 private final boolean iShort;
1714
1715 TextField(DateTimeFieldType fieldType, boolean isShort) {
1716 super();
1717 iFieldType = fieldType;
1718 iShort = isShort;
1719 }
1720
1721 public int estimatePrintedLength() {
1722 return iShort ? 6 : 20;
1723 }
1724
1725 public void printTo(
1726 StringBuffer buf, long instant, Chronology chrono,
1727 int displayOffset, DateTimeZone displayZone, Locale locale) {
1728 try {
1729 buf.append(print(instant, chrono, locale));
1730 } catch (RuntimeException e) {
1731 buf.append('\ufffd');
1732 }
1733 }
1734
1735 public void printTo(
1736 Writer out, long instant, Chronology chrono,
1737 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1738 try {
1739 out.write(print(instant, chrono, locale));
1740 } catch (RuntimeException e) {
1741 out.write('\ufffd');
1742 }
1743 }
1744
1745 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1746 try {
1747 buf.append(print(partial, locale));
1748 } catch (RuntimeException e) {
1749 buf.append('\ufffd');
1750 }
1751 }
1752
1753 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1754 try {
1755 out.write(print(partial, locale));
1756 } catch (RuntimeException e) {
1757 out.write('\ufffd');
1758 }
1759 }
1760
1761 private String print(long instant, Chronology chrono, Locale locale) {
1762 DateTimeField field = iFieldType.getField(chrono);
1763 if (iShort) {
1764 return field.getAsShortText(instant, locale);
1765 } else {
1766 return field.getAsText(instant, locale);
1767 }
1768 }
1769
1770 private String print(ReadablePartial partial, Locale locale) {
1771 if (partial.isSupported(iFieldType)) {
1772 DateTimeField field = iFieldType.getField(partial.getChronology());
1773 if (iShort) {
1774 return field.getAsShortText(partial, locale);
1775 } else {
1776 return field.getAsText(partial, locale);
1777 }
1778 } else {
1779 return "\ufffd";
1780 }
1781 }
1782
1783 public int estimateParsedLength() {
1784 return estimatePrintedLength();
1785 }
1786
1787 @SuppressWarnings("unchecked")
1788 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1789 Locale locale = bucket.getLocale();
1790 // handle languages which might have non ASCII A-Z or punctuation
1791 // bug 1788282
1792 Set<String> validValues = null;
1793 int maxLength = 0;
1794 synchronized (cParseCache) {
1795 Map<DateTimeFieldType, Object[]> innerMap = cParseCache.get(locale);
1796 if (innerMap == null) {
1797 innerMap = new HashMap<DateTimeFieldType, Object[]>();
1798 cParseCache.put(locale, innerMap);
1799 }
1800 Object[] array = innerMap.get(iFieldType);
1801 if (array == null) {
1802 validValues = new HashSet<String>(32);
1803 MutableDateTime dt = new MutableDateTime(0L, DateTimeZone.UTC);
1804 Property property = dt.property(iFieldType);
1805 int min = property.getMinimumValueOverall();
1806 int max = property.getMaximumValueOverall();
1807 if (max - min > 32) { // protect against invalid fields
1808 return ~position;
1809 }
1810 maxLength = property.getMaximumTextLength(locale);
1811 for (int i = min; i <= max; i++) {
1812 property.set(i);
1813 validValues.add(property.getAsShortText(locale));
1814 validValues.add(property.getAsShortText(locale).toLowerCase(locale));
1815 validValues.add(property.getAsShortText(locale).toUpperCase(locale));
1816 validValues.add(property.getAsText(locale));
1817 validValues.add(property.getAsText(locale).toLowerCase(locale));
1818 validValues.add(property.getAsText(locale).toUpperCase(locale));
1819 }
1820 if ("en".equals(locale.getLanguage()) && iFieldType == DateTimeFieldType.era()) {
1821 // hack to support for parsing "BCE" and "CE" if the language is English
1822 validValues.add("BCE");
1823 validValues.add("bce");
1824 validValues.add("CE");
1825 validValues.add("ce");
1826 maxLength = 3;
1827 }
1828 array = new Object[] {validValues, Integer.valueOf(maxLength)};
1829 innerMap.put(iFieldType, array);
1830 } else {
1831 validValues = (Set<String>) array[0];
1832 maxLength = ((Integer) array[1]).intValue();
1833 }
1834 }
1835 // match the longest string first using our knowledge of the max length
1836 int limit = Math.min(text.length(), position + maxLength);
1837 for (int i = limit; i > position; i--) {
1838 String match = text.substring(position, i);
1839 if (validValues.contains(match)) {
1840 bucket.saveField(iFieldType, match, locale);
1841 return i;
1842 }
1843 }
1844 return ~position;
1845 }
1846 }
1847
1848 //-----------------------------------------------------------------------
1849 static class Fraction
1850 implements DateTimePrinter, DateTimeParser {
1851
1852 private final DateTimeFieldType iFieldType;
1853 protected int iMinDigits;
1854 protected int iMaxDigits;
1855
1856 protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) {
1857 super();
1858 iFieldType = fieldType;
1859 // Limit the precision requirements.
1860 if (maxDigits > 18) {
1861 maxDigits = 18;
1862 }
1863 iMinDigits = minDigits;
1864 iMaxDigits = maxDigits;
1865 }
1866
1867 public int estimatePrintedLength() {
1868 return iMaxDigits;
1869 }
1870
1871 public void printTo(
1872 StringBuffer buf, long instant, Chronology chrono,
1873 int displayOffset, DateTimeZone displayZone, Locale locale) {
1874 try {
1875 printTo(buf, null, instant, chrono);
1876 } catch (IOException e) {
1877 // Not gonna happen.
1878 }
1879 }
1880
1881 public void printTo(
1882 Writer out, long instant, Chronology chrono,
1883 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1884 printTo(null, out, instant, chrono);
1885 }
1886
1887 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1888 // removed check whether field is supported, as input field is typically
1889 // secondOfDay which is unsupported by TimeOfDay
1890 long millis = partial.getChronology().set(partial, 0L);
1891 try {
1892 printTo(buf, null, millis, partial.getChronology());
1893 } catch (IOException e) {
1894 // Not gonna happen.
1895 }
1896 }
1897
1898 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1899 // removed check whether field is supported, as input field is typically
1900 // secondOfDay which is unsupported by TimeOfDay
1901 long millis = partial.getChronology().set(partial, 0L);
1902 printTo(null, out, millis, partial.getChronology());
1903 }
1904
1905 protected void printTo(StringBuffer buf, Writer out, long instant, Chronology chrono)
1906 throws IOException
1907 {
1908 DateTimeField field = iFieldType.getField(chrono);
1909 int minDigits = iMinDigits;
1910
1911 long fraction;
1912 try {
1913 fraction = field.remainder(instant);
1914 } catch (RuntimeException e) {
1915 if (buf != null) {
1916 appendUnknownString(buf, minDigits);
1917 } else {
1918 printUnknownString(out, minDigits);
1919 }
1920 return;
1921 }
1922
1923 if (fraction == 0) {
1924 if (buf != null) {
1925 while (--minDigits >= 0) {
1926 buf.append('0');
1927 }
1928 } else {
1929 while (--minDigits >= 0) {
1930 out.write('0');
1931 }
1932 }
1933 return;
1934 }
1935
1936 String str;
1937 long[] fractionData = getFractionData(fraction, field);
1938 long scaled = fractionData[0];
1939 int maxDigits = (int) fractionData[1];
1940
1941 if ((scaled & 0x7fffffff) == scaled) {
1942 str = Integer.toString((int) scaled);
1943 } else {
1944 str = Long.toString(scaled);
1945 }
1946
1947 int length = str.length();
1948 int digits = maxDigits;
1949 while (length < digits) {
1950 if (buf != null) {
1951 buf.append('0');
1952 } else {
1953 out.write('0');
1954 }
1955 minDigits--;
1956 digits--;
1957 }
1958
1959 if (minDigits < digits) {
1960 // Chop off as many trailing zero digits as necessary.
1961 while (minDigits < digits) {
1962 if (length <= 1 || str.charAt(length - 1) != '0') {
1963 break;
1964 }
1965 digits--;
1966 length--;
1967 }
1968 if (length < str.length()) {
1969 if (buf != null) {
1970 for (int i=0; i<length; i++) {
1971 buf.append(str.charAt(i));
1972 }
1973 } else {
1974 for (int i=0; i<length; i++) {
1975 out.write(str.charAt(i));
1976 }
1977 }
1978 return;
1979 }
1980 }
1981
1982 if (buf != null) {
1983 buf.append(str);
1984 } else {
1985 out.write(str);
1986 }
1987 }
1988
1989 private long[] getFractionData(long fraction, DateTimeField field) {
1990 long rangeMillis = field.getDurationField().getUnitMillis();
1991 long scalar;
1992 int maxDigits = iMaxDigits;
1993 while (true) {
1994 switch (maxDigits) {
1995 default: scalar = 1L; break;
1996 case 1: scalar = 10L; break;
1997 case 2: scalar = 100L; break;
1998 case 3: scalar = 1000L; break;
1999 case 4: scalar = 10000L; break;
2000 case 5: scalar = 100000L; break;
2001 case 6: scalar = 1000000L; break;
2002 case 7: scalar = 10000000L; break;
2003 case 8: scalar = 100000000L; break;
2004 case 9: scalar = 1000000000L; break;
2005 case 10: scalar = 10000000000L; break;
2006 case 11: scalar = 100000000000L; break;
2007 case 12: scalar = 1000000000000L; break;
2008 case 13: scalar = 10000000000000L; break;
2009 case 14: scalar = 100000000000000L; break;
2010 case 15: scalar = 1000000000000000L; break;
2011 case 16: scalar = 10000000000000000L; break;
2012 case 17: scalar = 100000000000000000L; break;
2013 case 18: scalar = 1000000000000000000L; break;
2014 }
2015 if (((rangeMillis * scalar) / scalar) == rangeMillis) {
2016 break;
2017 }
2018 // Overflowed: scale down.
2019 maxDigits--;
2020 }
2021
2022 return new long[] {fraction * scalar / rangeMillis, maxDigits};
2023 }
2024
2025 public int estimateParsedLength() {
2026 return iMaxDigits;
2027 }
2028
2029 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2030 DateTimeField field = iFieldType.getField(bucket.getChronology());
2031
2032 int limit = Math.min(iMaxDigits, text.length() - position);
2033
2034 long value = 0;
2035 long n = field.getDurationField().getUnitMillis() * 10;
2036 int length = 0;
2037 while (length < limit) {
2038 char c = text.charAt(position + length);
2039 if (c < '0' || c > '9') {
2040 break;
2041 }
2042 length++;
2043 long nn = n / 10;
2044 value += (c - '0') * nn;
2045 n = nn;
2046 }
2047
2048 value /= 10;
2049
2050 if (length == 0) {
2051 return ~position;
2052 }
2053
2054 if (value > Integer.MAX_VALUE) {
2055 return ~position;
2056 }
2057
2058 DateTimeField parseField = new PreciseDateTimeField(
2059 DateTimeFieldType.millisOfSecond(),
2060 MillisDurationField.INSTANCE,
2061 field.getDurationField());
2062
2063 bucket.saveField(parseField, (int) value);
2064
2065 return position + length;
2066 }
2067 }
2068
2069 //-----------------------------------------------------------------------
2070 static class TimeZoneOffset
2071 implements DateTimePrinter, DateTimeParser {
2072
2073 private final String iZeroOffsetPrintText;
2074 private final String iZeroOffsetParseText;
2075 private final boolean iShowSeparators;
2076 private final int iMinFields;
2077 private final int iMaxFields;
2078
2079 TimeZoneOffset(String zeroOffsetPrintText, String zeroOffsetParseText,
2080 boolean showSeparators,
2081 int minFields, int maxFields)
2082 {
2083 super();
2084 iZeroOffsetPrintText = zeroOffsetPrintText;
2085 iZeroOffsetParseText = zeroOffsetParseText;
2086 iShowSeparators = showSeparators;
2087 if (minFields <= 0 || maxFields < minFields) {
2088 throw new IllegalArgumentException();
2089 }
2090 if (minFields > 4) {
2091 minFields = 4;
2092 maxFields = 4;
2093 }
2094 iMinFields = minFields;
2095 iMaxFields = maxFields;
2096 }
2097
2098 public int estimatePrintedLength() {
2099 int est = 1 + iMinFields << 1;
2100 if (iShowSeparators) {
2101 est += iMinFields - 1;
2102 }
2103 if (iZeroOffsetPrintText != null && iZeroOffsetPrintText.length() > est) {
2104 est = iZeroOffsetPrintText.length();
2105 }
2106 return est;
2107 }
2108
2109 public void printTo(
2110 StringBuffer buf, long instant, Chronology chrono,
2111 int displayOffset, DateTimeZone displayZone, Locale locale) {
2112 if (displayZone == null) {
2113 return; // no zone
2114 }
2115 if (displayOffset == 0 && iZeroOffsetPrintText != null) {
2116 buf.append(iZeroOffsetPrintText);
2117 return;
2118 }
2119 if (displayOffset >= 0) {
2120 buf.append('+');
2121 } else {
2122 buf.append('-');
2123 displayOffset = -displayOffset;
2124 }
2125
2126 int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2127 FormatUtils.appendPaddedInteger(buf, hours, 2);
2128 if (iMaxFields == 1) {
2129 return;
2130 }
2131 displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2132 if (displayOffset == 0 && iMinFields <= 1) {
2133 return;
2134 }
2135
2136 int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2137 if (iShowSeparators) {
2138 buf.append(':');
2139 }
2140 FormatUtils.appendPaddedInteger(buf, minutes, 2);
2141 if (iMaxFields == 2) {
2142 return;
2143 }
2144 displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2145 if (displayOffset == 0 && iMinFields <= 2) {
2146 return;
2147 }
2148
2149 int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2150 if (iShowSeparators) {
2151 buf.append(':');
2152 }
2153 FormatUtils.appendPaddedInteger(buf, seconds, 2);
2154 if (iMaxFields == 3) {
2155 return;
2156 }
2157 displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2158 if (displayOffset == 0 && iMinFields <= 3) {
2159 return;
2160 }
2161
2162 if (iShowSeparators) {
2163 buf.append('.');
2164 }
2165 FormatUtils.appendPaddedInteger(buf, displayOffset, 3);
2166 }
2167
2168 public void printTo(
2169 Writer out, long instant, Chronology chrono,
2170 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2171 if (displayZone == null) {
2172 return; // no zone
2173 }
2174 if (displayOffset == 0 && iZeroOffsetPrintText != null) {
2175 out.write(iZeroOffsetPrintText);
2176 return;
2177 }
2178 if (displayOffset >= 0) {
2179 out.write('+');
2180 } else {
2181 out.write('-');
2182 displayOffset = -displayOffset;
2183 }
2184
2185 int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2186 FormatUtils.writePaddedInteger(out, hours, 2);
2187 if (iMaxFields == 1) {
2188 return;
2189 }
2190 displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2191 if (displayOffset == 0 && iMinFields == 1) {
2192 return;
2193 }
2194
2195 int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2196 if (iShowSeparators) {
2197 out.write(':');
2198 }
2199 FormatUtils.writePaddedInteger(out, minutes, 2);
2200 if (iMaxFields == 2) {
2201 return;
2202 }
2203 displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2204 if (displayOffset == 0 && iMinFields == 2) {
2205 return;
2206 }
2207
2208 int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2209 if (iShowSeparators) {
2210 out.write(':');
2211 }
2212 FormatUtils.writePaddedInteger(out, seconds, 2);
2213 if (iMaxFields == 3) {
2214 return;
2215 }
2216 displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2217 if (displayOffset == 0 && iMinFields == 3) {
2218 return;
2219 }
2220
2221 if (iShowSeparators) {
2222 out.write('.');
2223 }
2224 FormatUtils.writePaddedInteger(out, displayOffset, 3);
2225 }
2226
2227 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2228 // no zone info
2229 }
2230
2231 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2232 // no zone info
2233 }
2234
2235 public int estimateParsedLength() {
2236 return estimatePrintedLength();
2237 }
2238
2239 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2240 int limit = text.length() - position;
2241
2242 zeroOffset:
2243 if (iZeroOffsetParseText != null) {
2244 if (iZeroOffsetParseText.length() == 0) {
2245 // Peek ahead, looking for sign character.
2246 if (limit > 0) {
2247 char c = text.charAt(position);
2248 if (c == '-' || c == '+') {
2249 break zeroOffset;
2250 }
2251 }
2252 bucket.setOffset(Integer.valueOf(0));
2253 return position;
2254 }
2255 if (text.regionMatches(true, position, iZeroOffsetParseText, 0, iZeroOffsetParseText.length())) {
2256 bucket.setOffset(Integer.valueOf(0));
2257 return position + iZeroOffsetParseText.length();
2258 }
2259 }
2260
2261 // Format to expect is sign character followed by at least one digit.
2262
2263 if (limit <= 1) {
2264 return ~position;
2265 }
2266
2267 boolean negative;
2268 char c = text.charAt(position);
2269 if (c == '-') {
2270 negative = true;
2271 } else if (c == '+') {
2272 negative = false;
2273 } else {
2274 return ~position;
2275 }
2276
2277 limit--;
2278 position++;
2279
2280 // Format following sign is one of:
2281 //
2282 // hh
2283 // hhmm
2284 // hhmmss
2285 // hhmmssSSS
2286 // hh:mm
2287 // hh:mm:ss
2288 // hh:mm:ss.SSS
2289
2290 // First parse hours.
2291
2292 if (digitCount(text, position, 2) < 2) {
2293 // Need two digits for hour.
2294 return ~position;
2295 }
2296
2297 int offset;
2298
2299 int hours = FormatUtils.parseTwoDigits(text, position);
2300 if (hours > 23) {
2301 return ~position;
2302 }
2303 offset = hours * DateTimeConstants.MILLIS_PER_HOUR;
2304 limit -= 2;
2305 position += 2;
2306
2307 parse: {
2308 // Need to decide now if separators are expected or parsing
2309 // stops at hour field.
2310
2311 if (limit <= 0) {
2312 break parse;
2313 }
2314
2315 boolean expectSeparators;
2316 c = text.charAt(position);
2317 if (c == ':') {
2318 expectSeparators = true;
2319 limit--;
2320 position++;
2321 } else if (c >= '0' && c <= '9') {
2322 expectSeparators = false;
2323 } else {
2324 break parse;
2325 }
2326
2327 // Proceed to parse minutes.
2328
2329 int count = digitCount(text, position, 2);
2330 if (count == 0 && !expectSeparators) {
2331 break parse;
2332 } else if (count < 2) {
2333 // Need two digits for minute.
2334 return ~position;
2335 }
2336
2337 int minutes = FormatUtils.parseTwoDigits(text, position);
2338 if (minutes > 59) {
2339 return ~position;
2340 }
2341 offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2342 limit -= 2;
2343 position += 2;
2344
2345 // Proceed to parse seconds.
2346
2347 if (limit <= 0) {
2348 break parse;
2349 }
2350
2351 if (expectSeparators) {
2352 if (text.charAt(position) != ':') {
2353 break parse;
2354 }
2355 limit--;
2356 position++;
2357 }
2358
2359 count = digitCount(text, position, 2);
2360 if (count == 0 && !expectSeparators) {
2361 break parse;
2362 } else if (count < 2) {
2363 // Need two digits for second.
2364 return ~position;
2365 }
2366
2367 int seconds = FormatUtils.parseTwoDigits(text, position);
2368 if (seconds > 59) {
2369 return ~position;
2370 }
2371 offset += seconds * DateTimeConstants.MILLIS_PER_SECOND;
2372 limit -= 2;
2373 position += 2;
2374
2375 // Proceed to parse fraction of second.
2376
2377 if (limit <= 0) {
2378 break parse;
2379 }
2380
2381 if (expectSeparators) {
2382 if (text.charAt(position) != '.' && text.charAt(position) != ',') {
2383 break parse;
2384 }
2385 limit--;
2386 position++;
2387 }
2388
2389 count = digitCount(text, position, 3);
2390 if (count == 0 && !expectSeparators) {
2391 break parse;
2392 } else if (count < 1) {
2393 // Need at least one digit for fraction of second.
2394 return ~position;
2395 }
2396
2397 offset += (text.charAt(position++) - '0') * 100;
2398 if (count > 1) {
2399 offset += (text.charAt(position++) - '0') * 10;
2400 if (count > 2) {
2401 offset += text.charAt(position++) - '0';
2402 }
2403 }
2404 }
2405
2406 bucket.setOffset(Integer.valueOf(negative ? -offset : offset));
2407 return position;
2408 }
2409
2410 /**
2411 * Returns actual amount of digits to parse, but no more than original
2412 * 'amount' parameter.
2413 */
2414 private int digitCount(String text, int position, int amount) {
2415 int limit = Math.min(text.length() - position, amount);
2416 amount = 0;
2417 for (; limit > 0; limit--) {
2418 char c = text.charAt(position + amount);
2419 if (c < '0' || c > '9') {
2420 break;
2421 }
2422 amount++;
2423 }
2424 return amount;
2425 }
2426 }
2427
2428 //-----------------------------------------------------------------------
2429 static class TimeZoneName
2430 implements DateTimePrinter, DateTimeParser {
2431
2432 static final int LONG_NAME = 0;
2433 static final int SHORT_NAME = 1;
2434
2435 private final Map<String, DateTimeZone> iParseLookup;
2436 private final int iType;
2437
2438 TimeZoneName(int type, Map<String, DateTimeZone> parseLookup) {
2439 super();
2440 iType = type;
2441 iParseLookup = parseLookup;
2442 }
2443
2444 public int estimatePrintedLength() {
2445 return (iType == SHORT_NAME ? 4 : 20);
2446 }
2447
2448 public void printTo(
2449 StringBuffer buf, long instant, Chronology chrono,
2450 int displayOffset, DateTimeZone displayZone, Locale locale) {
2451 buf.append(print(instant - displayOffset, displayZone, locale));
2452 }
2453
2454 public void printTo(
2455 Writer out, long instant, Chronology chrono,
2456 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2457 out.write(print(instant - displayOffset, displayZone, locale));
2458 }
2459
2460 private String print(long instant, DateTimeZone displayZone, Locale locale) {
2461 if (displayZone == null) {
2462 return ""; // no zone
2463 }
2464 switch (iType) {
2465 case LONG_NAME:
2466 return displayZone.getName(instant, locale);
2467 case SHORT_NAME:
2468 return displayZone.getShortName(instant, locale);
2469 }
2470 return "";
2471 }
2472
2473 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2474 // no zone info
2475 }
2476
2477 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2478 // no zone info
2479 }
2480
2481 public int estimateParsedLength() {
2482 return (iType == SHORT_NAME ? 4 : 20);
2483 }
2484
2485 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2486 String str = text.substring(position);
2487 for (String name : iParseLookup.keySet()) {
2488 if (str.startsWith(name)) {
2489 bucket.setZone(iParseLookup.get(name));
2490 return position + name.length();
2491 }
2492 }
2493 return ~position;
2494 }
2495 }
2496
2497 //-----------------------------------------------------------------------
2498 static enum TimeZoneId
2499 implements DateTimePrinter, DateTimeParser {
2500
2501 INSTANCE;
2502 static final Set<String> ALL_IDS = DateTimeZone.getAvailableIDs();
2503 static final int MAX_LENGTH;
2504 static {
2505 int max = 0;
2506 for (String id : ALL_IDS) {
2507 max = Math.max(max, id.length());
2508 }
2509 MAX_LENGTH = max;
2510 }
2511
2512 public int estimatePrintedLength() {
2513 return MAX_LENGTH;
2514 }
2515
2516 public void printTo(
2517 StringBuffer buf, long instant, Chronology chrono,
2518 int displayOffset, DateTimeZone displayZone, Locale locale) {
2519 buf.append(displayZone != null ? displayZone.getID() : "");
2520 }
2521
2522 public void printTo(
2523 Writer out, long instant, Chronology chrono,
2524 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2525 out.write(displayZone != null ? displayZone.getID() : "");
2526 }
2527
2528 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2529 // no zone info
2530 }
2531
2532 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2533 // no zone info
2534 }
2535
2536 public int estimateParsedLength() {
2537 return MAX_LENGTH;
2538 }
2539
2540 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2541 String str = text.substring(position);
2542 String best = null;
2543 for (String id : ALL_IDS) {
2544 if (str.startsWith(id)) {
2545 if (best == null || id.length() > best.length()) {
2546 best = id;
2547 }
2548 }
2549 }
2550 if (best != null) {
2551 bucket.setZone(DateTimeZone.forID(best));
2552 return position + best.length();
2553 }
2554 return ~position;
2555 }
2556 }
2557
2558 //-----------------------------------------------------------------------
2559 static class Composite
2560 implements DateTimePrinter, DateTimeParser {
2561
2562 private final DateTimePrinter[] iPrinters;
2563 private final DateTimeParser[] iParsers;
2564
2565 private final int iPrintedLengthEstimate;
2566 private final int iParsedLengthEstimate;
2567
2568 Composite(List<Object> elementPairs) {
2569 super();
2570
2571 List<Object> printerList = new ArrayList<Object>();
2572 List<Object> parserList = new ArrayList<Object>();
2573
2574 decompose(elementPairs, printerList, parserList);
2575
2576 if (printerList.size() <= 0) {
2577 iPrinters = null;
2578 iPrintedLengthEstimate = 0;
2579 } else {
2580 int size = printerList.size();
2581 iPrinters = new DateTimePrinter[size];
2582 int printEst = 0;
2583 for (int i=0; i<size; i++) {
2584 DateTimePrinter printer = (DateTimePrinter) printerList.get(i);
2585 printEst += printer.estimatePrintedLength();
2586 iPrinters[i] = printer;
2587 }
2588 iPrintedLengthEstimate = printEst;
2589 }
2590
2591 if (parserList.size() <= 0) {
2592 iParsers = null;
2593 iParsedLengthEstimate = 0;
2594 } else {
2595 int size = parserList.size();
2596 iParsers = new DateTimeParser[size];
2597 int parseEst = 0;
2598 for (int i=0; i<size; i++) {
2599 DateTimeParser parser = (DateTimeParser) parserList.get(i);
2600 parseEst += parser.estimateParsedLength();
2601 iParsers[i] = parser;
2602 }
2603 iParsedLengthEstimate = parseEst;
2604 }
2605 }
2606
2607 public int estimatePrintedLength() {
2608 return iPrintedLengthEstimate;
2609 }
2610
2611 public void printTo(
2612 StringBuffer buf, long instant, Chronology chrono,
2613 int displayOffset, DateTimeZone displayZone, Locale locale) {
2614 DateTimePrinter[] elements = iPrinters;
2615 if (elements == null) {
2616 throw new UnsupportedOperationException();
2617 }
2618
2619 if (locale == null) {
2620 // Guard against default locale changing concurrently.
2621 locale = Locale.getDefault();
2622 }
2623
2624 int len = elements.length;
2625 for (int i = 0; i < len; i++) {
2626 elements[i].printTo(buf, instant, chrono, displayOffset, displayZone, locale);
2627 }
2628 }
2629
2630 public void printTo(
2631 Writer out, long instant, Chronology chrono,
2632 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2633 DateTimePrinter[] elements = iPrinters;
2634 if (elements == null) {
2635 throw new UnsupportedOperationException();
2636 }
2637
2638 if (locale == null) {
2639 // Guard against default locale changing concurrently.
2640 locale = Locale.getDefault();
2641 }
2642
2643 int len = elements.length;
2644 for (int i = 0; i < len; i++) {
2645 elements[i].printTo(out, instant, chrono, displayOffset, displayZone, locale);
2646 }
2647 }
2648
2649 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2650 DateTimePrinter[] elements = iPrinters;
2651 if (elements == null) {
2652 throw new UnsupportedOperationException();
2653 }
2654
2655 if (locale == null) {
2656 // Guard against default locale changing concurrently.
2657 locale = Locale.getDefault();
2658 }
2659
2660 int len = elements.length;
2661 for (int i=0; i<len; i++) {
2662 elements[i].printTo(buf, partial, locale);
2663 }
2664 }
2665
2666 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2667 DateTimePrinter[] elements = iPrinters;
2668 if (elements == null) {
2669 throw new UnsupportedOperationException();
2670 }
2671
2672 if (locale == null) {
2673 // Guard against default locale changing concurrently.
2674 locale = Locale.getDefault();
2675 }
2676
2677 int len = elements.length;
2678 for (int i=0; i<len; i++) {
2679 elements[i].printTo(out, partial, locale);
2680 }
2681 }
2682
2683 public int estimateParsedLength() {
2684 return iParsedLengthEstimate;
2685 }
2686
2687 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2688 DateTimeParser[] elements = iParsers;
2689 if (elements == null) {
2690 throw new UnsupportedOperationException();
2691 }
2692
2693 int len = elements.length;
2694 for (int i=0; i<len && position >= 0; i++) {
2695 position = elements[i].parseInto(bucket, text, position);
2696 }
2697 return position;
2698 }
2699
2700 boolean isPrinter() {
2701 return iPrinters != null;
2702 }
2703
2704 boolean isParser() {
2705 return iParsers != null;
2706 }
2707
2708 /**
2709 * Processes the element pairs, putting results into the given printer
2710 * and parser lists.
2711 */
2712 private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) {
2713 int size = elementPairs.size();
2714 for (int i=0; i<size; i+=2) {
2715 Object element = elementPairs.get(i);
2716 if (element instanceof DateTimePrinter) {
2717 if (element instanceof Composite) {
2718 addArrayToList(printerList, ((Composite)element).iPrinters);
2719 } else {
2720 printerList.add(element);
2721 }
2722 }
2723
2724 element = elementPairs.get(i + 1);
2725 if (element instanceof DateTimeParser) {
2726 if (element instanceof Composite) {
2727 addArrayToList(parserList, ((Composite)element).iParsers);
2728 } else {
2729 parserList.add(element);
2730 }
2731 }
2732 }
2733 }
2734
2735 private void addArrayToList(List<Object> list, Object[] array) {
2736 if (array != null) {
2737 for (int i=0; i<array.length; i++) {
2738 list.add(array[i]);
2739 }
2740 }
2741 }
2742 }
2743
2744 //-----------------------------------------------------------------------
2745 static class MatchingParser
2746 implements DateTimeParser {
2747
2748 private final DateTimeParser[] iParsers;
2749 private final int iParsedLengthEstimate;
2750
2751 MatchingParser(DateTimeParser[] parsers) {
2752 super();
2753 iParsers = parsers;
2754 int est = 0;
2755 for (int i=parsers.length; --i>=0 ;) {
2756 DateTimeParser parser = parsers[i];
2757 if (parser != null) {
2758 int len = parser.estimateParsedLength();
2759 if (len > est) {
2760 est = len;
2761 }
2762 }
2763 }
2764 iParsedLengthEstimate = est;
2765 }
2766
2767 public int estimateParsedLength() {
2768 return iParsedLengthEstimate;
2769 }
2770
2771 public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2772 DateTimeParser[] parsers = iParsers;
2773 int length = parsers.length;
2774
2775 final Object originalState = bucket.saveState();
2776 boolean isOptional = false;
2777
2778 int bestValidPos = position;
2779 Object bestValidState = null;
2780
2781 int bestInvalidPos = position;
2782
2783 for (int i=0; i<length; i++) {
2784 DateTimeParser parser = parsers[i];
2785 if (parser == null) {
2786 // The empty parser wins only if nothing is better.
2787 if (bestValidPos <= position) {
2788 return position;
2789 }
2790 isOptional = true;
2791 break;
2792 }
2793 int parsePos = parser.parseInto(bucket, text, position);
2794 if (parsePos >= position) {
2795 if (parsePos > bestValidPos) {
2796 if (parsePos >= text.length() ||
2797 (i + 1) >= length || parsers[i + 1] == null) {
2798
2799 // Completely parsed text or no more parsers to
2800 // check. Skip the rest.
2801 return parsePos;
2802 }
2803 bestValidPos = parsePos;
2804 bestValidState = bucket.saveState();
2805 }
2806 } else {
2807 if (parsePos < 0) {
2808 parsePos = ~parsePos;
2809 if (parsePos > bestInvalidPos) {
2810 bestInvalidPos = parsePos;
2811 }
2812 }
2813 }
2814 bucket.restoreState(originalState);
2815 }
2816
2817 if (bestValidPos > position || (bestValidPos == position && isOptional)) {
2818 // Restore the state to the best valid parse.
2819 if (bestValidState != null) {
2820 bucket.restoreState(bestValidState);
2821 }
2822 return bestValidPos;
2823 }
2824
2825 return ~bestInvalidPos;
2826 }
2827 }
2828
2829 }