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 }