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