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 StringBuffer buf = new StringBuffer(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 }