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.DurationField; 023 import org.joda.time.DurationFieldType; 024 import org.joda.time.MutablePeriod; 025 import org.joda.time.Period; 026 import org.joda.time.PeriodType; 027 import org.joda.time.ReadableInstant; 028 import org.joda.time.ReadablePartial; 029 import org.joda.time.ReadablePeriod; 030 import org.joda.time.chrono.ISOChronology; 031 import org.joda.time.field.FieldUtils; 032 033 /** 034 * BaseSingleFieldPeriod is an abstract implementation of ReadablePeriod that 035 * manages a single duration field, such as days or minutes. 036 * <p> 037 * This class should generally not be used directly by API users. 038 * The {@link ReadablePeriod} interface should be used when different 039 * kinds of period objects are to be referenced. 040 * <p> 041 * BaseSingleFieldPeriod subclasses may be mutable and not thread-safe. 042 * 043 * @author Stephen Colebourne 044 * @since 1.4 045 */ 046 public abstract class BaseSingleFieldPeriod 047 implements ReadablePeriod, Comparable<BaseSingleFieldPeriod>, Serializable { 048 049 /** Serialization version. */ 050 private static final long serialVersionUID = 9386874258972L; 051 052 /** The period in the units of this period. */ 053 private volatile int iPeriod; 054 055 //----------------------------------------------------------------------- 056 /** 057 * Calculates the number of whole units between the two specified datetimes. 058 * 059 * @param start the start instant, validated to not be null 060 * @param end the end instant, validated to not be null 061 * @param field the field type to use, must not be null 062 * @return the period 063 * @throws IllegalArgumentException if the instants are null or invalid 064 */ 065 protected static int between(ReadableInstant start, ReadableInstant end, DurationFieldType field) { 066 if (start == null || end == null) { 067 throw new IllegalArgumentException("ReadableInstant objects must not be null"); 068 } 069 Chronology chrono = DateTimeUtils.getInstantChronology(start); 070 int amount = field.getField(chrono).getDifference(end.getMillis(), start.getMillis()); 071 return amount; 072 } 073 074 //----------------------------------------------------------------------- 075 /** 076 * Calculates the number of whole units between the two specified partial datetimes. 077 * <p> 078 * The two partials must contain the same fields, for example you can specify 079 * two <code>LocalDate</code> objects. 080 * 081 * @param start the start partial date, validated to not be null 082 * @param end the end partial date, validated to not be null 083 * @param zeroInstance the zero instance constant, must not be null 084 * @return the period 085 * @throws IllegalArgumentException if the partials are null or invalid 086 */ 087 protected static int between(ReadablePartial start, ReadablePartial end, ReadablePeriod zeroInstance) { 088 if (start == null || end == null) { 089 throw new IllegalArgumentException("ReadablePartial objects must not be null"); 090 } 091 if (start.size() != end.size()) { 092 throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields"); 093 } 094 for (int i = 0, isize = start.size(); i < isize; i++) { 095 if (start.getFieldType(i) != end.getFieldType(i)) { 096 throw new IllegalArgumentException("ReadablePartial objects must have the same set of fields"); 097 } 098 } 099 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 }