001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp;
033
034import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH;
035import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR;
036
037import java.io.DataInput;
038import java.io.DataOutput;
039import java.io.IOException;
040import java.io.InvalidObjectException;
041import java.io.ObjectStreamException;
042import java.io.Serializable;
043import java.util.Objects;
044
045import org.threeten.bp.format.DateTimeFormatter;
046import org.threeten.bp.format.DateTimeFormatterBuilder;
047import org.threeten.bp.format.DateTimeParseException;
048import org.threeten.bp.jdk8.DefaultInterfaceTemporalAccessor;
049import org.threeten.bp.temporal.Chrono;
050import org.threeten.bp.temporal.ChronoField;
051import org.threeten.bp.temporal.ISOChrono;
052import org.threeten.bp.temporal.Temporal;
053import org.threeten.bp.temporal.TemporalAccessor;
054import org.threeten.bp.temporal.TemporalAdjuster;
055import org.threeten.bp.temporal.TemporalField;
056import org.threeten.bp.temporal.TemporalQueries;
057import org.threeten.bp.temporal.TemporalQuery;
058import org.threeten.bp.temporal.ValueRange;
059
060/**
061 * A month-day in the ISO-8601 calendar system, such as {@code --12-03}.
062 * <p>
063 * {@code MonthDay} is an immutable date-time object that represents the combination
064 * of a year and month. Any field that can be derived from a month and day, such as
065 * quarter-of-year, can be obtained.
066 * <p>
067 * This class does not store or represent a year, time or time-zone.
068 * For example, the value "December 3rd" can be stored in a {@code MonthDay}.
069 * <p>
070 * Since a {@code MonthDay} does not possess a year, the leap day of
071 * February 29th is considered valid.
072 * <p>
073 * This class implements {@link TemporalAccessor} rather than {@link Temporal}.
074 * This is because it is not possible to define whether February 29th is valid or not
075 * without external information, preventing the implementation of plus/minus.
076 * Related to this, {@code MonthDay} only provides access to query and set the fields
077 * {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH}.
078 * <p>
079 * The ISO-8601 calendar system is the modern civil calendar system used today
080 * in most of the world. It is equivalent to the proleptic Gregorian calendar
081 * system, in which todays's rules for leap years are applied for all time.
082 * For most applications written today, the ISO-8601 rules are entirely suitable.
083 * Any application that uses historical dates should consider using {@code HistoricDate}.
084 *
085 * <h3>Specification for implementors</h3>
086 * This class is immutable and thread-safe.
087 */
088public final class MonthDay
089        extends DefaultInterfaceTemporalAccessor
090        implements TemporalAccessor, TemporalAdjuster, Comparable<MonthDay>, Serializable {
091
092    /**
093     * Serialization version.
094     */
095    private static final long serialVersionUID = -939150713474957432L;
096    /**
097     * Parser.
098     */
099    private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
100        .appendLiteral("--")
101        .appendValue(MONTH_OF_YEAR, 2)
102        .appendLiteral('-')
103        .appendValue(DAY_OF_MONTH, 2)
104        .toFormatter();
105
106    /**
107     * The month-of-year, not null.
108     */
109    private final int month;
110    /**
111     * The day-of-month.
112     */
113    private final int day;
114
115    //-----------------------------------------------------------------------
116    /**
117     * Obtains the current month-day from the system clock in the default time-zone.
118     * <p>
119     * This will query the {@link Clock#systemDefaultZone() system clock} in the default
120     * time-zone to obtain the current month-day.
121     * <p>
122     * Using this method will prevent the ability to use an alternate clock for testing
123     * because the clock is hard-coded.
124     *
125     * @return the current month-day using the system clock and default time-zone, not null
126     */
127    public static MonthDay now() {
128        return now(Clock.systemDefaultZone());
129    }
130
131    /**
132     * Obtains the current month-day from the system clock in the specified time-zone.
133     * <p>
134     * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current month-day.
135     * Specifying the time-zone avoids dependence on the default time-zone.
136     * <p>
137     * Using this method will prevent the ability to use an alternate clock for testing
138     * because the clock is hard-coded.
139     *
140     * @param zone  the zone ID to use, not null
141     * @return the current month-day using the system clock, not null
142     */
143    public static MonthDay now(ZoneId zone) {
144        return now(Clock.system(zone));
145    }
146
147    /**
148     * Obtains the current month-day from the specified clock.
149     * <p>
150     * This will query the specified clock to obtain the current month-day.
151     * Using this method allows the use of an alternate clock for testing.
152     * The alternate clock may be introduced using {@link Clock dependency injection}.
153     *
154     * @param clock  the clock to use, not null
155     * @return the current month-day, not null
156     */
157    public static MonthDay now(Clock clock) {
158        final LocalDate now = LocalDate.now(clock);  // called once
159        return MonthDay.of(now.getMonth(), now.getDayOfMonth());
160    }
161
162    //-----------------------------------------------------------------------
163    /**
164     * Obtains an instance of {@code MonthDay}.
165     * <p>
166     * The day-of-month must be valid for the month within a leap year.
167     * Hence, for February, day 29 is valid.
168     * <p>
169     * For example, passing in April and day 31 will throw an exception, as
170     * there can never be April 31st in any year. By contrast, passing in
171     * February 29th is permitted, as that month-day can sometimes be valid.
172     *
173     * @param month  the month-of-year to represent, not null
174     * @param dayOfMonth  the day-of-month to represent, from 1 to 31
175     * @return the month-day, not null
176     * @throws DateTimeException if the value of any field is out of range
177     * @throws DateTimeException if the day-of-month is invalid for the month
178     */
179    public static MonthDay of(Month month, int dayOfMonth) {
180        Objects.requireNonNull(month, "month");
181        DAY_OF_MONTH.checkValidValue(dayOfMonth);
182        if (dayOfMonth > month.maxLength()) {
183            throw new DateTimeException("Illegal value for DayOfMonth field, value " + dayOfMonth +
184                    " is not valid for month " + month.name());
185        }
186        return new MonthDay(month.getValue(), dayOfMonth);
187    }
188
189    /**
190     * Obtains an instance of {@code MonthDay}.
191     * <p>
192     * The day-of-month must be valid for the month within a leap year.
193     * Hence, for month 2 (February), day 29 is valid.
194     * <p>
195     * For example, passing in month 4 (April) and day 31 will throw an exception, as
196     * there can never be April 31st in any year. By contrast, passing in
197     * February 29th is permitted, as that month-day can sometimes be valid.
198     *
199     * @param month  the month-of-year to represent, from 1 (January) to 12 (December)
200     * @param dayOfMonth  the day-of-month to represent, from 1 to 31
201     * @return the month-day, not null
202     * @throws DateTimeException if the value of any field is out of range
203     * @throws DateTimeException if the day-of-month is invalid for the month
204     */
205    public static MonthDay of(int month, int dayOfMonth) {
206        return of(Month.of(month), dayOfMonth);
207    }
208
209    //-----------------------------------------------------------------------
210    /**
211     * Obtains an instance of {@code MonthDay} from a temporal object.
212     * <p>
213     * A {@code TemporalAccessor} represents some form of date and time information.
214     * This factory converts the arbitrary temporal object to an instance of {@code MonthDay}.
215     * <p>
216     * The conversion extracts the {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} and
217     * {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} fields.
218     * The extraction is only permitted if the date-time has an ISO chronology.
219     * <p>
220     * This method matches the signature of the functional interface {@link TemporalQuery}
221     * allowing it to be used in queries via method reference, {@code MonthDay::from}.
222     *
223     * @param temporal  the temporal object to convert, not null
224     * @return the month-day, not null
225     * @throws DateTimeException if unable to convert to a {@code MonthDay}
226     */
227    public static MonthDay from(TemporalAccessor temporal) {
228        if (temporal instanceof MonthDay) {
229            return (MonthDay) temporal;
230        }
231        try {
232            if (ISOChrono.INSTANCE.equals(Chrono.from(temporal)) == false) {
233                temporal = LocalDate.from(temporal);
234            }
235            return of(temporal.get(MONTH_OF_YEAR), temporal.get(DAY_OF_MONTH));
236        } catch (DateTimeException ex) {
237            throw new DateTimeException("Unable to obtain MonthDay from TemporalAccessor: " + temporal.getClass(), ex);
238        }
239    }
240
241    //-----------------------------------------------------------------------
242    /**
243     * Obtains an instance of {@code MonthDay} from a text string such as {@code --12-03}.
244     * <p>
245     * The string must represent a valid month-day.
246     * The format is {@code --MM-dd}.
247     *
248     * @param text  the text to parse such as "--12-03", not null
249     * @return the parsed month-day, not null
250     * @throws DateTimeParseException if the text cannot be parsed
251     */
252    public static MonthDay parse(CharSequence text) {
253        return parse(text, PARSER);
254    }
255
256    /**
257     * Obtains an instance of {@code MonthDay} from a text string using a specific formatter.
258     * <p>
259     * The text is parsed using the formatter, returning a month-day.
260     *
261     * @param text  the text to parse, not null
262     * @param formatter  the formatter to use, not null
263     * @return the parsed month-day, not null
264     * @throws DateTimeParseException if the text cannot be parsed
265     */
266    public static MonthDay parse(CharSequence text, DateTimeFormatter formatter) {
267        Objects.requireNonNull(formatter, "formatter");
268        return formatter.parse(text, MonthDay.class);
269    }
270
271    //-----------------------------------------------------------------------
272    /**
273     * Constructor, previously validated.
274     *
275     * @param month  the month-of-year to represent, validated from 1 to 12
276     * @param dayOfMonth  the day-of-month to represent, validated from 1 to 29-31
277     */
278    private MonthDay(int month, int dayOfMonth) {
279        this.month = month;
280        this.day = dayOfMonth;
281    }
282
283    //-----------------------------------------------------------------------
284    /**
285     * Checks if the specified field is supported.
286     * <p>
287     * This checks if this month-day can be queried for the specified field.
288     * If false, then calling the {@link #range(TemporalField) range} and
289     * {@link #get(TemporalField) get} methods will throw an exception.
290     * <p>
291     * If the field is a {@link ChronoField} then the query is implemented here.
292     * The {@link #isSupported(TemporalField) supported fields} will return valid
293     * values based on this date-time.
294     * The supported fields are:
295     * <ul>
296     * <li>{@code MONTH_OF_YEAR}
297     * <li>{@code YEAR}
298     * </ul>
299     * All other {@code ChronoField} instances will return false.
300     * <p>
301     * If the field is not a {@code ChronoField}, then the result of this method
302     * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)}
303     * passing {@code this} as the argument.
304     * Whether the field is supported is determined by the field.
305     *
306     * @param field  the field to check, null returns false
307     * @return true if the field is supported on this month-day, false if not
308     */
309    @Override
310    public boolean isSupported(TemporalField field) {
311        if (field instanceof ChronoField) {
312            return field == MONTH_OF_YEAR || field == DAY_OF_MONTH;
313        }
314        return field != null && field.doIsSupported(this);
315    }
316
317    /**
318     * Gets the range of valid values for the specified field.
319     * <p>
320     * The range object expresses the minimum and maximum valid values for a field.
321     * This month-day is used to enhance the accuracy of the returned range.
322     * If it is not possible to return the range, because the field is not supported
323     * or for some other reason, an exception is thrown.
324     * <p>
325     * If the field is a {@link ChronoField} then the query is implemented here.
326     * The {@link #isSupported(TemporalField) supported fields} will return
327     * appropriate range instances.
328     * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
329     * <p>
330     * If the field is not a {@code ChronoField}, then the result of this method
331     * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)}
332     * passing {@code this} as the argument.
333     * Whether the range can be obtained is determined by the field.
334     *
335     * @param field  the field to query the range for, not null
336     * @return the range of valid values for the field, not null
337     * @throws DateTimeException if the range for the field cannot be obtained
338     */
339    @Override
340    public ValueRange range(TemporalField field) {
341        if (field == MONTH_OF_YEAR) {
342            return field.range();
343        } else if (field == DAY_OF_MONTH) {
344            return ValueRange.of(1, getMonth().minLength(), getMonth().maxLength());
345        }
346        return super.range(field);
347    }
348
349    /**
350     * Gets the value of the specified field from this month-day as an {@code int}.
351     * <p>
352     * This queries this month-day for the value for the specified field.
353     * The returned value will always be within the valid range of values for the field.
354     * If it is not possible to return the value, because the field is not supported
355     * or for some other reason, an exception is thrown.
356     * <p>
357     * If the field is a {@link ChronoField} then the query is implemented here.
358     * The {@link #isSupported(TemporalField) supported fields} will return valid
359     * values based on this month-day.
360     * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
361     * <p>
362     * If the field is not a {@code ChronoField}, then the result of this method
363     * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)}
364     * passing {@code this} as the argument. Whether the value can be obtained,
365     * and what the value represents, is determined by the field.
366     *
367     * @param field  the field to get, not null
368     * @return the value for the field
369     * @throws DateTimeException if a value for the field cannot be obtained
370     * @throws ArithmeticException if numeric overflow occurs
371     */
372    @Override  // override for Javadoc
373    public int get(TemporalField field) {
374        return range(field).checkValidIntValue(getLong(field), field);
375    }
376
377    /**
378     * Gets the value of the specified field from this month-day as a {@code long}.
379     * <p>
380     * This queries this month-day for the value for the specified field.
381     * If it is not possible to return the value, because the field is not supported
382     * or for some other reason, an exception is thrown.
383     * <p>
384     * If the field is a {@link ChronoField} then the query is implemented here.
385     * The {@link #isSupported(TemporalField) supported fields} will return valid
386     * values based on this month-day.
387     * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
388     * <p>
389     * If the field is not a {@code ChronoField}, then the result of this method
390     * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)}
391     * passing {@code this} as the argument. Whether the value can be obtained,
392     * and what the value represents, is determined by the field.
393     *
394     * @param field  the field to get, not null
395     * @return the value for the field
396     * @throws DateTimeException if a value for the field cannot be obtained
397     * @throws ArithmeticException if numeric overflow occurs
398     */
399    @Override
400    public long getLong(TemporalField field) {
401        if (field instanceof ChronoField) {
402            switch ((ChronoField) field) {
403                // alignedDOW and alignedWOM not supported because they cannot be set in with()
404                case DAY_OF_MONTH: return day;
405                case MONTH_OF_YEAR: return month;
406            }
407            throw new DateTimeException("Unsupported field: " + field.getName());
408        }
409        return field.doGet(this);
410    }
411
412    //-----------------------------------------------------------------------
413    /**
414     * Gets the month-of-year field using the {@code Month} enum.
415     * <p>
416     * This method returns the enum {@link Month} for the month.
417     * This avoids confusion as to what {@code int} values mean.
418     * If you need access to the primitive {@code int} value then the enum
419     * provides the {@link Month#getValue() int value}.
420     *
421     * @return the month-of-year, not null
422     */
423    public Month getMonth() {
424        return Month.of(month);
425    }
426
427    /**
428     * Gets the day-of-month field.
429     * <p>
430     * This method returns the primitive {@code int} value for the day-of-month.
431     *
432     * @return the day-of-month, from 1 to 31
433     */
434    public int getDayOfMonth() {
435        return day;
436    }
437
438    //-----------------------------------------------------------------------
439    /**
440     * Checks if the year is valid for this month-day.
441     * <p>
442     * This method checks whether this month and day and the input year form
443     * a valid date. This can only return false for February 29th.
444     *
445     * @param year  the year to validate, an out of range value returns false
446     * @return true if the year is valid for this month-day
447     * @see Year#isValidMonthDay(MonthDay)
448     */
449    public boolean isValidYear(int year) {
450        return (day == 29 && month == 2 && Year.isLeap(year) == false) == false;
451    }
452
453    //-----------------------------------------------------------------------
454    /**
455     * Returns a copy of this {@code MonthDay} with the month-of-year altered.
456     * <p>
457     * This returns a month-day with the specified month.
458     * If the day-of-month is invalid for the specified month, the day will
459     * be adjusted to the last valid day-of-month.
460     * <p>
461     * This instance is immutable and unaffected by this method call.
462     *
463     * @param month  the month-of-year to set in the returned month-day, from 1 (January) to 12 (December)
464     * @return a {@code MonthDay} based on this month-day with the requested month, not null
465     * @throws DateTimeException if the month-of-year value is invalid
466     */
467    public MonthDay withMonth(int month) {
468        return with(Month.of(month));
469    }
470
471    /**
472    * Returns a copy of this {@code MonthDay} with the month-of-year altered.
473    * <p>
474    * This returns a month-day with the specified month.
475    * If the day-of-month is invalid for the specified month, the day will
476    * be adjusted to the last valid day-of-month.
477    * <p>
478    * This instance is immutable and unaffected by this method call.
479    *
480    * @param month  the month-of-year to set in the returned month-day, not null
481    * @return a {@code MonthDay} based on this month-day with the requested month, not null
482    */
483    public MonthDay with(Month month) {
484        Objects.requireNonNull(month, "month");
485        if (month.getValue() == this.month) {
486            return this;
487        }
488        int day = Math.min(this.day, month.maxLength());
489        return new MonthDay(month.getValue(), day);
490    }
491
492    /**
493     * Returns a copy of this {@code MonthDay} with the day-of-month altered.
494     * <p>
495     * This returns a month-day with the specified day-of-month.
496     * If the day-of-month is invalid for the month, an exception is thrown.
497     * <p>
498     * This instance is immutable and unaffected by this method call.
499     *
500     * @param dayOfMonth  the day-of-month to set in the return month-day, from 1 to 31
501     * @return a {@code MonthDay} based on this month-day with the requested day, not null
502     * @throws DateTimeException if the day-of-month value is invalid
503     * @throws DateTimeException if the day-of-month is invalid for the month
504     */
505    public MonthDay withDayOfMonth(int dayOfMonth) {
506        if (dayOfMonth == this.day) {
507            return this;
508        }
509        return of(month, dayOfMonth);
510    }
511
512    //-----------------------------------------------------------------------
513    /**
514     * Queries this month-day using the specified query.
515     * <p>
516     * This queries this month-day using the specified query strategy object.
517     * The {@code TemporalQuery} object defines the logic to be used to
518     * obtain the result. Read the documentation of the query to understand
519     * what the result of this method will be.
520     * <p>
521     * The result of this method is obtained by invoking the
522     * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
523     * specified query passing {@code this} as the argument.
524     *
525     * @param <R> the type of the result
526     * @param query  the query to invoke, not null
527     * @return the query result, null may be returned (defined by the query)
528     * @throws DateTimeException if unable to query (defined by the query)
529     * @throws ArithmeticException if numeric overflow occurs (defined by the query)
530     */
531    @SuppressWarnings("unchecked")
532    @Override
533    public <R> R query(TemporalQuery<R> query) {
534        if (query == TemporalQueries.chrono()) {
535            return (R) ISOChrono.INSTANCE;
536        }
537        return super.query(query);
538    }
539
540    /**
541     * Adjusts the specified temporal object to have this month-day.
542     * <p>
543     * This returns a temporal object of the same observable type as the input
544     * with the month and day-of-month changed to be the same as this.
545     * <p>
546     * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)}
547     * twice, passing {@link ChronoField#MONTH_OF_YEAR} and
548     * {@link ChronoField#DAY_OF_MONTH} as the fields.
549     * If the specified temporal object does not use the ISO calendar system then
550     * a {@code DateTimeException} is thrown.
551     * <p>
552     * In most cases, it is clearer to reverse the calling pattern by using
553     * {@link Temporal#with(TemporalAdjuster)}:
554     * <pre>
555     *   // these two lines are equivalent, but the second approach is recommended
556     *   temporal = thisMonthDay.adjustInto(temporal);
557     *   temporal = temporal.with(thisMonthDay);
558     * </pre>
559     * <p>
560     * This instance is immutable and unaffected by this method call.
561     *
562     * @param temporal  the target object to be adjusted, not null
563     * @return the adjusted object, not null
564     * @throws DateTimeException if unable to make the adjustment
565     * @throws ArithmeticException if numeric overflow occurs
566     */
567    @Override
568    public Temporal adjustInto(Temporal temporal) {
569        if (Chrono.from(temporal).equals(ISOChrono.INSTANCE) == false) {
570            throw new DateTimeException("Adjustment only supported on ISO date-time");
571        }
572        temporal = temporal.with(MONTH_OF_YEAR, month);
573        return temporal.with(DAY_OF_MONTH, Math.min(temporal.range(DAY_OF_MONTH).getMaximum(), day));
574    }
575
576    //-----------------------------------------------------------------------
577    /**
578     * Returns a date formed from this month-day at the specified year.
579     * <p>
580     * This combines this month-day and the specified year to form a {@code LocalDate}.
581     * A month-day of February 29th will be adjusted to February 28th in the resulting
582     * date if the year is not a leap year.
583     * <p>
584     * This instance is immutable and unaffected by this method call.
585     *
586     * @param year  the year to use, from MIN_YEAR to MAX_YEAR
587     * @return the local date formed from this month-day and the specified year, not null
588     * @see Year#atMonthDay(MonthDay)
589     */
590    public LocalDate atYear(int year) {
591        return LocalDate.of(year, month, isValidYear(year) ? day : 28);
592    }
593
594    //-----------------------------------------------------------------------
595    /**
596     * Compares this month-day to another month-day.
597     * <p>
598     * The comparison is based first on value of the month, then on the value of the day.
599     * It is "consistent with equals", as defined by {@link Comparable}.
600     *
601     * @param other  the other month-day to compare to, not null
602     * @return the comparator value, negative if less, positive if greater
603     */
604    public int compareTo(MonthDay other) {
605        int cmp = (month - other.month);
606        if (cmp == 0) {
607            cmp = (day - other.day);
608        }
609        return cmp;
610    }
611
612    /**
613     * Is this month-day after the specified month-day.
614     *
615     * @param other  the other month-day to compare to, not null
616     * @return true if this is after the specified month-day
617     */
618    public boolean isAfter(MonthDay other) {
619        return compareTo(other) > 0;
620    }
621
622    /**
623     * Is this month-day before the specified month-day.
624     *
625     * @param other  the other month-day to compare to, not null
626     * @return true if this point is before the specified month-day
627     */
628    public boolean isBefore(MonthDay other) {
629        return compareTo(other) < 0;
630    }
631
632    //-----------------------------------------------------------------------
633    /**
634     * Checks if this month-day is equal to another month-day.
635     * <p>
636     * The comparison is based on the time-line position of the month-day within a year.
637     *
638     * @param obj  the object to check, null returns false
639     * @return true if this is equal to the other month-day
640     */
641    @Override
642    public boolean equals(Object obj) {
643        if (this == obj) {
644            return true;
645        }
646        if (obj instanceof MonthDay) {
647            MonthDay other = (MonthDay) obj;
648            return month == other.month && day == other.day;
649        }
650        return false;
651    }
652
653    /**
654     * A hash code for this month-day.
655     *
656     * @return a suitable hash code
657     */
658    @Override
659    public int hashCode() {
660        return (month << 6) + day;
661    }
662
663    //-----------------------------------------------------------------------
664    /**
665     * Outputs this month-day as a {@code String}, such as {@code --12-03}.
666     * <p>
667     * The output will be in the format {@code --MM-dd}:
668     *
669     * @return a string representation of this month-day, not null
670     */
671    @Override
672    public String toString() {
673        return new StringBuilder(10).append("--")
674            .append(month < 10 ? "0" : "").append(month)
675            .append(day < 10 ? "-0" : "-").append(day)
676            .toString();
677    }
678
679    /**
680     * Outputs this month-day as a {@code String} using the formatter.
681     * <p>
682     * This month-day will be passed to the formatter
683     * {@link DateTimeFormatter#print(TemporalAccessor) print method}.
684     *
685     * @param formatter  the formatter to use, not null
686     * @return the formatted month-day string, not null
687     * @throws DateTimeException if an error occurs during printing
688     */
689    public String toString(DateTimeFormatter formatter) {
690        Objects.requireNonNull(formatter, "formatter");
691        return formatter.print(this);
692    }
693
694    //-----------------------------------------------------------------------
695    private Object writeReplace() {
696        return new Ser(Ser.MONTH_DAY_TYPE, this);
697    }
698
699    /**
700     * Defend against malicious streams.
701     * @return never
702     * @throws InvalidObjectException always
703     */
704    private Object readResolve() throws ObjectStreamException {
705        throw new InvalidObjectException("Deserialization via serialization delegate");
706    }
707
708    void writeExternal(DataOutput out) throws IOException {
709        out.writeByte(month);
710        out.writeByte(day);
711    }
712
713    static MonthDay readExternal(DataInput in) throws IOException {
714        byte month = in.readByte();
715        byte day = in.readByte();
716        return MonthDay.of(month, day);
717    }
718
719}