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    }