View Javadoc

1   /*
2    *  Copyright 2001-2009 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.time;
17  
18  import java.io.Serializable;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.Locale;
23  
24  import org.joda.time.base.AbstractPartial;
25  import org.joda.time.field.AbstractPartialFieldProperty;
26  import org.joda.time.field.FieldUtils;
27  import org.joda.time.format.DateTimeFormat;
28  import org.joda.time.format.DateTimeFormatter;
29  import org.joda.time.format.ISODateTimeFormat;
30  
31  /**
32   * Partial is an immutable partial datetime supporting any set of datetime fields.
33   * <p>
34   * A Partial instance can be used to hold any combination of fields.
35   * The instance does not contain a time zone, so any datetime is local.
36   * <p>
37   * A Partial can be matched against an instant using {@link #isMatch(ReadableInstant)}.
38   * This method compares each field on this partial with those of the instant
39   * and determines if the partial matches the instant.
40   * Given this definition, an empty Partial instance represents any datetime
41   * and always matches.
42   * <p>
43   * Calculations on Partial are performed using a {@link Chronology}.
44   * This chronology is set to be in the UTC time zone for all calculations.
45   * <p>
46   * Each individual field can be queried in two ways:
47   * <ul>
48   * <li><code>get(DateTimeFieldType.monthOfYear())</code>
49   * <li><code>property(DateTimeFieldType.monthOfYear()).get()</code>
50   * </ul>
51   * The second technique also provides access to other useful methods on the
52   * field:
53   * <ul>
54   * <li>numeric value - <code>monthOfYear().get()</code>
55   * <li>text value - <code>monthOfYear().getAsText()</code>
56   * <li>short text value - <code>monthOfYear().getAsShortText()</code>
57   * <li>maximum/minimum values - <code>monthOfYear().getMaximumValue()</code>
58   * <li>add/subtract - <code>monthOfYear().addToCopy()</code>
59   * <li>set - <code>monthOfYear().setCopy()</code>
60   * </ul>
61   * <p>
62   * Partial is thread-safe and immutable, provided that the Chronology is as well.
63   * All standard Chronology classes supplied are thread-safe and immutable.
64   *
65   * @author Stephen Colebourne
66   * @since 1.1
67   */
68  public final class Partial
69          extends AbstractPartial
70          implements ReadablePartial, Serializable {
71  
72      /** Serialization version */
73      private static final long serialVersionUID = 12324121189002L;
74  
75      /** The chronology in use. */
76      private final Chronology iChronology;
77      /** The set of field types. */
78      private final DateTimeFieldType[] iTypes;
79      /** The values of each field in this partial. */
80      private final int[] iValues;
81      /** The formatter to use, [0] may miss some fields, [1] doesn't miss any fields. */
82      private transient DateTimeFormatter[] iFormatter;
83  
84      // Constructors
85      //-----------------------------------------------------------------------
86      /**
87       * Constructs a Partial with no fields or values, which can be considered
88       * to represent any date.
89       * <p>
90       * This is most useful when constructing partials, for example:
91       * <pre>
92       * Partial p = new Partial()
93       *     .with(DateTimeFieldType.dayOfWeek(), 5)
94       *     .with(DateTimeFieldType.hourOfDay(), 12)
95       *     .with(DateTimeFieldType.minuteOfHour(), 20);
96       * </pre>
97       * Note that, although this is a clean way to write code, it is fairly
98       * inefficient internally.
99       * <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 }