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.Locale; |
21 | |
22 | import org.joda.time.Chronology; |
23 | import org.joda.time.DateTime; |
24 | import org.joda.time.DateTimeUtils; |
25 | import org.joda.time.DateTimeZone; |
26 | import org.joda.time.MutableDateTime; |
27 | import org.joda.time.ReadWritableInstant; |
28 | import org.joda.time.ReadableInstant; |
29 | import org.joda.time.ReadablePartial; |
30 | |
31 | /** |
32 | * Controls the printing and parsing of a datetime to and from a string. |
33 | * <p> |
34 | * This class is the main API for printing and parsing used by most applications. |
35 | * Instances of this class are created via one of three factory classes: |
36 | * <ul> |
37 | * <li>{@link DateTimeFormat} - formats by pattern and style</li> |
38 | * <li>{@link ISODateTimeFormat} - ISO8601 formats</li> |
39 | * <li>{@link DateTimeFormatterBuilder} - complex formats created via method calls</li> |
40 | * </ul> |
41 | * <p> |
42 | * An instance of this class holds a reference internally to one printer and |
43 | * one parser. It is possible that one of these may be null, in which case the |
44 | * formatter cannot print/parse. This can be checked via the {@link #isPrinter()} |
45 | * and {@link #isParser()} methods. |
46 | * <p> |
47 | * The underlying printer/parser can be altered to behave exactly as required |
48 | * by using one of the decorator modifiers: |
49 | * <ul> |
50 | * <li>{@link #withLocale(Locale)} - returns a new formatter that uses the specified locale</li> |
51 | * <li>{@link #withZone(DateTimeZone)} - returns a new formatter that uses the specified time zone</li> |
52 | * <li>{@link #withChronology(Chronology)} - returns a new formatter that uses the specified chronology</li> |
53 | * <li>{@link #withOffsetParsed()} - returns a new formatter that returns the parsed time zone offset</li> |
54 | * </ul> |
55 | * Each of these returns a new formatter (instances of this class are immutable). |
56 | * <p> |
57 | * The main methods of the class are the <code>printXxx</code> and |
58 | * <code>parseXxx</code> methods. These are used as follows: |
59 | * <pre> |
60 | * // print using the defaults (default locale, chronology/zone of the datetime) |
61 | * String dateStr = formatter.print(dt); |
62 | * // print using the French locale |
63 | * String dateStr = formatter.withLocale(Locale.FRENCH).print(dt); |
64 | * // print using the UTC zone |
65 | * String dateStr = formatter.withZone(DateTimeZone.UTC).print(dt); |
66 | * |
67 | * // parse using the Paris zone |
68 | * DateTime date = formatter.withZone(DateTimeZone.forID("Europe/Paris")).parseDateTime(str); |
69 | * </pre> |
70 | * |
71 | * @author Brian S O'Neill |
72 | * @author Stephen Colebourne |
73 | * @author Fredrik Borgh |
74 | * @since 1.0 |
75 | */ |
76 | public class DateTimeFormatter { |
77 | |
78 | /** The internal printer used to output the datetime. */ |
79 | private final DateTimePrinter iPrinter; |
80 | /** The internal parser used to output the datetime. */ |
81 | private final DateTimeParser iParser; |
82 | /** The locale to use for printing and parsing. */ |
83 | private final Locale iLocale; |
84 | /** Whether the offset is parsed. */ |
85 | private final boolean iOffsetParsed; |
86 | /** The chronology to use as an override. */ |
87 | private final Chronology iChrono; |
88 | /** The zone to use as an override. */ |
89 | private final DateTimeZone iZone; |
90 | /* The pivot year to use for two-digit year parsing. */ |
91 | private final Integer iPivotYear; |
92 | |
93 | /** |
94 | * Creates a new formatter, however you will normally use the factory |
95 | * or the builder. |
96 | * |
97 | * @param printer the internal printer, null if cannot print |
98 | * @param parser the internal parser, null if cannot parse |
99 | */ |
100 | public DateTimeFormatter( |
101 | DateTimePrinter printer, DateTimeParser parser) { |
102 | super(); |
103 | iPrinter = printer; |
104 | iParser = parser; |
105 | iLocale = null; |
106 | iOffsetParsed = false; |
107 | iChrono = null; |
108 | iZone = null; |
109 | iPivotYear = null; |
110 | } |
111 | |
112 | /** |
113 | * Constructor. |
114 | */ |
115 | private DateTimeFormatter( |
116 | DateTimePrinter printer, DateTimeParser parser, |
117 | Locale locale, boolean offsetParsed, |
118 | Chronology chrono, DateTimeZone zone, |
119 | Integer pivotYear) { |
120 | super(); |
121 | iPrinter = printer; |
122 | iParser = parser; |
123 | iLocale = locale; |
124 | iOffsetParsed = offsetParsed; |
125 | iChrono = chrono; |
126 | iZone = zone; |
127 | iPivotYear = pivotYear; |
128 | } |
129 | |
130 | //----------------------------------------------------------------------- |
131 | /** |
132 | * Is this formatter capable of printing. |
133 | * |
134 | * @return true if this is a printer |
135 | */ |
136 | public boolean isPrinter() { |
137 | return (iPrinter != null); |
138 | } |
139 | |
140 | /** |
141 | * Gets the internal printer object that performs the real printing work. |
142 | * |
143 | * @return the internal printer; is null if printing not supported |
144 | */ |
145 | public DateTimePrinter getPrinter() { |
146 | return iPrinter; |
147 | } |
148 | |
149 | /** |
150 | * Is this formatter capable of parsing. |
151 | * |
152 | * @return true if this is a parser |
153 | */ |
154 | public boolean isParser() { |
155 | return (iParser != null); |
156 | } |
157 | |
158 | /** |
159 | * Gets the internal parser object that performs the real parsing work. |
160 | * |
161 | * @return the internal parser; is null if parsing not supported |
162 | */ |
163 | public DateTimeParser getParser() { |
164 | return iParser; |
165 | } |
166 | |
167 | //----------------------------------------------------------------------- |
168 | /** |
169 | * Returns a new formatter with a different locale that will be used |
170 | * for printing and parsing. |
171 | * <p> |
172 | * A DateTimeFormatter is immutable, so a new instance is returned, |
173 | * and the original is unaltered and still usable. |
174 | * |
175 | * @param locale the locale to use; if null, formatter uses default locale |
176 | * at invocation time |
177 | * @return the new formatter |
178 | */ |
179 | public DateTimeFormatter withLocale(Locale locale) { |
180 | if (locale == getLocale() || (locale != null && locale.equals(getLocale()))) { |
181 | return this; |
182 | } |
183 | return new DateTimeFormatter(iPrinter, iParser, locale, |
184 | iOffsetParsed, iChrono, iZone, iPivotYear); |
185 | } |
186 | |
187 | /** |
188 | * Gets the locale that will be used for printing and parsing. |
189 | * |
190 | * @return the locale to use; if null, formatter uses default locale at |
191 | * invocation time |
192 | */ |
193 | public Locale getLocale() { |
194 | return iLocale; |
195 | } |
196 | |
197 | //----------------------------------------------------------------------- |
198 | /** |
199 | * Returns a new formatter that will create a datetime with a time zone |
200 | * equal to that of the offset of the parsed string. |
201 | * <p> |
202 | * After calling this method, a string '2004-06-09T10:20:30-08:00' will |
203 | * create a datetime with a zone of -08:00 (a fixed zone, with no daylight |
204 | * savings rules). If the parsed string represents a local time (no zone |
205 | * offset) the parsed datetime will be in the default zone. |
206 | * <p> |
207 | * Calling this method sets the override zone to null. |
208 | * Calling the override zone method sets this flag off. |
209 | * |
210 | * @return the new formatter |
211 | */ |
212 | public DateTimeFormatter withOffsetParsed() { |
213 | if (iOffsetParsed == true) { |
214 | return this; |
215 | } |
216 | return new DateTimeFormatter(iPrinter, iParser, iLocale, |
217 | true, iChrono, null, iPivotYear); |
218 | } |
219 | |
220 | /** |
221 | * Checks whether the offset from the string is used as the zone of |
222 | * the parsed datetime. |
223 | * |
224 | * @return true if the offset from the string is used as the zone |
225 | */ |
226 | public boolean isOffsetParsed() { |
227 | return iOffsetParsed; |
228 | } |
229 | |
230 | //----------------------------------------------------------------------- |
231 | /** |
232 | * Returns a new formatter that will use the specified chronology in |
233 | * preference to that of the printed object, or ISO on a parse. |
234 | * <p> |
235 | * When printing, this chronolgy will be used in preference to the chronology |
236 | * from the datetime that would otherwise be used. |
237 | * <p> |
238 | * When parsing, this chronology will be set on the parsed datetime. |
239 | * <p> |
240 | * A null chronology means no-override. |
241 | * If both an override chronology and an override zone are set, the |
242 | * override zone will take precedence over the zone in the chronology. |
243 | * |
244 | * @param chrono the chronology to use as an override |
245 | * @return the new formatter |
246 | */ |
247 | public DateTimeFormatter withChronology(Chronology chrono) { |
248 | if (iChrono == chrono) { |
249 | return this; |
250 | } |
251 | return new DateTimeFormatter(iPrinter, iParser, iLocale, |
252 | iOffsetParsed, chrono, iZone, iPivotYear); |
253 | } |
254 | |
255 | /** |
256 | * Gets the chronology to use as an override. |
257 | * |
258 | * @return the chronology to use as an override |
259 | */ |
260 | public Chronology getChronolgy() { |
261 | return iChrono; |
262 | } |
263 | |
264 | //----------------------------------------------------------------------- |
265 | /** |
266 | * Returns a new formatter that will use the specified zone in preference |
267 | * to the zone of the printed object, or default zone on a parse. |
268 | * <p> |
269 | * When printing, this zone will be used in preference to the zone |
270 | * from the datetime that would otherwise be used. |
271 | * <p> |
272 | * When parsing, this zone will be set on the parsed datetime. |
273 | * <p> |
274 | * A null zone means of no-override. |
275 | * If both an override chronology and an override zone are set, the |
276 | * override zone will take precedence over the zone in the chronology. |
277 | * |
278 | * @param zone the zone to use as an override |
279 | * @return the new formatter |
280 | */ |
281 | public DateTimeFormatter withZone(DateTimeZone zone) { |
282 | if (iZone == zone) { |
283 | return this; |
284 | } |
285 | return new DateTimeFormatter(iPrinter, iParser, iLocale, |
286 | false, iChrono, zone, iPivotYear); |
287 | } |
288 | |
289 | /** |
290 | * Gets the zone to use as an override. |
291 | * |
292 | * @return the zone to use as an override |
293 | */ |
294 | public DateTimeZone getZone() { |
295 | return iZone; |
296 | } |
297 | |
298 | //----------------------------------------------------------------------- |
299 | /** |
300 | * Returns a new formatter that will use the specified pivot year for two |
301 | * digit year parsing in preference to that stored in the parser. |
302 | * <p> |
303 | * This setting is useful for changing the pivot year of formats built |
304 | * using a pattern - {@link DateTimeFormat#forPattern(String)}. |
305 | * <p> |
306 | * When parsing, this pivot year is used. Null means no-override. |
307 | * There is no effect when printing. |
308 | * <p> |
309 | * The pivot year enables a two digit year to be converted to a four |
310 | * digit year. The pivot represents the year in the middle of the |
311 | * supported range of years. Thus the full range of years that will |
312 | * be built is <code>(pivot - 50) .. (pivot + 49)</code>. |
313 | * |
314 | * <pre> |
315 | * pivot supported range 00 is 20 is 40 is 60 is 80 is |
316 | * --------------------------------------------------------------- |
317 | * 1950 1900..1999 1900 1920 1940 1960 1980 |
318 | * 1975 1925..2024 2000 2020 1940 1960 1980 |
319 | * 2000 1950..2049 2000 2020 2040 1960 1980 |
320 | * 2025 1975..2074 2000 2020 2040 2060 1980 |
321 | * 2050 2000..2099 2000 2020 2040 2060 2080 |
322 | * </pre> |
323 | * |
324 | * @param pivotYear the pivot year to use as an override when parsing |
325 | * @return the new formatter |
326 | * @since 1.1 |
327 | */ |
328 | public DateTimeFormatter withPivotYear(Integer pivotYear) { |
329 | if (iPivotYear == pivotYear || (iPivotYear != null && iPivotYear.equals(pivotYear))) { |
330 | return this; |
331 | } |
332 | return new DateTimeFormatter(iPrinter, iParser, iLocale, |
333 | iOffsetParsed, iChrono, iZone, pivotYear); |
334 | } |
335 | |
336 | /** |
337 | * Returns a new formatter that will use the specified pivot year for two |
338 | * digit year parsing in preference to that stored in the parser. |
339 | * <p> |
340 | * This setting is useful for changing the pivot year of formats built |
341 | * using a pattern - {@link DateTimeFormat#forPattern(String)}. |
342 | * <p> |
343 | * When parsing, this pivot year is used. |
344 | * There is no effect when printing. |
345 | * <p> |
346 | * The pivot year enables a two digit year to be converted to a four |
347 | * digit year. The pivot represents the year in the middle of the |
348 | * supported range of years. Thus the full range of years that will |
349 | * be built is <code>(pivot - 50) .. (pivot + 49)</code>. |
350 | * |
351 | * <pre> |
352 | * pivot supported range 00 is 20 is 40 is 60 is 80 is |
353 | * --------------------------------------------------------------- |
354 | * 1950 1900..1999 1900 1920 1940 1960 1980 |
355 | * 1975 1925..2024 2000 2020 1940 1960 1980 |
356 | * 2000 1950..2049 2000 2020 2040 1960 1980 |
357 | * 2025 1975..2074 2000 2020 2040 2060 1980 |
358 | * 2050 2000..2099 2000 2020 2040 2060 2080 |
359 | * </pre> |
360 | * |
361 | * @param pivotYear the pivot year to use as an override when parsing |
362 | * @return the new formatter |
363 | * @since 1.1 |
364 | */ |
365 | public DateTimeFormatter withPivotYear(int pivotYear) { |
366 | return withPivotYear(new Integer(pivotYear)); |
367 | } |
368 | |
369 | /** |
370 | * Gets the pivot year to use as an override. |
371 | * |
372 | * @return the pivot year to use as an override |
373 | * @since 1.1 |
374 | */ |
375 | public Integer getPivotYear() { |
376 | return iPivotYear; |
377 | } |
378 | |
379 | //----------------------------------------------------------------------- |
380 | /** |
381 | * Prints a ReadableInstant, using the chronology supplied by the instant. |
382 | * |
383 | * @param buf formatted instant is appended to this buffer |
384 | * @param instant instant to format, null means now |
385 | */ |
386 | public void printTo(StringBuffer buf, ReadableInstant instant) { |
387 | long millis = DateTimeUtils.getInstantMillis(instant); |
388 | Chronology chrono = DateTimeUtils.getInstantChronology(instant); |
389 | printTo(buf, millis, chrono); |
390 | } |
391 | |
392 | /** |
393 | * Prints a ReadableInstant, using the chronology supplied by the instant. |
394 | * |
395 | * @param out formatted instant is written out |
396 | * @param instant instant to format, null means now |
397 | */ |
398 | public void printTo(Writer out, ReadableInstant instant) throws IOException { |
399 | long millis = DateTimeUtils.getInstantMillis(instant); |
400 | Chronology chrono = DateTimeUtils.getInstantChronology(instant); |
401 | printTo(out, millis, chrono); |
402 | } |
403 | |
404 | //----------------------------------------------------------------------- |
405 | /** |
406 | * Prints an instant from milliseconds since 1970-01-01T00:00:00Z, |
407 | * using ISO chronology in the default DateTimeZone. |
408 | * |
409 | * @param buf formatted instant is appended to this buffer |
410 | * @param instant millis since 1970-01-01T00:00:00Z |
411 | */ |
412 | public void printTo(StringBuffer buf, long instant) { |
413 | printTo(buf, instant, null); |
414 | } |
415 | |
416 | /** |
417 | * Prints an instant from milliseconds since 1970-01-01T00:00:00Z, |
418 | * using ISO chronology in the default DateTimeZone. |
419 | * |
420 | * @param out formatted instant is written out |
421 | * @param instant millis since 1970-01-01T00:00:00Z |
422 | */ |
423 | public void printTo(Writer out, long instant) throws IOException { |
424 | printTo(out, instant, null); |
425 | } |
426 | |
427 | //----------------------------------------------------------------------- |
428 | /** |
429 | * Prints a ReadablePartial. |
430 | * <p> |
431 | * Neither the override chronology nor the override zone are used |
432 | * by this method. |
433 | * |
434 | * @param buf formatted partial is appended to this buffer |
435 | * @param partial partial to format |
436 | */ |
437 | public void printTo(StringBuffer buf, ReadablePartial partial) { |
438 | DateTimePrinter printer = requirePrinter(); |
439 | if (partial == null) { |
440 | throw new IllegalArgumentException("The partial must not be null"); |
441 | } |
442 | printer.printTo(buf, partial, iLocale); |
443 | } |
444 | |
445 | /** |
446 | * Prints a ReadablePartial. |
447 | * <p> |
448 | * Neither the override chronology nor the override zone are used |
449 | * by this method. |
450 | * |
451 | * @param out formatted partial is written out |
452 | * @param partial partial to format |
453 | */ |
454 | public void printTo(Writer out, ReadablePartial partial) throws IOException { |
455 | DateTimePrinter printer = requirePrinter(); |
456 | if (partial == null) { |
457 | throw new IllegalArgumentException("The partial must not be null"); |
458 | } |
459 | printer.printTo(out, partial, iLocale); |
460 | } |
461 | |
462 | //----------------------------------------------------------------------- |
463 | /** |
464 | * Prints a ReadableInstant to a String. |
465 | * <p> |
466 | * This method will use the override zone and the override chronololgy if |
467 | * they are set. Otherwise it will use the chronology and zone of the instant. |
468 | * |
469 | * @param instant instant to format, null means now |
470 | * @return the printed result |
471 | */ |
472 | public String print(ReadableInstant instant) { |
473 | StringBuffer buf = new StringBuffer(requirePrinter().estimatePrintedLength()); |
474 | printTo(buf, instant); |
475 | return buf.toString(); |
476 | } |
477 | |
478 | /** |
479 | * Prints a millisecond instant to a String. |
480 | * <p> |
481 | * This method will use the override zone and the override chronololgy if |
482 | * they are set. Otherwise it will use the ISO chronology and default zone. |
483 | * |
484 | * @param instant millis since 1970-01-01T00:00:00Z |
485 | * @return the printed result |
486 | */ |
487 | public String print(long instant) { |
488 | StringBuffer buf = new StringBuffer(requirePrinter().estimatePrintedLength()); |
489 | printTo(buf, instant); |
490 | return buf.toString(); |
491 | } |
492 | |
493 | /** |
494 | * Prints a ReadablePartial to a new String. |
495 | * <p> |
496 | * Neither the override chronology nor the override zone are used |
497 | * by this method. |
498 | * |
499 | * @param partial partial to format |
500 | * @return the printed result |
501 | */ |
502 | public String print(ReadablePartial partial) { |
503 | StringBuffer buf = new StringBuffer(requirePrinter().estimatePrintedLength()); |
504 | printTo(buf, partial); |
505 | return buf.toString(); |
506 | } |
507 | |
508 | private void printTo(StringBuffer buf, long instant, Chronology chrono) { |
509 | DateTimePrinter printer = requirePrinter(); |
510 | chrono = selectChronology(chrono); |
511 | // Shift instant into local time (UTC) to avoid excessive offset |
512 | // calculations when printing multiple fields in a composite printer. |
513 | DateTimeZone zone = chrono.getZone(); |
514 | int offset = zone.getOffset(instant); |
515 | long adjustedInstant = instant + offset; |
516 | if ((instant ^ adjustedInstant) < 0 && (instant ^ offset) >= 0) { |
517 | // Time zone offset overflow, so revert to UTC. |
518 | zone = DateTimeZone.UTC; |
519 | offset = 0; |
520 | adjustedInstant = instant; |
521 | } |
522 | printer.printTo(buf, adjustedInstant, chrono.withUTC(), offset, zone, iLocale); |
523 | } |
524 | |
525 | private void printTo(Writer buf, long instant, Chronology chrono) throws IOException { |
526 | DateTimePrinter printer = requirePrinter(); |
527 | chrono = selectChronology(chrono); |
528 | // Shift instant into local time (UTC) to avoid excessive offset |
529 | // calculations when printing multiple fields in a composite printer. |
530 | DateTimeZone zone = chrono.getZone(); |
531 | int offset = zone.getOffset(instant); |
532 | long adjustedInstant = instant + offset; |
533 | if ((instant ^ adjustedInstant) < 0 && (instant ^ offset) >= 0) { |
534 | // Time zone offset overflow, so revert to UTC. |
535 | zone = DateTimeZone.UTC; |
536 | offset = 0; |
537 | adjustedInstant = instant; |
538 | } |
539 | printer.printTo(buf, adjustedInstant, chrono.withUTC(), offset, zone, iLocale); |
540 | } |
541 | |
542 | /** |
543 | * Checks whether printing is supported. |
544 | * |
545 | * @throws UnsupportedOperationException if printing is not supported |
546 | */ |
547 | private DateTimePrinter requirePrinter() { |
548 | DateTimePrinter printer = iPrinter; |
549 | if (printer == null) { |
550 | throw new UnsupportedOperationException("Printing not supported"); |
551 | } |
552 | return printer; |
553 | } |
554 | |
555 | //----------------------------------------------------------------------- |
556 | /** |
557 | * Parses a datetime from the given text, at the given position, saving the |
558 | * result into the fields of the given ReadWritableInstant. If the parse |
559 | * succeeds, the return value is the new text position. Note that the parse |
560 | * may succeed without fully reading the text and in this case those fields |
561 | * that were read will be set. |
562 | * <p> |
563 | * Only those fields present in the string will be changed in the specified |
564 | * instant. All other fields will remain unaltered. Thus if the string only |
565 | * contains a year and a month, then the day and time will be retained from |
566 | * the input instant. If this is not the behaviour you want, then reset the |
567 | * fields before calling this method, or use {@link #parseDateTime(String)} |
568 | * or {@link #parseMutableDateTime(String)}. |
569 | * <p> |
570 | * If it fails, the return value is negative, but the instant may still be |
571 | * modified. To determine the position where the parse failed, apply the |
572 | * one's complement operator (~) on the return value. |
573 | * <p> |
574 | * The parse will use the chronology of the instant. |
575 | * |
576 | * @param instant an instant that will be modified, not null |
577 | * @param text the text to parse |
578 | * @param position position to start parsing from |
579 | * @return new position, negative value means parse failed - |
580 | * apply complement operator (~) to get position of failure |
581 | * @throws UnsupportedOperationException if parsing is not supported |
582 | * @throws IllegalArgumentException if the instant is null |
583 | * @throws IllegalArgumentException if any field is out of range |
584 | */ |
585 | public int parseInto(ReadWritableInstant instant, String text, int position) { |
586 | DateTimeParser parser = requireParser(); |
587 | if (instant == null) { |
588 | throw new IllegalArgumentException("Instant must not be null"); |
589 | } |
590 | |
591 | long instantMillis = instant.getMillis(); |
592 | Chronology chrono = instant.getChronology(); |
593 | long instantLocal = instantMillis + chrono.getZone().getOffset(instantMillis); |
594 | chrono = selectChronology(chrono); |
595 | |
596 | DateTimeParserBucket bucket = new DateTimeParserBucket |
597 | (instantLocal, chrono, iLocale, iPivotYear); |
598 | int newPos = parser.parseInto(bucket, text, position); |
599 | instant.setMillis(bucket.computeMillis(false, text)); |
600 | if (iOffsetParsed && bucket.getZone() == null) { |
601 | int parsedOffset = bucket.getOffset(); |
602 | DateTimeZone parsedZone = DateTimeZone.forOffsetMillis(parsedOffset); |
603 | chrono = chrono.withZone(parsedZone); |
604 | } |
605 | instant.setChronology(chrono); |
606 | return newPos; |
607 | } |
608 | |
609 | /** |
610 | * Parses a datetime from the given text, returning the number of |
611 | * milliseconds since the epoch, 1970-01-01T00:00:00Z. |
612 | * <p> |
613 | * The parse will use the ISO chronology, and the default time zone. |
614 | * If the text contains a time zone string then that will be taken into account. |
615 | * |
616 | * @param text text to parse |
617 | * @return parsed value expressed in milliseconds since the epoch |
618 | * @throws UnsupportedOperationException if parsing is not supported |
619 | * @throws IllegalArgumentException if the text to parse is invalid |
620 | */ |
621 | public long parseMillis(String text) { |
622 | DateTimeParser parser = requireParser(); |
623 | |
624 | Chronology chrono = selectChronology(iChrono); |
625 | DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear); |
626 | int newPos = parser.parseInto(bucket, text, 0); |
627 | if (newPos >= 0) { |
628 | if (newPos >= text.length()) { |
629 | return bucket.computeMillis(true, text); |
630 | } |
631 | } else { |
632 | newPos = ~newPos; |
633 | } |
634 | throw new IllegalArgumentException(FormatUtils.createErrorMessage(text, newPos)); |
635 | } |
636 | |
637 | /** |
638 | * Parses a datetime from the given text, returning a new DateTime. |
639 | * <p> |
640 | * The parse will use the zone and chronology specified on this formatter. |
641 | * <p> |
642 | * If the text contains a time zone string then that will be taken into |
643 | * account in adjusting the time of day as follows. |
644 | * If the {@link #withOffsetParsed()} has been called, then the resulting |
645 | * DateTime will have a fixed offset based on the parsed time zone. |
646 | * Otherwise the resulting DateTime will have the zone of this formatter, |
647 | * but the parsed zone may have caused the time to be adjusted. |
648 | * |
649 | * @param text the text to parse |
650 | * @return parsed value in a DateTime object |
651 | * @throws UnsupportedOperationException if parsing is not supported |
652 | * @throws IllegalArgumentException if the text to parse is invalid |
653 | */ |
654 | public DateTime parseDateTime(String text) { |
655 | DateTimeParser parser = requireParser(); |
656 | |
657 | Chronology chrono = selectChronology(null); |
658 | DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear); |
659 | int newPos = parser.parseInto(bucket, text, 0); |
660 | if (newPos >= 0) { |
661 | if (newPos >= text.length()) { |
662 | long millis = bucket.computeMillis(true, text); |
663 | if (iOffsetParsed && bucket.getZone() == null) { |
664 | int parsedOffset = bucket.getOffset(); |
665 | DateTimeZone parsedZone = DateTimeZone.forOffsetMillis(parsedOffset); |
666 | chrono = chrono.withZone(parsedZone); |
667 | } |
668 | return new DateTime(millis, chrono); |
669 | } |
670 | } else { |
671 | newPos = ~newPos; |
672 | } |
673 | throw new IllegalArgumentException(FormatUtils.createErrorMessage(text, newPos)); |
674 | } |
675 | |
676 | /** |
677 | * Parses a datetime from the given text, returning a new MutableDateTime. |
678 | * <p> |
679 | * The parse will use the zone and chronology specified on this formatter. |
680 | * <p> |
681 | * If the text contains a time zone string then that will be taken into |
682 | * account in adjusting the time of day as follows. |
683 | * If the {@link #withOffsetParsed()} has been called, then the resulting |
684 | * DateTime will have a fixed offset based on the parsed time zone. |
685 | * Otherwise the resulting DateTime will have the zone of this formatter, |
686 | * but the parsed zone may have caused the time to be adjusted. |
687 | * |
688 | * @param text the text to parse |
689 | * @return parsed value in a MutableDateTime object |
690 | * @throws UnsupportedOperationException if parsing is not supported |
691 | * @throws IllegalArgumentException if the text to parse is invalid |
692 | */ |
693 | public MutableDateTime parseMutableDateTime(String text) { |
694 | DateTimeParser parser = requireParser(); |
695 | |
696 | Chronology chrono = selectChronology(null); |
697 | DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear); |
698 | int newPos = parser.parseInto(bucket, text, 0); |
699 | if (newPos >= 0) { |
700 | if (newPos >= text.length()) { |
701 | long millis = bucket.computeMillis(true, text); |
702 | if (iOffsetParsed && bucket.getZone() == null) { |
703 | int parsedOffset = bucket.getOffset(); |
704 | DateTimeZone parsedZone = DateTimeZone.forOffsetMillis(parsedOffset); |
705 | chrono = chrono.withZone(parsedZone); |
706 | } |
707 | return new MutableDateTime(millis, chrono); |
708 | } |
709 | } else { |
710 | newPos = ~newPos; |
711 | } |
712 | throw new IllegalArgumentException(FormatUtils.createErrorMessage(text, newPos)); |
713 | } |
714 | |
715 | /** |
716 | * Checks whether parsing is supported. |
717 | * |
718 | * @throws UnsupportedOperationException if parsing is not supported |
719 | */ |
720 | private DateTimeParser requireParser() { |
721 | DateTimeParser parser = iParser; |
722 | if (parser == null) { |
723 | throw new UnsupportedOperationException("Parsing not supported"); |
724 | } |
725 | return parser; |
726 | } |
727 | |
728 | //----------------------------------------------------------------------- |
729 | /** |
730 | * Determines the correct chronology to use. |
731 | * |
732 | * @param chrono the proposed chronology |
733 | * @return the actual chronology |
734 | */ |
735 | private Chronology selectChronology(Chronology chrono) { |
736 | chrono = DateTimeUtils.getChronology(chrono); |
737 | if (iChrono != null) { |
738 | chrono = iChrono; |
739 | } |
740 | if (iZone != null) { |
741 | chrono = chrono.withZone(iZone); |
742 | } |
743 | return chrono; |
744 | } |
745 | |
746 | } |