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 */
016package org.joda.time.format;
017
018import java.io.IOException;
019import java.io.Writer;
020import java.util.Locale;
021
022import org.joda.time.Chronology;
023import org.joda.time.DateTime;
024import org.joda.time.DateTimeUtils;
025import org.joda.time.DateTimeZone;
026import org.joda.time.MutableDateTime;
027import org.joda.time.ReadWritableInstant;
028import org.joda.time.ReadableInstant;
029import 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 */
076public 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}