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 }