001 /*
002 * Copyright 2001-2011 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.base;
017
018 import java.io.Serializable;
019
020 import org.joda.time.Chronology;
021 import org.joda.time.DateTimeUtils;
022 import org.joda.time.Duration;
023 import org.joda.time.DurationFieldType;
024 import org.joda.time.MutablePeriod;
025 import org.joda.time.PeriodType;
026 import org.joda.time.ReadWritablePeriod;
027 import org.joda.time.ReadableDuration;
028 import org.joda.time.ReadableInstant;
029 import org.joda.time.ReadablePartial;
030 import org.joda.time.ReadablePeriod;
031 import org.joda.time.chrono.ISOChronology;
032 import org.joda.time.convert.ConverterManager;
033 import org.joda.time.convert.PeriodConverter;
034 import org.joda.time.field.FieldUtils;
035
036 /**
037 * BasePeriod is an abstract implementation of ReadablePeriod that stores
038 * data in a <code>PeriodType</code> and an <code>int[]</code>.
039 * <p>
040 * This class should generally not be used directly by API users.
041 * The {@link ReadablePeriod} interface should be used when different
042 * kinds of period objects are to be referenced.
043 * <p>
044 * BasePeriod subclasses may be mutable and not thread-safe.
045 *
046 * @author Brian S O'Neill
047 * @author Stephen Colebourne
048 * @since 1.0
049 */
050 public abstract class BasePeriod
051 extends AbstractPeriod
052 implements ReadablePeriod, Serializable {
053
054 /** Serialization version */
055 private static final long serialVersionUID = -2110953284060001145L;
056 /** Serialization version */
057 private static final ReadablePeriod DUMMY_PERIOD = new AbstractPeriod() {
058 public int getValue(int index) {
059 return 0;
060 }
061 public PeriodType getPeriodType() {
062 return PeriodType.time();
063 }
064 };
065
066 /** The type of period */
067 private final PeriodType iType;
068 /** The values */
069 private final int[] iValues;
070
071 //-----------------------------------------------------------------------
072 /**
073 * Creates a period from a set of field values.
074 *
075 * @param years amount of years in this period, which must be zero if unsupported
076 * @param months amount of months in this period, which must be zero if unsupported
077 * @param weeks amount of weeks in this period, which must be zero if unsupported
078 * @param days amount of days in this period, which must be zero if unsupported
079 * @param hours amount of hours in this period, which must be zero if unsupported
080 * @param minutes amount of minutes in this period, which must be zero if unsupported
081 * @param seconds amount of seconds in this period, which must be zero if unsupported
082 * @param millis amount of milliseconds in this period, which must be zero if unsupported
083 * @param type which set of fields this period supports
084 * @throws IllegalArgumentException if period type is invalid
085 * @throws IllegalArgumentException if an unsupported field's value is non-zero
086 */
087 protected BasePeriod(int years, int months, int weeks, int days,
088 int hours, int minutes, int seconds, int millis,
089 PeriodType type) {
090 super();
091 type = checkPeriodType(type);
092 iType = type;
093 iValues = setPeriodInternal(years, months, weeks, days, hours, minutes, seconds, millis); // internal method
094 }
095
096 /**
097 * Creates a period from the given interval endpoints.
098 *
099 * @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 }