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    }