001 /* 002 * Copyright 2001-2009 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; 017 018 import java.io.Serializable; 019 import java.util.ArrayList; 020 import java.util.Arrays; 021 import java.util.List; 022 import java.util.Locale; 023 024 import org.joda.time.base.AbstractPartial; 025 import org.joda.time.field.AbstractPartialFieldProperty; 026 import org.joda.time.field.FieldUtils; 027 import org.joda.time.format.DateTimeFormat; 028 import org.joda.time.format.DateTimeFormatter; 029 import org.joda.time.format.ISODateTimeFormat; 030 031 /** 032 * Partial is an immutable partial datetime supporting any set of datetime fields. 033 * <p> 034 * A Partial instance can be used to hold any combination of fields. 035 * The instance does not contain a time zone, so any datetime is local. 036 * <p> 037 * A Partial can be matched against an instant using {@link #isMatch(ReadableInstant)}. 038 * This method compares each field on this partial with those of the instant 039 * and determines if the partial matches the instant. 040 * Given this definition, an empty Partial instance represents any datetime 041 * and always matches. 042 * <p> 043 * Calculations on Partial are performed using a {@link Chronology}. 044 * This chronology is set to be in the UTC time zone for all calculations. 045 * <p> 046 * Each individual field can be queried in two ways: 047 * <ul> 048 * <li><code>get(DateTimeFieldType.monthOfYear())</code> 049 * <li><code>property(DateTimeFieldType.monthOfYear()).get()</code> 050 * </ul> 051 * The second technique also provides access to other useful methods on the 052 * field: 053 * <ul> 054 * <li>numeric value - <code>monthOfYear().get()</code> 055 * <li>text value - <code>monthOfYear().getAsText()</code> 056 * <li>short text value - <code>monthOfYear().getAsShortText()</code> 057 * <li>maximum/minimum values - <code>monthOfYear().getMaximumValue()</code> 058 * <li>add/subtract - <code>monthOfYear().addToCopy()</code> 059 * <li>set - <code>monthOfYear().setCopy()</code> 060 * </ul> 061 * <p> 062 * Partial is thread-safe and immutable, provided that the Chronology is as well. 063 * All standard Chronology classes supplied are thread-safe and immutable. 064 * 065 * @author Stephen Colebourne 066 * @since 1.1 067 */ 068 public final class Partial 069 extends AbstractPartial 070 implements ReadablePartial, Serializable { 071 072 /** Serialization version */ 073 private static final long serialVersionUID = 12324121189002L; 074 075 /** The chronology in use. */ 076 private final Chronology iChronology; 077 /** The set of field types. */ 078 private final DateTimeFieldType[] iTypes; 079 /** The values of each field in this partial. */ 080 private final int[] iValues; 081 /** The formatter to use, [0] may miss some fields, [1] doesn't miss any fields. */ 082 private transient DateTimeFormatter[] iFormatter; 083 084 // Constructors 085 //----------------------------------------------------------------------- 086 /** 087 * Constructs a Partial with no fields or values, which can be considered 088 * to represent any date. 089 * <p> 090 * This is most useful when constructing partials, for example: 091 * <pre> 092 * Partial p = new Partial() 093 * .with(DateTimeFieldType.dayOfWeek(), 5) 094 * .with(DateTimeFieldType.hourOfDay(), 12) 095 * .with(DateTimeFieldType.minuteOfHour(), 20); 096 * </pre> 097 * Note that, although this is a clean way to write code, it is fairly 098 * inefficient internally. 099 * <p> 100 * The constructor uses the default ISO chronology. 101 */ 102 public Partial() { 103 this((Chronology) null); 104 } 105 106 /** 107 * Constructs a Partial with no fields or values, which can be considered 108 * to represent any date. 109 * <p> 110 * This is most useful when constructing partials, for example: 111 * <pre> 112 * Partial p = new Partial(chrono) 113 * .with(DateTimeFieldType.dayOfWeek(), 5) 114 * .with(DateTimeFieldType.hourOfDay(), 12) 115 * .with(DateTimeFieldType.minuteOfHour(), 20); 116 * </pre> 117 * Note that, although this is a clean way to write code, it is fairly 118 * inefficient internally. 119 * 120 * @param chrono the chronology, null means ISO 121 */ 122 public Partial(Chronology chrono) { 123 super(); 124 iChronology = DateTimeUtils.getChronology(chrono).withUTC(); 125 iTypes = new DateTimeFieldType[0]; 126 iValues = new int[0]; 127 } 128 129 /** 130 * Constructs a Partial with the specified field and value. 131 * <p> 132 * The constructor uses the default ISO chronology. 133 * 134 * @param type the single type to create the partial from, not null 135 * @param value the value to store 136 * @throws IllegalArgumentException if the type or value is invalid 137 */ 138 public Partial(DateTimeFieldType type, int value) { 139 this(type, value, null); 140 } 141 142 /** 143 * Constructs a Partial with the specified field and value. 144 * <p> 145 * The constructor uses the specified chronology. 146 * 147 * @param type the single type to create the partial from, not null 148 * @param value the value to store 149 * @param chronology the chronology, null means ISO 150 * @throws IllegalArgumentException if the type or value is invalid 151 */ 152 public Partial(DateTimeFieldType type, int value, Chronology chronology) { 153 super(); 154 chronology = DateTimeUtils.getChronology(chronology).withUTC(); 155 iChronology = chronology; 156 if (type == null) { 157 throw new IllegalArgumentException("The field type must not be null"); 158 } 159 iTypes = new DateTimeFieldType[] {type}; 160 iValues = new int[] {value}; 161 chronology.validate(this, iValues); 162 } 163 164 /** 165 * Constructs a Partial with the specified fields and values. 166 * The fields must be specified in the order largest to smallest. 167 * <p> 168 * The constructor uses the specified chronology. 169 * 170 * @param types the types to create the partial from, not null 171 * @param values the values to store, not null 172 * @throws IllegalArgumentException if the types or values are invalid 173 */ 174 public Partial(DateTimeFieldType[] types, int[] values) { 175 this(types, values, null); 176 } 177 178 /** 179 * Constructs a Partial with the specified fields and values. 180 * The fields must be specified in the order largest to smallest. 181 * <p> 182 * The constructor uses the specified chronology. 183 * 184 * @param types the types to create the partial from, not null 185 * @param values the values to store, not null 186 * @param chronology the chronology, null means ISO 187 * @throws IllegalArgumentException if the types or values are invalid 188 */ 189 public Partial(DateTimeFieldType[] types, int[] values, Chronology chronology) { 190 super(); 191 chronology = DateTimeUtils.getChronology(chronology).withUTC(); 192 iChronology = chronology; 193 if (types == null) { 194 throw new IllegalArgumentException("Types array must not be null"); 195 } 196 if (values == null) { 197 throw new IllegalArgumentException("Values array must not be null"); 198 } 199 if (values.length != types.length) { 200 throw new IllegalArgumentException("Values array must be the same length as the types array"); 201 } 202 if (types.length == 0) { 203 iTypes = types; 204 iValues = values; 205 return; 206 } 207 for (int i = 0; i < types.length; i++) { 208 if (types[i] == null) { 209 throw new IllegalArgumentException("Types array must not contain null: index " + i); 210 } 211 } 212 DurationField lastUnitField = null; 213 for (int i = 0; i < types.length; i++) { 214 DateTimeFieldType loopType = types[i]; 215 DurationField loopUnitField = loopType.getDurationType().getField(iChronology); 216 if (i > 0) { 217 int compare = lastUnitField.compareTo(loopUnitField); 218 if (compare < 0 || (compare != 0 && loopUnitField.isSupported() == false)) { 219 throw new IllegalArgumentException("Types array must be in order largest-smallest: " + 220 types[i - 1].getName() + " < " + loopType.getName()); 221 } else if (compare == 0) { 222 if (types[i - 1].getRangeDurationType() == null) { 223 if (loopType.getRangeDurationType() == null) { 224 throw new IllegalArgumentException("Types array must not contain duplicate: " + loopType.getName()); 225 } 226 } else { 227 if (loopType.getRangeDurationType() == null) { 228 throw new IllegalArgumentException("Types array must be in order largest-smallest: " + 229 types[i - 1].getName() + " < " + loopType.getName()); 230 } 231 DurationField lastRangeField = types[i - 1].getRangeDurationType().getField(iChronology); 232 DurationField loopRangeField = loopType.getRangeDurationType().getField(iChronology); 233 if (lastRangeField.compareTo(loopRangeField) < 0) { 234 throw new IllegalArgumentException("Types array must be in order largest-smallest: " + 235 types[i - 1].getName() + " < " + loopType.getName()); 236 } 237 if (lastRangeField.compareTo(loopRangeField) == 0) { 238 throw new IllegalArgumentException("Types array must not contain duplicate: " + loopType.getName()); 239 } 240 } 241 } 242 } 243 lastUnitField = loopUnitField; 244 } 245 246 iTypes = (DateTimeFieldType[]) types.clone(); 247 chronology.validate(this, values); 248 iValues = (int[]) values.clone(); 249 } 250 251 /** 252 * Constructs a Partial by copying all the fields and types from 253 * another partial. 254 * <p> 255 * This is most useful when copying from a YearMonthDay or TimeOfDay. 256 */ 257 public Partial(ReadablePartial partial) { 258 super(); 259 if (partial == null) { 260 throw new IllegalArgumentException("The partial must not be null"); 261 } 262 iChronology = DateTimeUtils.getChronology(partial.getChronology()).withUTC(); 263 iTypes = new DateTimeFieldType[partial.size()]; 264 iValues = new int[partial.size()]; 265 for (int i = 0; i < partial.size(); i++) { 266 iTypes[i] = partial.getFieldType(i); 267 iValues[i] = partial.getValue(i); 268 } 269 } 270 271 /** 272 * Constructs a Partial with the specified values. 273 * This constructor assigns and performs no validation. 274 * 275 * @param partial the partial to copy 276 * @param values the values to store 277 * @throws IllegalArgumentException if the types or values are invalid 278 */ 279 Partial(Partial partial, int[] values) { 280 super(); 281 iChronology = partial.iChronology; 282 iTypes = partial.iTypes; 283 iValues = values; 284 } 285 286 /** 287 * Constructs a Partial with the specified chronology, fields and values. 288 * This constructor assigns and performs no validation. 289 * 290 * @param chronology the chronology 291 * @param types the types to create the partial from 292 * @param values the values to store 293 * @throws IllegalArgumentException if the types or values are invalid 294 */ 295 Partial(Chronology chronology, DateTimeFieldType[] types, int[] values) { 296 super(); 297 iChronology = chronology; 298 iTypes = types; 299 iValues = values; 300 } 301 302 //----------------------------------------------------------------------- 303 /** 304 * Gets the number of fields in this partial. 305 * 306 * @return the field count 307 */ 308 public int size() { 309 return iTypes.length; 310 } 311 312 /** 313 * Gets the chronology of the partial which is never null. 314 * <p> 315 * The {@link Chronology} is the calculation engine behind the partial and 316 * provides conversion and validation of the fields in a particular calendar system. 317 * 318 * @return the chronology, never null 319 */ 320 public Chronology getChronology() { 321 return iChronology; 322 } 323 324 /** 325 * Gets the field for a specific index in the chronology specified. 326 * 327 * @param index the index to retrieve 328 * @param chrono the chronology to use 329 * @return the field 330 * @throws IndexOutOfBoundsException if the index is invalid 331 */ 332 protected DateTimeField getField(int index, Chronology chrono) { 333 return iTypes[index].getField(chrono); 334 } 335 336 /** 337 * Gets the field type at the specified index. 338 * 339 * @param index the index to retrieve 340 * @return the field at the specified index 341 * @throws IndexOutOfBoundsException if the index is invalid 342 */ 343 public DateTimeFieldType getFieldType(int index) { 344 return iTypes[index]; 345 } 346 347 /** 348 * Gets an array of the field type of each of the fields that 349 * this partial supports. 350 * <p> 351 * The fields are returned largest to smallest. 352 * 353 * @return the array of field types (cloned), largest to smallest 354 */ 355 public DateTimeFieldType[] getFieldTypes() { 356 return (DateTimeFieldType[]) iTypes.clone(); 357 } 358 359 //----------------------------------------------------------------------- 360 /** 361 * Gets the value of the field at the specifed index. 362 * 363 * @param index the index 364 * @return the value 365 * @throws IndexOutOfBoundsException if the index is invalid 366 */ 367 public int getValue(int index) { 368 return iValues[index]; 369 } 370 371 /** 372 * Gets an array of the value of each of the fields that 373 * this partial supports. 374 * <p> 375 * The fields are returned largest to smallest. 376 * Each value corresponds to the same array index as <code>getFieldTypes()</code> 377 * 378 * @return the current values of each field (cloned), largest to smallest 379 */ 380 public int[] getValues() { 381 return (int[]) iValues.clone(); 382 } 383 384 //----------------------------------------------------------------------- 385 /** 386 * Creates a new Partial instance with the specified chronology. 387 * This instance is immutable and unaffected by this method call. 388 * <p> 389 * This method retains the values of the fields, thus the result will 390 * typically refer to a different instant. 391 * <p> 392 * The time zone of the specified chronology is ignored, as Partial 393 * operates without a time zone. 394 * 395 * @param newChronology the new chronology, null means ISO 396 * @return a copy of this datetime with a different chronology 397 * @throws IllegalArgumentException if the values are invalid for the new chronology 398 */ 399 public Partial withChronologyRetainFields(Chronology newChronology) { 400 newChronology = DateTimeUtils.getChronology(newChronology); 401 newChronology = newChronology.withUTC(); 402 if (newChronology == getChronology()) { 403 return this; 404 } else { 405 Partial newPartial = new Partial(newChronology, iTypes, iValues); 406 newChronology.validate(newPartial, iValues); 407 return newPartial; 408 } 409 } 410 411 //----------------------------------------------------------------------- 412 /** 413 * Gets a copy of this date with the specified field set to a new value. 414 * <p> 415 * If this partial did not previously support the field, the new one will. 416 * Contrast this behaviour with {@link #withField(DateTimeFieldType, int)}. 417 * <p> 418 * For example, if the field type is <code>dayOfMonth</code> then the day 419 * would be changed/added in the returned instance. 420 * 421 * @param fieldType the field type to set, not null 422 * @param value the value to set 423 * @return a copy of this instance with the field set 424 * @throws IllegalArgumentException if the value is null or invalid 425 */ 426 public Partial with(DateTimeFieldType fieldType, int value) { 427 if (fieldType == null) { 428 throw new IllegalArgumentException("The field type must not be null"); 429 } 430 int index = indexOf(fieldType); 431 if (index == -1) { 432 DateTimeFieldType[] newTypes = new DateTimeFieldType[iTypes.length + 1]; 433 int[] newValues = new int[newTypes.length]; 434 435 // find correct insertion point to keep largest-smallest order 436 int i = 0; 437 DurationField unitField = fieldType.getDurationType().getField(iChronology); 438 if (unitField.isSupported()) { 439 for (; i < iTypes.length; i++) { 440 DateTimeFieldType loopType = iTypes[i]; 441 DurationField loopUnitField = loopType.getDurationType().getField(iChronology); 442 if (loopUnitField.isSupported()) { 443 int compare = unitField.compareTo(loopUnitField); 444 if (compare > 0) { 445 break; 446 } else if (compare == 0) { 447 DurationField rangeField = fieldType.getRangeDurationType().getField(iChronology); 448 DurationField loopRangeField = loopType.getRangeDurationType().getField(iChronology); 449 if (rangeField.compareTo(loopRangeField) > 0) { 450 break; 451 } 452 } 453 } 454 } 455 } 456 System.arraycopy(iTypes, 0, newTypes, 0, i); 457 System.arraycopy(iValues, 0, newValues, 0, i); 458 newTypes[i] = fieldType; 459 newValues[i] = value; 460 System.arraycopy(iTypes, i, newTypes, i + 1, newTypes.length - i - 1); 461 System.arraycopy(iValues, i, newValues, i + 1, newValues.length - i - 1); 462 463 Partial newPartial = new Partial(iChronology, newTypes, newValues); 464 iChronology.validate(newPartial, newValues); 465 return newPartial; 466 } 467 if (value == getValue(index)) { 468 return this; 469 } 470 int[] newValues = getValues(); 471 newValues = getField(index).set(this, index, newValues, value); 472 return new Partial(this, newValues); 473 } 474 475 /** 476 * Gets a copy of this date with the specified field removed. 477 * <p> 478 * If this partial did not previously support the field, no error occurs. 479 * 480 * @param fieldType the field type to remove, may be null 481 * @return a copy of this instance with the field removed 482 */ 483 public Partial without(DateTimeFieldType fieldType) { 484 int index = indexOf(fieldType); 485 if (index != -1) { 486 DateTimeFieldType[] newTypes = new DateTimeFieldType[size() - 1]; 487 int[] newValues = new int[size() - 1]; 488 System.arraycopy(iTypes, 0, newTypes, 0, index); 489 System.arraycopy(iTypes, index + 1, newTypes, index, newTypes.length - index); 490 System.arraycopy(iValues, 0, newValues, 0, index); 491 System.arraycopy(iValues, index + 1, newValues, index, newValues.length - index); 492 Partial newPartial = new Partial(iChronology, newTypes, newValues); 493 iChronology.validate(newPartial, newValues); 494 return newPartial; 495 } 496 return this; 497 } 498 499 //----------------------------------------------------------------------- 500 /** 501 * Gets a copy of this Partial with the specified field set to a new value. 502 * <p> 503 * If this partial does not support the field, an exception is thrown. 504 * Contrast this behaviour with {@link #with(DateTimeFieldType, int)}. 505 * <p> 506 * For example, if the field type is <code>dayOfMonth</code> then the day 507 * would be changed in the returned instance if supported. 508 * 509 * @param fieldType the field type to set, not null 510 * @param value the value to set 511 * @return a copy of this instance with the field set 512 * @throws IllegalArgumentException if the value is null or invalid 513 */ 514 public Partial withField(DateTimeFieldType fieldType, int value) { 515 int index = indexOfSupported(fieldType); 516 if (value == getValue(index)) { 517 return this; 518 } 519 int[] newValues = getValues(); 520 newValues = getField(index).set(this, index, newValues, value); 521 return new Partial(this, newValues); 522 } 523 524 /** 525 * Gets a copy of this Partial with the value of the specified field increased. 526 * If this partial does not support the field, an exception is thrown. 527 * <p> 528 * If the addition is zero, then <code>this</code> is returned. 529 * The addition will overflow into larger fields (eg. minute to hour). 530 * However, it will not wrap around if the top maximum is reached. 531 * 532 * @param fieldType the field type to add to, not null 533 * @param amount the amount to add 534 * @return a copy of this instance with the field updated 535 * @throws IllegalArgumentException if the value is null or invalid 536 * @throws ArithmeticException if the new datetime exceeds the capacity 537 */ 538 public Partial withFieldAdded(DurationFieldType fieldType, int amount) { 539 int index = indexOfSupported(fieldType); 540 if (amount == 0) { 541 return this; 542 } 543 int[] newValues = getValues(); 544 newValues = getField(index).add(this, index, newValues, amount); 545 return new Partial(this, newValues); 546 } 547 548 /** 549 * Gets a copy of this Partial with the value of the specified field increased. 550 * If this partial does not support the field, an exception is thrown. 551 * <p> 552 * If the addition is zero, then <code>this</code> is returned. 553 * The addition will overflow into larger fields (eg. minute to hour). 554 * If the maximum is reached, the addition will wra. 555 * 556 * @param fieldType the field type to add to, not null 557 * @param amount the amount to add 558 * @return a copy of this instance with the field updated 559 * @throws IllegalArgumentException if the value is null or invalid 560 * @throws ArithmeticException if the new datetime exceeds the capacity 561 */ 562 public Partial withFieldAddWrapped(DurationFieldType fieldType, int amount) { 563 int index = indexOfSupported(fieldType); 564 if (amount == 0) { 565 return this; 566 } 567 int[] newValues = getValues(); 568 newValues = getField(index).addWrapPartial(this, index, newValues, amount); 569 return new Partial(this, newValues); 570 } 571 572 /** 573 * Gets a copy of this Partial with the specified period added. 574 * <p> 575 * If the addition is zero, then <code>this</code> is returned. 576 * Fields in the period that aren't present in the partial are ignored. 577 * <p> 578 * This method is typically used to add multiple copies of complex 579 * period instances. Adding one field is best achieved using the method 580 * {@link #withFieldAdded(DurationFieldType, int)}. 581 * 582 * @param period the period to add to this one, null means zero 583 * @param scalar the amount of times to add, such as -1 to subtract once 584 * @return a copy of this instance with the period added 585 * @throws ArithmeticException if the new datetime exceeds the capacity 586 */ 587 public Partial withPeriodAdded(ReadablePeriod period, int scalar) { 588 if (period == null || scalar == 0) { 589 return this; 590 } 591 int[] newValues = getValues(); 592 for (int i = 0; i < period.size(); i++) { 593 DurationFieldType fieldType = period.getFieldType(i); 594 int index = indexOf(fieldType); 595 if (index >= 0) { 596 newValues = getField(index).add(this, index, newValues, 597 FieldUtils.safeMultiply(period.getValue(i), scalar)); 598 } 599 } 600 return new Partial(this, newValues); 601 } 602 603 /** 604 * Gets a copy of this instance with the specified period added. 605 * <p> 606 * If the amount is zero or null, then <code>this</code> is returned. 607 * 608 * @param period the duration to add to this one, null means zero 609 * @return a copy of this instance with the period added 610 * @throws ArithmeticException if the new datetime exceeds the capacity of a long 611 */ 612 public Partial plus(ReadablePeriod period) { 613 return withPeriodAdded(period, 1); 614 } 615 616 /** 617 * Gets a copy of this instance with the specified period take away. 618 * <p> 619 * If the amount is zero or null, then <code>this</code> is returned. 620 * 621 * @param period the period to reduce this instant by 622 * @return a copy of this instance with the period taken away 623 * @throws ArithmeticException if the new datetime exceeds the capacity of a long 624 */ 625 public Partial minus(ReadablePeriod period) { 626 return withPeriodAdded(period, -1); 627 } 628 629 //----------------------------------------------------------------------- 630 /** 631 * Gets the property object for the specified type, which contains 632 * many useful methods for getting and manipulating the partial. 633 * <p> 634 * See also {@link ReadablePartial#get(DateTimeFieldType)}. 635 * 636 * @param type the field type to get the property for, not null 637 * @return the property object 638 * @throws IllegalArgumentException if the field is null or unsupported 639 */ 640 public Property property(DateTimeFieldType type) { 641 return new Property(this, indexOfSupported(type)); 642 } 643 644 //----------------------------------------------------------------------- 645 /** 646 * Does this partial match the specified instant. 647 * <p> 648 * A match occurs when all the fields of this partial are the same as the 649 * corresponding fields on the specified instant. 650 * 651 * @param instant an instant to check against, null means now in default zone 652 * @return true if this partial matches the specified instant 653 */ 654 public boolean isMatch(ReadableInstant instant) { 655 long millis = DateTimeUtils.getInstantMillis(instant); 656 Chronology chrono = DateTimeUtils.getInstantChronology(instant); 657 for (int i = 0; i < iTypes.length; i++) { 658 int value = iTypes[i].getField(chrono).get(millis); 659 if (value != iValues[i]) { 660 return false; 661 } 662 } 663 return true; 664 } 665 666 /** 667 * Does this partial match the specified partial. 668 * <p> 669 * A match occurs when all the fields of this partial are the same as the 670 * corresponding fields on the specified partial. 671 * 672 * @param partial a partial to check against, must not be null 673 * @return true if this partial matches the specified partial 674 * @throws IllegalArgumentException if the partial is null 675 * @throws IllegalArgumentException if the fields of the two partials do not match 676 * @since 1.5 677 */ 678 public boolean isMatch(ReadablePartial partial) { 679 if (partial == null) { 680 throw new IllegalArgumentException("The partial must not be null"); 681 } 682 for (int i = 0; i < iTypes.length; i++) { 683 int value = partial.get(iTypes[i]); 684 if (value != iValues[i]) { 685 return false; 686 } 687 } 688 return true; 689 } 690 691 //----------------------------------------------------------------------- 692 /** 693 * Gets a formatter suitable for the fields in this partial. 694 * <p> 695 * If there is no appropriate ISO format, null is returned. 696 * This method may return a formatter that does not display all the 697 * fields of the partial. This might occur when you have overlapping 698 * fields, such as dayOfWeek and dayOfMonth. 699 * 700 * @return a formatter suitable for the fields in this partial, null 701 * if none is suitable 702 */ 703 public DateTimeFormatter getFormatter() { 704 DateTimeFormatter[] f = iFormatter; 705 if (f == null) { 706 if (size() == 0) { 707 return null; 708 } 709 f = new DateTimeFormatter[2]; 710 try { 711 List<DateTimeFieldType> list = new ArrayList<DateTimeFieldType>(Arrays.asList(iTypes)); 712 f[0] = ISODateTimeFormat.forFields(list, true, false); 713 if (list.size() == 0) { 714 f[1] = f[0]; 715 } 716 } catch (IllegalArgumentException ex) { 717 // ignore 718 } 719 iFormatter = f; 720 } 721 return f[0]; 722 } 723 724 //----------------------------------------------------------------------- 725 /** 726 * Output the date in an appropriate ISO8601 format. 727 * <p> 728 * This method will output the partial in one of two ways. 729 * If {@link #getFormatter()} 730 * <p> 731 * If there is no appropriate ISO format a dump of the fields is output 732 * via {@link #toStringList()}. 733 * 734 * @return ISO8601 formatted string 735 */ 736 public String toString() { 737 DateTimeFormatter[] f = iFormatter; 738 if (f == null) { 739 getFormatter(); 740 f = iFormatter; 741 if (f == null) { 742 return toStringList(); 743 } 744 } 745 DateTimeFormatter f1 = f[1]; 746 if (f1 == null) { 747 return toStringList(); 748 } 749 return f1.print(this); 750 } 751 752 /** 753 * Gets a string version of the partial that lists all the fields. 754 * <p> 755 * This method exists to provide a better debugging toString than 756 * the standard toString. This method lists all the fields and their 757 * values in a style similar to the collections framework. 758 * 759 * @return a toString format that lists all the fields 760 */ 761 public String toStringList() { 762 int size = size(); 763 StringBuilder buf = new StringBuilder(20 * size); 764 buf.append('['); 765 for (int i = 0; i < size; i++) { 766 if (i > 0) { 767 buf.append(',').append(' '); 768 } 769 buf.append(iTypes[i].getName()); 770 buf.append('='); 771 buf.append(iValues[i]); 772 } 773 buf.append(']'); 774 return buf.toString(); 775 } 776 777 /** 778 * Output the date using the specified format pattern. 779 * Unsupported fields will appear as special unicode characters. 780 * 781 * @param pattern the pattern specification, null means use <code>toString</code> 782 * @see org.joda.time.format.DateTimeFormat 783 */ 784 public String toString(String pattern) { 785 if (pattern == null) { 786 return toString(); 787 } 788 return DateTimeFormat.forPattern(pattern).print(this); 789 } 790 791 /** 792 * Output the date using the specified format pattern. 793 * Unsupported fields will appear as special unicode characters. 794 * 795 * @param pattern the pattern specification, null means use <code>toString</code> 796 * @param locale Locale to use, null means default 797 * @see org.joda.time.format.DateTimeFormat 798 */ 799 public String toString(String pattern, Locale locale) { 800 if (pattern == null) { 801 return toString(); 802 } 803 return DateTimeFormat.forPattern(pattern).withLocale(locale).print(this); 804 } 805 806 //----------------------------------------------------------------------- 807 /** 808 * The property class for <code>Partial</code>. 809 * <p> 810 * This class binds a <code>Partial</code> to a <code>DateTimeField</code>. 811 * 812 * @author Stephen Colebourne 813 * @since 1.1 814 */ 815 public static class Property extends AbstractPartialFieldProperty implements Serializable { 816 817 /** Serialization version */ 818 private static final long serialVersionUID = 53278362873888L; 819 820 /** The partial */ 821 private final Partial iPartial; 822 /** The field index */ 823 private final int iFieldIndex; 824 825 /** 826 * Constructs a property. 827 * 828 * @param partial the partial instance 829 * @param fieldIndex the index in the partial 830 */ 831 Property(Partial partial, int fieldIndex) { 832 super(); 833 iPartial = partial; 834 iFieldIndex = fieldIndex; 835 } 836 837 /** 838 * Gets the field that this property uses. 839 * 840 * @return the field 841 */ 842 public DateTimeField getField() { 843 return iPartial.getField(iFieldIndex); 844 } 845 846 /** 847 * Gets the partial that this property belongs to. 848 * 849 * @return the partial 850 */ 851 protected ReadablePartial getReadablePartial() { 852 return iPartial; 853 } 854 855 /** 856 * Gets the partial that this property belongs to. 857 * 858 * @return the partial 859 */ 860 public Partial getPartial() { 861 return iPartial; 862 } 863 864 /** 865 * Gets the value of this field. 866 * 867 * @return the field value 868 */ 869 public int get() { 870 return iPartial.getValue(iFieldIndex); 871 } 872 873 //----------------------------------------------------------------------- 874 /** 875 * Adds to the value of this field in a copy of this Partial. 876 * <p> 877 * The value will be added to this field. If the value is too large to be 878 * added solely to this field then it will affect larger fields. 879 * Smaller fields are unaffected. 880 * <p> 881 * If the result would be too large, beyond the maximum year, then an 882 * IllegalArgumentException is thrown. 883 * <p> 884 * The Partial attached to this property is unchanged by this call. 885 * Instead, a new instance is returned. 886 * 887 * @param valueToAdd the value to add to the field in the copy 888 * @return a copy of the Partial with the field value changed 889 * @throws IllegalArgumentException if the value isn't valid 890 */ 891 public Partial addToCopy(int valueToAdd) { 892 int[] newValues = iPartial.getValues(); 893 newValues = getField().add(iPartial, iFieldIndex, newValues, valueToAdd); 894 return new Partial(iPartial, newValues); 895 } 896 897 /** 898 * Adds to the value of this field in a copy of this Partial wrapping 899 * within this field if the maximum value is reached. 900 * <p> 901 * The value will be added to this field. If the value is too large to be 902 * added solely to this field then it wraps within this field. 903 * Other fields are unaffected. 904 * <p> 905 * For example, 906 * <code>2004-12-20</code> addWrapField one month returns <code>2004-01-20</code>. 907 * <p> 908 * The Partial attached to this property is unchanged by this call. 909 * Instead, a new instance is returned. 910 * 911 * @param valueToAdd the value to add to the field in the copy 912 * @return a copy of the Partial with the field value changed 913 * @throws IllegalArgumentException if the value isn't valid 914 */ 915 public Partial addWrapFieldToCopy(int valueToAdd) { 916 int[] newValues = iPartial.getValues(); 917 newValues = getField().addWrapField(iPartial, iFieldIndex, newValues, valueToAdd); 918 return new Partial(iPartial, newValues); 919 } 920 921 //----------------------------------------------------------------------- 922 /** 923 * Sets this field in a copy of the Partial. 924 * <p> 925 * The Partial attached to this property is unchanged by this call. 926 * Instead, a new instance is returned. 927 * 928 * @param value the value to set the field in the copy to 929 * @return a copy of the Partial with the field value changed 930 * @throws IllegalArgumentException if the value isn't valid 931 */ 932 public Partial setCopy(int value) { 933 int[] newValues = iPartial.getValues(); 934 newValues = getField().set(iPartial, iFieldIndex, newValues, value); 935 return new Partial(iPartial, newValues); 936 } 937 938 /** 939 * Sets this field in a copy of the Partial to a parsed text value. 940 * <p> 941 * The Partial attached to this property is unchanged by this call. 942 * Instead, a new instance is returned. 943 * 944 * @param text the text value to set 945 * @param locale optional locale to use for selecting a text symbol 946 * @return a copy of the Partial with the field value changed 947 * @throws IllegalArgumentException if the text value isn't valid 948 */ 949 public Partial setCopy(String text, Locale locale) { 950 int[] newValues = iPartial.getValues(); 951 newValues = getField().set(iPartial, iFieldIndex, newValues, text, locale); 952 return new Partial(iPartial, newValues); 953 } 954 955 /** 956 * Sets this field in a copy of the Partial to a parsed text value. 957 * <p> 958 * The Partial attached to this property is unchanged by this call. 959 * Instead, a new instance is returned. 960 * 961 * @param text the text value to set 962 * @return a copy of the Partial with the field value changed 963 * @throws IllegalArgumentException if the text value isn't valid 964 */ 965 public Partial setCopy(String text) { 966 return setCopy(text, null); 967 } 968 969 //----------------------------------------------------------------------- 970 /** 971 * Returns a new Partial with this field set to the maximum value 972 * for this field. 973 * <p> 974 * The Partial attached to this property is unchanged by this call. 975 * 976 * @return a copy of the Partial with this field set to its maximum 977 * @since 1.2 978 */ 979 public Partial withMaximumValue() { 980 return setCopy(getMaximumValue()); 981 } 982 983 /** 984 * Returns a new Partial with this field set to the minimum value 985 * for this field. 986 * <p> 987 * The Partial attached to this property is unchanged by this call. 988 * 989 * @return a copy of the Partial with this field set to its minimum 990 * @since 1.2 991 */ 992 public Partial withMinimumValue() { 993 return setCopy(getMinimumValue()); 994 } 995 } 996 997 }