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