001: /*
002: * Copyright 2001-2006 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 implements ReadablePeriod,
047: Comparable, Serializable {
048:
049: /** Serialization version. */
050: private static final long serialVersionUID = 9386874258972L;
051:
052: /** The period in the units of this period. */
053: private 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,
066: ReadableInstant end, DurationFieldType field) {
067: if (start == null || end == null) {
068: throw new IllegalArgumentException(
069: "ReadableInstant objects must not be null");
070: }
071: Chronology chrono = DateTimeUtils.getInstantChronology(start);
072: int amount = field.getField(chrono).getDifference(
073: end.getMillis(), start.getMillis());
074: return amount;
075: }
076:
077: //-----------------------------------------------------------------------
078: /**
079: * Calculates the number of whole units between the two specified partial datetimes.
080: * <p>
081: * The two partials must contain the same fields, for example you can specify
082: * two <code>LocalDate</code> objects.
083: *
084: * @param start the start partial date, validated to not be null
085: * @param end the end partial date, validated to not be null
086: * @param zeroInstance the zero instance constant, must not be null
087: * @return the period
088: * @throws IllegalArgumentException if the partials are null or invalid
089: */
090: protected static int between(ReadablePartial start,
091: ReadablePartial end, ReadablePeriod zeroInstance) {
092: if (start == null || end == null) {
093: throw new IllegalArgumentException(
094: "ReadablePartial objects must not be null");
095: }
096: if (start.size() != end.size()) {
097: throw new IllegalArgumentException(
098: "ReadablePartial objects must have the same set of fields");
099: }
100: for (int i = 0, isize = start.size(); i < isize; i++) {
101: if (start.getFieldType(i) != end.getFieldType(i)) {
102: throw new IllegalArgumentException(
103: "ReadablePartial objects must have the same set of fields");
104: }
105: }
106: if (DateTimeUtils.isContiguous(start) == false) {
107: throw new IllegalArgumentException(
108: "ReadablePartial objects must be contiguous");
109: }
110: Chronology chrono = DateTimeUtils.getChronology(
111: start.getChronology()).withUTC();
112: int[] values = chrono.get(zeroInstance, chrono.set(start, 0L),
113: chrono.set(end, 0L));
114: return values[0];
115: }
116:
117: /**
118: * Creates a new instance representing the number of complete standard length units
119: * in the specified period.
120: * <p>
121: * This factory method converts all fields from the period to hours using standardised
122: * durations for each field. Only those fields which have a precise duration in
123: * the ISO UTC chronology can be converted.
124: * <ul>
125: * <li>One week consists of 7 days.
126: * <li>One day consists of 24 hours.
127: * <li>One hour consists of 60 minutes.
128: * <li>One minute consists of 60 seconds.
129: * <li>One second consists of 1000 milliseconds.
130: * </ul>
131: * Months and Years are imprecise and periods containing these values cannot be converted.
132: *
133: * @param period the period to get the number of hours from, must not be null
134: * @param millisPerUnit the number of milliseconds in one standard unit of this period
135: * @throws IllegalArgumentException if the period contains imprecise duration values
136: */
137: protected static int standardPeriodIn(ReadablePeriod period,
138: long millisPerUnit) {
139: if (period == null) {
140: return 0;
141: }
142: Chronology iso = ISOChronology.getInstanceUTC();
143: long duration = 0L;
144: for (int i = 0; i < period.size(); i++) {
145: int value = period.getValue(i);
146: if (value != 0) {
147: DurationField field = period.getFieldType(i).getField(
148: iso);
149: if (field.isPrecise() == false) {
150: throw new IllegalArgumentException(
151: "Cannot convert period to duration as "
152: + field.getName()
153: + " is not precise in the period "
154: + period);
155: }
156: duration = FieldUtils.safeAdd(duration, FieldUtils
157: .safeMultiply(field.getUnitMillis(), value));
158: }
159: }
160: return FieldUtils.safeToInt(duration / millisPerUnit);
161: }
162:
163: //-----------------------------------------------------------------------
164: /**
165: * Creates a new instance representing the specified period.
166: *
167: * @param period the period to represent
168: */
169: protected BaseSingleFieldPeriod(int period) {
170: super ();
171: iPeriod = period;
172: }
173:
174: //-----------------------------------------------------------------------
175: /**
176: * Gets the amount of this period.
177: *
178: * @return the period value
179: */
180: protected int getValue() {
181: return iPeriod;
182: }
183:
184: /**
185: * Sets the amount of this period.
186: * To make a subclass immutable you must declare it final, or block this method.
187: *
188: * @param value the period value
189: */
190: protected void setValue(int value) {
191: iPeriod = value;
192: }
193:
194: //-----------------------------------------------------------------------
195: /**
196: * Gets the single duration field type.
197: *
198: * @return the duration field type, not null
199: */
200: public abstract DurationFieldType getFieldType();
201:
202: /**
203: * Gets the period type which matches the duration field type.
204: *
205: * @return the period type, not null
206: */
207: public abstract PeriodType getPeriodType();
208:
209: //-----------------------------------------------------------------------
210: /**
211: * Gets the number of fields that this period supports, which is one.
212: *
213: * @return the number of fields supported, which is one
214: */
215: public int size() {
216: return 1;
217: }
218:
219: /**
220: * Gets the field type at the specified index.
221: * <p>
222: * The only index supported by this period is zero which returns the
223: * field type of this class.
224: *
225: * @param index the index to retrieve, which must be zero
226: * @return the field at the specified index
227: * @throws IndexOutOfBoundsException if the index is invalid
228: */
229: public DurationFieldType getFieldType(int index) {
230: if (index != 0) {
231: throw new IndexOutOfBoundsException(String.valueOf(index));
232: }
233: return getFieldType();
234: }
235:
236: /**
237: * Gets the value at the specified index.
238: * <p>
239: * The only index supported by this period is zero.
240: *
241: * @param index the index to retrieve, which must be zero
242: * @return the value of the field at the specified index
243: * @throws IndexOutOfBoundsException if the index is invalid
244: */
245: public int getValue(int index) {
246: if (index != 0) {
247: throw new IndexOutOfBoundsException(String.valueOf(index));
248: }
249: return getValue();
250: }
251:
252: /**
253: * Gets the value of a duration field represented by this period.
254: * <p>
255: * If the field type specified does not match the type used by this class
256: * then zero is returned.
257: *
258: * @param type the field type to query, null returns zero
259: * @return the value of that field, zero if field not supported
260: */
261: public int get(DurationFieldType type) {
262: if (type == getFieldType()) {
263: return getValue();
264: }
265: return 0;
266: }
267:
268: /**
269: * Checks whether the duration field specified is supported by this period.
270: *
271: * @param type the type to check, may be null which returns false
272: * @return true if the field is supported
273: */
274: public boolean isSupported(DurationFieldType type) {
275: return (type == getFieldType());
276: }
277:
278: //-----------------------------------------------------------------------
279: /**
280: * Get this period as an immutable <code>Period</code> object.
281: * The period will use <code>PeriodType.standard()</code>.
282: *
283: * @return a <code>Period</code> representing the same number of days
284: */
285: public Period toPeriod() {
286: return Period.ZERO.withFields(this );
287: }
288:
289: /**
290: * Get this object as a <code>MutablePeriod</code>.
291: * <p>
292: * This will always return a new <code>MutablePeriod</code> with the same fields.
293: * The period will use <code>PeriodType.standard()</code>.
294: *
295: * @return a MutablePeriod using the same field set and values
296: */
297: public MutablePeriod toMutablePeriod() {
298: MutablePeriod period = new MutablePeriod();
299: period.add(this );
300: return period;
301: }
302:
303: //-----------------------------------------------------------------------
304: /**
305: * Compares this object with the specified object for equality based on the
306: * value of each field. All ReadablePeriod instances are accepted, but only
307: * those with a matching <code>PeriodType</code> can return true.
308: *
309: * @param period a readable period to check against
310: * @return true if all the field values are equal, false if
311: * not or the period is null or of an incorrect type
312: */
313: public boolean equals(Object period) {
314: if (this == period) {
315: return true;
316: }
317: if (period instanceof ReadablePeriod == false) {
318: return false;
319: }
320: ReadablePeriod other = (ReadablePeriod) period;
321: return (other.getPeriodType() == getPeriodType() && other
322: .getValue(0) == getValue());
323: }
324:
325: /**
326: * Gets a hash code for the period as defined by ReadablePeriod.
327: *
328: * @return a hash code
329: */
330: public int hashCode() {
331: int total = 17;
332: total = 27 * total + getValue();
333: total = 27 * total + getFieldType().hashCode();
334: return total;
335: }
336:
337: /**
338: * Compares this period to another object of the same class.
339: *
340: * @param other the other period, must not be null
341: * @return zero if equal, positive if greater, negative if less
342: * @throws NullPointerException if the other period is null
343: * @throws ClassCastException if the other period is of a different type
344: */
345: public int compareTo(Object other) {
346: if (other.getClass() != getClass()) {
347: throw new ClassCastException(getClass()
348: + " cannot be compared to " + other.getClass());
349: }
350: int otherValue = ((BaseSingleFieldPeriod) other).getValue();
351: int this Value = getValue();
352: if (this Value > otherValue) {
353: return 1;
354: }
355: if (this Value < otherValue) {
356: return -1;
357: }
358: return 0;
359: }
360:
361: }
|