1 | /* |
2 | * Copyright 2001-2005 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.field; |
17 | |
18 | import java.io.Serializable; |
19 | import java.util.Locale; |
20 | |
21 | import org.joda.time.Chronology; |
22 | import org.joda.time.DateTimeField; |
23 | import org.joda.time.DateTimeFieldType; |
24 | import org.joda.time.DateTimeUtils; |
25 | import org.joda.time.DurationField; |
26 | import org.joda.time.Interval; |
27 | import org.joda.time.ReadableInstant; |
28 | import org.joda.time.ReadablePartial; |
29 | |
30 | /** |
31 | * AbstractReadableInstantFieldProperty is a base class for binding a |
32 | * ReadableInstant to a DateTimeField. |
33 | * <p> |
34 | * It allows the date and time manipulation code to be field based yet |
35 | * still easy to use. |
36 | * <p> |
37 | * AbstractReadableInstantFieldProperty itself is thread-safe and immutable, |
38 | * but the ReadableInstant being operated on may be mutable and not |
39 | * thread-safe. |
40 | * |
41 | * @author Stephen Colebourne |
42 | * @author Brian S O'Neill |
43 | * @author Mike Schrag |
44 | * @since 1.0 |
45 | */ |
46 | public abstract class AbstractReadableInstantFieldProperty implements Serializable { |
47 | |
48 | /** Serialization version. */ |
49 | private static final long serialVersionUID = 1971226328211649661L; |
50 | |
51 | /** |
52 | * Constructor. |
53 | */ |
54 | public AbstractReadableInstantFieldProperty() { |
55 | super(); |
56 | } |
57 | |
58 | //----------------------------------------------------------------------- |
59 | /** |
60 | * Gets the field being used. |
61 | * |
62 | * @return the field |
63 | */ |
64 | public abstract DateTimeField getField(); |
65 | |
66 | /** |
67 | * Gets the field type being used. |
68 | * |
69 | * @return the field type |
70 | */ |
71 | public DateTimeFieldType getFieldType() { |
72 | return getField().getType(); |
73 | } |
74 | |
75 | /** |
76 | * Gets the name of the field. |
77 | * |
78 | * @return the field name |
79 | */ |
80 | public String getName() { |
81 | return getField().getName(); |
82 | } |
83 | |
84 | /** |
85 | * Gets the milliseconds of the datetime that this property is linked to. |
86 | * |
87 | * @return the milliseconds |
88 | */ |
89 | protected abstract long getMillis(); |
90 | |
91 | /** |
92 | * Gets the chronology of the datetime that this property is linked to. |
93 | * <p> |
94 | * This implementation throws UnsupportedOperationException, and must be |
95 | * implemented by subclasses to enable the equals() and hashCode() methods. |
96 | * |
97 | * @return the chronology |
98 | * @since 1.4 |
99 | */ |
100 | protected Chronology getChronology() { |
101 | throw new UnsupportedOperationException( |
102 | "The method getChronology() was added in v1.4 and needs " + |
103 | "to be implemented by subclasses of AbstractReadableInstantFieldProperty"); |
104 | } |
105 | |
106 | //----------------------------------------------------------------------- |
107 | /** |
108 | * Gets the value of this property from the instant. |
109 | * <p> |
110 | * For example, the following two lines of code are equivalent: |
111 | * <pre> |
112 | * datetime.getDayOfMonth(); |
113 | * datetime.dayOfMonth().get(); |
114 | * </pre> |
115 | * |
116 | * @return the current value |
117 | * @see DateTimeField#get |
118 | */ |
119 | public int get() { |
120 | return getField().get(getMillis()); |
121 | } |
122 | |
123 | /** |
124 | * Gets the value of this property from the instant as a string. |
125 | * <p> |
126 | * This method returns the value converted to a <code>String</code> |
127 | * using <code>Integer.toString</code>. This method does NOT return |
128 | * textual descriptions such as 'Monday' or 'January'. |
129 | * See {@link #getAsText()} and {@link #getAsShortText()} for those. |
130 | * |
131 | * @return the current value |
132 | * @see DateTimeField#get |
133 | * @since 1.1 |
134 | */ |
135 | public String getAsString() { |
136 | return Integer.toString(get()); |
137 | } |
138 | |
139 | /** |
140 | * Gets the textual value of this property from the instant as a |
141 | * string in the default locale. |
142 | * <p> |
143 | * This method returns the value converted to a <code>String</code> |
144 | * returning the appropriate textual description wherever possible. |
145 | * Thus, a day of week of 1 would return 'Monday' in English. |
146 | * |
147 | * @return the current text value |
148 | * @see DateTimeField#getAsText |
149 | */ |
150 | public String getAsText() { |
151 | return getAsText(null); |
152 | } |
153 | |
154 | /** |
155 | * Gets the textual value of this property from the instant as a |
156 | * string in the specified locale. |
157 | * <p> |
158 | * This method returns the value converted to a <code>String</code> |
159 | * returning the appropriate textual description wherever possible. |
160 | * Thus, a day of week of 1 would return 'Monday' in English. |
161 | * |
162 | * @param locale locale to use for selecting a text symbol, null means default |
163 | * @return the current text value |
164 | * @see DateTimeField#getAsText |
165 | */ |
166 | public String getAsText(Locale locale) { |
167 | return getField().getAsText(getMillis(), locale); |
168 | } |
169 | |
170 | /** |
171 | * Gets the short textual value of this property from the instant as a |
172 | * string in the default locale. |
173 | * <p> |
174 | * This method returns the value converted to a <code>String</code> |
175 | * returning the appropriate textual description wherever possible. |
176 | * Thus, a day of week of 1 would return 'Mon' in English. |
177 | * |
178 | * @return the current text value |
179 | * @see DateTimeField#getAsShortText |
180 | */ |
181 | public String getAsShortText() { |
182 | return getAsShortText(null); |
183 | } |
184 | |
185 | /** |
186 | * Gets the short textual value of this property from the instant as a |
187 | * string in the specified locale. |
188 | * <p> |
189 | * This method returns the value converted to a <code>String</code> |
190 | * returning the appropriate textual description wherever possible. |
191 | * Thus, a day of week of 1 would return 'Mon' in English. |
192 | * |
193 | * @param locale locale to use for selecting a text symbol, null means default |
194 | * @return the current text value |
195 | * @see DateTimeField#getAsShortText |
196 | */ |
197 | public String getAsShortText(Locale locale) { |
198 | return getField().getAsShortText(getMillis(), locale); |
199 | } |
200 | |
201 | //----------------------------------------------------------------------- |
202 | /** |
203 | * Returns the difference between this field property instant and the one |
204 | * passed in, in the units of this field. The sign of the difference |
205 | * matches that of compareTo. In other words, this field property's instant |
206 | * is the minuend. |
207 | * |
208 | * @param instant the subtrahend, null means now |
209 | * @return the difference in the units of this field |
210 | * @see DateTimeField#getDifference |
211 | */ |
212 | public int getDifference(ReadableInstant instant) { |
213 | if (instant == null) { |
214 | return getField().getDifference(getMillis(), DateTimeUtils.currentTimeMillis()); |
215 | } |
216 | return getField().getDifference(getMillis(), instant.getMillis()); |
217 | } |
218 | |
219 | /** |
220 | * Returns the difference between this field property instant and the one |
221 | * passed in, in the units of this field. The sign of the difference |
222 | * matches that of compareTo. In other words, this field property's instant |
223 | * is the minuend. |
224 | * |
225 | * @param instant the subtrahend, null means now |
226 | * @return the difference in the units of this field |
227 | * @see DateTimeField#getDifference |
228 | */ |
229 | public long getDifferenceAsLong(ReadableInstant instant) { |
230 | if (instant == null) { |
231 | return getField().getDifferenceAsLong(getMillis(), DateTimeUtils.currentTimeMillis()); |
232 | } |
233 | return getField().getDifferenceAsLong(getMillis(), instant.getMillis()); |
234 | } |
235 | |
236 | //----------------------------------------------------------------------- |
237 | /** |
238 | * Returns the duration per unit value of this field. For example, if this |
239 | * field represents "hour of day", then the duration is an hour. |
240 | * |
241 | * @return the duration of this field, or UnsupportedDurationField |
242 | */ |
243 | public DurationField getDurationField() { |
244 | return getField().getDurationField(); |
245 | } |
246 | |
247 | /** |
248 | * Returns the range duration of this field. For example, if this field |
249 | * represents "hour of day", then the range duration is a day. |
250 | * |
251 | * @return the range duration of this field, or null if field has no range |
252 | */ |
253 | public DurationField getRangeDurationField() { |
254 | return getField().getRangeDurationField(); |
255 | } |
256 | |
257 | /** |
258 | * Gets whether this field is leap. |
259 | * |
260 | * @return true if a leap field |
261 | * @see DateTimeField#isLeap |
262 | */ |
263 | public boolean isLeap() { |
264 | return getField().isLeap(getMillis()); |
265 | } |
266 | |
267 | /** |
268 | * Gets the amount by which this field is leap. |
269 | * |
270 | * @return the amount by which the field is leap |
271 | * @see DateTimeField#getLeapAmount |
272 | */ |
273 | public int getLeapAmount() { |
274 | return getField().getLeapAmount(getMillis()); |
275 | } |
276 | |
277 | /** |
278 | * If this field were to leap, then it would be in units described by the |
279 | * returned duration. If this field doesn't ever leap, null is returned. |
280 | */ |
281 | public DurationField getLeapDurationField() { |
282 | return getField().getLeapDurationField(); |
283 | } |
284 | |
285 | //----------------------------------------------------------------------- |
286 | /** |
287 | * Gets the minimum value for the field ignoring the current time. |
288 | * |
289 | * @return the minimum value |
290 | * @see DateTimeField#getMinimumValue |
291 | */ |
292 | public int getMinimumValueOverall() { |
293 | return getField().getMinimumValue(); |
294 | } |
295 | |
296 | /** |
297 | * Gets the minimum value for the field. |
298 | * |
299 | * @return the minimum value |
300 | * @see DateTimeField#getMinimumValue |
301 | */ |
302 | public int getMinimumValue() { |
303 | return getField().getMinimumValue(getMillis()); |
304 | } |
305 | |
306 | /** |
307 | * Gets the maximum value for the field ignoring the current time. |
308 | * |
309 | * @return the maximum value |
310 | * @see DateTimeField#getMaximumValue |
311 | */ |
312 | public int getMaximumValueOverall() { |
313 | return getField().getMaximumValue(); |
314 | } |
315 | |
316 | /** |
317 | * Gets the maximum value for the field. |
318 | * |
319 | * @return the maximum value |
320 | * @see DateTimeField#getMaximumValue |
321 | */ |
322 | public int getMaximumValue() { |
323 | return getField().getMaximumValue(getMillis()); |
324 | } |
325 | |
326 | /** |
327 | * Gets the maximum text length for the field. |
328 | * |
329 | * @param locale optional locale to use for selecting a text symbol |
330 | * @return the maximum length |
331 | * @see DateTimeField#getMaximumTextLength |
332 | */ |
333 | public int getMaximumTextLength(Locale locale) { |
334 | return getField().getMaximumTextLength(locale); |
335 | } |
336 | |
337 | /** |
338 | * Gets the maximum short text length for the field. |
339 | * |
340 | * @param locale optional locale to use for selecting a text symbol |
341 | * @return the maximum length |
342 | * @see DateTimeField#getMaximumShortTextLength |
343 | */ |
344 | public int getMaximumShortTextLength(Locale locale) { |
345 | return getField().getMaximumShortTextLength(locale); |
346 | } |
347 | |
348 | |
349 | /** |
350 | * Returns the fractional duration milliseconds of this field. |
351 | * |
352 | * @see DateTimeField#remainder |
353 | * @return remainder duration, in milliseconds |
354 | */ |
355 | public long remainder() { |
356 | return getField().remainder(getMillis()); |
357 | } |
358 | |
359 | /** |
360 | * Returns the interval that represents the range of the minimum |
361 | * and maximum values of this field. |
362 | * <p> |
363 | * For example, <code>datetime.monthOfYear().toInterval()</code> |
364 | * will return an interval over the whole month. |
365 | * |
366 | * @return the interval of this field |
367 | * @since 1.2 |
368 | */ |
369 | public Interval toInterval() { |
370 | DateTimeField field = getField(); |
371 | long start = field.roundFloor(getMillis()); |
372 | long end = field.add(start, 1); |
373 | Interval interval = new Interval(start, end); |
374 | return interval; |
375 | } |
376 | |
377 | //----------------------------------------------------------------------- |
378 | /** |
379 | * Compare this field to the same field on another instant. |
380 | * <p> |
381 | * The comparison is based on the value of the same field type, irrespective |
382 | * of any difference in chronology. Thus, if this property represents the |
383 | * hourOfDay field, then the hourOfDay field of the other instant will be queried |
384 | * whether in the same chronology or not. |
385 | * |
386 | * @param instant the instant to compare to |
387 | * @return negative value if this is less, 0 if equal, or positive value if greater |
388 | * @throws IllegalArgumentException if the instant is null |
389 | */ |
390 | public int compareTo(ReadableInstant instant) { |
391 | if (instant == null) { |
392 | throw new IllegalArgumentException("The instant must not be null"); |
393 | } |
394 | int thisValue = get(); |
395 | int otherValue = instant.get(getFieldType()); |
396 | if (thisValue < otherValue) { |
397 | return -1; |
398 | } else if (thisValue > otherValue) { |
399 | return 1; |
400 | } else { |
401 | return 0; |
402 | } |
403 | } |
404 | |
405 | //----------------------------------------------------------------------- |
406 | /** |
407 | * Compare this field to the same field on another partial instant. |
408 | * <p> |
409 | * The comparison is based on the value of the same field type, irrespective |
410 | * of any difference in chronology. Thus, if this property represents the |
411 | * hourOfDay field, then the hourOfDay field of the other partial will be queried |
412 | * whether in the same chronology or not. |
413 | * |
414 | * @param partial the partial to compare to |
415 | * @return negative value if this is less, 0 if equal, or positive value if greater |
416 | * @throws IllegalArgumentException if the partial is null |
417 | * @throws IllegalArgumentException if the partial doesn't support this field |
418 | */ |
419 | public int compareTo(ReadablePartial partial) { |
420 | if (partial == null) { |
421 | throw new IllegalArgumentException("The partial must not be null"); |
422 | } |
423 | int thisValue = get(); |
424 | int otherValue = partial.get(getFieldType()); |
425 | if (thisValue < otherValue) { |
426 | return -1; |
427 | } else if (thisValue > otherValue) { |
428 | return 1; |
429 | } else { |
430 | return 0; |
431 | } |
432 | } |
433 | |
434 | //----------------------------------------------------------------------- |
435 | /** |
436 | * Compares this property to another. |
437 | * |
438 | * @param object the object to compare to |
439 | * @return true if equal |
440 | */ |
441 | public boolean equals(Object object) { |
442 | if (this == object) { |
443 | return true; |
444 | } |
445 | if (object instanceof AbstractReadableInstantFieldProperty == false) { |
446 | return false; |
447 | } |
448 | AbstractReadableInstantFieldProperty other = (AbstractReadableInstantFieldProperty) object; |
449 | return |
450 | get() == other.get() && |
451 | getFieldType().equals(other.getFieldType()) && |
452 | FieldUtils.equals(getChronology(), other.getChronology()); |
453 | } |
454 | |
455 | /** |
456 | * Returns a hashcode compatible with the equals method. |
457 | * |
458 | * @return the hashcode |
459 | */ |
460 | public int hashCode() { |
461 | return get() * 17 + getFieldType().hashCode() + getChronology().hashCode(); |
462 | } |
463 | |
464 | //----------------------------------------------------------------------- |
465 | /** |
466 | * Output a debugging string. |
467 | * |
468 | * @return debugging string |
469 | */ |
470 | public String toString() { |
471 | return "Property[" + getName() + "]"; |
472 | } |
473 | |
474 | } |