0001: /*
0002: * Copyright 2001-2006 Stephen Colebourne
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.joda.time;
0017:
0018: import java.io.Serializable;
0019: import java.util.ArrayList;
0020: import java.util.Arrays;
0021: import java.util.List;
0022: import java.util.Locale;
0023:
0024: import org.joda.time.base.AbstractPartial;
0025: import org.joda.time.field.AbstractPartialFieldProperty;
0026: import org.joda.time.field.FieldUtils;
0027: import org.joda.time.format.DateTimeFormat;
0028: import org.joda.time.format.DateTimeFormatter;
0029: import org.joda.time.format.ISODateTimeFormat;
0030:
0031: /**
0032: * Partial is an immutable partial datetime supporting any set of datetime fields.
0033: * <p>
0034: * A Partial instance can be used to hold any combination of fields.
0035: * The instance does not contain a time zone, so any datetime is local.
0036: * <p>
0037: * A Partial can be matched against an instant using {@link #isMatch(ReadableInstant)}.
0038: * This method compares each field on this partial with those of the instant
0039: * and determines if the partial matches the instant.
0040: * Given this definition, an empty Partial instance represents any datetime
0041: * and always matches.
0042: * <p>
0043: * Calculations on Partial are performed using a {@link Chronology}.
0044: * This chronology is set to be in the UTC time zone for all calculations.
0045: * <p>
0046: * Each individual field can be queried in two ways:
0047: * <ul>
0048: * <li><code>get(DateTimeFieldType.monthOfYear())</code>
0049: * <li><code>property(DateTimeFieldType.monthOfYear()).get()</code>
0050: * </ul>
0051: * The second technique also provides access to other useful methods on the
0052: * field:
0053: * <ul>
0054: * <li>numeric value - <code>monthOfYear().get()</code>
0055: * <li>text value - <code>monthOfYear().getAsText()</code>
0056: * <li>short text value - <code>monthOfYear().getAsShortText()</code>
0057: * <li>maximum/minimum values - <code>monthOfYear().getMaximumValue()</code>
0058: * <li>add/subtract - <code>monthOfYear().addToCopy()</code>
0059: * <li>set - <code>monthOfYear().setCopy()</code>
0060: * </ul>
0061: * <p>
0062: * Partial is thread-safe and immutable, provided that the Chronology is as well.
0063: * All standard Chronology classes supplied are thread-safe and immutable.
0064: *
0065: * @author Stephen Colebourne
0066: * @since 1.1
0067: */
0068: public final class Partial extends AbstractPartial implements
0069: ReadablePartial, Serializable {
0070:
0071: /** Serialization version */
0072: private static final long serialVersionUID = 12324121189002L;
0073:
0074: /** The chronology in use. */
0075: private final Chronology iChronology;
0076: /** The set of field types. */
0077: private final DateTimeFieldType[] iTypes;
0078: /** The values of each field in this partial. */
0079: private final int[] iValues;
0080: /** The formatter to use, [0] may miss some fields, [1] doesn't miss any fields. */
0081: private transient DateTimeFormatter[] iFormatter;
0082:
0083: // Constructors
0084: //-----------------------------------------------------------------------
0085: /**
0086: * Constructs a Partial with no fields or values, which can be considered
0087: * to represent any date.
0088: * <p>
0089: * This is most useful when constructing partials, for example:
0090: * <pre>
0091: * Partial p = new Partial()
0092: * .with(DateTimeFieldType.dayOfWeek(), 5)
0093: * .with(DateTimeFieldType.hourOfDay(), 12)
0094: * .with(DateTimeFieldType.minuteOfHour(), 20);
0095: * </pre>
0096: * Note that, although this is a clean way to write code, it is fairly
0097: * inefficient internally.
0098: * <p>
0099: * The constructor uses the default ISO chronology.
0100: */
0101: public Partial() {
0102: this ((Chronology) null);
0103: }
0104:
0105: /**
0106: * Constructs a Partial with no fields or values, which can be considered
0107: * to represent any date.
0108: * <p>
0109: * This is most useful when constructing partials, for example:
0110: * <pre>
0111: * Partial p = new Partial(chrono)
0112: * .with(DateTimeFieldType.dayOfWeek(), 5)
0113: * .with(DateTimeFieldType.hourOfDay(), 12)
0114: * .with(DateTimeFieldType.minuteOfHour(), 20);
0115: * </pre>
0116: * Note that, although this is a clean way to write code, it is fairly
0117: * inefficient internally.
0118: *
0119: * @param chrono the chronology, null means ISO
0120: */
0121: public Partial(Chronology chrono) {
0122: super ();
0123: iChronology = DateTimeUtils.getChronology(chrono).withUTC();
0124: iTypes = new DateTimeFieldType[0];
0125: iValues = new int[0];
0126: }
0127:
0128: /**
0129: * Constructs a Partial with the specified field and value.
0130: * <p>
0131: * The constructor uses the default ISO chronology.
0132: *
0133: * @param type the single type to create the partial from, not null
0134: * @param value the value to store
0135: * @throws IllegalArgumentException if the type or value is invalid
0136: */
0137: public Partial(DateTimeFieldType type, int value) {
0138: this (type, value, null);
0139: }
0140:
0141: /**
0142: * Constructs a Partial with the specified field and value.
0143: * <p>
0144: * The constructor uses the specified chronology.
0145: *
0146: * @param type the single type to create the partial from, not null
0147: * @param value the value to store
0148: * @param chronology the chronology, null means ISO
0149: * @throws IllegalArgumentException if the type or value is invalid
0150: */
0151: public Partial(DateTimeFieldType type, int value,
0152: Chronology chronology) {
0153: super ();
0154: chronology = DateTimeUtils.getChronology(chronology).withUTC();
0155: iChronology = chronology;
0156: if (type == null) {
0157: throw new IllegalArgumentException(
0158: "The field type must not be null");
0159: }
0160: iTypes = new DateTimeFieldType[] { type };
0161: iValues = new int[] { value };
0162: chronology.validate(this , iValues);
0163: }
0164:
0165: /**
0166: * Constructs a Partial with the specified fields and values.
0167: * The fields must be specified in the order largest to smallest.
0168: * <p>
0169: * The constructor uses the specified chronology.
0170: *
0171: * @param types the types to create the partial from, not null
0172: * @param values the values to store, not null
0173: * @throws IllegalArgumentException if the types or values are invalid
0174: */
0175: public Partial(DateTimeFieldType[] types, int[] values) {
0176: this (types, values, null);
0177: }
0178:
0179: /**
0180: * Constructs a Partial with the specified fields and values.
0181: * The fields must be specified in the order largest to smallest.
0182: * <p>
0183: * The constructor uses the specified chronology.
0184: *
0185: * @param types the types to create the partial from, not null
0186: * @param values the values to store, not null
0187: * @param chronology the chronology, null means ISO
0188: * @throws IllegalArgumentException if the types or values are invalid
0189: */
0190: public Partial(DateTimeFieldType[] types, int[] values,
0191: Chronology chronology) {
0192: super ();
0193: chronology = DateTimeUtils.getChronology(chronology).withUTC();
0194: iChronology = chronology;
0195: if (types == null) {
0196: throw new IllegalArgumentException(
0197: "Types array must not be null");
0198: }
0199: if (values == null) {
0200: throw new IllegalArgumentException(
0201: "Values array must not be null");
0202: }
0203: if (values.length != types.length) {
0204: throw new IllegalArgumentException(
0205: "Values array must be the same length as the types array");
0206: }
0207: if (types.length == 0) {
0208: iTypes = types;
0209: iValues = values;
0210: return;
0211: }
0212: for (int i = 0; i < types.length; i++) {
0213: if (types[i] == null) {
0214: throw new IllegalArgumentException(
0215: "Types array must not contain null: index " + i);
0216: }
0217: }
0218: DurationField lastUnitField = null;
0219: for (int i = 0; i < types.length; i++) {
0220: DateTimeFieldType loopType = types[i];
0221: DurationField loopUnitField = loopType.getDurationType()
0222: .getField(iChronology);
0223: if (i > 0) {
0224: int compare = lastUnitField.compareTo(loopUnitField);
0225: if (compare < 0
0226: || (compare != 0 && loopUnitField.isSupported() == false)) {
0227: throw new IllegalArgumentException(
0228: "Types array must be in order largest-smallest: "
0229: + types[i - 1].getName() + " < "
0230: + loopType.getName());
0231: } else if (compare == 0) {
0232: if (types[i - 1].getRangeDurationType() == null) {
0233: if (loopType.getRangeDurationType() == null) {
0234: throw new IllegalArgumentException(
0235: "Types array must not contain duplicate: "
0236: + loopType.getName());
0237: }
0238: } else {
0239: if (loopType.getRangeDurationType() == null) {
0240: throw new IllegalArgumentException(
0241: "Types array must be in order largest-smallest: "
0242: + types[i - 1].getName()
0243: + " < "
0244: + loopType.getName());
0245: }
0246: DurationField lastRangeField = types[i - 1]
0247: .getRangeDurationType().getField(
0248: iChronology);
0249: DurationField loopRangeField = loopType
0250: .getRangeDurationType().getField(
0251: iChronology);
0252: if (lastRangeField.compareTo(loopRangeField) < 0) {
0253: throw new IllegalArgumentException(
0254: "Types array must be in order largest-smallest: "
0255: + types[i - 1].getName()
0256: + " < "
0257: + loopType.getName());
0258: }
0259: if (lastRangeField.compareTo(loopRangeField) == 0) {
0260: throw new IllegalArgumentException(
0261: "Types array must not contain duplicate: "
0262: + loopType.getName());
0263: }
0264: }
0265: }
0266: }
0267: lastUnitField = loopUnitField;
0268: }
0269:
0270: iTypes = (DateTimeFieldType[]) types.clone();
0271: chronology.validate(this , values);
0272: iValues = (int[]) values.clone();
0273: }
0274:
0275: /**
0276: * Constructs a Partial by copying all the fields and types from
0277: * another partial.
0278: * <p>
0279: * This is most useful when copying from a YearMonthDay or TimeOfDay.
0280: */
0281: public Partial(ReadablePartial partial) {
0282: super ();
0283: if (partial == null) {
0284: throw new IllegalArgumentException(
0285: "The partial must not be null");
0286: }
0287: iChronology = DateTimeUtils.getChronology(
0288: partial.getChronology()).withUTC();
0289: iTypes = new DateTimeFieldType[partial.size()];
0290: iValues = new int[partial.size()];
0291: for (int i = 0; i < partial.size(); i++) {
0292: iTypes[i] = partial.getFieldType(i);
0293: iValues[i] = partial.getValue(i);
0294: }
0295: }
0296:
0297: /**
0298: * Constructs a Partial with the specified values.
0299: * This constructor assigns and performs no validation.
0300: *
0301: * @param partial the partial to copy
0302: * @param values the values to store
0303: * @throws IllegalArgumentException if the types or values are invalid
0304: */
0305: Partial(Partial partial, int[] values) {
0306: super ();
0307: iChronology = partial.iChronology;
0308: iTypes = partial.iTypes;
0309: iValues = values;
0310: }
0311:
0312: /**
0313: * Constructs a Partial with the specified chronology, fields and values.
0314: * This constructor assigns and performs no validation.
0315: *
0316: * @param chronology the chronology
0317: * @param types the types to create the partial from
0318: * @param values the values to store
0319: * @throws IllegalArgumentException if the types or values are invalid
0320: */
0321: Partial(Chronology chronology, DateTimeFieldType[] types,
0322: int[] values) {
0323: super ();
0324: iChronology = chronology;
0325: iTypes = types;
0326: iValues = values;
0327: }
0328:
0329: //-----------------------------------------------------------------------
0330: /**
0331: * Gets the number of fields in this partial.
0332: *
0333: * @return the field count
0334: */
0335: public int size() {
0336: return iTypes.length;
0337: }
0338:
0339: /**
0340: * Gets the chronology of the partial which is never null.
0341: * <p>
0342: * The {@link Chronology} is the calculation engine behind the partial and
0343: * provides conversion and validation of the fields in a particular calendar system.
0344: *
0345: * @return the chronology, never null
0346: */
0347: public Chronology getChronology() {
0348: return iChronology;
0349: }
0350:
0351: /**
0352: * Gets the field for a specific index in the chronology specified.
0353: *
0354: * @param index the index to retrieve
0355: * @param chrono the chronology to use
0356: * @return the field
0357: * @throws IndexOutOfBoundsException if the index is invalid
0358: */
0359: protected DateTimeField getField(int index, Chronology chrono) {
0360: return iTypes[index].getField(chrono);
0361: }
0362:
0363: /**
0364: * Gets the field type at the specified index.
0365: *
0366: * @param index the index to retrieve
0367: * @return the field at the specified index
0368: * @throws IndexOutOfBoundsException if the index is invalid
0369: */
0370: public DateTimeFieldType getFieldType(int index) {
0371: return iTypes[index];
0372: }
0373:
0374: /**
0375: * Gets an array of the field type of each of the fields that
0376: * this partial supports.
0377: * <p>
0378: * The fields are returned largest to smallest.
0379: *
0380: * @return the array of field types (cloned), largest to smallest
0381: */
0382: public DateTimeFieldType[] getFieldTypes() {
0383: return (DateTimeFieldType[]) iTypes.clone();
0384: }
0385:
0386: //-----------------------------------------------------------------------
0387: /**
0388: * Gets the value of the field at the specifed index.
0389: *
0390: * @param index the index
0391: * @return the value
0392: * @throws IndexOutOfBoundsException if the index is invalid
0393: */
0394: public int getValue(int index) {
0395: return iValues[index];
0396: }
0397:
0398: /**
0399: * Gets an array of the value of each of the fields that
0400: * this partial supports.
0401: * <p>
0402: * The fields are returned largest to smallest.
0403: * Each value corresponds to the same array index as <code>getFieldTypes()</code>
0404: *
0405: * @return the current values of each field (cloned), largest to smallest
0406: */
0407: public int[] getValues() {
0408: return (int[]) iValues.clone();
0409: }
0410:
0411: //-----------------------------------------------------------------------
0412: /**
0413: * Creates a new Partial instance with the specified chronology.
0414: * This instance is immutable and unaffected by this method call.
0415: * <p>
0416: * This method retains the values of the fields, thus the result will
0417: * typically refer to a different instant.
0418: * <p>
0419: * The time zone of the specified chronology is ignored, as Partial
0420: * operates without a time zone.
0421: *
0422: * @param newChronology the new chronology, null means ISO
0423: * @return a copy of this datetime with a different chronology
0424: * @throws IllegalArgumentException if the values are invalid for the new chronology
0425: */
0426: public Partial withChronologyRetainFields(Chronology newChronology) {
0427: newChronology = DateTimeUtils.getChronology(newChronology);
0428: newChronology = newChronology.withUTC();
0429: if (newChronology == getChronology()) {
0430: return this ;
0431: } else {
0432: Partial newPartial = new Partial(newChronology, iTypes,
0433: iValues);
0434: newChronology.validate(newPartial, iValues);
0435: return newPartial;
0436: }
0437: }
0438:
0439: //-----------------------------------------------------------------------
0440: /**
0441: * Gets a copy of this date with the specified field set to a new value.
0442: * <p>
0443: * If this partial did not previously support the field, the new one will.
0444: * Contrast this behaviour with {@link #withField(DateTimeFieldType, int)}.
0445: * <p>
0446: * For example, if the field type is <code>dayOfMonth</code> then the day
0447: * would be changed/added in the returned instance.
0448: *
0449: * @param fieldType the field type to set, not null
0450: * @param value the value to set
0451: * @return a copy of this instance with the field set
0452: * @throws IllegalArgumentException if the value is null or invalid
0453: */
0454: public Partial with(DateTimeFieldType fieldType, int value) {
0455: if (fieldType == null) {
0456: throw new IllegalArgumentException(
0457: "The field type must not be null");
0458: }
0459: int index = indexOf(fieldType);
0460: if (index == -1) {
0461: DateTimeFieldType[] newTypes = new DateTimeFieldType[iTypes.length + 1];
0462: int[] newValues = new int[newTypes.length];
0463:
0464: // find correct insertion point to keep largest-smallest order
0465: int i = 0;
0466: DurationField unitField = fieldType.getDurationType()
0467: .getField(iChronology);
0468: if (unitField.isSupported()) {
0469: for (; i < iTypes.length; i++) {
0470: DateTimeFieldType loopType = iTypes[i];
0471: DurationField loopUnitField = loopType
0472: .getDurationType().getField(iChronology);
0473: if (loopUnitField.isSupported()) {
0474: int compare = unitField
0475: .compareTo(loopUnitField);
0476: if (compare > 0) {
0477: break;
0478: } else if (compare == 0) {
0479: DurationField rangeField = fieldType
0480: .getRangeDurationType().getField(
0481: iChronology);
0482: DurationField loopRangeField = loopType
0483: .getRangeDurationType().getField(
0484: iChronology);
0485: if (rangeField.compareTo(loopRangeField) > 0) {
0486: break;
0487: }
0488: }
0489: }
0490: }
0491: }
0492: System.arraycopy(iTypes, 0, newTypes, 0, i);
0493: System.arraycopy(iValues, 0, newValues, 0, i);
0494: newTypes[i] = fieldType;
0495: newValues[i] = value;
0496: System.arraycopy(iTypes, i, newTypes, i + 1,
0497: newTypes.length - i - 1);
0498: System.arraycopy(iValues, i, newValues, i + 1,
0499: newValues.length - i - 1);
0500:
0501: Partial newPartial = new Partial(iChronology, newTypes,
0502: newValues);
0503: iChronology.validate(newPartial, newValues);
0504: return newPartial;
0505: }
0506: if (value == getValue(index)) {
0507: return this ;
0508: }
0509: int[] newValues = getValues();
0510: newValues = getField(index).set(this , index, newValues, value);
0511: return new Partial(this , newValues);
0512: }
0513:
0514: /**
0515: * Gets a copy of this date with the specified field removed.
0516: * <p>
0517: * If this partial did not previously support the field, no error occurs.
0518: *
0519: * @param fieldType the field type to remove, may be null
0520: * @return a copy of this instance with the field removed
0521: */
0522: public Partial without(DateTimeFieldType fieldType) {
0523: int index = indexOf(fieldType);
0524: if (index != -1) {
0525: DateTimeFieldType[] newTypes = new DateTimeFieldType[size() - 1];
0526: int[] newValues = new int[size() - 1];
0527: System.arraycopy(iTypes, 0, newTypes, 0, index);
0528: System.arraycopy(iTypes, index + 1, newTypes, index,
0529: newTypes.length - index);
0530: System.arraycopy(iValues, 0, newValues, 0, index);
0531: System.arraycopy(iValues, index + 1, newValues, index,
0532: newValues.length - index);
0533: Partial newPartial = new Partial(iChronology, newTypes,
0534: newValues);
0535: iChronology.validate(newPartial, newValues);
0536: return newPartial;
0537: }
0538: return this ;
0539: }
0540:
0541: //-----------------------------------------------------------------------
0542: /**
0543: * Gets a copy of this Partial with the specified field set to a new value.
0544: * <p>
0545: * If this partial does not support the field, an exception is thrown.
0546: * Contrast this behaviour with {@link #with(DateTimeFieldType, int)}.
0547: * <p>
0548: * For example, if the field type is <code>dayOfMonth</code> then the day
0549: * would be changed in the returned instance if supported.
0550: *
0551: * @param fieldType the field type to set, not null
0552: * @param value the value to set
0553: * @return a copy of this instance with the field set
0554: * @throws IllegalArgumentException if the value is null or invalid
0555: */
0556: public Partial withField(DateTimeFieldType fieldType, int value) {
0557: int index = indexOfSupported(fieldType);
0558: if (value == getValue(index)) {
0559: return this ;
0560: }
0561: int[] newValues = getValues();
0562: newValues = getField(index).set(this , index, newValues, value);
0563: return new Partial(this , newValues);
0564: }
0565:
0566: /**
0567: * Gets a copy of this Partial with the value of the specified field increased.
0568: * If this partial does not support the field, an exception is thrown.
0569: * <p>
0570: * If the addition is zero, then <code>this</code> is returned.
0571: * The addition will overflow into larger fields (eg. minute to hour).
0572: * However, it will not wrap around if the top maximum is reached.
0573: *
0574: * @param fieldType the field type to add to, not null
0575: * @param amount the amount to add
0576: * @return a copy of this instance with the field updated
0577: * @throws IllegalArgumentException if the value is null or invalid
0578: * @throws ArithmeticException if the new datetime exceeds the capacity
0579: */
0580: public Partial withFieldAdded(DurationFieldType fieldType,
0581: int amount) {
0582: int index = indexOfSupported(fieldType);
0583: if (amount == 0) {
0584: return this ;
0585: }
0586: int[] newValues = getValues();
0587: newValues = getField(index).add(this , index, newValues, amount);
0588: return new Partial(this , newValues);
0589: }
0590:
0591: /**
0592: * Gets a copy of this Partial with the value of the specified field increased.
0593: * If this partial does not support the field, an exception is thrown.
0594: * <p>
0595: * If the addition is zero, then <code>this</code> is returned.
0596: * The addition will overflow into larger fields (eg. minute to hour).
0597: * If the maximum is reached, the addition will wra.
0598: *
0599: * @param fieldType the field type to add to, not null
0600: * @param amount the amount to add
0601: * @return a copy of this instance with the field updated
0602: * @throws IllegalArgumentException if the value is null or invalid
0603: * @throws ArithmeticException if the new datetime exceeds the capacity
0604: */
0605: public Partial withFieldAddWrapped(DurationFieldType fieldType,
0606: int amount) {
0607: int index = indexOfSupported(fieldType);
0608: if (amount == 0) {
0609: return this ;
0610: }
0611: int[] newValues = getValues();
0612: newValues = getField(index).addWrapPartial(this , index,
0613: newValues, amount);
0614: return new Partial(this , newValues);
0615: }
0616:
0617: /**
0618: * Gets a copy of this Partial with the specified period added.
0619: * <p>
0620: * If the addition is zero, then <code>this</code> is returned.
0621: * Fields in the period that aren't present in the partial are ignored.
0622: * <p>
0623: * This method is typically used to add multiple copies of complex
0624: * period instances. Adding one field is best achieved using the method
0625: * {@link #withFieldAdded(DurationFieldType, int)}.
0626: *
0627: * @param period the period to add to this one, null means zero
0628: * @param scalar the amount of times to add, such as -1 to subtract once
0629: * @return a copy of this instance with the period added
0630: * @throws ArithmeticException if the new datetime exceeds the capacity
0631: */
0632: public Partial withPeriodAdded(ReadablePeriod period, int scalar) {
0633: if (period == null || scalar == 0) {
0634: return this ;
0635: }
0636: int[] newValues = getValues();
0637: for (int i = 0; i < period.size(); i++) {
0638: DurationFieldType fieldType = period.getFieldType(i);
0639: int index = indexOf(fieldType);
0640: if (index >= 0) {
0641: newValues = getField(index).add(
0642: this ,
0643: index,
0644: newValues,
0645: FieldUtils.safeMultiply(period.getValue(i),
0646: scalar));
0647: }
0648: }
0649: return new Partial(this , newValues);
0650: }
0651:
0652: /**
0653: * Gets a copy of this instance with the specified period added.
0654: * <p>
0655: * If the amount is zero or null, then <code>this</code> is returned.
0656: *
0657: * @param period the duration to add to this one, null means zero
0658: * @return a copy of this instance with the period added
0659: * @throws ArithmeticException if the new datetime exceeds the capacity of a long
0660: */
0661: public Partial plus(ReadablePeriod period) {
0662: return withPeriodAdded(period, 1);
0663: }
0664:
0665: /**
0666: * Gets a copy of this instance with the specified period take away.
0667: * <p>
0668: * If the amount is zero or null, then <code>this</code> is returned.
0669: *
0670: * @param period the period to reduce this instant by
0671: * @return a copy of this instance with the period taken away
0672: * @throws ArithmeticException if the new datetime exceeds the capacity of a long
0673: */
0674: public Partial minus(ReadablePeriod period) {
0675: return withPeriodAdded(period, -1);
0676: }
0677:
0678: //-----------------------------------------------------------------------
0679: /**
0680: * Gets the property object for the specified type, which contains
0681: * many useful methods for getting and manipulating the partial.
0682: * <p>
0683: * See also {@link ReadablePartial#get(DateTimeFieldType)}.
0684: *
0685: * @param type the field type to get the property for, not null
0686: * @return the property object
0687: * @throws IllegalArgumentException if the field is null or unsupported
0688: */
0689: public Property property(DateTimeFieldType type) {
0690: return new Property(this , indexOfSupported(type));
0691: }
0692:
0693: //-----------------------------------------------------------------------
0694: /**
0695: * Does this partial match the specified instant.
0696: * <p>
0697: * A match occurs when all the fields of this partial are the same as the
0698: * corresponding fields on the specified instant.
0699: *
0700: * @param instant an instant to check against, null means now in default zone
0701: * @return true if this partial matches the specified instant
0702: */
0703: public boolean isMatch(ReadableInstant instant) {
0704: long millis = DateTimeUtils.getInstantMillis(instant);
0705: Chronology chrono = DateTimeUtils.getInstantChronology(instant);
0706: for (int i = 0; i < iTypes.length; i++) {
0707: int value = iTypes[i].getField(chrono).get(millis);
0708: if (value != iValues[i]) {
0709: return false;
0710: }
0711: }
0712: return true;
0713: }
0714:
0715: /**
0716: * Does this partial match the specified partial.
0717: * <p>
0718: * A match occurs when all the fields of this partial are the same as the
0719: * corresponding fields on the specified partial.
0720: *
0721: * @param partial a partial to check against, must not be null
0722: * @return true if this partial matches the specified partial
0723: * @throws IllegalArgumentException if the partial is null
0724: * @throws IllegalArgumentException if the fields of the two partials do not match
0725: * @since 1.5
0726: */
0727: public boolean isMatch(ReadablePartial partial) {
0728: if (partial == null) {
0729: throw new IllegalArgumentException(
0730: "The partial must not be null");
0731: }
0732: for (int i = 0; i < iTypes.length; i++) {
0733: int value = partial.get(iTypes[i]);
0734: if (value != iValues[i]) {
0735: return false;
0736: }
0737: }
0738: return true;
0739: }
0740:
0741: //-----------------------------------------------------------------------
0742: /**
0743: * Gets a formatter suitable for the fields in this partial.
0744: * <p>
0745: * If there is no appropriate ISO format, null is returned.
0746: * This method may return a formatter that does not display all the
0747: * fields of the partial. This might occur when you have overlapping
0748: * fields, such as dayOfWeek and dayOfMonth.
0749: *
0750: * @return a formatter suitable for the fields in this partial, null
0751: * if none is suitable
0752: */
0753: public DateTimeFormatter getFormatter() {
0754: DateTimeFormatter[] f = iFormatter;
0755: if (f == null) {
0756: if (size() == 0) {
0757: return null;
0758: }
0759: f = new DateTimeFormatter[2];
0760: try {
0761: List list = new ArrayList(Arrays.asList(iTypes));
0762: f[0] = ISODateTimeFormat.forFields(list, true, false);
0763: if (list.size() == 0) {
0764: f[1] = f[0];
0765: }
0766: } catch (IllegalArgumentException ex) {
0767: // ignore
0768: }
0769: iFormatter = f;
0770: }
0771: return f[0];
0772: }
0773:
0774: //-----------------------------------------------------------------------
0775: /**
0776: * Output the date in an appropriate ISO8601 format.
0777: * <p>
0778: * This method will output the partial in one of two ways.
0779: * If {@link #getFormatter()}
0780: * <p>
0781: * If there is no appropriate ISO format a dump of the fields is output
0782: * via {@link #toStringList()}.
0783: *
0784: * @return ISO8601 formatted string
0785: */
0786: public String toString() {
0787: DateTimeFormatter[] f = iFormatter;
0788: if (f == null) {
0789: getFormatter();
0790: f = iFormatter;
0791: if (f == null) {
0792: return toStringList();
0793: }
0794: }
0795: DateTimeFormatter f1 = f[1];
0796: if (f1 == null) {
0797: return toStringList();
0798: }
0799: return f1.print(this );
0800: }
0801:
0802: /**
0803: * Gets a string version of the partial that lists all the fields.
0804: * <p>
0805: * This method exists to provide a better debugging toString than
0806: * the standard toString. This method lists all the fields and their
0807: * values in a style similar to the collections framework.
0808: *
0809: * @return a toString format that lists all the fields
0810: */
0811: public String toStringList() {
0812: int size = size();
0813: StringBuffer buf = new StringBuffer(20 * size);
0814: buf.append('[');
0815: for (int i = 0; i < size; i++) {
0816: if (i > 0) {
0817: buf.append(',').append(' ');
0818: }
0819: buf.append(iTypes[i].getName());
0820: buf.append('=');
0821: buf.append(iValues[i]);
0822: }
0823: buf.append(']');
0824: return buf.toString();
0825: }
0826:
0827: /**
0828: * Output the date using the specified format pattern.
0829: * Unsupported fields will appear as special unicode characters.
0830: *
0831: * @param pattern the pattern specification, null means use <code>toString</code>
0832: * @see org.joda.time.format.DateTimeFormat
0833: */
0834: public String toString(String pattern) {
0835: if (pattern == null) {
0836: return toString();
0837: }
0838: return DateTimeFormat.forPattern(pattern).print(this );
0839: }
0840:
0841: /**
0842: * Output the date using the specified format pattern.
0843: * Unsupported fields will appear as special unicode characters.
0844: *
0845: * @param pattern the pattern specification, null means use <code>toString</code>
0846: * @param locale Locale to use, null means default
0847: * @see org.joda.time.format.DateTimeFormat
0848: */
0849: public String toString(String pattern, Locale locale) {
0850: if (pattern == null) {
0851: return toString();
0852: }
0853: return DateTimeFormat.forPattern(pattern).withLocale(locale)
0854: .print(this );
0855: }
0856:
0857: //-----------------------------------------------------------------------
0858: /**
0859: * The property class for <code>Partial</code>.
0860: * <p>
0861: * This class binds a <code>Partial</code> to a <code>DateTimeField</code>.
0862: *
0863: * @author Stephen Colebourne
0864: * @since 1.1
0865: */
0866: public static class Property extends AbstractPartialFieldProperty
0867: implements Serializable {
0868:
0869: /** Serialization version */
0870: private static final long serialVersionUID = 53278362873888L;
0871:
0872: /** The partial */
0873: private final Partial iPartial;
0874: /** The field index */
0875: private final int iFieldIndex;
0876:
0877: /**
0878: * Constructs a property.
0879: *
0880: * @param partial the partial instance
0881: * @param fieldIndex the index in the partial
0882: */
0883: Property(Partial partial, int fieldIndex) {
0884: super ();
0885: iPartial = partial;
0886: iFieldIndex = fieldIndex;
0887: }
0888:
0889: /**
0890: * Gets the field that this property uses.
0891: *
0892: * @return the field
0893: */
0894: public DateTimeField getField() {
0895: return iPartial.getField(iFieldIndex);
0896: }
0897:
0898: /**
0899: * Gets the partial that this property belongs to.
0900: *
0901: * @return the partial
0902: */
0903: protected ReadablePartial getReadablePartial() {
0904: return iPartial;
0905: }
0906:
0907: /**
0908: * Gets the partial that this property belongs to.
0909: *
0910: * @return the partial
0911: */
0912: public Partial getPartial() {
0913: return iPartial;
0914: }
0915:
0916: /**
0917: * Gets the value of this field.
0918: *
0919: * @return the field value
0920: */
0921: public int get() {
0922: return iPartial.getValue(iFieldIndex);
0923: }
0924:
0925: //-----------------------------------------------------------------------
0926: /**
0927: * Adds to the value of this field in a copy of this Partial.
0928: * <p>
0929: * The value will be added to this field. If the value is too large to be
0930: * added solely to this field then it will affect larger fields.
0931: * Smaller fields are unaffected.
0932: * <p>
0933: * If the result would be too large, beyond the maximum year, then an
0934: * IllegalArgumentException is thrown.
0935: * <p>
0936: * The Partial attached to this property is unchanged by this call.
0937: * Instead, a new instance is returned.
0938: *
0939: * @param valueToAdd the value to add to the field in the copy
0940: * @return a copy of the Partial with the field value changed
0941: * @throws IllegalArgumentException if the value isn't valid
0942: */
0943: public Partial addToCopy(int valueToAdd) {
0944: int[] newValues = iPartial.getValues();
0945: newValues = getField().add(iPartial, iFieldIndex,
0946: newValues, valueToAdd);
0947: return new Partial(iPartial, newValues);
0948: }
0949:
0950: /**
0951: * Adds to the value of this field in a copy of this Partial wrapping
0952: * within this field if the maximum value is reached.
0953: * <p>
0954: * The value will be added to this field. If the value is too large to be
0955: * added solely to this field then it wraps within this field.
0956: * Other fields are unaffected.
0957: * <p>
0958: * For example,
0959: * <code>2004-12-20</code> addWrapField one month returns <code>2004-01-20</code>.
0960: * <p>
0961: * The Partial attached to this property is unchanged by this call.
0962: * Instead, a new instance is returned.
0963: *
0964: * @param valueToAdd the value to add to the field in the copy
0965: * @return a copy of the Partial with the field value changed
0966: * @throws IllegalArgumentException if the value isn't valid
0967: */
0968: public Partial addWrapFieldToCopy(int valueToAdd) {
0969: int[] newValues = iPartial.getValues();
0970: newValues = getField().addWrapField(iPartial, iFieldIndex,
0971: newValues, valueToAdd);
0972: return new Partial(iPartial, newValues);
0973: }
0974:
0975: //-----------------------------------------------------------------------
0976: /**
0977: * Sets this field in a copy of the Partial.
0978: * <p>
0979: * The Partial attached to this property is unchanged by this call.
0980: * Instead, a new instance is returned.
0981: *
0982: * @param value the value to set the field in the copy to
0983: * @return a copy of the Partial with the field value changed
0984: * @throws IllegalArgumentException if the value isn't valid
0985: */
0986: public Partial setCopy(int value) {
0987: int[] newValues = iPartial.getValues();
0988: newValues = getField().set(iPartial, iFieldIndex,
0989: newValues, value);
0990: return new Partial(iPartial, newValues);
0991: }
0992:
0993: /**
0994: * Sets this field in a copy of the Partial to a parsed text value.
0995: * <p>
0996: * The Partial attached to this property is unchanged by this call.
0997: * Instead, a new instance is returned.
0998: *
0999: * @param text the text value to set
1000: * @param locale optional locale to use for selecting a text symbol
1001: * @return a copy of the Partial with the field value changed
1002: * @throws IllegalArgumentException if the text value isn't valid
1003: */
1004: public Partial setCopy(String text, Locale locale) {
1005: int[] newValues = iPartial.getValues();
1006: newValues = getField().set(iPartial, iFieldIndex,
1007: newValues, text, locale);
1008: return new Partial(iPartial, newValues);
1009: }
1010:
1011: /**
1012: * Sets this field in a copy of the Partial to a parsed text value.
1013: * <p>
1014: * The Partial attached to this property is unchanged by this call.
1015: * Instead, a new instance is returned.
1016: *
1017: * @param text the text value to set
1018: * @return a copy of the Partial with the field value changed
1019: * @throws IllegalArgumentException if the text value isn't valid
1020: */
1021: public Partial setCopy(String text) {
1022: return setCopy(text, null);
1023: }
1024:
1025: //-----------------------------------------------------------------------
1026: /**
1027: * Returns a new Partial with this field set to the maximum value
1028: * for this field.
1029: * <p>
1030: * The Partial attached to this property is unchanged by this call.
1031: *
1032: * @return a copy of the Partial with this field set to its maximum
1033: * @since 1.2
1034: */
1035: public Partial withMaximumValue() {
1036: return setCopy(getMaximumValue());
1037: }
1038:
1039: /**
1040: * Returns a new Partial with this field set to the minimum value
1041: * for this field.
1042: * <p>
1043: * The Partial attached to this property is unchanged by this call.
1044: *
1045: * @return a copy of the Partial with this field set to its minimum
1046: * @since 1.2
1047: */
1048: public Partial withMinimumValue() {
1049: return setCopy(getMinimumValue());
1050: }
1051: }
1052:
1053: }
|