1 /* 2 * Copyright 2001-2011 Stephen Colebourne 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.joda.time.base; 17 18 import java.io.Serializable; 19 20 import org.joda.time.Chronology; 21 import org.joda.time.DateTimeUtils; 22 import org.joda.time.DurationField; 23 import org.joda.time.DurationFieldType; 24 import org.joda.time.MutablePeriod; 25 import org.joda.time.Period; 26 import org.joda.time.PeriodType; 27 import org.joda.time.ReadableInstant; 28 import org.joda.time.ReadablePartial; 29 import org.joda.time.ReadablePeriod; 30 import org.joda.time.chrono.ISOChronology; 31 import org.joda.time.field.FieldUtils; 32 33 /** 34 * BaseSingleFieldPeriod is an abstract implementation of ReadablePeriod that 35 * manages a single duration field, such as days or minutes. 36 * <p> 37 * This class should generally not be used directly by API users. 38 * The {@link ReadablePeriod} interface should be used when different 39 * kinds of period objects are to be referenced. 40 * <p> 41 * BaseSingleFieldPeriod subclasses may be mutable and not thread-safe. 42 * 43 * @author Stephen Colebourne 44 * @since 1.4 45 */ 46 public abstract class BaseSingleFieldPeriod 47 implements ReadablePeriod, Comparable<BaseSingleFieldPeriod>, Serializable { 48 49 /** Serialization version. */ 50 private static final long serialVersionUID = 9386874258972L; 51 52 /** The period in the units of this period. */ 53 private volatile int iPeriod; 54 55 //----------------------------------------------------------------------- 56 /** 57 * Calculates the number of whole units between the two specified datetimes. 58 * 59 * @param start the start instant, validated to not be null 60 * @param end the end instant, validated to not be null 61 * @param field the field type to use, must not be null 62 * @return the period 63 * @throws IllegalArgumentException if the instants are null or invalid 64 */ 65 protected static int between(ReadableInstant start, ReadableInstant end, DurationFieldType field) { 66 if (start == null || end == null) { 67 throw new IllegalArgumentException("ReadableInstant objects must not be null"); 68 } 69 Chronology chrono = DateTimeUtils.getInstantChronology(start); 70 int amount = field.getField(chrono).getDifference(end.getMillis(), start.getMillis()); 71 return amount; 72 } 73 74 //----------------------------------------------------------------------- 75 /** 76 * Calculates the number of whole units between the two specified partial datetimes. 77 * <p> 78 * The two partials must contain the same fields, for example you can specify 79 * two <code>LocalDate</code> objects. 80 * 81 * @param start the start partial date, validated to not be null 82 * @param end the end partial date, validated to not be null 83 * @param zeroInstance the zero instance constant, must not be null 84 * @return the period 85 * @throws IllegalArgumentException if the partials are null or invalid 86 */ 87 protected static int between(ReadablePartial start, ReadablePartial end, ReadablePeriod zeroInstance) { 88 if (start == null || end == null) { 89 throw new IllegalArgumentException("ReadablePartial objects must not be null"); 90 } 91 if (start.size() != end.size()) { 92 throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields"); 93 } 94 for (int i = 0, isize = start.size(); i < isize; i++) { 95 if (start.getFieldType(i) != end.getFieldType(i)) { 96 throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields"); 97 } 98 } 99 if (DateTimeUtils.isContiguous(start) == false) { 100 throw new IllegalArgumentException("ReadablePartial objects must be contiguous"); 101 } 102 Chronology chrono = DateTimeUtils.getChronology(start.getChronology()).withUTC(); 103 int[] values = chrono.get(zeroInstance, chrono.set(start, 0L), chrono.set(end, 0L)); 104 return values[0]; 105 } 106 107 /** 108 * Creates a new instance representing the number of complete standard length units 109 * in the specified period. 110 * <p> 111 * This factory method converts all fields from the period to hours using standardised 112 * durations for each field. Only those fields which have a precise duration in 113 * the ISO UTC chronology can be converted. 114 * <ul> 115 * <li>One week consists of 7 days. 116 * <li>One day consists of 24 hours. 117 * <li>One hour consists of 60 minutes. 118 * <li>One minute consists of 60 seconds. 119 * <li>One second consists of 1000 milliseconds. 120 * </ul> 121 * Months and Years are imprecise and periods containing these values cannot be converted. 122 * 123 * @param period the period to get the number of hours from, must not be null 124 * @param millisPerUnit the number of milliseconds in one standard unit of this period 125 * @throws IllegalArgumentException if the period contains imprecise duration values 126 */ 127 protected static int standardPeriodIn(ReadablePeriod period, long millisPerUnit) { 128 if (period == null) { 129 return 0; 130 } 131 Chronology iso = ISOChronology.getInstanceUTC(); 132 long duration = 0L; 133 for (int i = 0; i < period.size(); i++) { 134 int value = period.getValue(i); 135 if (value != 0) { 136 DurationField field = period.getFieldType(i).getField(iso); 137 if (field.isPrecise() == false) { 138 throw new IllegalArgumentException( 139 "Cannot convert period to duration as " + field.getName() + 140 " is not precise in the period " + period); 141 } 142 duration = FieldUtils.safeAdd(duration, FieldUtils.safeMultiply(field.getUnitMillis(), value)); 143 } 144 } 145 return FieldUtils.safeToInt(duration / millisPerUnit); 146 } 147 148 //----------------------------------------------------------------------- 149 /** 150 * Creates a new instance representing the specified period. 151 * 152 * @param period the period to represent 153 */ 154 protected BaseSingleFieldPeriod(int period) { 155 super(); 156 iPeriod = period; 157 } 158 159 //----------------------------------------------------------------------- 160 /** 161 * Gets the amount of this period. 162 * 163 * @return the period value 164 */ 165 protected int getValue() { 166 return iPeriod; 167 } 168 169 /** 170 * Sets the amount of this period. 171 * To make a subclass immutable you must declare it final, or block this method. 172 * 173 * @param value the period value 174 */ 175 protected void setValue(int value) { 176 iPeriod = value; 177 } 178 179 //----------------------------------------------------------------------- 180 /** 181 * Gets the single duration field type. 182 * 183 * @return the duration field type, not null 184 */ 185 public abstract DurationFieldType getFieldType(); 186 187 /** 188 * Gets the period type which matches the duration field type. 189 * 190 * @return the period type, not null 191 */ 192 public abstract PeriodType getPeriodType(); 193 194 //----------------------------------------------------------------------- 195 /** 196 * Gets the number of fields that this period supports, which is one. 197 * 198 * @return the number of fields supported, which is one 199 */ 200 public int size() { 201 return 1; 202 } 203 204 /** 205 * Gets the field type at the specified index. 206 * <p> 207 * The only index supported by this period is zero which returns the 208 * field type of this class. 209 * 210 * @param index the index to retrieve, which must be zero 211 * @return the field at the specified index 212 * @throws IndexOutOfBoundsException if the index is invalid 213 */ 214 public DurationFieldType getFieldType(int index) { 215 if (index != 0) { 216 throw new IndexOutOfBoundsException(String.valueOf(index)); 217 } 218 return getFieldType(); 219 } 220 221 /** 222 * Gets the value at the specified index. 223 * <p> 224 * The only index supported by this period is zero. 225 * 226 * @param index the index to retrieve, which must be zero 227 * @return the value of the field at the specified index 228 * @throws IndexOutOfBoundsException if the index is invalid 229 */ 230 public int getValue(int index) { 231 if (index != 0) { 232 throw new IndexOutOfBoundsException(String.valueOf(index)); 233 } 234 return getValue(); 235 } 236 237 /** 238 * Gets the value of a duration field represented by this period. 239 * <p> 240 * If the field type specified does not match the type used by this class 241 * then zero is returned. 242 * 243 * @param type the field type to query, null returns zero 244 * @return the value of that field, zero if field not supported 245 */ 246 public int get(DurationFieldType type) { 247 if (type == getFieldType()) { 248 return getValue(); 249 } 250 return 0; 251 } 252 253 /** 254 * Checks whether the duration field specified is supported by this period. 255 * 256 * @param type the type to check, may be null which returns false 257 * @return true if the field is supported 258 */ 259 public boolean isSupported(DurationFieldType type) { 260 return (type == getFieldType()); 261 } 262 263 //----------------------------------------------------------------------- 264 /** 265 * Get this period as an immutable <code>Period</code> object. 266 * The period will use <code>PeriodType.standard()</code>. 267 * 268 * @return a <code>Period</code> representing the same number of days 269 */ 270 public Period toPeriod() { 271 return Period.ZERO.withFields(this); 272 } 273 274 /** 275 * Get this object as a <code>MutablePeriod</code>. 276 * <p> 277 * This will always return a new <code>MutablePeriod</code> with the same fields. 278 * The period will use <code>PeriodType.standard()</code>. 279 * 280 * @return a MutablePeriod using the same field set and values 281 */ 282 public MutablePeriod toMutablePeriod() { 283 MutablePeriod period = new MutablePeriod(); 284 period.add(this); 285 return period; 286 } 287 288 //----------------------------------------------------------------------- 289 /** 290 * Compares this object with the specified object for equality based on the 291 * value of each field. All ReadablePeriod instances are accepted, but only 292 * those with a matching <code>PeriodType</code> can return true. 293 * 294 * @param period a readable period to check against 295 * @return true if all the field values are equal, false if 296 * not or the period is null or of an incorrect type 297 */ 298 public boolean equals(Object period) { 299 if (this == period) { 300 return true; 301 } 302 if (period instanceof ReadablePeriod == false) { 303 return false; 304 } 305 ReadablePeriod other = (ReadablePeriod) period; 306 return (other.getPeriodType() == getPeriodType() && other.getValue(0) == getValue()); 307 } 308 309 /** 310 * Gets a hash code for the period as defined by ReadablePeriod. 311 * 312 * @return a hash code 313 */ 314 public int hashCode() { 315 int total = 17; 316 total = 27 * total + getValue(); 317 total = 27 * total + getFieldType().hashCode(); 318 return total; 319 } 320 321 /** 322 * Compares this period to another object of the same class. 323 * 324 * @param other the other period, must not be null 325 * @return zero if equal, positive if greater, negative if less 326 * @throws NullPointerException if the other period is null 327 * @throws ClassCastException if the other period is of a different type 328 */ 329 public int compareTo(BaseSingleFieldPeriod other) { 330 if (other.getClass() != getClass()) { 331 throw new ClassCastException(getClass() + " cannot be compared to " + other.getClass()); 332 } 333 int otherValue = other.getValue(); 334 int thisValue = getValue(); 335 if (thisValue > otherValue) { 336 return 1; 337 } 338 if (thisValue < otherValue) { 339 return -1; 340 } 341 return 0; 342 } 343 344 }