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}