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}