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