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