001 /*
002 * Copyright 2001-2005 Stephen Colebourne
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.joda.time.format;
017
018 import java.io.IOException;
019 import java.io.Writer;
020 import java.util.Locale;
021
022 import org.joda.time.Chronology;
023 import org.joda.time.DateTime;
024 import org.joda.time.DateTimeUtils;
025 import org.joda.time.DateTimeZone;
026 import org.joda.time.MutableDateTime;
027 import org.joda.time.ReadWritableInstant;
028 import org.joda.time.ReadableInstant;
029 import org.joda.time.ReadablePartial;
030
031 /**
032 * Controls the printing and parsing of a datetime to and from a string.
033 * <p>
034 * This class is the main API for printing and parsing used by most applications.
035 * Instances of this class are created via one of three factory classes:
036 * <ul>
037 * <li>{@link DateTimeFormat} - formats by pattern and style</li>
038 * <li>{@link ISODateTimeFormat} - ISO8601 formats</li>
039 * <li>{@link DateTimeFormatterBuilder} - complex formats created via method calls</li>
040 * </ul>
041 * <p>
042 * An instance of this class holds a reference internally to one printer and
043 * one parser. It is possible that one of these may be null, in which case the
044 * formatter cannot print/parse. This can be checked via the {@link #isPrinter()}
045 * and {@link #isParser()} methods.
046 * <p>
047 * The underlying printer/parser can be altered to behave exactly as required
048 * by using one of the decorator modifiers:
049 * <ul>
050 * <li>{@link #withLocale(Locale)} - returns a new formatter that uses the specified locale</li>
051 * <li>{@link #withZone(DateTimeZone)} - returns a new formatter that uses the specified time zone</li>
052 * <li>{@link #withChronology(Chronology)} - returns a new formatter that uses the specified chronology</li>
053 * <li>{@link #withOffsetParsed()} - returns a new formatter that returns the parsed time zone offset</li>
054 * </ul>
055 * Each of these returns a new formatter (instances of this class are immutable).
056 * <p>
057 * The main methods of the class are the <code>printXxx</code> and
058 * <code>parseXxx</code> methods. These are used as follows:
059 * <pre>
060 * // print using the defaults (default locale, chronology/zone of the datetime)
061 * String dateStr = formatter.print(dt);
062 * // print using the French locale
063 * String dateStr = formatter.withLocale(Locale.FRENCH).print(dt);
064 * // print using the UTC zone
065 * String dateStr = formatter.withZone(DateTimeZone.UTC).print(dt);
066 *
067 * // parse using the Paris zone
068 * DateTime date = formatter.withZone(DateTimeZone.forID("Europe/Paris")).parseDateTime(str);
069 * </pre>
070 *
071 * @author Brian S O'Neill
072 * @author Stephen Colebourne
073 * @author Fredrik Borgh
074 * @since 1.0
075 */
076 public class DateTimeFormatter {
077
078 /** The internal printer used to output the datetime. */
079 private final DateTimePrinter iPrinter;
080 /** The internal parser used to output the datetime. */
081 private final DateTimeParser iParser;
082 /** The locale to use for printing and parsing. */
083 private final Locale iLocale;
084 /** Whether the offset is parsed. */
085 private final boolean iOffsetParsed;
086 /** The chronology to use as an override. */
087 private final Chronology iChrono;
088 /** The zone to use as an override. */
089 private final DateTimeZone iZone;
090 /* The pivot year to use for two-digit year parsing. */
091 private final Integer iPivotYear;
092
093 /**
094 * Creates a new formatter, however you will normally use the factory
095 * or the builder.
096 *
097 * @param printer the internal printer, null if cannot print
098 * @param parser the internal parser, null if cannot parse
099 */
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 getChronology() {
261 return iChrono;
262 }
263
264 /**
265 * Gets the chronology to use as an override.
266 *
267 * @return the chronology to use as an override
268 * @deprecated Use the method with the correct spelling
269 */
270 public Chronology getChronolgy() {
271 return iChrono;
272 }
273
274 //-----------------------------------------------------------------------
275 /**
276 * Returns a new formatter that will use the specified zone in preference
277 * to the zone of the printed object, or default zone on a parse.
278 * <p>
279 * When printing, this zone will be used in preference to the zone
280 * from the datetime that would otherwise be used.
281 * <p>
282 * When parsing, this zone will be set on the parsed datetime.
283 * <p>
284 * A null zone means of no-override.
285 * If both an override chronology and an override zone are set, the
286 * override zone will take precedence over the zone in the chronology.
287 *
288 * @param zone the zone to use as an override
289 * @return the new formatter
290 */
291 public DateTimeFormatter withZone(DateTimeZone zone) {
292 if (iZone == zone) {
293 return this;
294 }
295 return new DateTimeFormatter(iPrinter, iParser, iLocale,
296 false, iChrono, zone, iPivotYear);
297 }
298
299 /**
300 * Gets the zone to use as an override.
301 *
302 * @return the zone to use as an override
303 */
304 public DateTimeZone getZone() {
305 return iZone;
306 }
307
308 //-----------------------------------------------------------------------
309 /**
310 * Returns a new formatter that will use the specified pivot year for two
311 * digit year parsing in preference to that stored in the parser.
312 * <p>
313 * This setting is useful for changing the pivot year of formats built
314 * using a pattern - {@link DateTimeFormat#forPattern(String)}.
315 * <p>
316 * When parsing, this pivot year is used. Null means no-override.
317 * There is no effect when printing.
318 * <p>
319 * The pivot year enables a two digit year to be converted to a four
320 * digit year. The pivot represents the year in the middle of the
321 * supported range of years. Thus the full range of years that will
322 * be built is <code>(pivot - 50) .. (pivot + 49)</code>.
323 *
324 * <pre>
325 * pivot supported range 00 is 20 is 40 is 60 is 80 is
326 * ---------------------------------------------------------------
327 * 1950 1900..1999 1900 1920 1940 1960 1980
328 * 1975 1925..2024 2000 2020 1940 1960 1980
329 * 2000 1950..2049 2000 2020 2040 1960 1980
330 * 2025 1975..2074 2000 2020 2040 2060 1980
331 * 2050 2000..2099 2000 2020 2040 2060 2080
332 * </pre>
333 *
334 * @param pivotYear the pivot year to use as an override when parsing
335 * @return the new formatter
336 * @since 1.1
337 */
338 public DateTimeFormatter withPivotYear(Integer pivotYear) {
339 if (iPivotYear == pivotYear || (iPivotYear != null && iPivotYear.equals(pivotYear))) {
340 return this;
341 }
342 return new DateTimeFormatter(iPrinter, iParser, iLocale,
343 iOffsetParsed, iChrono, iZone, pivotYear);
344 }
345
346 /**
347 * Returns a new formatter that will use the specified pivot year for two
348 * digit year parsing in preference to that stored in the parser.
349 * <p>
350 * This setting is useful for changing the pivot year of formats built
351 * using a pattern - {@link DateTimeFormat#forPattern(String)}.
352 * <p>
353 * When parsing, this pivot year is used.
354 * There is no effect when printing.
355 * <p>
356 * The pivot year enables a two digit year to be converted to a four
357 * digit year. The pivot represents the year in the middle of the
358 * supported range of years. Thus the full range of years that will
359 * be built is <code>(pivot - 50) .. (pivot + 49)</code>.
360 *
361 * <pre>
362 * pivot supported range 00 is 20 is 40 is 60 is 80 is
363 * ---------------------------------------------------------------
364 * 1950 1900..1999 1900 1920 1940 1960 1980
365 * 1975 1925..2024 2000 2020 1940 1960 1980
366 * 2000 1950..2049 2000 2020 2040 1960 1980
367 * 2025 1975..2074 2000 2020 2040 2060 1980
368 * 2050 2000..2099 2000 2020 2040 2060 2080
369 * </pre>
370 *
371 * @param pivotYear the pivot year to use as an override when parsing
372 * @return the new formatter
373 * @since 1.1
374 */
375 public DateTimeFormatter withPivotYear(int pivotYear) {
376 return withPivotYear(new Integer(pivotYear));
377 }
378
379 /**
380 * Gets the pivot year to use as an override.
381 *
382 * @return the pivot year to use as an override
383 * @since 1.1
384 */
385 public Integer getPivotYear() {
386 return iPivotYear;
387 }
388
389 //-----------------------------------------------------------------------
390 /**
391 * Prints a ReadableInstant, using the chronology supplied by the instant.
392 *
393 * @param buf formatted instant is appended to this buffer
394 * @param instant instant to format, null means now
395 */
396 public void printTo(StringBuffer buf, ReadableInstant instant) {
397 long millis = DateTimeUtils.getInstantMillis(instant);
398 Chronology chrono = DateTimeUtils.getInstantChronology(instant);
399 printTo(buf, millis, chrono);
400 }
401
402 /**
403 * Prints a ReadableInstant, using the chronology supplied by the instant.
404 *
405 * @param out formatted instant is written out
406 * @param instant instant to format, null means now
407 */
408 public void printTo(Writer out, ReadableInstant instant) throws IOException {
409 long millis = DateTimeUtils.getInstantMillis(instant);
410 Chronology chrono = DateTimeUtils.getInstantChronology(instant);
411 printTo(out, millis, chrono);
412 }
413
414 //-----------------------------------------------------------------------
415 /**
416 * Prints an instant from milliseconds since 1970-01-01T00:00:00Z,
417 * using ISO chronology in the default DateTimeZone.
418 *
419 * @param buf formatted instant is appended to this buffer
420 * @param instant millis since 1970-01-01T00:00:00Z
421 */
422 public void printTo(StringBuffer buf, long instant) {
423 printTo(buf, instant, null);
424 }
425
426 /**
427 * Prints an instant from milliseconds since 1970-01-01T00:00:00Z,
428 * using ISO chronology in the default DateTimeZone.
429 *
430 * @param out formatted instant is written out
431 * @param instant millis since 1970-01-01T00:00:00Z
432 */
433 public void printTo(Writer out, long instant) throws IOException {
434 printTo(out, instant, null);
435 }
436
437 //-----------------------------------------------------------------------
438 /**
439 * Prints a ReadablePartial.
440 * <p>
441 * Neither the override chronology nor the override zone are used
442 * by this method.
443 *
444 * @param buf formatted partial is appended to this buffer
445 * @param partial partial to format
446 */
447 public void printTo(StringBuffer buf, ReadablePartial partial) {
448 DateTimePrinter printer = requirePrinter();
449 if (partial == null) {
450 throw new IllegalArgumentException("The partial must not be null");
451 }
452 printer.printTo(buf, partial, iLocale);
453 }
454
455 /**
456 * Prints a ReadablePartial.
457 * <p>
458 * Neither the override chronology nor the override zone are used
459 * by this method.
460 *
461 * @param out formatted partial is written out
462 * @param partial partial to format
463 */
464 public void printTo(Writer out, ReadablePartial partial) throws IOException {
465 DateTimePrinter printer = requirePrinter();
466 if (partial == null) {
467 throw new IllegalArgumentException("The partial must not be null");
468 }
469 printer.printTo(out, partial, iLocale);
470 }
471
472 //-----------------------------------------------------------------------
473 /**
474 * Prints a ReadableInstant to a String.
475 * <p>
476 * This method will use the override zone and the override chronololgy if
477 * they are set. Otherwise it will use the chronology and zone of the instant.
478 *
479 * @param instant instant to format, null means now
480 * @return the printed result
481 */
482 public String print(ReadableInstant instant) {
483 StringBuffer buf = new StringBuffer(requirePrinter().estimatePrintedLength());
484 printTo(buf, instant);
485 return buf.toString();
486 }
487
488 /**
489 * Prints a millisecond instant to a String.
490 * <p>
491 * This method will use the override zone and the override chronololgy if
492 * they are set. Otherwise it will use the ISO chronology and default zone.
493 *
494 * @param instant millis since 1970-01-01T00:00:00Z
495 * @return the printed result
496 */
497 public String print(long instant) {
498 StringBuffer buf = new StringBuffer(requirePrinter().estimatePrintedLength());
499 printTo(buf, instant);
500 return buf.toString();
501 }
502
503 /**
504 * Prints a ReadablePartial to a new String.
505 * <p>
506 * Neither the override chronology nor the override zone are used
507 * by this method.
508 *
509 * @param partial partial to format
510 * @return the printed result
511 */
512 public String print(ReadablePartial partial) {
513 StringBuffer buf = new StringBuffer(requirePrinter().estimatePrintedLength());
514 printTo(buf, partial);
515 return buf.toString();
516 }
517
518 private void printTo(StringBuffer buf, long instant, Chronology chrono) {
519 DateTimePrinter printer = requirePrinter();
520 chrono = selectChronology(chrono);
521 // Shift instant into local time (UTC) to avoid excessive offset
522 // calculations when printing multiple fields in a composite printer.
523 DateTimeZone zone = chrono.getZone();
524 int offset = zone.getOffset(instant);
525 long adjustedInstant = instant + offset;
526 if ((instant ^ adjustedInstant) < 0 && (instant ^ offset) >= 0) {
527 // Time zone offset overflow, so revert to UTC.
528 zone = DateTimeZone.UTC;
529 offset = 0;
530 adjustedInstant = instant;
531 }
532 printer.printTo(buf, adjustedInstant, chrono.withUTC(), offset, zone, iLocale);
533 }
534
535 private void printTo(Writer buf, long instant, Chronology chrono) throws IOException {
536 DateTimePrinter printer = requirePrinter();
537 chrono = selectChronology(chrono);
538 // Shift instant into local time (UTC) to avoid excessive offset
539 // calculations when printing multiple fields in a composite printer.
540 DateTimeZone zone = chrono.getZone();
541 int offset = zone.getOffset(instant);
542 long adjustedInstant = instant + offset;
543 if ((instant ^ adjustedInstant) < 0 && (instant ^ offset) >= 0) {
544 // Time zone offset overflow, so revert to UTC.
545 zone = DateTimeZone.UTC;
546 offset = 0;
547 adjustedInstant = instant;
548 }
549 printer.printTo(buf, adjustedInstant, chrono.withUTC(), offset, zone, iLocale);
550 }
551
552 /**
553 * Checks whether printing is supported.
554 *
555 * @throws UnsupportedOperationException if printing is not supported
556 */
557 private DateTimePrinter requirePrinter() {
558 DateTimePrinter printer = iPrinter;
559 if (printer == null) {
560 throw new UnsupportedOperationException("Printing not supported");
561 }
562 return printer;
563 }
564
565 //-----------------------------------------------------------------------
566 /**
567 * Parses a datetime from the given text, at the given position, saving the
568 * result into the fields of the given ReadWritableInstant. If the parse
569 * succeeds, the return value is the new text position. Note that the parse
570 * may succeed without fully reading the text and in this case those fields
571 * that were read will be set.
572 * <p>
573 * Only those fields present in the string will be changed in the specified
574 * instant. All other fields will remain unaltered. Thus if the string only
575 * contains a year and a month, then the day and time will be retained from
576 * the input instant. If this is not the behaviour you want, then reset the
577 * fields before calling this method, or use {@link #parseDateTime(String)}
578 * or {@link #parseMutableDateTime(String)}.
579 * <p>
580 * If it fails, the return value is negative, but the instant may still be
581 * modified. To determine the position where the parse failed, apply the
582 * one's complement operator (~) on the return value.
583 * <p>
584 * The parse will use the chronology of the instant.
585 *
586 * @param instant an instant that will be modified, not null
587 * @param text the text to parse
588 * @param position position to start parsing from
589 * @return new position, negative value means parse failed -
590 * apply complement operator (~) to get position of failure
591 * @throws UnsupportedOperationException if parsing is not supported
592 * @throws IllegalArgumentException if the instant is null
593 * @throws IllegalArgumentException if any field is out of range
594 */
595 public int parseInto(ReadWritableInstant instant, String text, int position) {
596 DateTimeParser parser = requireParser();
597 if (instant == null) {
598 throw new IllegalArgumentException("Instant must not be null");
599 }
600
601 long instantMillis = instant.getMillis();
602 Chronology chrono = instant.getChronology();
603 long instantLocal = instantMillis + chrono.getZone().getOffset(instantMillis);
604 chrono = selectChronology(chrono);
605
606 DateTimeParserBucket bucket = new DateTimeParserBucket
607 (instantLocal, chrono, iLocale, iPivotYear);
608 int newPos = parser.parseInto(bucket, text, position);
609 instant.setMillis(bucket.computeMillis(false, text));
610 if (iOffsetParsed && bucket.getZone() == null) {
611 int parsedOffset = bucket.getOffset();
612 DateTimeZone parsedZone = DateTimeZone.forOffsetMillis(parsedOffset);
613 chrono = chrono.withZone(parsedZone);
614 }
615 instant.setChronology(chrono);
616 return newPos;
617 }
618
619 /**
620 * Parses a datetime from the given text, returning the number of
621 * milliseconds since the epoch, 1970-01-01T00:00:00Z.
622 * <p>
623 * The parse will use the ISO chronology, and the default time zone.
624 * If the text contains a time zone string then that will be taken into account.
625 *
626 * @param text text to parse
627 * @return parsed value expressed in milliseconds since the epoch
628 * @throws UnsupportedOperationException if parsing is not supported
629 * @throws IllegalArgumentException if the text to parse is invalid
630 */
631 public long parseMillis(String text) {
632 DateTimeParser parser = requireParser();
633
634 Chronology chrono = selectChronology(iChrono);
635 DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear);
636 int newPos = parser.parseInto(bucket, text, 0);
637 if (newPos >= 0) {
638 if (newPos >= text.length()) {
639 return bucket.computeMillis(true, text);
640 }
641 } else {
642 newPos = ~newPos;
643 }
644 throw new IllegalArgumentException(FormatUtils.createErrorMessage(text, newPos));
645 }
646
647 /**
648 * Parses a datetime from the given text, returning a new DateTime.
649 * <p>
650 * The parse will use the zone and chronology specified on this formatter.
651 * <p>
652 * If the text contains a time zone string then that will be taken into
653 * account in adjusting the time of day as follows.
654 * If the {@link #withOffsetParsed()} has been called, then the resulting
655 * DateTime will have a fixed offset based on the parsed time zone.
656 * Otherwise the resulting DateTime will have the zone of this formatter,
657 * but the parsed zone may have caused the time to be adjusted.
658 *
659 * @param text the text to parse
660 * @return parsed value in a DateTime object
661 * @throws UnsupportedOperationException if parsing is not supported
662 * @throws IllegalArgumentException if the text to parse is invalid
663 */
664 public DateTime parseDateTime(String text) {
665 DateTimeParser parser = requireParser();
666
667 Chronology chrono = selectChronology(null);
668 DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear);
669 int newPos = parser.parseInto(bucket, text, 0);
670 if (newPos >= 0) {
671 if (newPos >= text.length()) {
672 long millis = bucket.computeMillis(true, text);
673 if (iOffsetParsed && bucket.getZone() == null) {
674 int parsedOffset = bucket.getOffset();
675 DateTimeZone parsedZone = DateTimeZone.forOffsetMillis(parsedOffset);
676 chrono = chrono.withZone(parsedZone);
677 }
678 return new DateTime(millis, chrono);
679 }
680 } else {
681 newPos = ~newPos;
682 }
683 throw new IllegalArgumentException(FormatUtils.createErrorMessage(text, newPos));
684 }
685
686 /**
687 * Parses a datetime from the given text, returning a new MutableDateTime.
688 * <p>
689 * The parse will use the zone and chronology specified on this formatter.
690 * <p>
691 * If the text contains a time zone string then that will be taken into
692 * account in adjusting the time of day as follows.
693 * If the {@link #withOffsetParsed()} has been called, then the resulting
694 * DateTime will have a fixed offset based on the parsed time zone.
695 * Otherwise the resulting DateTime will have the zone of this formatter,
696 * but the parsed zone may have caused the time to be adjusted.
697 *
698 * @param text the text to parse
699 * @return parsed value in a MutableDateTime object
700 * @throws UnsupportedOperationException if parsing is not supported
701 * @throws IllegalArgumentException if the text to parse is invalid
702 */
703 public MutableDateTime parseMutableDateTime(String text) {
704 DateTimeParser parser = requireParser();
705
706 Chronology chrono = selectChronology(null);
707 DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear);
708 int newPos = parser.parseInto(bucket, text, 0);
709 if (newPos >= 0) {
710 if (newPos >= text.length()) {
711 long millis = bucket.computeMillis(true, text);
712 if (iOffsetParsed && bucket.getZone() == null) {
713 int parsedOffset = bucket.getOffset();
714 DateTimeZone parsedZone = DateTimeZone.forOffsetMillis(parsedOffset);
715 chrono = chrono.withZone(parsedZone);
716 }
717 return new MutableDateTime(millis, chrono);
718 }
719 } else {
720 newPos = ~newPos;
721 }
722 throw new IllegalArgumentException(FormatUtils.createErrorMessage(text, newPos));
723 }
724
725 /**
726 * Checks whether parsing is supported.
727 *
728 * @throws UnsupportedOperationException if parsing is not supported
729 */
730 private DateTimeParser requireParser() {
731 DateTimeParser parser = iParser;
732 if (parser == null) {
733 throw new UnsupportedOperationException("Parsing not supported");
734 }
735 return parser;
736 }
737
738 //-----------------------------------------------------------------------
739 /**
740 * Determines the correct chronology to use.
741 *
742 * @param chrono the proposed chronology
743 * @return the actual chronology
744 */
745 private Chronology selectChronology(Chronology chrono) {
746 chrono = DateTimeUtils.getChronology(chrono);
747 if (iChrono != null) {
748 chrono = iChrono;
749 }
750 if (iZone != null) {
751 chrono = chrono.withZone(iZone);
752 }
753 return chrono;
754 }
755
756 }