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 }