001/*
002 *  Copyright 2001-2010 Stephen Colebourne
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.joda.time;
017
018import java.io.Serializable;
019import java.util.ArrayList;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.List;
023import java.util.Locale;
024
025import org.joda.convert.FromString;
026import org.joda.convert.ToString;
027import org.joda.time.base.BasePartial;
028import org.joda.time.chrono.ISOChronology;
029import org.joda.time.field.AbstractPartialFieldProperty;
030import org.joda.time.field.FieldUtils;
031import org.joda.time.format.DateTimeFormat;
032import org.joda.time.format.DateTimeFormatter;
033import org.joda.time.format.DateTimeFormatterBuilder;
034import org.joda.time.format.ISODateTimeFormat;
035
036/**
037 * MonthDay is an immutable partial supporting the monthOfYear and dayOfMonth fields.
038 * <p>
039 * NOTE: This class only supports the two fields listed above.
040 * It is impossible to query any other fields, such as dayOfWeek or centuryOfEra.
041 * <p>
042 * Calculations on MonthDay are performed using a {@link Chronology}.
043 * This chronology is set to be in the UTC time zone for all calculations.
044 * <p>
045 * One use case for this class is to store a birthday without the year (to avoid
046 * storing the age of the person).
047 * This class can be used as the gMonthDay type in XML Schema.
048 * <p>
049 * Each individual field can be queried in two ways:
050 * <ul>
051 * <li><code>getMonthOfYear()</code>
052 * <li><code>monthOfYear().get()</code>
053 * </ul>
054 * The second technique also provides access to other useful methods on the
055 * field:
056 * <ul>
057 * <li>numeric value - <code>monthOfYear().get()</code>
058 * <li>text value - <code>monthOfYear().getAsText()</code>
059 * <li>short text value - <code>monthOfYear().getAsShortText()</code>
060 * <li>maximum/minimum values - <code>monthOfYear().getMaximumValue()</code>
061 * <li>add/subtract - <code>monthOfYear().addToCopy()</code>
062 * <li>set - <code>monthOfYear().setCopy()</code>
063 * </ul>
064 * <p>
065 * MonthDay is thread-safe and immutable, provided that the Chronology is as well.
066 * All standard Chronology classes supplied are thread-safe and immutable.
067 *
068 * @author Chris Pheby
069 * @since 2.0
070 */
071public final class MonthDay
072        extends BasePartial
073        implements ReadablePartial, Serializable {
074
075    /** Serialization version */
076    private static final long serialVersionUID = 2954560699050434609L;
077
078    /** The singleton set of field types */
079    private static final DateTimeFieldType[] FIELD_TYPES = new DateTimeFieldType[] {
080        DateTimeFieldType.monthOfYear(),
081        DateTimeFieldType.dayOfMonth(), };
082
083    /** The singleton set of field types */
084    private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
085        .appendOptional(ISODateTimeFormat.localDateParser().getParser())
086        .appendOptional(DateTimeFormat.forPattern("--MM-dd").getParser()).toFormatter();
087
088    /** The index of the monthOfYear field in the field array */
089    public static final int MONTH_OF_YEAR = 0;
090    /** The index of the day field in the field array */
091    public static final int DAY_OF_MONTH = 1;
092
093    //-----------------------------------------------------------------------
094    /**
095     * Obtains a {@code MonthDay} set to the current system millisecond time
096     * using <code>ISOChronology</code> in the default time zone.
097     * The resulting object does not use the zone.
098     * 
099     * @return the current month-day, not null
100     * @since 2.0
101     */
102    public static MonthDay now() {
103        return new MonthDay();
104    }
105
106    /**
107     * Obtains a {@code MonthDay} set to the current system millisecond time
108     * using <code>ISOChronology</code> in the specified time zone.
109     * The resulting object does not use the zone.
110     *
111     * @param zone  the time zone, not null
112     * @return the current month-day, not null
113     * @since 2.0
114     */
115    public static MonthDay now(DateTimeZone zone) {
116        if (zone == null) {
117            throw new NullPointerException("Zone must not be null");
118        }
119        return new MonthDay(zone);
120    }
121
122    /**
123     * Obtains a {@code MonthDay} set to the current system millisecond time
124     * using the specified chronology.
125     * The resulting object does not use the zone.
126     *
127     * @param chronology  the chronology, not null
128     * @return the current month-day, not null
129     * @since 2.0
130     */
131    public static MonthDay now(Chronology chronology) {
132        if (chronology == null) {
133            throw new NullPointerException("Chronology must not be null");
134        }
135        return new MonthDay(chronology);
136    }
137
138    //-----------------------------------------------------------------------
139    /**
140     * Parses a {@code MonthDay} from the specified string.
141     * <p>
142     * This uses {@link ISODateTimeFormat#localDateParser()} or the format {@code --MM-dd}.
143     * 
144     * @param str  the string to parse, not null
145     * @since 2.0
146     */
147    @FromString
148    public static MonthDay parse(String str) {
149        return parse(str, PARSER);
150    }
151
152    /**
153     * Parses a {@code MonthDay} from the specified string using a formatter.
154     * 
155     * @param str  the string to parse, not null
156     * @param formatter  the formatter to use, not null
157     * @since 2.0
158     */
159    public static MonthDay parse(String str, DateTimeFormatter formatter) {
160        LocalDate date = formatter.parseLocalDate(str);
161        return new MonthDay(date.getMonthOfYear(), date.getDayOfMonth());
162    }
163
164    //-----------------------------------------------------------------------
165    /**
166     * Constructs a MonthDay from a <code>java.util.Calendar</code>
167     * using exactly the same field values avoiding any time zone effects.
168     * <p>
169     * Each field is queried from the Calendar and assigned to the MonthDay.
170     * <p>
171     * This factory method ignores the type of the calendar and always
172     * creates a MonthDay with ISO chronology. It is expected that you
173     * will only pass in instances of <code>GregorianCalendar</code> however
174     * this is not validated.
175     *
176     * @param calendar  the Calendar to extract fields from
177     * @return the created MonthDay, never null
178     * @throws IllegalArgumentException if the calendar is null
179     * @throws IllegalArgumentException if the monthOfYear or dayOfMonth is invalid for the ISO chronology
180     */
181    public static MonthDay fromCalendarFields(Calendar calendar) {
182        if (calendar == null) {
183            throw new IllegalArgumentException("The calendar must not be null");
184        }
185        return new MonthDay(calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH));
186    }
187
188    /**
189     * Constructs a MonthDay from a <code>java.util.Date</code>
190     * using exactly the same field values avoiding any time zone effects.
191     * <p>
192     * Each field is queried from the Date and assigned to the MonthDay.
193     * <p>
194     * This factory method always creates a MonthDay with ISO chronology.
195     *
196     * @param date  the Date to extract fields from
197     * @return the created MonthDay, never null
198     * @throws IllegalArgumentException if the calendar is null
199     * @throws IllegalArgumentException if the monthOfYear or dayOfMonth is invalid for the ISO chronology
200     */
201    @SuppressWarnings("deprecation")
202    public static MonthDay fromDateFields(Date date) {
203        if (date == null) {
204            throw new IllegalArgumentException("The date must not be null");
205        }
206        return new MonthDay(date.getMonth() + 1, date.getDate());
207    }
208
209    //-----------------------------------------------------------------------
210    /**
211     * Constructs a MonthDay with the current monthOfYear, using ISOChronology in
212     * the default zone to extract the fields.
213     * <p>
214     * The constructor uses the default time zone, resulting in the local time
215     * being initialised. Once the constructor is complete, all further calculations
216     * are performed without reference to a time-zone (by switching to UTC).
217     * 
218     * @see #now()
219     */
220    public MonthDay() {
221        super();
222    }
223
224    /**
225     * Constructs a MonthDay with the current month-day, using ISOChronology in
226     * the specified zone to extract the fields.
227     * <p>
228     * The constructor uses the specified time zone to obtain the current month-day.
229     * Once the constructor is complete, all further calculations
230     * are performed without reference to a time-zone (by switching to UTC).
231     * 
232     * @param zone  the zone to use, null means default zone
233     * @see #now(DateTimeZone)
234     */
235    public MonthDay(DateTimeZone zone) {
236        super(ISOChronology.getInstance(zone));
237    }
238
239    /**
240     * Constructs a MonthDay with the current month-day, using the specified chronology
241     * and zone to extract the fields.
242     * <p>
243     * The constructor uses the time zone of the chronology specified.
244     * Once the constructor is complete, all further calculations are performed
245     * without reference to a time-zone (by switching to UTC).
246     *
247     * @param chronology  the chronology, null means ISOChronology in the default zone
248     * @see #now(Chronology)
249     */
250    public MonthDay(Chronology chronology) {
251        super(chronology);
252    }
253
254    /**
255     * Constructs a MonthDay extracting the partial fields from the specified
256     * milliseconds using the ISOChronology in the default zone.
257     * <p>
258     * The constructor uses the default time zone, resulting in the local time
259     * being initialised. Once the constructor is complete, all further calculations
260     * are performed without reference to a time-zone (by switching to UTC).
261     *
262     * @param instant  the milliseconds from 1970-01-01T00:00:00Z
263     */
264    public MonthDay(long instant) {
265        super(instant);
266    }
267
268    /**
269     * Constructs a MonthDay extracting the partial fields from the specified
270     * milliseconds using the chronology provided.
271     * <p>
272     * The constructor uses the time zone of the chronology specified.
273     * Once the constructor is complete, all further calculations are performed
274     * without reference to a time-zone (by switching to UTC).
275     *
276     * @param instant  the milliseconds from 1970-01-01T00:00:00Z
277     * @param chronology  the chronology, null means ISOChronology in the default zone
278     */
279    public MonthDay(long instant, Chronology chronology) {
280        super(instant, chronology);
281    }
282
283    /**
284     * Constructs a MonthDay from an Object that represents some form of time.
285     * <p>
286     * The recognised object types are defined in
287     * {@link org.joda.time.convert.ConverterManager ConverterManager} and
288     * include ReadableInstant, String, Calendar and Date.
289     * The String formats are described by {@link ISODateTimeFormat#localDateParser()}.
290     * <p>
291     * The chronology used will be derived from the object, defaulting to ISO.
292     *
293     * @param instant  the date-time object, null means now
294     * @throws IllegalArgumentException if the instant is invalid
295     */
296    public MonthDay(Object instant) {
297        super(instant, null, ISODateTimeFormat.localDateParser());
298    }
299
300    /**
301     * Constructs a MonthDay from an Object that represents some form of time,
302     * using the specified chronology.
303     * <p>
304     * The recognised object types are defined in
305     * {@link org.joda.time.convert.ConverterManager ConverterManager} and
306     * include ReadableInstant, String, Calendar and Date.
307     * The String formats are described by {@link ISODateTimeFormat#localDateParser()}.
308     * <p>
309     * The constructor uses the time zone of the chronology specified.
310     * Once the constructor is complete, all further calculations are performed
311     * without reference to a time-zone (by switching to UTC).
312     * The specified chronology overrides that of the object.
313     *
314     * @param instant  the date-time object, null means now
315     * @param chronology  the chronology, null means ISO default
316     * @throws IllegalArgumentException if the instant is invalid
317     */
318    public MonthDay(Object instant, Chronology chronology) {
319        super(instant, DateTimeUtils.getChronology(chronology), ISODateTimeFormat.localDateParser());
320    }
321
322    /**
323     * Constructs a MonthDay with specified year and month
324     * using <code>ISOChronology</code>.
325     * <p>
326     * The constructor uses the no time zone initialising the fields as provided.
327     * Once the constructor is complete, all further calculations
328     * are performed without reference to a time-zone (by switching to UTC).
329     *
330     * @param monthOfYear  the month of the year
331     * @param dayOfMonth  the day of the month
332     */
333    public MonthDay(int monthOfYear, int dayOfMonth) {
334        this(monthOfYear, dayOfMonth, null);
335    }
336
337    /**
338     * Constructs an instance set to the specified monthOfYear and dayOfMonth
339     * using the specified chronology, whose zone is ignored.
340     * <p>
341     * If the chronology is null, <code>ISOChronology</code> is used.
342     * <p>
343     * The constructor uses the time zone of the chronology specified.
344     * Once the constructor is complete, all further calculations are performed
345     * without reference to a time-zone (by switching to UTC).
346     *
347     * @param monthOfYear  the month of the year
348     * @param dayOfMonth  the day of the month
349     * @param chronology  the chronology, null means ISOChronology in the default zone
350     */
351    public MonthDay(int monthOfYear, int dayOfMonth, Chronology chronology) {
352        super(new int[] {monthOfYear, dayOfMonth}, chronology);
353    }
354
355    /**
356     * Constructs a MonthDay with chronology from this instance and new values.
357     *
358     * @param partial  the partial to base this new instance on
359     * @param values  the new set of values
360     */
361    MonthDay(MonthDay partial, int[] values) {
362        super(partial, values);
363    }
364
365    /**
366     * Constructs a MonthDay with values from this instance and a new chronology.
367     *
368     * @param partial  the partial to base this new instance on
369     * @param chrono  the new chronology
370     */
371    MonthDay(MonthDay partial, Chronology chrono) {
372        super(partial, chrono);
373    }
374
375    /**
376     * Handle broken serialization from other tools.
377     * @return the resolved object, not null
378     */
379    private Object readResolve() {
380        if (DateTimeZone.UTC.equals(getChronology().getZone()) == false) {
381            return new MonthDay(this, getChronology().withUTC());
382        }
383        return this;
384    }
385
386    //-----------------------------------------------------------------------
387    /**
388     * Gets the number of fields in this partial, which is two.
389     * The supported fields are MonthOfYear and DayOfMonth.
390     * Note that only these fields may be queried.
391     *
392     * @return the field count, two
393     */
394    public int size() {
395        return 2;
396    }
397
398    /**
399     * Gets the field for a specific index in the chronology specified.
400     * <p>
401     * This method must not use any instance variables.
402     * 
403     * @param index  the index to retrieve
404     * @param chrono  the chronology to use
405     * @return the field, never null
406     */
407    protected DateTimeField getField(int index, Chronology chrono) {
408        switch (index) {
409        case MONTH_OF_YEAR:
410            return chrono.monthOfYear();
411        case DAY_OF_MONTH:
412            return chrono.dayOfMonth();
413        default:
414            throw new IndexOutOfBoundsException("Invalid index: " + index);
415        }
416    }
417
418    /**
419     * Gets the field type at the specified index.
420     *
421     * @param index  the index to retrieve
422     * @return the field at the specified index, never null
423     * @throws IndexOutOfBoundsException if the index is invalid
424     */
425    public DateTimeFieldType getFieldType(int index) {
426        return FIELD_TYPES[index];
427    }
428
429    /**
430     * Gets an array of the field type of each of the fields that this partial supports.
431     * <p>
432     * The fields are returned largest to smallest, Month, Day.
433     *
434     * @return the array of field types (cloned), largest to smallest, never null
435     */
436    public DateTimeFieldType[] getFieldTypes() {
437        return (DateTimeFieldType[]) FIELD_TYPES.clone();
438    }
439
440    //-----------------------------------------------------------------------
441    /**
442     * Returns a copy of this month-day with the specified chronology.
443     * This instance is immutable and unaffected by this method call.
444     * <p>
445     * This method retains the values of the fields, thus the result will
446     * typically refer to a different instant.
447     * <p>
448     * The time zone of the specified chronology is ignored, as MonthDay
449     * operates without a time zone.
450     *
451     * @param newChronology  the new chronology, null means ISO
452     * @return a copy of this month-day with a different chronology, never null
453     * @throws IllegalArgumentException if the values are invalid for the new chronology
454     */
455    public MonthDay withChronologyRetainFields(Chronology newChronology) {
456        newChronology = DateTimeUtils.getChronology(newChronology);
457        newChronology = newChronology.withUTC();
458        if (newChronology == getChronology()) {
459            return this;
460        } else {
461            MonthDay newMonthDay = new MonthDay(this, newChronology);
462            newChronology.validate(newMonthDay, getValues());
463            return newMonthDay;
464        }
465    }
466
467    /**
468     * Returns a copy of this month-day with the specified field set to a new value.
469     * <p>
470     * For example, if the field type is <code>dayOfMonth</code> then the day
471     * would be changed in the returned instance.
472     * <p>
473     * These three lines are equivalent:
474     * <pre>
475     * MonthDay updated = md.withField(DateTimeFieldType.dayOfMonth(), 6);
476     * MonthDay updated = md.dayOfMonth().setCopy(6);
477     * MonthDay updated = md.property(DateTimeFieldType.dayOfMonth()).setCopy(6);
478     * </pre>
479     *
480     * @param fieldType  the field type to set, not null
481     * @param value  the value to set
482     * @return a copy of this instance with the field set, never null
483     * @throws IllegalArgumentException if the value is null or invalid
484     */
485    public MonthDay withField(DateTimeFieldType fieldType, int value) {
486        int index = indexOfSupported(fieldType);
487        if (value == getValue(index)) {
488            return this;
489        }
490        int[] newValues = getValues();
491        newValues = getField(index).set(this, index, newValues, value);
492        return new MonthDay(this, newValues);
493    }
494
495    /**
496     * Returns a copy of this month-day with the value of the specified field increased.
497     * <p>
498     * If the addition is zero, then <code>this</code> is returned.
499     * <p>
500     * These three lines are equivalent:
501     * <pre>
502     * MonthDay added = md.withFieldAdded(DurationFieldType.days(), 6);
503     * MonthDay added = md.plusDays(6);
504     * MonthDay added = md.dayOfMonth().addToCopy(6);
505     * </pre>
506     * 
507     * @param fieldType  the field type to add to, not null
508     * @param amount  the amount to add
509     * @return a copy of this instance with the field updated, never null
510     * @throws IllegalArgumentException if the value is null or invalid
511     * @throws ArithmeticException if the new date-time exceeds the capacity
512     */
513    public MonthDay withFieldAdded(DurationFieldType fieldType, int amount) {
514        int index = indexOfSupported(fieldType);
515        if (amount == 0) {
516            return this;
517        }
518        int[] newValues = getValues();
519        newValues = getField(index).add(this, index, newValues, amount);
520        return new MonthDay(this, newValues);
521    }
522
523    /**
524     * Returns a copy of this month-day with the specified period added.
525     * <p>
526     * If the addition is zero, then <code>this</code> is returned.
527     * Fields in the period that aren't present in the partial are ignored.
528     * <p>
529     * This method is typically used to add multiple copies of complex
530     * period instances. Adding one field is best achieved using methods
531     * like {@link #withFieldAdded(DurationFieldType, int)}
532     * or {@link #plusMonths(int)}.
533     * 
534     * @param period  the period to add to this one, null means zero
535     * @param scalar  the amount of times to add, such as -1 to subtract once
536     * @return a copy of this instance with the period added, never null
537     * @throws ArithmeticException if the new date-time exceeds the capacity
538     */
539    public MonthDay withPeriodAdded(ReadablePeriod period, int scalar) {
540        if (period == null || scalar == 0) {
541            return this;
542        }
543        int[] newValues = getValues();
544        for (int i = 0; i < period.size(); i++) {
545            DurationFieldType fieldType = period.getFieldType(i);
546            int index = indexOf(fieldType);
547            if (index >= 0) {
548                newValues = getField(index).add(this, index, newValues,
549                        FieldUtils.safeMultiply(period.getValue(i), scalar));
550            }
551        }
552        return new MonthDay(this, newValues);
553    }
554
555    //-----------------------------------------------------------------------
556    /**
557     * Returns a copy of this month-day with the specified period added.
558     * <p>
559     * If the amount is zero or null, then <code>this</code> is returned.
560     * <p>
561     * This method is typically used to add complex period instances.
562     * Adding one field is best achieved using methods
563     * like {@link #plusMonths(int)}.
564     * 
565     * @param period  the duration to add to this one, null means zero
566     * @return a copy of this instance with the period added, never null
567     * @throws ArithmeticException if the new month-day exceeds the capacity
568     */
569    public MonthDay plus(ReadablePeriod period) {
570        return withPeriodAdded(period, 1);
571    }
572
573    //-----------------------------------------------------------------------
574    /**
575     * Returns a copy of this month-day plus the specified number of months.
576     * <p>
577     * This month-day instance is immutable and unaffected by this method call.
578     * The month will wrap at the end of the year from December to January.
579     * The day will be adjusted to the last valid value if necessary.
580     * <p>
581     * The following three lines are identical in effect:
582     * <pre>
583     * MonthDay added = md.plusMonths(6);
584     * MonthDay added = md.plus(Period.months(6));
585     * MonthDay added = md.withFieldAdded(DurationFieldType.months(), 6);
586     * </pre>
587     *
588     * @param months  the amount of months to add, may be negative
589     * @return the new month-day plus the increased months, never null
590     */
591    public MonthDay plusMonths(int months) {
592        return withFieldAdded(DurationFieldType.months(), months);
593    }
594
595    /**
596     * Returns a copy of this month-day plus the specified number of days.
597     * <p>
598     * This month-day instance is immutable and unaffected by this method call.
599     * The month will wrap at the end of the year from December to January.
600     * <p>
601     * The following three lines are identical in effect:
602     * <pre>
603     * MonthDay added = md.plusDays(6);
604     * MonthDay added = md.plus(Period.days(6));
605     * MonthDay added = md.withFieldAdded(DurationFieldType.days(), 6);
606     * </pre>
607     *
608     * @param days  the amount of days to add, may be negative
609     * @return the new month-day plus the increased days, never null
610     */
611    public MonthDay plusDays(int days) {
612        return withFieldAdded(DurationFieldType.days(), days);
613    }
614
615    //-----------------------------------------------------------------------
616    /**
617     * Returns a copy of this month-day with the specified period taken away.
618     * <p>
619     * If the amount is zero or null, then <code>this</code> is returned.
620     * <p>
621     * This method is typically used to subtract complex period instances.
622     * Subtracting one field is best achieved using methods
623     * like {@link #minusMonths(int)}.
624     * 
625     * @param period  the period to reduce this instant by
626     * @return a copy of this instance with the period taken away, never null
627     * @throws ArithmeticException if the new month-day exceeds the capacity
628     */
629    public MonthDay minus(ReadablePeriod period) {
630        return withPeriodAdded(period, -1);
631    }
632
633    //-----------------------------------------------------------------------
634    /**
635     * Returns a copy of this month-day minus the specified number of months.
636     * <p>
637     * This MonthDay instance is immutable and unaffected by this method call.
638     * The month will wrap at the end of the year from January to December.
639     * The day will be adjusted to the last valid value if necessary.
640     * <p>
641     * The following three lines are identical in effect:
642     * <pre>
643     * MonthDay subtracted = md.minusMonths(6);
644     * MonthDay subtracted = md.minus(Period.months(6));
645     * MonthDay subtracted = md.withFieldAdded(DurationFieldType.months(), -6);
646     * </pre>
647     *
648     * @param months  the amount of months to subtract, may be negative
649     * @return the new month-day minus the increased months, never null
650     */
651    public MonthDay minusMonths(int months) {
652        return withFieldAdded(DurationFieldType.months(), FieldUtils.safeNegate(months));
653    }
654
655    /**
656     * Returns a copy of this month-day minus the specified number of months.
657     * <p>
658     * This month-day instance is immutable and unaffected by this method call.
659     * The month will wrap at the end of the year from January to December.
660     * <p>
661     * The following three lines are identical in effect:
662     * <pre>
663     * MonthDay subtracted = md.minusDays(6);
664     * MonthDay subtracted = md.minus(Period.days(6));
665     * MonthDay subtracted = md.withFieldAdded(DurationFieldType.days(), -6);
666     * </pre>
667     *
668     * @param days  the amount of days to subtract, may be negative
669     * @return the new month-day minus the increased days, never null
670     */
671    public MonthDay minusDays(int days) {
672        return withFieldAdded(DurationFieldType.days(), FieldUtils.safeNegate(days));
673    }
674
675    //-----------------------------------------------------------------------
676    /**
677     * Converts this object to a LocalDate with the same month-day and chronology.
678     *
679     * @param year  the year to use, valid for chronology
680     * @return a LocalDate with the same month-day and chronology, never null
681     */
682    public LocalDate toLocalDate(int year) {
683        return new LocalDate(year, getMonthOfYear(), getDayOfMonth(), getChronology());
684    }
685
686    //-----------------------------------------------------------------------
687    /**
688     * Get the month of year field value.
689     *
690     * @return the month of year
691     */
692    public int getMonthOfYear() {
693        return getValue(MONTH_OF_YEAR);
694    }
695
696    /**
697     * Get the day of month field value.
698     *
699     * @return the day of month
700     */
701    public int getDayOfMonth() {
702        return getValue(DAY_OF_MONTH);
703    }
704
705    //-----------------------------------------------------------------------
706    /**
707     * Returns a copy of this month-day with the month of year field updated.
708     * <p>
709     * MonthDay is immutable, so there are no set methods.
710     * Instead, this method returns a new instance with the value of
711     * month of year changed.
712     *
713     * @param monthOfYear  the month of year to set
714     * @return a copy of this object with the field set, never null
715     * @throws IllegalArgumentException if the value is invalid
716     */
717    public MonthDay withMonthOfYear(int monthOfYear) {
718        int[] newValues = getValues();
719        newValues = getChronology().monthOfYear().set(this, MONTH_OF_YEAR, newValues, monthOfYear);
720        return new MonthDay(this, newValues);
721    }
722
723    /**
724     * Returns a copy of this month-day with the day of month field updated.
725     * <p>
726     * MonthDay is immutable, so there are no set methods.
727     * Instead, this method returns a new instance with the value of
728     * day of month changed.
729     *
730     * @param dayOfMonth  the day of month to set
731     * @return a copy of this object with the field set, never null
732     * @throws IllegalArgumentException if the value is invalid
733     */
734    public MonthDay withDayOfMonth(int dayOfMonth) {
735        int[] newValues = getValues();
736        newValues = getChronology().dayOfMonth().set(this, DAY_OF_MONTH, newValues, dayOfMonth);
737        return new MonthDay(this, newValues);
738    }
739
740    //-----------------------------------------------------------------------
741    /**
742     * Gets the property object for the specified type, which contains
743     * many useful methods.
744     *
745     * @param type  the field type to get the property for
746     * @return the property object
747     * @throws IllegalArgumentException if the field is null or unsupported
748     */
749    public Property property(DateTimeFieldType type) {
750        return new Property(this, indexOfSupported(type));
751    }
752
753    //-----------------------------------------------------------------------
754    /**
755     * Get the month of year field property which provides access to advanced functionality.
756     * 
757     * @return the month of year property
758     */
759    public Property monthOfYear() {
760        return new Property(this, MONTH_OF_YEAR);
761    }
762
763    /**
764     * Get the day of month field property which provides access to advanced functionality.
765     * 
766     * @return the day of month property
767     */
768    public Property dayOfMonth() {
769        return new Property(this, DAY_OF_MONTH);
770    }
771
772    //-----------------------------------------------------------------------
773    /**
774     * Output the month-day in ISO8601 format (--MM-dd).
775     *
776     * @return ISO8601 time formatted string.
777     */
778    @ToString
779    public String toString() {
780        List<DateTimeFieldType> fields = new ArrayList<DateTimeFieldType>();
781        fields.add(DateTimeFieldType.monthOfYear());
782        fields.add(DateTimeFieldType.dayOfMonth());
783        return ISODateTimeFormat.forFields(fields, true, true).print(this);
784    }
785
786    /**
787     * Output the month-day using the specified format pattern.
788     *
789     * @param pattern  the pattern specification, null means use <code>toString</code>
790     * @see org.joda.time.format.DateTimeFormat
791     */
792    public String toString(String pattern) {
793        if (pattern == null) {
794            return toString();
795        }
796        return DateTimeFormat.forPattern(pattern).print(this);
797    }
798
799    /**
800     * Output the month-day using the specified format pattern.
801     *
802     * @param pattern  the pattern specification, null means use <code>toString</code>
803     * @param locale  Locale to use, null means default
804     * @see org.joda.time.format.DateTimeFormat
805     */
806    public String toString(String pattern, Locale locale) throws IllegalArgumentException {
807        if (pattern == null) {
808            return toString();
809        }
810        return DateTimeFormat.forPattern(pattern).withLocale(locale).print(this);
811    }
812
813    //-----------------------------------------------------------------------
814    /**
815     * The property class for <code>MonthDay</code>.
816     * <p>
817     * This class binds a <code>YearMonth</code> to a <code>DateTimeField</code>.
818     * 
819     * @author Chris Pheby
820     * @since 2.0
821     */
822    public static class Property extends AbstractPartialFieldProperty implements Serializable {
823
824        /** Serialization version */
825        private static final long serialVersionUID = 5727734012190224363L;
826
827        /** The partial */
828        private final MonthDay iBase;
829        /** The field index */
830        private final int iFieldIndex;
831
832        /**
833         * Constructs a property.
834         * 
835         * @param partial  the partial instance
836         * @param fieldIndex  the index in the partial
837         */
838        Property(MonthDay partial, int fieldIndex) {
839            super();
840            iBase = partial;
841            iFieldIndex = fieldIndex;
842        }
843
844        /**
845         * Gets the field that this property uses.
846         * 
847         * @return the field
848         */
849        public DateTimeField getField() {
850            return iBase.getField(iFieldIndex);
851        }
852
853        /**
854         * Gets the partial that this property belongs to.
855         * 
856         * @return the partial
857         */
858        protected ReadablePartial getReadablePartial() {
859            return iBase;
860        }
861
862        /**
863         * Gets the partial that this property belongs to.
864         * 
865         * @return the partial
866         */
867        public MonthDay getMonthDay() {
868            return iBase;
869        }
870
871        /**
872         * Gets the value of this field.
873         * 
874         * @return the field value
875         */
876        public int get() {
877            return iBase.getValue(iFieldIndex);
878        }
879
880        //-----------------------------------------------------------------------
881        /**
882         * Adds to the value of this field in a copy of this MonthDay.
883         * <p>
884         * The value will be added to this field. If the value is too large to be
885         * added solely to this field then it will affect larger fields.
886         * Smaller fields are unaffected.
887         * <p>
888         * The MonthDay attached to this property is unchanged by this call.
889         * Instead, a new instance is returned.
890         * 
891         * @param valueToAdd  the value to add to the field in the copy
892         * @return a copy of the MonthDay with the field value changed
893         * @throws IllegalArgumentException if the value isn't valid
894         */
895        public MonthDay addToCopy(int valueToAdd) {
896            int[] newValues = iBase.getValues();
897            newValues = getField().add(iBase, iFieldIndex, newValues, valueToAdd);
898            return new MonthDay(iBase, newValues);
899        }
900
901        /**
902         * Adds to the value of this field in a copy of this MonthDay wrapping
903         * within this field if the maximum value is reached.
904         * <p>
905         * The value will be added to this field. If the value is too large to be
906         * added solely to this field then it wraps within this field.
907         * Other fields are unaffected.
908         * <p>
909         * For example,
910         * <code>--12-30</code> addWrapField one month returns <code>--01-30</code>.
911         * <p>
912         * The MonthDay attached to this property is unchanged by this call.
913         * Instead, a new instance is returned.
914         * 
915         * @param valueToAdd  the value to add to the field in the copy
916         * @return a copy of the MonthDay with the field value changed
917         * @throws IllegalArgumentException if the value isn't valid
918         */
919        public MonthDay addWrapFieldToCopy(int valueToAdd) {
920            int[] newValues = iBase.getValues();
921            newValues = getField().addWrapField(iBase, iFieldIndex, newValues, valueToAdd);
922            return new MonthDay(iBase, newValues);
923        }
924
925        //-----------------------------------------------------------------------
926        /**
927         * Sets this field in a copy of the MonthDay.
928         * <p>
929         * The MonthDay attached to this property is unchanged by this call.
930         * Instead, a new instance is returned.
931         * 
932         * @param value  the value to set the field in the copy to
933         * @return a copy of the MonthDay with the field value changed
934         * @throws IllegalArgumentException if the value isn't valid
935         */
936        public MonthDay setCopy(int value) {
937            int[] newValues = iBase.getValues();
938            newValues = getField().set(iBase, iFieldIndex, newValues, value);
939            return new MonthDay(iBase, newValues);
940        }
941
942        /**
943         * Sets this field in a copy of the MonthDay to a parsed text value.
944         * <p>
945         * The MonthDay attached to this property is unchanged by this call.
946         * Instead, a new instance is returned.
947         * 
948         * @param text  the text value to set
949         * @param locale  optional locale to use for selecting a text symbol
950         * @return a copy of the MonthDay with the field value changed
951         * @throws IllegalArgumentException if the text value isn't valid
952         */
953        public MonthDay setCopy(String text, Locale locale) {
954            int[] newValues = iBase.getValues();
955            newValues = getField().set(iBase, iFieldIndex, newValues, text, locale);
956            return new MonthDay(iBase, newValues);
957        }
958
959        /**
960         * Sets this field in a copy of the MonthDay to a parsed text value.
961         * <p>
962         * The MonthDay attached to this property is unchanged by this call.
963         * Instead, a new instance is returned.
964         * 
965         * @param text  the text value to set
966         * @return a copy of the MonthDay with the field value changed
967         * @throws IllegalArgumentException if the text value isn't valid
968         */
969        public MonthDay setCopy(String text) {
970            return setCopy(text, null);
971        }
972    }
973
974}