View Javadoc

1   /*
2    *  Copyright 2001-2011 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.base;
17  
18  import java.io.Serializable;
19  
20  import org.joda.time.Chronology;
21  import org.joda.time.DateTimeUtils;
22  import org.joda.time.Duration;
23  import org.joda.time.DurationFieldType;
24  import org.joda.time.MutablePeriod;
25  import org.joda.time.PeriodType;
26  import org.joda.time.ReadWritablePeriod;
27  import org.joda.time.ReadableDuration;
28  import org.joda.time.ReadableInstant;
29  import org.joda.time.ReadablePartial;
30  import org.joda.time.ReadablePeriod;
31  import org.joda.time.chrono.ISOChronology;
32  import org.joda.time.convert.ConverterManager;
33  import org.joda.time.convert.PeriodConverter;
34  import org.joda.time.field.FieldUtils;
35  
36  /**
37   * BasePeriod is an abstract implementation of ReadablePeriod that stores
38   * data in a <code>PeriodType</code> and an <code>int[]</code>.
39   * <p>
40   * This class should generally not be used directly by API users.
41   * The {@link ReadablePeriod} interface should be used when different 
42   * kinds of period objects are to be referenced.
43   * <p>
44   * BasePeriod subclasses may be mutable and not thread-safe.
45   *
46   * @author Brian S O'Neill
47   * @author Stephen Colebourne
48   * @since 1.0
49   */
50  public abstract class BasePeriod
51          extends AbstractPeriod
52          implements ReadablePeriod, Serializable {
53  
54      /** Serialization version */
55      private static final long serialVersionUID = -2110953284060001145L;
56      /** Serialization version */
57      private static final ReadablePeriod DUMMY_PERIOD = new AbstractPeriod() {
58          public int getValue(int index) {
59              return 0;
60          }
61          public PeriodType getPeriodType() {
62              return PeriodType.time();
63          }
64      };
65  
66      /** The type of period */
67      private final PeriodType iType;
68      /** The values */
69      private final int[] iValues;
70  
71      //-----------------------------------------------------------------------
72      /**
73       * Creates a period from a set of field values.
74       *
75       * @param years  amount of years in this period, which must be zero if unsupported
76       * @param months  amount of months in this period, which must be zero if unsupported
77       * @param weeks  amount of weeks in this period, which must be zero if unsupported
78       * @param days  amount of days in this period, which must be zero if unsupported
79       * @param hours  amount of hours in this period, which must be zero if unsupported
80       * @param minutes  amount of minutes in this period, which must be zero if unsupported
81       * @param seconds  amount of seconds in this period, which must be zero if unsupported
82       * @param millis  amount of milliseconds in this period, which must be zero if unsupported
83       * @param type  which set of fields this period supports
84       * @throws IllegalArgumentException if period type is invalid
85       * @throws IllegalArgumentException if an unsupported field's value is non-zero
86       */
87      protected BasePeriod(int years, int months, int weeks, int days,
88                           int hours, int minutes, int seconds, int millis,
89                           PeriodType type) {
90          super();
91          type = checkPeriodType(type);
92          iType = type;
93          iValues = setPeriodInternal(years, months, weeks, days, hours, minutes, seconds, millis); // internal method
94      }
95  
96      /**
97       * Creates a period from the given interval endpoints.
98       *
99       * @param startInstant  interval start, in milliseconds
100      * @param endInstant  interval end, in milliseconds
101      * @param type  which set of fields this period supports, null means standard
102      * @param chrono  the chronology to use, null means ISO default
103      * @throws IllegalArgumentException if period type is invalid
104      */
105     protected BasePeriod(long startInstant, long endInstant, PeriodType type, Chronology chrono) {
106         super();
107         type = checkPeriodType(type);
108         chrono = DateTimeUtils.getChronology(chrono);
109         iType = type;
110         iValues = chrono.get(this, startInstant, endInstant);
111     }
112 
113     /**
114      * Creates a period from the given interval endpoints.
115      *
116      * @param startInstant  interval start, null means now
117      * @param endInstant  interval end, null means now
118      * @param type  which set of fields this period supports, null means standard
119      * @throws IllegalArgumentException if period type is invalid
120      */
121     protected BasePeriod(ReadableInstant startInstant, ReadableInstant endInstant, PeriodType type) {
122         super();
123         type = checkPeriodType(type);
124         if (startInstant == null && endInstant == null) {
125             iType = type;
126             iValues = new int[size()];
127         } else {
128             long startMillis = DateTimeUtils.getInstantMillis(startInstant);
129             long endMillis = DateTimeUtils.getInstantMillis(endInstant);
130             Chronology chrono = DateTimeUtils.getIntervalChronology(startInstant, endInstant);
131             iType = type;
132             iValues = chrono.get(this, startMillis, endMillis);
133         }
134     }
135 
136     /**
137      * Creates a period from the given duration and end point.
138      * <p>
139      * The two partials must contain the same fields, thus you can
140      * specify two <code>LocalDate</code> objects, or two <code>LocalTime</code>
141      * objects, but not one of each.
142      * As these are Partial objects, time zones have no effect on the result.
143      * <p>
144      * The two partials must also both be contiguous - see
145      * {@link DateTimeUtils#isContiguous(ReadablePartial)} for a
146      * definition. Both <code>LocalDate</code> and <code>LocalTime</code> are contiguous.
147      *
148      * @param start  the start of the period, must not be null
149      * @param end  the end of the period, must not be null
150      * @param type  which set of fields this period supports, null means standard
151      * @throws IllegalArgumentException if the partials are null or invalid
152      * @since 1.1
153      */
154     protected BasePeriod(ReadablePartial start, ReadablePartial end, PeriodType type) {
155         super();
156         if (start == null || end == null) {
157             throw new IllegalArgumentException("ReadablePartial objects must not be null");
158         }
159         if (start instanceof BaseLocal && end instanceof BaseLocal && start.getClass() == end.getClass()) {
160             // for performance
161             type = checkPeriodType(type);
162             long startMillis = ((BaseLocal) start).getLocalMillis();
163             long endMillis = ((BaseLocal) end).getLocalMillis();
164             Chronology chrono = start.getChronology();
165             chrono = DateTimeUtils.getChronology(chrono);
166             iType = type;
167             iValues = chrono.get(this, startMillis, endMillis);
168         } else {
169             if (start.size() != end.size()) {
170                 throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields");
171             }
172             for (int i = 0, isize = start.size(); i < isize; i++) {
173                 if (start.getFieldType(i) != end.getFieldType(i)) {
174                     throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields");
175                 }
176             }
177             if (DateTimeUtils.isContiguous(start) == false) {
178                 throw new IllegalArgumentException("ReadablePartial objects must be contiguous");
179             }
180             iType = checkPeriodType(type);
181             Chronology chrono = DateTimeUtils.getChronology(start.getChronology()).withUTC();
182             iValues = chrono.get(this, chrono.set(start, 0L), chrono.set(end, 0L));
183         }
184     }
185 
186     /**
187      * Creates a period from the given start point and duration.
188      *
189      * @param startInstant  the interval start, null means now
190      * @param duration  the duration of the interval, null means zero-length
191      * @param type  which set of fields this period supports, null means standard
192      */
193     protected BasePeriod(ReadableInstant startInstant, ReadableDuration duration, PeriodType type) {
194         super();
195         type = checkPeriodType(type);
196         long startMillis = DateTimeUtils.getInstantMillis(startInstant);
197         long durationMillis = DateTimeUtils.getDurationMillis(duration);
198         long endMillis = FieldUtils.safeAdd(startMillis, durationMillis);
199         Chronology chrono = DateTimeUtils.getInstantChronology(startInstant);
200         iType = type;
201         iValues = chrono.get(this, startMillis, endMillis);
202     }
203 
204     /**
205      * Creates a period from the given duration and end point.
206      *
207      * @param duration  the duration of the interval, null means zero-length
208      * @param endInstant  the interval end, null means now
209      * @param type  which set of fields this period supports, null means standard
210      */
211     protected BasePeriod(ReadableDuration duration, ReadableInstant endInstant, PeriodType type) {
212         super();
213         type = checkPeriodType(type);
214         long durationMillis = DateTimeUtils.getDurationMillis(duration);
215         long endMillis = DateTimeUtils.getInstantMillis(endInstant);
216         long startMillis = FieldUtils.safeSubtract(endMillis, durationMillis);
217         Chronology chrono = DateTimeUtils.getInstantChronology(endInstant);
218         iType = type;
219         iValues = chrono.get(this, startMillis, endMillis);
220     }
221 
222     /**
223      * Creates a period from the given millisecond duration with the standard period type
224      * and ISO rules, ensuring that the calculation is performed with the time-only period type.
225      * <p>
226      * The calculation uses the hour, minute, second and millisecond fields.
227      *
228      * @param duration  the duration, in milliseconds
229      */
230     protected BasePeriod(long duration) {
231         super();
232         // bug [3264409]
233         // calculation uses period type from a period object (bad design)
234         // thus we use a dummy period object with the time type
235         iType = PeriodType.standard();
236         int[] values = ISOChronology.getInstanceUTC().get(DUMMY_PERIOD, duration);
237         iValues = new int[8];
238         System.arraycopy(values, 0, iValues, 4, 4);
239     }
240 
241     /**
242      * Creates a period from the given millisecond duration, which is only really
243      * suitable for durations less than one day.
244      * <p>
245      * Only fields that are precise will be used.
246      * Thus the largest precise field may have a large value.
247      *
248      * @param duration  the duration, in milliseconds
249      * @param type  which set of fields this period supports, null means standard
250      * @param chrono  the chronology to use, null means ISO default
251      * @throws IllegalArgumentException if period type is invalid
252      */
253     protected BasePeriod(long duration, PeriodType type, Chronology chrono) {
254         super();
255         type = checkPeriodType(type);
256         chrono = DateTimeUtils.getChronology(chrono);
257         iType = type;
258         iValues = chrono.get(this, duration);
259     }
260 
261     /**
262      * Creates a new period based on another using the {@link ConverterManager}.
263      *
264      * @param period  the period to convert
265      * @param type  which set of fields this period supports, null means use type from object
266      * @param chrono  the chronology to use, null means ISO default
267      * @throws IllegalArgumentException if period is invalid
268      * @throws IllegalArgumentException if an unsupported field's value is non-zero
269      */
270     protected BasePeriod(Object period, PeriodType type, Chronology chrono) {
271         super();
272         PeriodConverter converter = ConverterManager.getInstance().getPeriodConverter(period);
273         type = (type == null ? converter.getPeriodType(period) : type);
274         type = checkPeriodType(type);
275         iType = type;
276         if (this instanceof ReadWritablePeriod) {
277             iValues = new int[size()];
278             chrono = DateTimeUtils.getChronology(chrono);
279             converter.setInto((ReadWritablePeriod) this, period, chrono);
280         } else {
281             iValues = new MutablePeriod(period, type, chrono).getValues();
282         }
283     }
284 
285     /**
286      * Constructor used when we trust ourselves.
287      * Do not expose publically.
288      *
289      * @param values  the values to use, not null, not cloned
290      * @param type  which set of fields this period supports, not null
291      */
292     protected BasePeriod(int[] values, PeriodType type) {
293         super();
294         iType = type;
295         iValues = values;
296     }
297 
298     //-----------------------------------------------------------------------
299     /**
300      * Validates a period type, converting nulls to a default value and
301      * checking the type is suitable for this instance.
302      * 
303      * @param type  the type to check, may be null
304      * @return the validated type to use, not null
305      * @throws IllegalArgumentException if the period type is invalid
306      */
307     protected PeriodType checkPeriodType(PeriodType type) {
308         return DateTimeUtils.getPeriodType(type);
309     }
310 
311     //-----------------------------------------------------------------------
312     /**
313      * Gets the period type.
314      *
315      * @return the period type
316      */
317     public PeriodType getPeriodType() {
318         return iType;
319     }
320 
321     /**
322      * Gets the value at the specified index.
323      *
324      * @param index  the index to retrieve
325      * @return the value of the field at the specified index
326      * @throws IndexOutOfBoundsException if the index is invalid
327      */
328     public int getValue(int index) {
329         return iValues[index];
330     }
331 
332     //-----------------------------------------------------------------------
333     /**
334      * Gets the total millisecond duration of this period relative to a start instant.
335      * <p>
336      * This method adds the period to the specified instant in order to
337      * calculate the duration.
338      * <p>
339      * An instant must be supplied as the duration of a period varies.
340      * For example, a period of 1 month could vary between the equivalent of
341      * 28 and 31 days in milliseconds due to different length months.
342      * Similarly, a day can vary at Daylight Savings cutover, typically between
343      * 23 and 25 hours.
344      *
345      * @param startInstant  the instant to add the period to, thus obtaining the duration
346      * @return the total length of the period as a duration relative to the start instant
347      * @throws ArithmeticException if the millis exceeds the capacity of the duration
348      */
349     public Duration toDurationFrom(ReadableInstant startInstant) {
350         long startMillis = DateTimeUtils.getInstantMillis(startInstant);
351         Chronology chrono = DateTimeUtils.getInstantChronology(startInstant);
352         long endMillis = chrono.add(this, startMillis, 1);
353         return new Duration(startMillis, endMillis);
354     }
355 
356     /**
357      * Gets the total millisecond duration of this period relative to an
358      * end instant.
359      * <p>
360      * This method subtracts the period from the specified instant in order
361      * to calculate the duration.
362      * <p>
363      * An instant must be supplied as the duration of a period varies.
364      * For example, a period of 1 month could vary between the equivalent of
365      * 28 and 31 days in milliseconds due to different length months.
366      * Similarly, a day can vary at Daylight Savings cutover, typically between
367      * 23 and 25 hours.
368      *
369      * @param endInstant  the instant to subtract the period from, thus obtaining the duration
370      * @return the total length of the period as a duration relative to the end instant
371      * @throws ArithmeticException if the millis exceeds the capacity of the duration
372      */
373     public Duration toDurationTo(ReadableInstant endInstant) {
374         long endMillis = DateTimeUtils.getInstantMillis(endInstant);
375         Chronology chrono = DateTimeUtils.getInstantChronology(endInstant);
376         long startMillis = chrono.add(this, endMillis, -1);
377         return new Duration(startMillis, endMillis);
378     }
379 
380     //-----------------------------------------------------------------------
381     /**
382      * Checks whether a field type is supported, and if so adds the new value
383      * to the relevant index in the specified array.
384      * 
385      * @param type  the field type
386      * @param values  the array to update
387      * @param newValue  the new value to store if successful
388      */
389     private void checkAndUpdate(DurationFieldType type, int[] values, int newValue) {
390         int index = indexOf(type);
391         if (index == -1) {
392             if (newValue != 0) {
393                 throw new IllegalArgumentException(
394                     "Period does not support field '" + type.getName() + "'");
395             }
396         } else {
397             values[index] = newValue;
398         }
399     }
400 
401     //-----------------------------------------------------------------------
402     /**
403      * Sets all the fields of this period from another.
404      * 
405      * @param period  the period to copy from, not null
406      * @throws IllegalArgumentException if an unsupported field's value is non-zero
407      */
408     protected void setPeriod(ReadablePeriod period) {
409         if (period == null) {
410             setValues(new int[size()]);
411         } else {
412             setPeriodInternal(period);
413         }
414     }
415 
416     /**
417      * Private method called from constructor.
418      */
419     private void setPeriodInternal(ReadablePeriod period) {
420         int[] newValues = new int[size()];
421         for (int i = 0, isize = period.size(); i < isize; i++) {
422             DurationFieldType type = period.getFieldType(i);
423             int value = period.getValue(i);
424             checkAndUpdate(type, newValues, value);
425         }
426         setValues(newValues);
427     }
428 
429     /**
430      * Sets the eight standard the fields in one go.
431      * 
432      * @param years  amount of years in this period, which must be zero if unsupported
433      * @param months  amount of months in this period, which must be zero if unsupported
434      * @param weeks  amount of weeks in this period, which must be zero if unsupported
435      * @param days  amount of days in this period, which must be zero if unsupported
436      * @param hours  amount of hours in this period, which must be zero if unsupported
437      * @param minutes  amount of minutes in this period, which must be zero if unsupported
438      * @param seconds  amount of seconds in this period, which must be zero if unsupported
439      * @param millis  amount of milliseconds in this period, which must be zero if unsupported
440      * @throws IllegalArgumentException if an unsupported field's value is non-zero
441      */
442     protected void setPeriod(int years, int months, int weeks, int days,
443                              int hours, int minutes, int seconds, int millis) {
444         int[] newValues = setPeriodInternal(years, months, weeks, days, hours, minutes, seconds, millis);
445         setValues(newValues);
446     }
447 
448     /**
449      * Private method called from constructor.
450      */
451     private int[] setPeriodInternal(int years, int months, int weeks, int days,
452                                    int hours, int minutes, int seconds, int millis) {
453         int[] newValues = new int[size()];
454         checkAndUpdate(DurationFieldType.years(), newValues, years);
455         checkAndUpdate(DurationFieldType.months(), newValues, months);
456         checkAndUpdate(DurationFieldType.weeks(), newValues, weeks);
457         checkAndUpdate(DurationFieldType.days(), newValues, days);
458         checkAndUpdate(DurationFieldType.hours(), newValues, hours);
459         checkAndUpdate(DurationFieldType.minutes(), newValues, minutes);
460         checkAndUpdate(DurationFieldType.seconds(), newValues, seconds);
461         checkAndUpdate(DurationFieldType.millis(), newValues, millis);
462         return newValues;
463     }
464 
465     //-----------------------------------------------------------------------
466     /**
467      * Sets the value of a field in this period.
468      * 
469      * @param field  the field to set
470      * @param value  the value to set
471      * @throws IllegalArgumentException if field is is null or not supported.
472      */
473     protected void setField(DurationFieldType field, int value) {
474         setFieldInto(iValues, field, value);
475     }
476 
477     /**
478      * Sets the value of a field in this period.
479      * 
480      * @param values  the array of values to update
481      * @param field  the field to set
482      * @param value  the value to set
483      * @throws IllegalArgumentException if field is null or not supported.
484      */
485     protected void setFieldInto(int[] values, DurationFieldType field, int value) {
486         int index = indexOf(field);
487         if (index == -1) {
488             if (value != 0 || field == null) {
489                 throw new IllegalArgumentException(
490                     "Period does not support field '" + field + "'");
491             }
492         } else {
493             values[index] = value;
494         }
495     }
496 
497     /**
498      * Adds the value of a field in this period.
499      * 
500      * @param field  the field to set
501      * @param value  the value to set
502      * @throws IllegalArgumentException if field is is null or not supported.
503      */
504     protected void addField(DurationFieldType field, int value) {
505         addFieldInto(iValues, field, value);
506     }
507 
508     /**
509      * Adds the value of a field in this period.
510      * 
511      * @param values  the array of values to update
512      * @param field  the field to set
513      * @param value  the value to set
514      * @throws IllegalArgumentException if field is is null or not supported.
515      */
516     protected void addFieldInto(int[] values, DurationFieldType field, int value) {
517         int index = indexOf(field);
518         if (index == -1) {
519             if (value != 0 || field == null) {
520                 throw new IllegalArgumentException(
521                     "Period does not support field '" + field + "'");
522             }
523         } else {
524             values[index] = FieldUtils.safeAdd(values[index], value);
525         }
526     }
527 
528     /**
529      * Merges the fields from another period.
530      * 
531      * @param period  the period to add from, not null
532      * @throws IllegalArgumentException if an unsupported field's value is non-zero
533      */
534     protected void mergePeriod(ReadablePeriod period) {
535         if (period != null) {
536             setValues(mergePeriodInto(getValues(), period));
537         }
538     }
539 
540     /**
541      * Merges the fields from another period.
542      * 
543      * @param values  the array of values to update
544      * @param period  the period to add from, not null
545      * @return the updated values
546      * @throws IllegalArgumentException if an unsupported field's value is non-zero
547      */
548     protected int[] mergePeriodInto(int[] values, ReadablePeriod period) {
549         for (int i = 0, isize = period.size(); i < isize; i++) {
550             DurationFieldType type = period.getFieldType(i);
551             int value = period.getValue(i);
552             checkAndUpdate(type, values, value);
553         }
554         return values;
555     }
556 
557     /**
558      * Adds the fields from another period.
559      * 
560      * @param period  the period to add from, not null
561      * @throws IllegalArgumentException if an unsupported field's value is non-zero
562      */
563     protected void addPeriod(ReadablePeriod period) {
564         if (period != null) {
565             setValues(addPeriodInto(getValues(), period));
566         }
567     }
568 
569     /**
570      * Adds the fields from another period.
571      * 
572      * @param values  the array of values to update
573      * @param period  the period to add from, not null
574      * @return the updated values
575      * @throws IllegalArgumentException if an unsupported field's value is non-zero
576      */
577     protected int[] addPeriodInto(int[] values, ReadablePeriod period) {
578         for (int i = 0, isize = period.size(); i < isize; i++) {
579             DurationFieldType type = period.getFieldType(i);
580             int value = period.getValue(i);
581             if (value != 0) {
582                 int index = indexOf(type);
583                 if (index == -1) {
584                     throw new IllegalArgumentException(
585                         "Period does not support field '" + type.getName() + "'");
586                 } else {
587                     values[index] = FieldUtils.safeAdd(getValue(index), value);
588                 }
589             }
590         }
591         return values;
592     }
593 
594     //-----------------------------------------------------------------------
595     /**
596      * Sets the value of the field at the specified index.
597      * 
598      * @param index  the index
599      * @param value  the value to set
600      * @throws IndexOutOfBoundsException if the index is invalid
601      */
602     protected void setValue(int index, int value) {
603         iValues[index] = value;
604     }
605 
606     /**
607      * Sets the values of all fields.
608      * <p>
609      * In version 2.0 and later, this method copies the array into the original.
610      * This is because the instance variable has been changed to be final to satisfy the Java Memory Model.
611      * This only impacts subclasses that are mutable.
612      * 
613      * @param values  the array of values
614      */
615     protected void setValues(int[] values) {
616         System.arraycopy(values, 0, iValues, 0, iValues.length);
617     }
618 
619 }