1 | /* |
2 | * Copyright 2001-2006 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, Serializable { |
48 | |
49 | /** Serialization version. */ |
50 | private static final long serialVersionUID = 9386874258972L; |
51 | |
52 | /** The period in the units of this period. */ |
53 | private 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(Object other) { |
330 | if (other.getClass() != getClass()) { |
331 | throw new ClassCastException(getClass() + " cannot be compared to " + other.getClass()); |
332 | } |
333 | int otherValue = ((BaseSingleFieldPeriod) 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 | } |