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; |
17 | |
18 | import java.io.Serializable; |
19 | import java.util.ArrayList; |
20 | import java.util.Arrays; |
21 | import java.util.List; |
22 | import java.util.Locale; |
23 | |
24 | import org.joda.time.base.AbstractPartial; |
25 | import org.joda.time.field.AbstractPartialFieldProperty; |
26 | import org.joda.time.field.FieldUtils; |
27 | import org.joda.time.format.DateTimeFormat; |
28 | import org.joda.time.format.DateTimeFormatter; |
29 | import org.joda.time.format.ISODateTimeFormat; |
30 | |
31 | /** |
32 | * Partial is an immutable partial datetime supporting any set of datetime fields. |
33 | * <p> |
34 | * A Partial instance can be used to hold any combination of fields. |
35 | * The instance does not contain a time zone, so any datetime is local. |
36 | * <p> |
37 | * A Partial can be matched against an instant using {@link #isMatch(ReadableInstant)}. |
38 | * This method compares each field on this partial with those of the instant |
39 | * and determines if the partial matches the instant. |
40 | * Given this definition, an empty Partial instance represents any datetime |
41 | * and always matches. |
42 | * <p> |
43 | * Calculations on Partial are performed using a {@link Chronology}. |
44 | * This chronology is set to be in the UTC time zone for all calculations. |
45 | * <p> |
46 | * Each individual field can be queried in two ways: |
47 | * <ul> |
48 | * <li><code>get(DateTimeFieldType.monthOfYear())</code> |
49 | * <li><code>property(DateTimeFieldType.monthOfYear()).get()</code> |
50 | * </ul> |
51 | * The second technique also provides access to other useful methods on the |
52 | * field: |
53 | * <ul> |
54 | * <li>numeric value - <code>monthOfYear().get()</code> |
55 | * <li>text value - <code>monthOfYear().getAsText()</code> |
56 | * <li>short text value - <code>monthOfYear().getAsShortText()</code> |
57 | * <li>maximum/minimum values - <code>monthOfYear().getMaximumValue()</code> |
58 | * <li>add/subtract - <code>monthOfYear().addToCopy()</code> |
59 | * <li>set - <code>monthOfYear().setCopy()</code> |
60 | * </ul> |
61 | * <p> |
62 | * Partial is thread-safe and immutable, provided that the Chronology is as well. |
63 | * All standard Chronology classes supplied are thread-safe and immutable. |
64 | * |
65 | * @author Stephen Colebourne |
66 | * @since 1.1 |
67 | */ |
68 | public final class Partial |
69 | extends AbstractPartial |
70 | implements ReadablePartial, Serializable { |
71 | |
72 | /** Serialization version */ |
73 | private static final long serialVersionUID = 12324121189002L; |
74 | |
75 | /** The chronology in use. */ |
76 | private final Chronology iChronology; |
77 | /** The set of field types. */ |
78 | private final DateTimeFieldType[] iTypes; |
79 | /** The values of each field in this partial. */ |
80 | private final int[] iValues; |
81 | /** The formatter to use, [0] may miss some fields, [1] doesn't miss any fields. */ |
82 | private transient DateTimeFormatter[] iFormatter; |
83 | |
84 | // Constructors |
85 | //----------------------------------------------------------------------- |
86 | /** |
87 | * Constructs a Partial with no fields or values, which can be considered |
88 | * to represent any date. |
89 | * <p> |
90 | * This is most useful when constructing partials, for example: |
91 | * <pre> |
92 | * Partial p = new Partial() |
93 | * .with(DateTimeFieldType.dayOfWeek(), 5) |
94 | * .with(DateTimeFieldType.hourOfDay(), 12) |
95 | * .with(DateTimeFieldType.minuteOfHour(), 20); |
96 | * </pre> |
97 | * Note that, although this is a clean way to write code, it is fairly |
98 | * inefficient internally. |
99 | * <p> |
100 | * The constructor uses the default ISO chronology. |
101 | */ |
102 | public Partial() { |
103 | this((Chronology) null); |
104 | } |
105 | |
106 | /** |
107 | * Constructs a Partial with no fields or values, which can be considered |
108 | * to represent any date. |
109 | * <p> |
110 | * This is most useful when constructing partials, for example: |
111 | * <pre> |
112 | * Partial p = new Partial(chrono) |
113 | * .with(DateTimeFieldType.dayOfWeek(), 5) |
114 | * .with(DateTimeFieldType.hourOfDay(), 12) |
115 | * .with(DateTimeFieldType.minuteOfHour(), 20); |
116 | * </pre> |
117 | * Note that, although this is a clean way to write code, it is fairly |
118 | * inefficient internally. |
119 | * |
120 | * @param chrono the chronology, null means ISO |
121 | */ |
122 | public Partial(Chronology chrono) { |
123 | super(); |
124 | iChronology = DateTimeUtils.getChronology(chrono).withUTC(); |
125 | iTypes = new DateTimeFieldType[0]; |
126 | iValues = new int[0]; |
127 | } |
128 | |
129 | /** |
130 | * Constructs a Partial with the specified field and value. |
131 | * <p> |
132 | * The constructor uses the default ISO chronology. |
133 | * |
134 | * @param type the single type to create the partial from, not null |
135 | * @param value the value to store |
136 | * @throws IllegalArgumentException if the type or value is invalid |
137 | */ |
138 | public Partial(DateTimeFieldType type, int value) { |
139 | this(type, value, null); |
140 | } |
141 | |
142 | /** |
143 | * Constructs a Partial with the specified field and value. |
144 | * <p> |
145 | * The constructor uses the specified chronology. |
146 | * |
147 | * @param type the single type to create the partial from, not null |
148 | * @param value the value to store |
149 | * @param chronology the chronology, null means ISO |
150 | * @throws IllegalArgumentException if the type or value is invalid |
151 | */ |
152 | public Partial(DateTimeFieldType type, int value, Chronology chronology) { |
153 | super(); |
154 | chronology = DateTimeUtils.getChronology(chronology).withUTC(); |
155 | iChronology = chronology; |
156 | if (type == null) { |
157 | throw new IllegalArgumentException("The field type must not be null"); |
158 | } |
159 | iTypes = new DateTimeFieldType[] {type}; |
160 | iValues = new int[] {value}; |
161 | chronology.validate(this, iValues); |
162 | } |
163 | |
164 | /** |
165 | * Constructs a Partial with the specified fields and values. |
166 | * The fields must be specified in the order largest to smallest. |
167 | * <p> |
168 | * The constructor uses the specified chronology. |
169 | * |
170 | * @param types the types to create the partial from, not null |
171 | * @param values the values to store, not null |
172 | * @throws IllegalArgumentException if the types or values are invalid |
173 | */ |
174 | public Partial(DateTimeFieldType[] types, int[] values) { |
175 | this(types, values, null); |
176 | } |
177 | |
178 | /** |
179 | * Constructs a Partial with the specified fields and values. |
180 | * The fields must be specified in the order largest to smallest. |
181 | * <p> |
182 | * The constructor uses the specified chronology. |
183 | * |
184 | * @param types the types to create the partial from, not null |
185 | * @param values the values to store, not null |
186 | * @param chronology the chronology, null means ISO |
187 | * @throws IllegalArgumentException if the types or values are invalid |
188 | */ |
189 | public Partial(DateTimeFieldType[] types, int[] values, Chronology chronology) { |
190 | super(); |
191 | chronology = DateTimeUtils.getChronology(chronology).withUTC(); |
192 | iChronology = chronology; |
193 | if (types == null) { |
194 | throw new IllegalArgumentException("Types array must not be null"); |
195 | } |
196 | if (values == null) { |
197 | throw new IllegalArgumentException("Values array must not be null"); |
198 | } |
199 | if (values.length != types.length) { |
200 | throw new IllegalArgumentException("Values array must be the same length as the types array"); |
201 | } |
202 | if (types.length == 0) { |
203 | iTypes = types; |
204 | iValues = values; |
205 | return; |
206 | } |
207 | for (int i = 0; i < types.length; i++) { |
208 | if (types[i] == null) { |
209 | throw new IllegalArgumentException("Types array must not contain null: index " + i); |
210 | } |
211 | } |
212 | DurationField lastUnitField = null; |
213 | for (int i = 0; i < types.length; i++) { |
214 | DateTimeFieldType loopType = types[i]; |
215 | DurationField loopUnitField = loopType.getDurationType().getField(iChronology); |
216 | if (i > 0) { |
217 | int compare = lastUnitField.compareTo(loopUnitField); |
218 | if (compare < 0 || (compare != 0 && loopUnitField.isSupported() == false)) { |
219 | throw new IllegalArgumentException("Types array must be in order largest-smallest: " + |
220 | types[i - 1].getName() + " < " + loopType.getName()); |
221 | } else if (compare == 0) { |
222 | if (types[i - 1].getRangeDurationType() == null) { |
223 | if (loopType.getRangeDurationType() == null) { |
224 | throw new IllegalArgumentException("Types array must not contain duplicate: " + loopType.getName()); |
225 | } |
226 | } else { |
227 | if (loopType.getRangeDurationType() == null) { |
228 | throw new IllegalArgumentException("Types array must be in order largest-smallest: " + |
229 | types[i - 1].getName() + " < " + loopType.getName()); |
230 | } |
231 | DurationField lastRangeField = types[i - 1].getRangeDurationType().getField(iChronology); |
232 | DurationField loopRangeField = loopType.getRangeDurationType().getField(iChronology); |
233 | if (lastRangeField.compareTo(loopRangeField) < 0) { |
234 | throw new IllegalArgumentException("Types array must be in order largest-smallest: " + |
235 | types[i - 1].getName() + " < " + loopType.getName()); |
236 | } |
237 | if (lastRangeField.compareTo(loopRangeField) == 0) { |
238 | throw new IllegalArgumentException("Types array must not contain duplicate: " + loopType.getName()); |
239 | } |
240 | } |
241 | } |
242 | } |
243 | lastUnitField = loopUnitField; |
244 | } |
245 | |
246 | iTypes = (DateTimeFieldType[]) types.clone(); |
247 | chronology.validate(this, values); |
248 | iValues = (int[]) values.clone(); |
249 | } |
250 | |
251 | /** |
252 | * Constructs a Partial by copying all the fields and types from |
253 | * another partial. |
254 | * <p> |
255 | * This is most useful when copying from a YearMonthDay or TimeOfDay. |
256 | */ |
257 | public Partial(ReadablePartial partial) { |
258 | super(); |
259 | if (partial == null) { |
260 | throw new IllegalArgumentException("The partial must not be null"); |
261 | } |
262 | iChronology = DateTimeUtils.getChronology(partial.getChronology()).withUTC(); |
263 | iTypes = new DateTimeFieldType[partial.size()]; |
264 | iValues = new int[partial.size()]; |
265 | for (int i = 0; i < partial.size(); i++) { |
266 | iTypes[i] = partial.getFieldType(i); |
267 | iValues[i] = partial.getValue(i); |
268 | } |
269 | } |
270 | |
271 | /** |
272 | * Constructs a Partial with the specified values. |
273 | * This constructor assigns and performs no validation. |
274 | * |
275 | * @param partial the partial to copy |
276 | * @param values the values to store |
277 | * @throws IllegalArgumentException if the types or values are invalid |
278 | */ |
279 | Partial(Partial partial, int[] values) { |
280 | super(); |
281 | iChronology = partial.iChronology; |
282 | iTypes = partial.iTypes; |
283 | iValues = values; |
284 | } |
285 | |
286 | /** |
287 | * Constructs a Partial with the specified chronology, fields and values. |
288 | * This constructor assigns and performs no validation. |
289 | * |
290 | * @param chronology the chronology |
291 | * @param types the types to create the partial from |
292 | * @param values the values to store |
293 | * @throws IllegalArgumentException if the types or values are invalid |
294 | */ |
295 | Partial(Chronology chronology, DateTimeFieldType[] types, int[] values) { |
296 | super(); |
297 | iChronology = chronology; |
298 | iTypes = types; |
299 | iValues = values; |
300 | } |
301 | |
302 | //----------------------------------------------------------------------- |
303 | /** |
304 | * Gets the number of fields in this partial. |
305 | * |
306 | * @return the field count |
307 | */ |
308 | public int size() { |
309 | return iTypes.length; |
310 | } |
311 | |
312 | /** |
313 | * Gets the chronology of the partial which is never null. |
314 | * <p> |
315 | * The {@link Chronology} is the calculation engine behind the partial and |
316 | * provides conversion and validation of the fields in a particular calendar system. |
317 | * |
318 | * @return the chronology, never null |
319 | */ |
320 | public Chronology getChronology() { |
321 | return iChronology; |
322 | } |
323 | |
324 | /** |
325 | * Gets the field for a specific index in the chronology specified. |
326 | * |
327 | * @param index the index to retrieve |
328 | * @param chrono the chronology to use |
329 | * @return the field |
330 | * @throws IndexOutOfBoundsException if the index is invalid |
331 | */ |
332 | protected DateTimeField getField(int index, Chronology chrono) { |
333 | return iTypes[index].getField(chrono); |
334 | } |
335 | |
336 | /** |
337 | * Gets the field type at the specified index. |
338 | * |
339 | * @param index the index to retrieve |
340 | * @return the field at the specified index |
341 | * @throws IndexOutOfBoundsException if the index is invalid |
342 | */ |
343 | public DateTimeFieldType getFieldType(int index) { |
344 | return iTypes[index]; |
345 | } |
346 | |
347 | /** |
348 | * Gets an array of the field type of each of the fields that |
349 | * this partial supports. |
350 | * <p> |
351 | * The fields are returned largest to smallest. |
352 | * |
353 | * @return the array of field types (cloned), largest to smallest |
354 | */ |
355 | public DateTimeFieldType[] getFieldTypes() { |
356 | return (DateTimeFieldType[]) iTypes.clone(); |
357 | } |
358 | |
359 | //----------------------------------------------------------------------- |
360 | /** |
361 | * Gets the value of the field at the specifed index. |
362 | * |
363 | * @param index the index |
364 | * @return the value |
365 | * @throws IndexOutOfBoundsException if the index is invalid |
366 | */ |
367 | public int getValue(int index) { |
368 | return iValues[index]; |
369 | } |
370 | |
371 | /** |
372 | * Gets an array of the value of each of the fields that |
373 | * this partial supports. |
374 | * <p> |
375 | * The fields are returned largest to smallest. |
376 | * Each value corresponds to the same array index as <code>getFieldTypes()</code> |
377 | * |
378 | * @return the current values of each field (cloned), largest to smallest |
379 | */ |
380 | public int[] getValues() { |
381 | return (int[]) iValues.clone(); |
382 | } |
383 | |
384 | //----------------------------------------------------------------------- |
385 | /** |
386 | * Creates a new Partial instance with the specified chronology. |
387 | * This instance is immutable and unaffected by this method call. |
388 | * <p> |
389 | * This method retains the values of the fields, thus the result will |
390 | * typically refer to a different instant. |
391 | * <p> |
392 | * The time zone of the specified chronology is ignored, as Partial |
393 | * operates without a time zone. |
394 | * |
395 | * @param newChronology the new chronology, null means ISO |
396 | * @return a copy of this datetime with a different chronology |
397 | * @throws IllegalArgumentException if the values are invalid for the new chronology |
398 | */ |
399 | public Partial withChronologyRetainFields(Chronology newChronology) { |
400 | newChronology = DateTimeUtils.getChronology(newChronology); |
401 | newChronology = newChronology.withUTC(); |
402 | if (newChronology == getChronology()) { |
403 | return this; |
404 | } else { |
405 | Partial newPartial = new Partial(newChronology, iTypes, iValues); |
406 | newChronology.validate(newPartial, iValues); |
407 | return newPartial; |
408 | } |
409 | } |
410 | |
411 | //----------------------------------------------------------------------- |
412 | /** |
413 | * Gets a copy of this date with the specified field set to a new value. |
414 | * <p> |
415 | * If this partial did not previously support the field, the new one will. |
416 | * Contrast this behaviour with {@link #withField(DateTimeFieldType, int)}. |
417 | * <p> |
418 | * For example, if the field type is <code>dayOfMonth</code> then the day |
419 | * would be changed/added in the returned instance. |
420 | * |
421 | * @param fieldType the field type to set, not null |
422 | * @param value the value to set |
423 | * @return a copy of this instance with the field set |
424 | * @throws IllegalArgumentException if the value is null or invalid |
425 | */ |
426 | public Partial with(DateTimeFieldType fieldType, int value) { |
427 | if (fieldType == null) { |
428 | throw new IllegalArgumentException("The field type must not be null"); |
429 | } |
430 | int index = indexOf(fieldType); |
431 | if (index == -1) { |
432 | DateTimeFieldType[] newTypes = new DateTimeFieldType[iTypes.length + 1]; |
433 | int[] newValues = new int[newTypes.length]; |
434 | |
435 | // find correct insertion point to keep largest-smallest order |
436 | int i = 0; |
437 | DurationField unitField = fieldType.getDurationType().getField(iChronology); |
438 | if (unitField.isSupported()) { |
439 | for (; i < iTypes.length; i++) { |
440 | DateTimeFieldType loopType = iTypes[i]; |
441 | DurationField loopUnitField = loopType.getDurationType().getField(iChronology); |
442 | if (loopUnitField.isSupported()) { |
443 | int compare = unitField.compareTo(loopUnitField); |
444 | if (compare > 0) { |
445 | break; |
446 | } else if (compare == 0) { |
447 | DurationField rangeField = fieldType.getRangeDurationType().getField(iChronology); |
448 | DurationField loopRangeField = loopType.getRangeDurationType().getField(iChronology); |
449 | if (rangeField.compareTo(loopRangeField) > 0) { |
450 | break; |
451 | } |
452 | } |
453 | } |
454 | } |
455 | } |
456 | System.arraycopy(iTypes, 0, newTypes, 0, i); |
457 | System.arraycopy(iValues, 0, newValues, 0, i); |
458 | newTypes[i] = fieldType; |
459 | newValues[i] = value; |
460 | System.arraycopy(iTypes, i, newTypes, i + 1, newTypes.length - i - 1); |
461 | System.arraycopy(iValues, i, newValues, i + 1, newValues.length - i - 1); |
462 | |
463 | Partial newPartial = new Partial(iChronology, newTypes, newValues); |
464 | iChronology.validate(newPartial, newValues); |
465 | return newPartial; |
466 | } |
467 | if (value == getValue(index)) { |
468 | return this; |
469 | } |
470 | int[] newValues = getValues(); |
471 | newValues = getField(index).set(this, index, newValues, value); |
472 | return new Partial(this, newValues); |
473 | } |
474 | |
475 | /** |
476 | * Gets a copy of this date with the specified field removed. |
477 | * <p> |
478 | * If this partial did not previously support the field, no error occurs. |
479 | * |
480 | * @param fieldType the field type to remove, may be null |
481 | * @return a copy of this instance with the field removed |
482 | */ |
483 | public Partial without(DateTimeFieldType fieldType) { |
484 | int index = indexOf(fieldType); |
485 | if (index != -1) { |
486 | DateTimeFieldType[] newTypes = new DateTimeFieldType[size() - 1]; |
487 | int[] newValues = new int[size() - 1]; |
488 | System.arraycopy(iTypes, 0, newTypes, 0, index); |
489 | System.arraycopy(iTypes, index + 1, newTypes, index, newTypes.length - index); |
490 | System.arraycopy(iValues, 0, newValues, 0, index); |
491 | System.arraycopy(iValues, index + 1, newValues, index, newValues.length - index); |
492 | Partial newPartial = new Partial(iChronology, newTypes, newValues); |
493 | iChronology.validate(newPartial, newValues); |
494 | return newPartial; |
495 | } |
496 | return this; |
497 | } |
498 | |
499 | //----------------------------------------------------------------------- |
500 | /** |
501 | * Gets a copy of this Partial with the specified field set to a new value. |
502 | * <p> |
503 | * If this partial does not support the field, an exception is thrown. |
504 | * Contrast this behaviour with {@link #with(DateTimeFieldType, int)}. |
505 | * <p> |
506 | * For example, if the field type is <code>dayOfMonth</code> then the day |
507 | * would be changed in the returned instance if supported. |
508 | * |
509 | * @param fieldType the field type to set, not null |
510 | * @param value the value to set |
511 | * @return a copy of this instance with the field set |
512 | * @throws IllegalArgumentException if the value is null or invalid |
513 | */ |
514 | public Partial withField(DateTimeFieldType fieldType, int value) { |
515 | int index = indexOfSupported(fieldType); |
516 | if (value == getValue(index)) { |
517 | return this; |
518 | } |
519 | int[] newValues = getValues(); |
520 | newValues = getField(index).set(this, index, newValues, value); |
521 | return new Partial(this, newValues); |
522 | } |
523 | |
524 | /** |
525 | * Gets a copy of this Partial with the value of the specified field increased. |
526 | * If this partial does not support the field, an exception is thrown. |
527 | * <p> |
528 | * If the addition is zero, then <code>this</code> is returned. |
529 | * The addition will overflow into larger fields (eg. minute to hour). |
530 | * However, it will not wrap around if the top maximum is reached. |
531 | * |
532 | * @param fieldType the field type to add to, not null |
533 | * @param amount the amount to add |
534 | * @return a copy of this instance with the field updated |
535 | * @throws IllegalArgumentException if the value is null or invalid |
536 | * @throws ArithmeticException if the new datetime exceeds the capacity |
537 | */ |
538 | public Partial withFieldAdded(DurationFieldType fieldType, int amount) { |
539 | int index = indexOfSupported(fieldType); |
540 | if (amount == 0) { |
541 | return this; |
542 | } |
543 | int[] newValues = getValues(); |
544 | newValues = getField(index).add(this, index, newValues, amount); |
545 | return new Partial(this, newValues); |
546 | } |
547 | |
548 | /** |
549 | * Gets a copy of this Partial with the value of the specified field increased. |
550 | * If this partial does not support the field, an exception is thrown. |
551 | * <p> |
552 | * If the addition is zero, then <code>this</code> is returned. |
553 | * The addition will overflow into larger fields (eg. minute to hour). |
554 | * If the maximum is reached, the addition will wra. |
555 | * |
556 | * @param fieldType the field type to add to, not null |
557 | * @param amount the amount to add |
558 | * @return a copy of this instance with the field updated |
559 | * @throws IllegalArgumentException if the value is null or invalid |
560 | * @throws ArithmeticException if the new datetime exceeds the capacity |
561 | */ |
562 | public Partial withFieldAddWrapped(DurationFieldType fieldType, int amount) { |
563 | int index = indexOfSupported(fieldType); |
564 | if (amount == 0) { |
565 | return this; |
566 | } |
567 | int[] newValues = getValues(); |
568 | newValues = getField(index).addWrapPartial(this, index, newValues, amount); |
569 | return new Partial(this, newValues); |
570 | } |
571 | |
572 | /** |
573 | * Gets a copy of this Partial with the specified period added. |
574 | * <p> |
575 | * If the addition is zero, then <code>this</code> is returned. |
576 | * Fields in the period that aren't present in the partial are ignored. |
577 | * <p> |
578 | * This method is typically used to add multiple copies of complex |
579 | * period instances. Adding one field is best achieved using the method |
580 | * {@link #withFieldAdded(DurationFieldType, int)}. |
581 | * |
582 | * @param period the period to add to this one, null means zero |
583 | * @param scalar the amount of times to add, such as -1 to subtract once |
584 | * @return a copy of this instance with the period added |
585 | * @throws ArithmeticException if the new datetime exceeds the capacity |
586 | */ |
587 | public Partial withPeriodAdded(ReadablePeriod period, int scalar) { |
588 | if (period == null || scalar == 0) { |
589 | return this; |
590 | } |
591 | int[] newValues = getValues(); |
592 | for (int i = 0; i < period.size(); i++) { |
593 | DurationFieldType fieldType = period.getFieldType(i); |
594 | int index = indexOf(fieldType); |
595 | if (index >= 0) { |
596 | newValues = getField(index).add(this, index, newValues, |
597 | FieldUtils.safeMultiply(period.getValue(i), scalar)); |
598 | } |
599 | } |
600 | return new Partial(this, newValues); |
601 | } |
602 | |
603 | /** |
604 | * Gets a copy of this instance with the specified period added. |
605 | * <p> |
606 | * If the amount is zero or null, then <code>this</code> is returned. |
607 | * |
608 | * @param period the duration to add to this one, null means zero |
609 | * @return a copy of this instance with the period added |
610 | * @throws ArithmeticException if the new datetime exceeds the capacity of a long |
611 | */ |
612 | public Partial plus(ReadablePeriod period) { |
613 | return withPeriodAdded(period, 1); |
614 | } |
615 | |
616 | /** |
617 | * Gets a copy of this instance with the specified period take away. |
618 | * <p> |
619 | * If the amount is zero or null, then <code>this</code> is returned. |
620 | * |
621 | * @param period the period to reduce this instant by |
622 | * @return a copy of this instance with the period taken away |
623 | * @throws ArithmeticException if the new datetime exceeds the capacity of a long |
624 | */ |
625 | public Partial minus(ReadablePeriod period) { |
626 | return withPeriodAdded(period, -1); |
627 | } |
628 | |
629 | //----------------------------------------------------------------------- |
630 | /** |
631 | * Gets the property object for the specified type, which contains |
632 | * many useful methods for getting and manipulating the partial. |
633 | * <p> |
634 | * See also {@link ReadablePartial#get(DateTimeFieldType)}. |
635 | * |
636 | * @param type the field type to get the property for, not null |
637 | * @return the property object |
638 | * @throws IllegalArgumentException if the field is null or unsupported |
639 | */ |
640 | public Property property(DateTimeFieldType type) { |
641 | return new Property(this, indexOfSupported(type)); |
642 | } |
643 | |
644 | //----------------------------------------------------------------------- |
645 | /** |
646 | * Does this partial match the specified instant. |
647 | * <p> |
648 | * A match occurs when all the fields of this partial are the same as the |
649 | * corresponding fields on the specified instant. |
650 | * |
651 | * @param instant an instant to check against, null means now in default zone |
652 | * @return true if this partial matches the specified instant |
653 | */ |
654 | public boolean isMatch(ReadableInstant instant) { |
655 | long millis = DateTimeUtils.getInstantMillis(instant); |
656 | Chronology chrono = DateTimeUtils.getInstantChronology(instant); |
657 | for (int i = 0; i < iTypes.length; i++) { |
658 | int value = iTypes[i].getField(chrono).get(millis); |
659 | if (value != iValues[i]) { |
660 | return false; |
661 | } |
662 | } |
663 | return true; |
664 | } |
665 | |
666 | /** |
667 | * Does this partial match the specified partial. |
668 | * <p> |
669 | * A match occurs when all the fields of this partial are the same as the |
670 | * corresponding fields on the specified partial. |
671 | * |
672 | * @param partial a partial to check against, must not be null |
673 | * @return true if this partial matches the specified partial |
674 | * @throws IllegalArgumentException if the partial is null |
675 | * @throws IllegalArgumentException if the fields of the two partials do not match |
676 | * @since 1.5 |
677 | */ |
678 | public boolean isMatch(ReadablePartial partial) { |
679 | if (partial == null) { |
680 | throw new IllegalArgumentException("The partial must not be null"); |
681 | } |
682 | for (int i = 0; i < iTypes.length; i++) { |
683 | int value = partial.get(iTypes[i]); |
684 | if (value != iValues[i]) { |
685 | return false; |
686 | } |
687 | } |
688 | return true; |
689 | } |
690 | |
691 | //----------------------------------------------------------------------- |
692 | /** |
693 | * Gets a formatter suitable for the fields in this partial. |
694 | * <p> |
695 | * If there is no appropriate ISO format, null is returned. |
696 | * This method may return a formatter that does not display all the |
697 | * fields of the partial. This might occur when you have overlapping |
698 | * fields, such as dayOfWeek and dayOfMonth. |
699 | * |
700 | * @return a formatter suitable for the fields in this partial, null |
701 | * if none is suitable |
702 | */ |
703 | public DateTimeFormatter getFormatter() { |
704 | DateTimeFormatter[] f = iFormatter; |
705 | if (f == null) { |
706 | if (size() == 0) { |
707 | return null; |
708 | } |
709 | f = new DateTimeFormatter[2]; |
710 | try { |
711 | List list = new ArrayList(Arrays.asList(iTypes)); |
712 | f[0] = ISODateTimeFormat.forFields(list, true, false); |
713 | if (list.size() == 0) { |
714 | f[1] = f[0]; |
715 | } |
716 | } catch (IllegalArgumentException ex) { |
717 | // ignore |
718 | } |
719 | iFormatter = f; |
720 | } |
721 | return f[0]; |
722 | } |
723 | |
724 | //----------------------------------------------------------------------- |
725 | /** |
726 | * Output the date in an appropriate ISO8601 format. |
727 | * <p> |
728 | * This method will output the partial in one of two ways. |
729 | * If {@link #getFormatter()} |
730 | * <p> |
731 | * If there is no appropriate ISO format a dump of the fields is output |
732 | * via {@link #toStringList()}. |
733 | * |
734 | * @return ISO8601 formatted string |
735 | */ |
736 | public String toString() { |
737 | DateTimeFormatter[] f = iFormatter; |
738 | if (f == null) { |
739 | getFormatter(); |
740 | f = iFormatter; |
741 | if (f == null) { |
742 | return toStringList(); |
743 | } |
744 | } |
745 | DateTimeFormatter f1 = f[1]; |
746 | if (f1 == null) { |
747 | return toStringList(); |
748 | } |
749 | return f1.print(this); |
750 | } |
751 | |
752 | /** |
753 | * Gets a string version of the partial that lists all the fields. |
754 | * <p> |
755 | * This method exists to provide a better debugging toString than |
756 | * the standard toString. This method lists all the fields and their |
757 | * values in a style similar to the collections framework. |
758 | * |
759 | * @return a toString format that lists all the fields |
760 | */ |
761 | public String toStringList() { |
762 | int size = size(); |
763 | StringBuffer buf = new StringBuffer(20 * size); |
764 | buf.append('['); |
765 | for (int i = 0; i < size; i++) { |
766 | if (i > 0) { |
767 | buf.append(',').append(' '); |
768 | } |
769 | buf.append(iTypes[i].getName()); |
770 | buf.append('='); |
771 | buf.append(iValues[i]); |
772 | } |
773 | buf.append(']'); |
774 | return buf.toString(); |
775 | } |
776 | |
777 | /** |
778 | * Output the date using the specified format pattern. |
779 | * Unsupported fields will appear as special unicode characters. |
780 | * |
781 | * @param pattern the pattern specification, null means use <code>toString</code> |
782 | * @see org.joda.time.format.DateTimeFormat |
783 | */ |
784 | public String toString(String pattern) { |
785 | if (pattern == null) { |
786 | return toString(); |
787 | } |
788 | return DateTimeFormat.forPattern(pattern).print(this); |
789 | } |
790 | |
791 | /** |
792 | * Output the date using the specified format pattern. |
793 | * Unsupported fields will appear as special unicode characters. |
794 | * |
795 | * @param pattern the pattern specification, null means use <code>toString</code> |
796 | * @param locale Locale to use, null means default |
797 | * @see org.joda.time.format.DateTimeFormat |
798 | */ |
799 | public String toString(String pattern, Locale locale) { |
800 | if (pattern == null) { |
801 | return toString(); |
802 | } |
803 | return DateTimeFormat.forPattern(pattern).withLocale(locale).print(this); |
804 | } |
805 | |
806 | //----------------------------------------------------------------------- |
807 | /** |
808 | * The property class for <code>Partial</code>. |
809 | * <p> |
810 | * This class binds a <code>Partial</code> to a <code>DateTimeField</code>. |
811 | * |
812 | * @author Stephen Colebourne |
813 | * @since 1.1 |
814 | */ |
815 | public static class Property extends AbstractPartialFieldProperty implements Serializable { |
816 | |
817 | /** Serialization version */ |
818 | private static final long serialVersionUID = 53278362873888L; |
819 | |
820 | /** The partial */ |
821 | private final Partial iPartial; |
822 | /** The field index */ |
823 | private final int iFieldIndex; |
824 | |
825 | /** |
826 | * Constructs a property. |
827 | * |
828 | * @param partial the partial instance |
829 | * @param fieldIndex the index in the partial |
830 | */ |
831 | Property(Partial partial, int fieldIndex) { |
832 | super(); |
833 | iPartial = partial; |
834 | iFieldIndex = fieldIndex; |
835 | } |
836 | |
837 | /** |
838 | * Gets the field that this property uses. |
839 | * |
840 | * @return the field |
841 | */ |
842 | public DateTimeField getField() { |
843 | return iPartial.getField(iFieldIndex); |
844 | } |
845 | |
846 | /** |
847 | * Gets the partial that this property belongs to. |
848 | * |
849 | * @return the partial |
850 | */ |
851 | protected ReadablePartial getReadablePartial() { |
852 | return iPartial; |
853 | } |
854 | |
855 | /** |
856 | * Gets the partial that this property belongs to. |
857 | * |
858 | * @return the partial |
859 | */ |
860 | public Partial getPartial() { |
861 | return iPartial; |
862 | } |
863 | |
864 | /** |
865 | * Gets the value of this field. |
866 | * |
867 | * @return the field value |
868 | */ |
869 | public int get() { |
870 | return iPartial.getValue(iFieldIndex); |
871 | } |
872 | |
873 | //----------------------------------------------------------------------- |
874 | /** |
875 | * Adds to the value of this field in a copy of this Partial. |
876 | * <p> |
877 | * The value will be added to this field. If the value is too large to be |
878 | * added solely to this field then it will affect larger fields. |
879 | * Smaller fields are unaffected. |
880 | * <p> |
881 | * If the result would be too large, beyond the maximum year, then an |
882 | * IllegalArgumentException is thrown. |
883 | * <p> |
884 | * The Partial attached to this property is unchanged by this call. |
885 | * Instead, a new instance is returned. |
886 | * |
887 | * @param valueToAdd the value to add to the field in the copy |
888 | * @return a copy of the Partial with the field value changed |
889 | * @throws IllegalArgumentException if the value isn't valid |
890 | */ |
891 | public Partial addToCopy(int valueToAdd) { |
892 | int[] newValues = iPartial.getValues(); |
893 | newValues = getField().add(iPartial, iFieldIndex, newValues, valueToAdd); |
894 | return new Partial(iPartial, newValues); |
895 | } |
896 | |
897 | /** |
898 | * Adds to the value of this field in a copy of this Partial wrapping |
899 | * within this field if the maximum value is reached. |
900 | * <p> |
901 | * The value will be added to this field. If the value is too large to be |
902 | * added solely to this field then it wraps within this field. |
903 | * Other fields are unaffected. |
904 | * <p> |
905 | * For example, |
906 | * <code>2004-12-20</code> addWrapField one month returns <code>2004-01-20</code>. |
907 | * <p> |
908 | * The Partial attached to this property is unchanged by this call. |
909 | * Instead, a new instance is returned. |
910 | * |
911 | * @param valueToAdd the value to add to the field in the copy |
912 | * @return a copy of the Partial with the field value changed |
913 | * @throws IllegalArgumentException if the value isn't valid |
914 | */ |
915 | public Partial addWrapFieldToCopy(int valueToAdd) { |
916 | int[] newValues = iPartial.getValues(); |
917 | newValues = getField().addWrapField(iPartial, iFieldIndex, newValues, valueToAdd); |
918 | return new Partial(iPartial, newValues); |
919 | } |
920 | |
921 | //----------------------------------------------------------------------- |
922 | /** |
923 | * Sets this field in a copy of the Partial. |
924 | * <p> |
925 | * The Partial attached to this property is unchanged by this call. |
926 | * Instead, a new instance is returned. |
927 | * |
928 | * @param value the value to set the field in the copy to |
929 | * @return a copy of the Partial with the field value changed |
930 | * @throws IllegalArgumentException if the value isn't valid |
931 | */ |
932 | public Partial setCopy(int value) { |
933 | int[] newValues = iPartial.getValues(); |
934 | newValues = getField().set(iPartial, iFieldIndex, newValues, value); |
935 | return new Partial(iPartial, newValues); |
936 | } |
937 | |
938 | /** |
939 | * Sets this field in a copy of the Partial to a parsed text value. |
940 | * <p> |
941 | * The Partial attached to this property is unchanged by this call. |
942 | * Instead, a new instance is returned. |
943 | * |
944 | * @param text the text value to set |
945 | * @param locale optional locale to use for selecting a text symbol |
946 | * @return a copy of the Partial with the field value changed |
947 | * @throws IllegalArgumentException if the text value isn't valid |
948 | */ |
949 | public Partial setCopy(String text, Locale locale) { |
950 | int[] newValues = iPartial.getValues(); |
951 | newValues = getField().set(iPartial, iFieldIndex, newValues, text, locale); |
952 | return new Partial(iPartial, newValues); |
953 | } |
954 | |
955 | /** |
956 | * Sets this field in a copy of the Partial to a parsed text value. |
957 | * <p> |
958 | * The Partial attached to this property is unchanged by this call. |
959 | * Instead, a new instance is returned. |
960 | * |
961 | * @param text the text value to set |
962 | * @return a copy of the Partial with the field value changed |
963 | * @throws IllegalArgumentException if the text value isn't valid |
964 | */ |
965 | public Partial setCopy(String text) { |
966 | return setCopy(text, null); |
967 | } |
968 | |
969 | //----------------------------------------------------------------------- |
970 | /** |
971 | * Returns a new Partial with this field set to the maximum value |
972 | * for this field. |
973 | * <p> |
974 | * The Partial attached to this property is unchanged by this call. |
975 | * |
976 | * @return a copy of the Partial with this field set to its maximum |
977 | * @since 1.2 |
978 | */ |
979 | public Partial withMaximumValue() { |
980 | return setCopy(getMaximumValue()); |
981 | } |
982 | |
983 | /** |
984 | * Returns a new Partial with this field set to the minimum value |
985 | * for this field. |
986 | * <p> |
987 | * The Partial attached to this property is unchanged by this call. |
988 | * |
989 | * @return a copy of the Partial with this field set to its minimum |
990 | * @since 1.2 |
991 | */ |
992 | public Partial withMinimumValue() { |
993 | return setCopy(getMinimumValue()); |
994 | } |
995 | } |
996 | |
997 | } |