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 */ 016package org.joda.time.base; 017 018import java.io.Serializable; 019 020import org.joda.time.Chronology; 021import org.joda.time.DateTimeUtils; 022import org.joda.time.Duration; 023import org.joda.time.DurationFieldType; 024import org.joda.time.MutablePeriod; 025import org.joda.time.PeriodType; 026import org.joda.time.ReadWritablePeriod; 027import org.joda.time.ReadableDuration; 028import org.joda.time.ReadableInstant; 029import org.joda.time.ReadablePartial; 030import org.joda.time.ReadablePeriod; 031import org.joda.time.convert.ConverterManager; 032import org.joda.time.convert.PeriodConverter; 033import 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 */ 049public 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}