1 /* 2 * hunt-time: A time library for D programming language. 3 * 4 * Copyright (C) 2015-2018 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.time.MonthDay; 13 14 import hunt.time.temporal.ChronoField; 15 16 import hunt.stream.DataInput; 17 import hunt.stream.DataOutput; 18 import hunt.Exceptions; 19 20 //import hunt.io.ObjectInputStream; 21 import hunt.stream.Common; 22 import hunt.time.chrono.Chronology; 23 import hunt.time.chrono.IsoChronology; 24 // import hunt.time.format.DateTimeFormatter; 25 // import hunt.time.format.DateTimeFormatterBuilder; 26 import hunt.time.format.DateTimeParseException; 27 import hunt.time.temporal.ChronoField; 28 import hunt.time.temporal.Temporal; 29 import hunt.time.temporal.TemporalAccessor; 30 import hunt.time.temporal.TemporalAdjuster; 31 import hunt.time.temporal.TemporalField; 32 import hunt.time.temporal.TemporalQueries; 33 import hunt.time.temporal.TemporalQuery; 34 import hunt.time.Exceptions; 35 import hunt.time.temporal.ValueRange; 36 import hunt.time.ZoneId; 37 import hunt.time.Clock; 38 import hunt.time.Month; 39 import hunt.time.LocalDate; 40 import hunt.time.Exceptions; 41 import hunt.time.Year; 42 import hunt.time.Month; 43 import hunt.time.Ser; 44 import hunt.time.util.Common; 45 import hunt.math.Helper; 46 import hunt.Functions; 47 import hunt.util.StringBuilder; 48 import hunt.util.Common; 49 50 import std.conv; 51 /** 52 * A month-day _in the ISO-8601 calendar system, such as {@code --12-03}. 53 * !(p) 54 * {@code MonthDay} is an immutable date-time object that represents the combination 55 * of a month and day-of-month. Any field that can be derived from a month and day, 56 * such as quarter-of-year, can be obtained. 57 * !(p) 58 * This class does not store or represent a year, time or time-zone. 59 * For example, the value "December 3rd" can be stored _in a {@code MonthDay}. 60 * !(p) 61 * Since a {@code MonthDay} does not possess a year, the leap day of 62 * February 29th is considered valid. 63 * !(p) 64 * This class implements {@link TemporalAccessor} rather than {@link Temporal}. 65 * This is because it is not possible to define whether February 29th is valid or not 66 * without external information, preventing the implementation of plus/minus. 67 * Related to this, {@code MonthDay} only provides access to query and set the fields 68 * {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH}. 69 * !(p) 70 * The ISO-8601 calendar system is the modern civil calendar system used today 71 * _in most of the world. It is equivalent to the proleptic Gregorian calendar 72 * system, _in which today's rules for leap years are applied for all time. 73 * For most applications written today, the ISO-8601 rules are entirely suitable. 74 * However, any application that makes use of historical dates, and requires them 75 * to be accurate will find the ISO-8601 approach unsuitable. 76 * 77 * !(p) 78 * This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a> 79 * class; use of identity-sensitive operations (including reference equality 80 * ({@code ==}), identity hash code, or synchronization) on instances of 81 * {@code MonthDay} may have unpredictable results and should be avoided. 82 * The {@code equals} method should be used for comparisons. 83 * 84 * @implSpec 85 * This class is immutable and thread-safe. 86 * 87 * @since 1.8 88 */ 89 final class MonthDay 90 : TemporalAccessor, TemporalAdjuster, Comparable!(MonthDay) { // , Serializable 91 92 /** 93 * Parser. 94 */ 95 // __gshared DateTimeFormatter _PARSER; 96 97 /** 98 * The month-of-year, not null. 99 */ 100 private int month; 101 /** 102 * The day-of-month. 103 */ 104 private int day; 105 106 // static ref DateTimeFormatter PARSER() 107 // { 108 // if(_PARSER is null) 109 // { 110 // _PARSER = new DateTimeFormatterBuilder() 111 // .appendLiteral("--") 112 // .appendValue(ChronoField.MONTH_OF_YEAR, 2) 113 // .appendLiteral('-') 114 // .appendValue(ChronoField.DAY_OF_MONTH, 2) 115 // .toFormatter(); 116 // } 117 // return _PARSER; 118 // } 119 120 // shared static this() 121 // { 122 // PARSER = new DateTimeFormatterBuilder() 123 // .appendLiteral("--") 124 // .appendValue(ChronoField.MONTH_OF_YEAR, 2) 125 // .appendLiteral('-') 126 // .appendValue(ChronoField.DAY_OF_MONTH, 2) 127 // .toFormatter(); 128 // mixin(MakeGlobalVar!(DateTimeFormatter)("PARSER",`new DateTimeFormatterBuilder() 129 // .appendLiteral("--") 130 // .appendValue(ChronoField.MONTH_OF_YEAR, 2) 131 // .appendLiteral('-') 132 // .appendValue(ChronoField.DAY_OF_MONTH, 2) 133 // .toFormatter()`)); 134 // } 135 136 //----------------------------------------------------------------------- 137 /** 138 * Obtains the current month-day from the system clock _in the default time-zone. 139 * !(p) 140 * This will query the {@link Clock#systemDefaultZone() system clock} _in the default 141 * time-zone to obtain the current month-day. 142 * !(p) 143 * Using this method will prevent the ability to use an alternate clock for testing 144 * because the clock is hard-coded. 145 * 146 * @return the current month-day using the system clock and default time-zone, not null 147 */ 148 static MonthDay now() { 149 return now(Clock.systemDefaultZone()); 150 } 151 152 /** 153 * Obtains the current month-day from the system clock _in the specified time-zone. 154 * !(p) 155 * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current month-day. 156 * Specifying the time-zone avoids dependence on the default time-zone. 157 * !(p) 158 * Using this method will prevent the ability to use an alternate clock for testing 159 * because the clock is hard-coded. 160 * 161 * @param zone the zone ID to use, not null 162 * @return the current month-day using the system clock, not null 163 */ 164 static MonthDay now(ZoneId zone) { 165 return now(Clock.system(zone)); 166 } 167 168 /** 169 * Obtains the current month-day from the specified clock. 170 * !(p) 171 * This will query the specified clock to obtain the current month-day. 172 * Using this method allows the use of an alternate clock for testing. 173 * The alternate clock may be introduced using {@link Clock dependency injection}. 174 * 175 * @param clock the clock to use, not null 176 * @return the current month-day, not null 177 */ 178 static MonthDay now(Clock clock) { 179 LocalDate now = LocalDate.now(clock); // called once 180 return MonthDay.of(now.getMonth(), now.getDayOfMonth()); 181 } 182 183 //----------------------------------------------------------------------- 184 /** 185 * Obtains an instance of {@code MonthDay}. 186 * !(p) 187 * The day-of-month must be valid for the month within a leap year. 188 * Hence, for February, day 29 is valid. 189 * !(p) 190 * For example, passing _in April and day 31 will throw an exception, as 191 * there can never be April 31st _in any year. By contrast, passing _in 192 * February 29th is permitted, as that month-day can sometimes be valid. 193 * 194 * @param month the month-of-year to represent, not null 195 * @param dayOfMonth the day-of-month to represent, from 1 to 31 196 * @return the month-day, not null 197 * @throws DateTimeException if the value of any field is _out of range, 198 * or if the day-of-month is invalid for the month 199 */ 200 static MonthDay of(Month month, int dayOfMonth) { 201 assert(month, "month"); 202 ChronoField.DAY_OF_MONTH.checkValidValue(dayOfMonth); 203 if (dayOfMonth > month.maxLength()) { 204 throw new DateTimeException("Illegal value for DayOfMonth field, value " ~ dayOfMonth.to!string ~ 205 " is not valid for month " ~ month.name()); 206 } 207 return new MonthDay(month.getValue(), dayOfMonth); 208 } 209 210 /** 211 * Obtains an instance of {@code MonthDay}. 212 * !(p) 213 * The day-of-month must be valid for the month within a leap year. 214 * Hence, for month 2 (February), day 29 is valid. 215 * !(p) 216 * For example, passing _in month 4 (April) and day 31 will throw an exception, as 217 * there can never be April 31st _in any year. By contrast, passing _in 218 * February 29th is permitted, as that month-day can sometimes be valid. 219 * 220 * @param month the month-of-year to represent, from 1 (January) to 12 (December) 221 * @param dayOfMonth the day-of-month to represent, from 1 to 31 222 * @return the month-day, not null 223 * @throws DateTimeException if the value of any field is _out of range, 224 * or if the day-of-month is invalid for the month 225 */ 226 static MonthDay of(int month, int dayOfMonth) { 227 return of(Month.of(month), dayOfMonth); 228 } 229 230 //----------------------------------------------------------------------- 231 /** 232 * Obtains an instance of {@code MonthDay} from a temporal object. 233 * !(p) 234 * This obtains a month-day based on the specified temporal. 235 * A {@code TemporalAccessor} represents an arbitrary set of date and time information, 236 * which this factory converts to an instance of {@code MonthDay}. 237 * !(p) 238 * The conversion extracts the {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} and 239 * {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} fields. 240 * The extraction is only permitted if the temporal object has an ISO 241 * chronology, or can be converted to a {@code LocalDate}. 242 * !(p) 243 * This method matches the signature of the functional interface {@link TemporalQuery} 244 * allowing it to be used as a query via method reference, {@code MonthDay::from}. 245 * 246 * @param temporal the temporal object to convert, not null 247 * @return the month-day, not null 248 * @throws DateTimeException if unable to convert to a {@code MonthDay} 249 */ 250 static MonthDay from(TemporalAccessor temporal) { 251 if (cast(MonthDay)(temporal) !is null) { 252 return cast(MonthDay) temporal; 253 } 254 try { 255 if ((IsoChronology.INSTANCE == Chronology.from(temporal)) == false) { 256 temporal = LocalDate.from(temporal); 257 } 258 return of(temporal.get(ChronoField.MONTH_OF_YEAR), temporal.get(ChronoField.DAY_OF_MONTH)); 259 } catch (DateTimeException ex) { 260 throw new DateTimeException("Unable to obtain MonthDay from TemporalAccessor: " ~ 261 typeid(temporal).name ~ " of type " ~ typeid(temporal).stringof, ex); 262 } 263 } 264 265 //----------------------------------------------------------------------- 266 /** 267 * Obtains an instance of {@code MonthDay} from a text string such as {@code --12-03}. 268 * !(p) 269 * The string must represent a valid month-day. 270 * The format is {@code --MM-dd}. 271 * 272 * @param text the text to parse such as "--12-03", not null 273 * @return the parsed month-day, not null 274 * @throws DateTimeParseException if the text cannot be parsed 275 */ 276 // static MonthDay parse(string text) { 277 // return parse(text, MonthDay.PARSER()); 278 // } 279 280 /** 281 * Obtains an instance of {@code MonthDay} from a text string using a specific formatter. 282 * !(p) 283 * The text is parsed using the formatter, returning a month-day. 284 * 285 * @param text the text to parse, not null 286 * @param formatter the formatter to use, not null 287 * @return the parsed month-day, not null 288 * @throws DateTimeParseException if the text cannot be parsed 289 */ 290 // static MonthDay parse(string text, DateTimeFormatter formatter) { 291 // assert(formatter, "formatter"); 292 // return formatter.parse(text, new class TemporalQuery!MonthDay{ 293 // MonthDay queryFrom(TemporalAccessor temporal) 294 // { 295 // if (cast(MonthDay)(temporal) !is null) { 296 // return cast(MonthDay) temporal; 297 // } 298 // try { 299 // if ((IsoChronology.INSTANCE == Chronology.from(temporal)) == false) { 300 // temporal = LocalDate.from(temporal); 301 // } 302 // return of(temporal.get(ChronoField.MONTH_OF_YEAR), temporal.get(ChronoField.DAY_OF_MONTH)); 303 // } catch (DateTimeException ex) { 304 // throw new DateTimeException("Unable to obtain MonthDay from TemporalAccessor: " ~ 305 // typeid(temporal).name ~ " of type " ~ typeid(temporal).stringof, ex); 306 // } 307 // } 308 // }); 309 // } 310 311 //----------------------------------------------------------------------- 312 /** 313 * Constructor, previously validated. 314 * 315 * @param month the month-of-year to represent, validated from 1 to 12 316 * @param dayOfMonth the day-of-month to represent, validated from 1 to 29-31 317 */ 318 private this(int month, int dayOfMonth) { 319 this.month = month; 320 this.day = dayOfMonth; 321 } 322 323 //----------------------------------------------------------------------- 324 /** 325 * Checks if the specified field is supported. 326 * !(p) 327 * This checks if this month-day can be queried for the specified field. 328 * If false, then calling the {@link #range(TemporalField) range} and 329 * {@link #get(TemporalField) get} methods will throw an exception. 330 * !(p) 331 * If the field is a {@link ChronoField} then the query is implemented here. 332 * The supported fields are: 333 * !(ul) 334 * !(li){@code MONTH_OF_YEAR} 335 * !(li){@code YEAR} 336 * </ul> 337 * All other {@code ChronoField} instances will return false. 338 * !(p) 339 * If the field is not a {@code ChronoField}, then the result of this method 340 * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)} 341 * passing {@code this} as the argument. 342 * Whether the field is supported is determined by the field. 343 * 344 * @param field the field to check, null returns false 345 * @return true if the field is supported on this month-day, false if not 346 */ 347 override 348 bool isSupported(TemporalField field) { 349 if (cast(ChronoField)(field) !is null) { 350 return field == ChronoField.MONTH_OF_YEAR || field == ChronoField.DAY_OF_MONTH; 351 } 352 return field !is null && field.isSupportedBy(this); 353 } 354 355 /** 356 * Gets the range of valid values for the specified field. 357 * !(p) 358 * The range object expresses the minimum and maximum valid values for a field. 359 * This month-day is used to enhance the accuracy of the returned range. 360 * If it is not possible to return the range, because the field is not supported 361 * or for some other reason, an exception is thrown. 362 * !(p) 363 * If the field is a {@link ChronoField} then the query is implemented here. 364 * The {@link #isSupported(TemporalField) supported fields} will return 365 * appropriate range instances. 366 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 367 * !(p) 368 * If the field is not a {@code ChronoField}, then the result of this method 369 * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} 370 * passing {@code this} as the argument. 371 * Whether the range can be obtained is determined by the field. 372 * 373 * @param field the field to query the range for, not null 374 * @return the range of valid values for the field, not null 375 * @throws DateTimeException if the range for the field cannot be obtained 376 * @throws UnsupportedTemporalTypeException if the field is not supported 377 */ 378 override 379 ValueRange range(TemporalField field) { 380 if (field == ChronoField.MONTH_OF_YEAR) { 381 return field.range(); 382 } else if (field == ChronoField.DAY_OF_MONTH) { 383 return ValueRange.of(1, getMonth().minLength(), getMonth().maxLength()); 384 } 385 return /* TemporalAccessor. super.*/super_range(field); 386 } 387 ValueRange super_range(TemporalField field) { 388 if (cast(ChronoField)(field) !is null) { 389 if (isSupported(field)) { 390 return field.range(); 391 } 392 throw new UnsupportedTemporalTypeException("Unsupported field: " ~ typeid(field).name); 393 } 394 assert(field, "field"); 395 return field.rangeRefinedBy(this); 396 } 397 398 /** 399 * Gets the value of the specified field from this month-day as an {@code int}. 400 * !(p) 401 * This queries this month-day for the value of the specified field. 402 * The returned value will always be within the valid range of values for the field. 403 * If it is not possible to return the value, because the field is not supported 404 * or for some other reason, an exception is thrown. 405 * !(p) 406 * If the field is a {@link ChronoField} then the query is implemented here. 407 * The {@link #isSupported(TemporalField) supported fields} will return valid 408 * values based on this month-day. 409 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 410 * !(p) 411 * If the field is not a {@code ChronoField}, then the result of this method 412 * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} 413 * passing {@code this} as the argument. Whether the value can be obtained, 414 * and what the value represents, is determined by the field. 415 * 416 * @param field the field to get, not null 417 * @return the value for the field 418 * @throws DateTimeException if a value for the field cannot be obtained or 419 * the value is outside the range of valid values for the field 420 * @throws UnsupportedTemporalTypeException if the field is not supported or 421 * the range of values exceeds an {@code int} 422 * @throws ArithmeticException if numeric overflow occurs 423 */ 424 override // override for Javadoc 425 int get(TemporalField field) { 426 return range(field).checkValidIntValue(getLong(field), field); 427 } 428 429 /** 430 * Gets the value of the specified field from this month-day as a {@code long}. 431 * !(p) 432 * This queries this month-day for the value of the specified field. 433 * If it is not possible to return the value, because the field is not supported 434 * or for some other reason, an exception is thrown. 435 * !(p) 436 * If the field is a {@link ChronoField} then the query is implemented here. 437 * The {@link #isSupported(TemporalField) supported fields} will return valid 438 * values based on this month-day. 439 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 440 * !(p) 441 * If the field is not a {@code ChronoField}, then the result of this method 442 * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} 443 * passing {@code this} as the argument. Whether the value can be obtained, 444 * and what the value represents, is determined by the field. 445 * 446 * @param field the field to get, not null 447 * @return the value for the field 448 * @throws DateTimeException if a value for the field cannot be obtained 449 * @throws UnsupportedTemporalTypeException if the field is not supported 450 * @throws ArithmeticException if numeric overflow occurs 451 */ 452 override 453 long getLong(TemporalField field) { 454 if (cast(ChronoField)(field) !is null) { 455 auto f = cast(ChronoField) field; 456 { 457 // alignedDOW and alignedWOM not supported because they cannot be set _in _with() 458 if( f == ChronoField.DAY_OF_MONTH) return day; 459 if( f == ChronoField.MONTH_OF_YEAR) return month; 460 } 461 throw new UnsupportedTemporalTypeException("Unsupported field: " ~ f.toString); 462 } 463 return field.getFrom(this); 464 } 465 466 //----------------------------------------------------------------------- 467 /** 468 * Gets the month-of-year field from 1 to 12. 469 * !(p) 470 * This method returns the month as an {@code int} from 1 to 12. 471 * Application code is frequently clearer if the enum {@link Month} 472 * is used by calling {@link #getMonth()}. 473 * 474 * @return the month-of-year, from 1 to 12 475 * @see #getMonth() 476 */ 477 int getMonthValue() { 478 return month; 479 } 480 481 /** 482 * Gets the month-of-year field using the {@code Month} enum. 483 * !(p) 484 * This method returns the enum {@link Month} for the month. 485 * This avoids confusion as to what {@code int} values mean. 486 * If you need access to the primitive {@code int} value then the enum 487 * provides the {@link Month#getValue() int value}. 488 * 489 * @return the month-of-year, not null 490 * @see #getMonthValue() 491 */ 492 Month getMonth() { 493 return Month.of(month); 494 } 495 496 /** 497 * Gets the day-of-month field. 498 * !(p) 499 * This method returns the primitive {@code int} value for the day-of-month. 500 * 501 * @return the day-of-month, from 1 to 31 502 */ 503 int getDayOfMonth() { 504 return day; 505 } 506 507 //----------------------------------------------------------------------- 508 /** 509 * Checks if the year is valid for this month-day. 510 * !(p) 511 * This method checks whether this month and day and the input year form 512 * a valid date. This can only return false for February 29th. 513 * 514 * @param year the year to validate 515 * @return true if the year is valid for this month-day 516 * @see Year#isValidMonthDay(MonthDay) 517 */ 518 bool isValidYear(int year) { 519 return (day == 29 && month == 2 && Year.isLeap(year) == false) == false; 520 } 521 522 //----------------------------------------------------------------------- 523 /** 524 * Returns a copy of this {@code MonthDay} with the month-of-year altered. 525 * !(p) 526 * This returns a month-day with the specified month. 527 * If the day-of-month is invalid for the specified month, the day will 528 * be adjusted to the last valid day-of-month. 529 * !(p) 530 * This instance is immutable and unaffected by this method call. 531 * 532 * @param month the month-of-year to set _in the returned month-day, from 1 (January) to 12 (December) 533 * @return a {@code MonthDay} based on this month-day with the requested month, not null 534 * @throws DateTimeException if the month-of-year value is invalid 535 */ 536 MonthDay withMonth(int month) { 537 return _with(Month.of(month)); 538 } 539 540 /** 541 * Returns a copy of this {@code MonthDay} with the month-of-year altered. 542 * !(p) 543 * This returns a month-day with the specified month. 544 * If the day-of-month is invalid for the specified month, the day will 545 * be adjusted to the last valid day-of-month. 546 * !(p) 547 * This instance is immutable and unaffected by this method call. 548 * 549 * @param month the month-of-year to set _in the returned month-day, not null 550 * @return a {@code MonthDay} based on this month-day with the requested month, not null 551 */ 552 MonthDay _with(Month month) { 553 assert(month, "month"); 554 if (month.getValue() == this.month) { 555 return this; 556 } 557 int day = MathHelper.min(this.day, month.maxLength()); 558 return new MonthDay(month.getValue(), day); 559 } 560 561 /** 562 * Returns a copy of this {@code MonthDay} with the day-of-month altered. 563 * !(p) 564 * This returns a month-day with the specified day-of-month. 565 * If the day-of-month is invalid for the month, an exception is thrown. 566 * !(p) 567 * This instance is immutable and unaffected by this method call. 568 * 569 * @param dayOfMonth the day-of-month to set _in the return month-day, from 1 to 31 570 * @return a {@code MonthDay} based on this month-day with the requested day, not null 571 * @throws DateTimeException if the day-of-month value is invalid, 572 * or if the day-of-month is invalid for the month 573 */ 574 MonthDay withDayOfMonth(int dayOfMonth) { 575 if (dayOfMonth == this.day) { 576 return this; 577 } 578 return of(month, dayOfMonth); 579 } 580 581 //----------------------------------------------------------------------- 582 /** 583 * Queries this month-day using the specified query. 584 * !(p) 585 * This queries this month-day using the specified query strategy object. 586 * The {@code TemporalQuery} object defines the logic to be used to 587 * obtain the result. Read the documentation of the query to understand 588 * what the result of this method will be. 589 * !(p) 590 * The result of this method is obtained by invoking the 591 * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the 592 * specified query passing {@code this} as the argument. 593 * 594 * @param !(R) the type of the result 595 * @param query the query to invoke, not null 596 * @return the query result, null may be returned (defined by the query) 597 * @throws DateTimeException if unable to query (defined by the query) 598 * @throws ArithmeticException if numeric overflow occurs (defined by the query) 599 */ 600 /*@SuppressWarnings("unchecked")*/ 601 // override 602 R query(R)(TemporalQuery!(R) query) { 603 if (query == TemporalQueries.chronology()) { 604 return cast(R) IsoChronology.INSTANCE; 605 } 606 return /* TemporalAccessor. */super_query(query); 607 } 608 R super_query(R)(TemporalQuery!(R) query) { 609 if (query == TemporalQueries.zoneId() 610 || query == TemporalQueries.chronology() 611 || query == TemporalQueries.precision()) { 612 return null; 613 } 614 return query.queryFrom(this); 615 } 616 /** 617 * Adjusts the specified temporal object to have this month-day. 618 * !(p) 619 * This returns a temporal object of the same observable type as the input 620 * with the month and day-of-month changed to be the same as this. 621 * !(p) 622 * The adjustment is equivalent to using {@link Temporal#_with(TemporalField, long)} 623 * twice, passing {@link ChronoField#MONTH_OF_YEAR} and 624 * {@link ChronoField#DAY_OF_MONTH} as the fields. 625 * If the specified temporal object does not use the ISO calendar system then 626 * a {@code DateTimeException} is thrown. 627 * !(p) 628 * In most cases, it is clearer to reverse the calling pattern by using 629 * {@link Temporal#_with(TemporalAdjuster)}: 630 * !(pre) 631 * // these two lines are equivalent, but the second approach is recommended 632 * temporal = thisMonthDay.adjustInto(temporal); 633 * temporal = temporal._with(thisMonthDay); 634 * </pre> 635 * !(p) 636 * This instance is immutable and unaffected by this method call. 637 * 638 * @param temporal the target object to be adjusted, not null 639 * @return the adjusted object, not null 640 * @throws DateTimeException if unable to make the adjustment 641 * @throws ArithmeticException if numeric overflow occurs 642 */ 643 override 644 Temporal adjustInto(Temporal temporal) { 645 if ((Chronology.from(temporal) == IsoChronology.INSTANCE) == false) { 646 throw new DateTimeException("Adjustment only supported on ISO date-time"); 647 } 648 temporal = temporal._with(ChronoField.MONTH_OF_YEAR, month); 649 return temporal._with(ChronoField.DAY_OF_MONTH, MathHelper.min(temporal.range(ChronoField.DAY_OF_MONTH).getMaximum(), day)); 650 } 651 652 /** 653 * Formats this month-day using the specified formatter. 654 * !(p) 655 * This month-day will be passed to the formatter to produce a string. 656 * 657 * @param formatter the formatter to use, not null 658 * @return the formatted month-day string, not null 659 * @throws DateTimeException if an error occurs during printing 660 */ 661 // string format(DateTimeFormatter formatter) { 662 // assert(formatter, "formatter"); 663 // return formatter.format(this); 664 // } 665 666 //----------------------------------------------------------------------- 667 /** 668 * Combines this month-day with a year to create a {@code LocalDate}. 669 * !(p) 670 * This returns a {@code LocalDate} formed from this month-day and the specified year. 671 * !(p) 672 * A month-day of February 29th will be adjusted to February 28th _in the resulting 673 * date if the year is not a leap year. 674 * !(p) 675 * This instance is immutable and unaffected by this method call. 676 * 677 * @param year the year to use, from MIN_YEAR to MAX_YEAR 678 * @return the local date formed from this month-day and the specified year, not null 679 * @throws DateTimeException if the year is outside the valid range of years 680 */ 681 LocalDate atYear(int year) { 682 return LocalDate.of(year, month, isValidYear(year) ? day : 28); 683 } 684 685 //----------------------------------------------------------------------- 686 /** 687 * Compares this month-day to another month-day. 688 * !(p) 689 * The comparison is based first on value of the month, then on the value of the day. 690 * It is "consistent with equals", as defined by {@link Comparable}. 691 * 692 * @param other the other month-day to compare to, not null 693 * @return the comparator value, negative if less, positive if greater 694 */ 695 // override 696 int compareTo(MonthDay other) { 697 int cmp = (month - other.month); 698 if (cmp == 0) { 699 cmp = (day - other.day); 700 } 701 return cmp; 702 } 703 704 override 705 int opCmp(MonthDay other) { 706 return compareTo(other); 707 } 708 709 /** 710 * Checks if this month-day is after the specified month-day. 711 * 712 * @param other the other month-day to compare to, not null 713 * @return true if this is after the specified month-day 714 */ 715 bool isAfter(MonthDay other) { 716 return compareTo(other) > 0; 717 } 718 719 /** 720 * Checks if this month-day is before the specified month-day. 721 * 722 * @param other the other month-day to compare to, not null 723 * @return true if this point is before the specified month-day 724 */ 725 bool isBefore(MonthDay other) { 726 return compareTo(other) < 0; 727 } 728 729 //----------------------------------------------------------------------- 730 /** 731 * Checks if this month-day is equal to another month-day. 732 * !(p) 733 * The comparison is based on the time-line position of the month-day within a year. 734 * 735 * @param obj the object to check, null returns false 736 * @return true if this is equal to the other month-day 737 */ 738 override 739 bool opEquals(Object obj) { 740 if (this is obj) { 741 return true; 742 } 743 if (cast(MonthDay)(obj) !is null) { 744 MonthDay other = cast(MonthDay) obj; 745 return month == other.month && day == other.day; 746 } 747 return false; 748 } 749 750 /** 751 * A hash code for this month-day. 752 * 753 * @return a suitable hash code 754 */ 755 override 756 size_t toHash() @trusted nothrow { 757 return (month << 6) + day; 758 } 759 760 //----------------------------------------------------------------------- 761 /** 762 * Outputs this month-day as a {@code string}, such as {@code --12-03}. 763 * !(p) 764 * The output will be _in the format {@code --MM-dd}: 765 * 766 * @return a string representation of this month-day, not null 767 */ 768 override 769 string toString() { 770 return new StringBuilder(10).append("--") 771 .append(month < 10 ? "0" : "").append(month) 772 .append(day < 10 ? "-0" : "-").append(day) 773 .toString(); 774 } 775 776 //----------------------------------------------------------------------- 777 /** 778 * Writes the object using a 779 * <a href="{@docRoot}/serialized-form.html#hunt.time.Ser">dedicated serialized form</a>. 780 * @serialData 781 * !(pre) 782 * _out.writeByte(13); // identifies a MonthDay 783 * _out.writeByte(month); 784 * _out.writeByte(day); 785 * </pre> 786 * 787 * @return the instance of {@code Ser}, not null 788 */ 789 private Object writeReplace() { 790 return new Ser(Ser.MONTH_DAY_TYPE, this); 791 } 792 793 /** 794 * Defend against malicious streams. 795 * 796 * @param s the stream to read 797 * @throws InvalidObjectException always 798 */ 799 ///@gxc 800 // private void readObject(ObjectInputStream s) /*throws InvalidObjectException*/ { 801 // throw new InvalidObjectException("Deserialization via serialization delegate"); 802 // } 803 804 void writeExternal(DataOutput _out) /*throws IOException*/ { 805 _out.writeByte(month); 806 _out.writeByte(day); 807 } 808 809 static MonthDay readExternal(DataInput _in) /*throws IOException*/ { 810 byte month = _in.readByte(); 811 byte day = _in.readByte(); 812 return MonthDay.of(month, day); 813 } 814 815 }