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.Period;
13 
14 import hunt.time.temporal.ChronoUnit;
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.ChronoLocalDate;
23 import hunt.time.chrono.ChronoPeriod;
24 import hunt.time.chrono.Chronology;
25 import hunt.time.chrono.IsoChronology;
26 import hunt.time.format.DateTimeParseException;
27 import hunt.time.temporal.ChronoUnit;
28 import hunt.time.temporal.Temporal;
29 import hunt.time.temporal.TemporalAccessor;
30 import hunt.time.temporal.TemporalAmount;
31 import hunt.time.temporal.TemporalQueries;
32 import hunt.time.temporal.TemporalUnit;
33 import hunt.time.Exceptions;
34 import hunt.collection.List;
35 import hunt.time.LocalDate;
36 import hunt.util.Comparator;
37 import hunt.time.Exceptions;
38 import hunt.Integer;
39 import hunt.Long;
40 import hunt.math.Helper;
41 import hunt.collection;
42 import hunt.time.Ser;
43 import hunt.text.Common;
44 import std.conv;
45 import std.regex;
46 import hunt.util.StringBuilder;
47 import hunt.time.util.QueryHelper;
48 import hunt.time.util.Common;
49 // import hunt.util.regex.Matcher;
50 // import hunt.util.regex.Pattern;
51 
52 /**
53  * A date-based amount of time _in the ISO-8601 calendar system,
54  * such as '2 years, 3 months and 4 days'.
55  * !(p)
56  * This class models a quantity or amount of time _in terms of years, months and days.
57  * See {@link Duration} for the time-based equivalent to this class.
58  * !(p)
59  * Durations and periods differ _in their treatment of daylight savings time
60  * when added to {@link ZonedDateTime}. A {@code Duration} will add an exact
61  * number of seconds, thus a duration of one day is always exactly 24 hours.
62  * By contrast, a {@code Period} will add a conceptual day, trying to maintain
63  * the local time.
64  * !(p)
65  * For example, consider adding a period of one day and a duration of one day to
66  * 18:00 on the evening before a daylight savings gap. The {@code Period} will add
67  * the conceptual day and result _in a {@code ZonedDateTime} at 18:00 the following day.
68  * By contrast, the {@code Duration} will add exactly 24 hours, resulting _in a
69  * {@code ZonedDateTime} at 19:00 the following day (assuming a one hour DST gap).
70  * !(p)
71  * The supported units of a period are {@link ChronoUnit#YEARS YEARS},
72  * {@link ChronoUnit#MONTHS MONTHS} and {@link ChronoUnit#DAYS DAYS}.
73  * All three fields are always present, but may be set to zero.
74  * !(p)
75  * The ISO-8601 calendar system is the modern civil calendar system used today
76  * _in most of the world. It is equivalent to the proleptic Gregorian calendar
77  * system, _in which today's rules for leap years are applied for all time.
78  * !(p)
79  * The period is modeled as a directed amount of time, meaning that individual parts of the
80  * period may be negative.
81  *
82  * !(p)
83  * This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>
84  * class; use of identity-sensitive operations (including reference equality
85  * ({@code ==}), identity hash code, or synchronization) on instances of
86  * {@code Period} may have unpredictable results and should be avoided.
87  * The {@code equals} method should be used for comparisons.
88  *
89  * @implSpec
90  * This class is immutable and thread-safe.
91  *
92  * @since 1.8
93  */
94 public final class Period
95         : ChronoPeriod { // , Serializable
96 
97     /**
98      * A constant for a period of zero.
99      */
100     // public __gshared Period ZERO;
101     /**
102      * Serialization version.
103      */
104     private enum long serialVersionUID = -3587258372562876L;
105     /**
106      * The pattern for parsing.
107      */
108     private enum  PATTERN =
109             "([-+]?)P(?:([-+]?[0-9]+)Y)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)W)?(?:([-+]?[0-9]+)D)?";
110 
111     /**
112      * The set of supported units.
113      */
114     __gshared List!(TemporalUnit) _SUPPORTED_UNITS;
115 
116     /**
117      * The number of years.
118      */
119     private  int years;
120     /**
121      * The number of months.
122      */
123     private  int months;
124     /**
125      * The number of days.
126      */
127     private  int days;
128 
129     public static ref List!(TemporalUnit) SUPPORTED_UNITS()
130     {
131         if(_SUPPORTED_UNITS is null)
132         {
133             _SUPPORTED_UNITS = new ArrayList!TemporalUnit();
134             _SUPPORTED_UNITS.add(ChronoUnit.YEARS);
135             _SUPPORTED_UNITS.add(ChronoUnit.MONTHS);
136             _SUPPORTED_UNITS.add(ChronoUnit.DAYS);
137         }
138         return _SUPPORTED_UNITS;
139     }
140     // shared static this()
141     // {
142         // ZERO = new Period(0, 0, 0);
143         mixin(MakeGlobalVar!(Period)("ZERO",`new Period(0, 0, 0)`));
144         
145     // }
146 
147     //-----------------------------------------------------------------------
148     /**
149      * Obtains a {@code Period} representing a number of years.
150      * !(p)
151      * The resulting period will have the specified years.
152      * The months and days units will be zero.
153      *
154      * @param years  the number of years, positive or negative
155      * @return the period of years, not null
156      */
157     public static Period ofYears(int years) {
158         return create(years, 0, 0);
159     }
160 
161     /**
162      * Obtains a {@code Period} representing a number of months.
163      * !(p)
164      * The resulting period will have the specified months.
165      * The years and days units will be zero.
166      *
167      * @param months  the number of months, positive or negative
168      * @return the period of months, not null
169      */
170     public static Period ofMonths(int months) {
171         return create(0, months, 0);
172     }
173 
174     /**
175      * Obtains a {@code Period} representing a number of weeks.
176      * !(p)
177      * The resulting period will be day-based, with the amount of days
178      * equal to the number of weeks multiplied by 7.
179      * The years and months units will be zero.
180      *
181      * @param weeks  the number of weeks, positive or negative
182      * @return the period, with the input weeks converted to days, not null
183      */
184     public static Period ofWeeks(int weeks) {
185         return create(0, 0, MathHelper.multiplyExact(weeks, 7));
186     }
187 
188     /**
189      * Obtains a {@code Period} representing a number of days.
190      * !(p)
191      * The resulting period will have the specified days.
192      * The years and months units will be zero.
193      *
194      * @param days  the number of days, positive or negative
195      * @return the period of days, not null
196      */
197     public static Period ofDays(int days) {
198         return create(0, 0, days);
199     }
200 
201     //-----------------------------------------------------------------------
202     /**
203      * Obtains a {@code Period} representing a number of years, months and days.
204      * !(p)
205      * This creates an instance based on years, months and days.
206      *
207      * @param years  the amount of years, may be negative
208      * @param months  the amount of months, may be negative
209      * @param days  the amount of days, may be negative
210      * @return the period of years, months and days, not null
211      */
212     public static Period of(int years, int months, int days) {
213         return create(years, months, days);
214     }
215 
216     //-----------------------------------------------------------------------
217     /**
218      * Obtains an instance of {@code Period} from a temporal amount.
219      * !(p)
220      * This obtains a period based on the specified amount.
221      * A {@code TemporalAmount} represents an  amount of time, which may be
222      * date-based or time-based, which this factory extracts to a {@code Period}.
223      * !(p)
224      * The conversion loops around the set of units from the amount and uses
225      * the {@link ChronoUnit#YEARS YEARS}, {@link ChronoUnit#MONTHS MONTHS}
226      * and {@link ChronoUnit#DAYS DAYS} units to create a period.
227      * If any other units are found then an exception is thrown.
228      * !(p)
229      * If the amount is a {@code ChronoPeriod} then it must use the ISO chronology.
230      *
231      * @param amount  the temporal amount to convert, not null
232      * @return the equivalent period, not null
233      * @throws DateTimeException if unable to convert to a {@code Period}
234      * @throws ArithmeticException if the amount of years, months or days exceeds an int
235      */
236     public static Period from(TemporalAmount amount) {
237         if (cast(Period)(amount) !is null) {
238             return cast(Period) amount;
239         }
240         if (cast(ChronoPeriod)(amount) !is null) {
241             if ((IsoChronology.INSTANCE == (cast(ChronoPeriod) amount).getChronology()) == false) {
242                 throw new DateTimeException("Period requires ISO chronology: " ~ typeid(amount).name);
243             }
244         }
245         assert(amount, "amount");
246         int years = 0;
247         int months = 0;
248         int days = 0;
249         foreach(TemporalUnit unit ; amount.getUnits()) {
250             long unitAmount = amount.get(unit);
251             if (unit == ChronoUnit.YEARS) {
252                 years = MathHelper.toIntExact(unitAmount);
253             } else if (unit == ChronoUnit.MONTHS) {
254                 months = MathHelper.toIntExact(unitAmount);
255             } else if (unit == ChronoUnit.DAYS) {
256                 days = MathHelper.toIntExact(unitAmount);
257             } else {
258                 throw new DateTimeException("Unit must be Years, Months or Days, but was " ~ typeid(unit).name);
259             }
260         }
261         return create(years, months, days);
262     }
263 
264     //-----------------------------------------------------------------------
265     /**
266      * Obtains a {@code Period} from a text string such as {@code PnYnMnD}.
267      * !(p)
268      * This will parse the string produced by {@code toString()} which is
269      * based on the ISO-8601 period formats {@code PnYnMnD} and {@code PnW}.
270      * !(p)
271      * The string starts with an optional sign, denoted by the ASCII negative
272      * or positive symbol. If negative, the whole period is negated.
273      * The ASCII letter "P" is next _in upper or lower case.
274      * There are then four sections, each consisting of a number and a suffix.
275      * At least one of the four sections must be present.
276      * The sections have suffixes _in ASCII of "Y", "M", "W" and "D" for
277      * years, months, weeks and days, accepted _in upper or lower case.
278      * The suffixes must occur _in order.
279      * The number part of each section must consist of ASCII digits.
280      * The number may be prefixed by the ASCII negative or positive symbol.
281      * The number must parse to an {@code int}.
282      * !(p)
283      * The leading plus/minus sign, and negative values for other units are
284      * not part of the ISO-8601 standard. In addition, ISO-8601 does not
285      * permit mixing between the {@code PnYnMnD} and {@code PnW} formats.
286      * Any week-based input is multiplied by 7 and treated as a number of days.
287      * !(p)
288      * For example, the following are valid inputs:
289      * !(pre)
290      *   "P2Y"             -- Period.ofYears(2)
291      *   "P3M"             -- Period.ofMonths(3)
292      *   "P4W"             -- Period.ofWeeks(4)
293      *   "P5D"             -- Period.ofDays(5)
294      *   "P1Y2M3D"         -- Period.of(1, 2, 3)
295      *   "P1Y2M3W4D"       -- Period.of(1, 2, 25)
296      *   "P-1Y2M"          -- Period.of(-1, 2, 0)
297      *   "-P1Y2M"          -- Period.of(-1, -2, 0)
298      * </pre>
299      *
300      * @param text  the text to parse, not null
301      * @return the parsed period, not null
302      * @throws DateTimeParseException if the text cannot be parsed to a period
303      */
304     public static Period parse(string text) {
305         assert(text, "text");
306         auto matchers = matchAll(text,PATTERN);
307         if (!matchers.empty()) {
308             auto matcher = matchers.front();
309 
310             int negate = (charMatch(text, matcher.captures[0], '-') ? -1 : 1);
311             string yearStart = matcher.captures[1];
312             string monthStart = matcher.captures[2];
313             string weekStart = matcher.captures[3];
314             string dayStart = matcher.captures[4];
315             if (yearStart.length >= 0 || monthStart.length >= 0 || weekStart.length >= 0 || dayStart.length >= 0) {
316                 try {
317                     int years = parseNumber(text, yearStart, negate);
318                     int months = parseNumber(text, monthStart, negate);
319                     int weeks = parseNumber(text, weekStart, negate);
320                     int days = parseNumber(text, dayStart, negate);
321                     days = MathHelper.addExact(days, MathHelper.multiplyExact(weeks, 7));
322                     return create(years, months, days);
323                 } catch (NumberFormatException ex) {
324                     throw new DateTimeParseException("Text cannot be parsed to a Period", text, 0, ex);
325                 }
326             }
327         }
328         throw new DateTimeParseException("Text cannot be parsed to a Period", text, 0);
329     }
330 
331     private static bool charMatch(string text, string data, char c) {
332         return (data.length == 1 && data[0] == c);
333     }
334 
335     private static int parseNumber(string text, string data ,int negate) {
336         import std.string : isNumeric;
337         if (!isNumeric(data) || data.length == 0) {
338             return 0;
339         }
340         int val = to!int(data);
341         try {
342             return MathHelper.multiplyExact(val, negate);
343         } catch (ArithmeticException ex) {
344             throw new DateTimeParseException("Text cannot be parsed to a Period", text, 0, ex);
345         }
346     }
347 
348     //-----------------------------------------------------------------------
349     /**
350      * Obtains a {@code Period} consisting of the number of years, months,
351      * and days between two dates.
352      * !(p)
353      * The start date is included, but the end date is not.
354      * The period is calculated by removing complete months, then calculating
355      * the remaining number of days, adjusting to ensure that both have the same sign.
356      * The number of months is then split into years and months based on a 12 month year.
357      * A month is considered if the end day-of-month is greater than or equal to the start day-of-month.
358      * For example, from {@code 2010-01-15} to {@code 2011-03-18} is one year, two months and three days.
359      * !(p)
360      * The result of this method can be a negative period if the end is before the start.
361      * The negative sign will be the same _in each of year, month and day.
362      *
363      * @param startDateInclusive  the start date, inclusive, not null
364      * @param endDateExclusive  the end date, exclusive, not null
365      * @return the period between this date and the end date, not null
366      * @see ChronoLocalDate#until(ChronoLocalDate)
367      */
368     public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) {
369         return startDateInclusive.until(endDateExclusive);
370     }
371 
372     //-----------------------------------------------------------------------
373     /**
374      * Creates an instance.
375      *
376      * @param years  the amount
377      * @param months  the amount
378      * @param days  the amount
379      */
380     private static Period create(int years, int months, int days) {
381         if ((years | months | days) == 0) {
382             return ZERO;
383         }
384         return new Period(years, months, days);
385     }
386 
387     /**
388      * Constructor.
389      *
390      * @param years  the amount
391      * @param months  the amount
392      * @param days  the amount
393      */
394     this(int years, int months, int days) {
395         this.years = years;
396         this.months = months;
397         this.days = days;
398     }
399 
400     //-----------------------------------------------------------------------
401     /**
402      * Gets the value of the requested unit.
403      * !(p)
404      * This returns a value for each of the three supported units,
405      * {@link ChronoUnit#YEARS YEARS}, {@link ChronoUnit#MONTHS MONTHS} and
406      * {@link ChronoUnit#DAYS DAYS}.
407      * All other units throw an exception.
408      *
409      * @param unit the {@code TemporalUnit} for which to return the value
410      * @return the long value of the unit
411      * @throws DateTimeException if the unit is not supported
412      * @throws UnsupportedTemporalTypeException if the unit is not supported
413      */
414     override
415     public long get(TemporalUnit unit) {
416         if (unit == ChronoUnit.YEARS) {
417             return getYears();
418         } else if (unit == ChronoUnit.MONTHS) {
419             return getMonths();
420         } else if (unit == ChronoUnit.DAYS) {
421             return getDays();
422         } else {
423             throw new UnsupportedTemporalTypeException("Unsupported unit: " ~ typeid(unit).name);
424         }
425     }
426 
427     /**
428      * Gets the set of units supported by this period.
429      * !(p)
430      * The supported units are {@link ChronoUnit#YEARS YEARS},
431      * {@link ChronoUnit#MONTHS MONTHS} and {@link ChronoUnit#DAYS DAYS}.
432      * They are returned _in the order years, months, days.
433      * !(p)
434      * This set can be used _in conjunction with {@link #get(TemporalUnit)}
435      * to access the entire state of the period.
436      *
437      * @return a list containing the years, months and days units, not null
438      */
439     override
440     public List!(TemporalUnit) getUnits() {
441         return SUPPORTED_UNITS;
442     }
443 
444     /**
445      * Gets the chronology of this period, which is the ISO calendar system.
446      * !(p)
447      * The {@code Chronology} represents the calendar system _in use.
448      * The ISO-8601 calendar system is the modern civil calendar system used today
449      * _in most of the world. It is equivalent to the proleptic Gregorian calendar
450      * system, _in which today's rules for leap years are applied for all time.
451      *
452      * @return the ISO chronology, not null
453      */
454     override
455     public IsoChronology getChronology() {
456         return IsoChronology.INSTANCE;
457     }
458 
459     //-----------------------------------------------------------------------
460     /**
461      * Checks if all three units of this period are zero.
462      * !(p)
463      * A zero period has the value zero for the years, months and days units.
464      *
465      * @return true if this period is zero-length
466      */
467     public bool isZero() {
468         return (this == ZERO);
469     }
470 
471     /**
472      * Checks if any of the three units of this period are negative.
473      * !(p)
474      * This checks whether the years, months or days units are less than zero.
475      *
476      * @return true if any unit of this period is negative
477      */
478     public bool isNegative() {
479         return years < 0 || months < 0 || days < 0;
480     }
481 
482     //-----------------------------------------------------------------------
483     /**
484      * Gets the amount of years of this period.
485      * !(p)
486      * This returns the years unit.
487      * !(p)
488      * The months unit is not automatically normalized with the years unit.
489      * This means that a period of "15 months" is different to a period
490      * of "1 year and 3 months".
491      *
492      * @return the amount of years of this period, may be negative
493      */
494     public int getYears() {
495         return years;
496     }
497 
498     /**
499      * Gets the amount of months of this period.
500      * !(p)
501      * This returns the months unit.
502      * !(p)
503      * The months unit is not automatically normalized with the years unit.
504      * This means that a period of "15 months" is different to a period
505      * of "1 year and 3 months".
506      *
507      * @return the amount of months of this period, may be negative
508      */
509     public int getMonths() {
510         return months;
511     }
512 
513     /**
514      * Gets the amount of days of this period.
515      * !(p)
516      * This returns the days unit.
517      *
518      * @return the amount of days of this period, may be negative
519      */
520     public int getDays() {
521         return days;
522     }
523 
524     //-----------------------------------------------------------------------
525     /**
526      * Returns a copy of this period with the specified amount of years.
527      * !(p)
528      * This sets the amount of the years unit _in a copy of this period.
529      * The months and days units are unaffected.
530      * !(p)
531      * The months unit is not automatically normalized with the years unit.
532      * This means that a period of "15 months" is different to a period
533      * of "1 year and 3 months".
534      * !(p)
535      * This instance is immutable and unaffected by this method call.
536      *
537      * @param years  the years to represent, may be negative
538      * @return a {@code Period} based on this period with the requested years, not null
539      */
540     public Period withYears(int years) {
541         if (years == this.years) {
542             return this;
543         }
544         return create(years, months, days);
545     }
546 
547     /**
548      * Returns a copy of this period with the specified amount of months.
549      * !(p)
550      * This sets the amount of the months unit _in a copy of this period.
551      * The years and days units are unaffected.
552      * !(p)
553      * The months unit is not automatically normalized with the years unit.
554      * This means that a period of "15 months" is different to a period
555      * of "1 year and 3 months".
556      * !(p)
557      * This instance is immutable and unaffected by this method call.
558      *
559      * @param months  the months to represent, may be negative
560      * @return a {@code Period} based on this period with the requested months, not null
561      */
562     public Period withMonths(int months) {
563         if (months == this.months) {
564             return this;
565         }
566         return create(years, months, days);
567     }
568 
569     /**
570      * Returns a copy of this period with the specified amount of days.
571      * !(p)
572      * This sets the amount of the days unit _in a copy of this period.
573      * The years and months units are unaffected.
574      * !(p)
575      * This instance is immutable and unaffected by this method call.
576      *
577      * @param days  the days to represent, may be negative
578      * @return a {@code Period} based on this period with the requested days, not null
579      */
580     public Period withDays(int days) {
581         if (days == this.days) {
582             return this;
583         }
584         return create(years, months, days);
585     }
586 
587     //-----------------------------------------------------------------------
588     /**
589      * Returns a copy of this period with the specified period added.
590      * !(p)
591      * This operates separately on the years, months and days.
592      * No normalization is performed.
593      * !(p)
594      * For example, "1 year, 6 months and 3 days" plus "2 years, 2 months and 2 days"
595      * returns "3 years, 8 months and 5 days".
596      * !(p)
597      * The specified amount is typically an instance of {@code Period}.
598      * Other types are interpreted using {@link Period#from(TemporalAmount)}.
599      * !(p)
600      * This instance is immutable and unaffected by this method call.
601      *
602      * @param amountToAdd  the amount to add, not null
603      * @return a {@code Period} based on this period with the requested period added, not null
604      * @throws DateTimeException if the specified amount has a non-ISO chronology or
605      *  contains an invalid unit
606      * @throws ArithmeticException if numeric overflow occurs
607      */
608     public Period plus(TemporalAmount amountToAdd) {
609         Period isoAmount = Period.from(amountToAdd);
610         return create(
611                 MathHelper.addExact(years, isoAmount.years),
612                 MathHelper.addExact(months, isoAmount.months),
613                 MathHelper.addExact(days, isoAmount.days));
614     }
615 
616     /**
617      * Returns a copy of this period with the specified years added.
618      * !(p)
619      * This adds the amount to the years unit _in a copy of this period.
620      * The months and days units are unaffected.
621      * For example, "1 year, 6 months and 3 days" plus 2 years returns "3 years, 6 months and 3 days".
622      * !(p)
623      * This instance is immutable and unaffected by this method call.
624      *
625      * @param yearsToAdd  the years to add, positive or negative
626      * @return a {@code Period} based on this period with the specified years added, not null
627      * @throws ArithmeticException if numeric overflow occurs
628      */
629     public Period plusYears(long yearsToAdd) {
630         if (yearsToAdd == 0) {
631             return this;
632         }
633         return create(MathHelper.toIntExact(MathHelper.addExact(years, yearsToAdd)), months, days);
634     }
635 
636     /**
637      * Returns a copy of this period with the specified months added.
638      * !(p)
639      * This adds the amount to the months unit _in a copy of this period.
640      * The years and days units are unaffected.
641      * For example, "1 year, 6 months and 3 days" plus 2 months returns "1 year, 8 months and 3 days".
642      * !(p)
643      * This instance is immutable and unaffected by this method call.
644      *
645      * @param monthsToAdd  the months to add, positive or negative
646      * @return a {@code Period} based on this period with the specified months added, not null
647      * @throws ArithmeticException if numeric overflow occurs
648      */
649     public Period plusMonths(long monthsToAdd) {
650         if (monthsToAdd == 0) {
651             return this;
652         }
653         return create(years, MathHelper.toIntExact(MathHelper.addExact(months, monthsToAdd)), days);
654     }
655 
656     /**
657      * Returns a copy of this period with the specified days added.
658      * !(p)
659      * This adds the amount to the days unit _in a copy of this period.
660      * The years and months units are unaffected.
661      * For example, "1 year, 6 months and 3 days" plus 2 days returns "1 year, 6 months and 5 days".
662      * !(p)
663      * This instance is immutable and unaffected by this method call.
664      *
665      * @param daysToAdd  the days to add, positive or negative
666      * @return a {@code Period} based on this period with the specified days added, not null
667      * @throws ArithmeticException if numeric overflow occurs
668      */
669     public Period plusDays(long daysToAdd) {
670         if (daysToAdd == 0) {
671             return this;
672         }
673         return create(years, months, MathHelper.toIntExact(MathHelper.addExact(days, daysToAdd)));
674     }
675 
676     //-----------------------------------------------------------------------
677     /**
678      * Returns a copy of this period with the specified period subtracted.
679      * !(p)
680      * This operates separately on the years, months and days.
681      * No normalization is performed.
682      * !(p)
683      * For example, "1 year, 6 months and 3 days" minus "2 years, 2 months and 2 days"
684      * returns "-1 years, 4 months and 1 day".
685      * !(p)
686      * The specified amount is typically an instance of {@code Period}.
687      * Other types are interpreted using {@link Period#from(TemporalAmount)}.
688      * !(p)
689      * This instance is immutable and unaffected by this method call.
690      *
691      * @param amountToSubtract  the amount to subtract, not null
692      * @return a {@code Period} based on this period with the requested period subtracted, not null
693      * @throws DateTimeException if the specified amount has a non-ISO chronology or
694      *  contains an invalid unit
695      * @throws ArithmeticException if numeric overflow occurs
696      */
697     public Period minus(TemporalAmount amountToSubtract) {
698         Period isoAmount = Period.from(amountToSubtract);
699         return create(
700                 MathHelper.subtractExact(years, isoAmount.years),
701                 MathHelper.subtractExact(months, isoAmount.months),
702                 MathHelper.subtractExact(days, isoAmount.days));
703     }
704 
705     /**
706      * Returns a copy of this period with the specified years subtracted.
707      * !(p)
708      * This subtracts the amount from the years unit _in a copy of this period.
709      * The months and days units are unaffected.
710      * For example, "1 year, 6 months and 3 days" minus 2 years returns "-1 years, 6 months and 3 days".
711      * !(p)
712      * This instance is immutable and unaffected by this method call.
713      *
714      * @param yearsToSubtract  the years to subtract, positive or negative
715      * @return a {@code Period} based on this period with the specified years subtracted, not null
716      * @throws ArithmeticException if numeric overflow occurs
717      */
718     public Period minusYears(long yearsToSubtract) {
719         return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract));
720     }
721 
722     /**
723      * Returns a copy of this period with the specified months subtracted.
724      * !(p)
725      * This subtracts the amount from the months unit _in a copy of this period.
726      * The years and days units are unaffected.
727      * For example, "1 year, 6 months and 3 days" minus 2 months returns "1 year, 4 months and 3 days".
728      * !(p)
729      * This instance is immutable and unaffected by this method call.
730      *
731      * @param monthsToSubtract  the years to subtract, positive or negative
732      * @return a {@code Period} based on this period with the specified months subtracted, not null
733      * @throws ArithmeticException if numeric overflow occurs
734      */
735     public Period minusMonths(long monthsToSubtract) {
736         return (monthsToSubtract == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-monthsToSubtract));
737     }
738 
739     /**
740      * Returns a copy of this period with the specified days subtracted.
741      * !(p)
742      * This subtracts the amount from the days unit _in a copy of this period.
743      * The years and months units are unaffected.
744      * For example, "1 year, 6 months and 3 days" minus 2 days returns "1 year, 6 months and 1 day".
745      * !(p)
746      * This instance is immutable and unaffected by this method call.
747      *
748      * @param daysToSubtract  the months to subtract, positive or negative
749      * @return a {@code Period} based on this period with the specified days subtracted, not null
750      * @throws ArithmeticException if numeric overflow occurs
751      */
752     public Period minusDays(long daysToSubtract) {
753         return (daysToSubtract == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-daysToSubtract));
754     }
755 
756     //-----------------------------------------------------------------------
757     /**
758      * Returns a new instance with each element _in this period multiplied
759      * by the specified scalar.
760      * !(p)
761      * This returns a period with each of the years, months and days units
762      * individually multiplied.
763      * For example, a period of "2 years, -3 months and 4 days" multiplied by
764      * 3 will return "6 years, -9 months and 12 days".
765      * No normalization is performed.
766      *
767      * @param scalar  the scalar to multiply by, not null
768      * @return a {@code Period} based on this period with the amounts multiplied by the scalar, not null
769      * @throws ArithmeticException if numeric overflow occurs
770      */
771     public Period multipliedBy(int scalar) {
772         if (this == ZERO || scalar == 1) {
773             return this;
774         }
775         return create(
776                 MathHelper.multiplyExact(years, scalar),
777                 MathHelper.multiplyExact(months, scalar),
778                 MathHelper.multiplyExact(days, scalar));
779     }
780 
781     /**
782      * Returns a new instance with each amount _in this period negated.
783      * !(p)
784      * This returns a period with each of the years, months and days units
785      * individually negated.
786      * For example, a period of "2 years, -3 months and 4 days" will be
787      * negated to "-2 years, 3 months and -4 days".
788      * No normalization is performed.
789      *
790      * @return a {@code Period} based on this period with the amounts negated, not null
791      * @throws ArithmeticException if numeric overflow occurs, which only happens if
792      *  one of the units has the value {@code Long.MIN_VALUE}
793      */
794     public Period negated() {
795         return multipliedBy(-1);
796     }
797 
798     //-----------------------------------------------------------------------
799     /**
800      * Returns a copy of this period with the years and months normalized.
801      * !(p)
802      * This normalizes the years and months units, leaving the days unit unchanged.
803      * The months unit is adjusted to have an absolute value less than 12,
804      * with the years unit being adjusted to compensate. For example, a period of
805      * "1 Year and 15 months" will be normalized to "2 years and 3 months".
806      * !(p)
807      * The sign of the years and months units will be the same after normalization.
808      * For example, a period of "1 year and -25 months" will be normalized to
809      * "-1 year and -1 month".
810      * !(p)
811      * This instance is immutable and unaffected by this method call.
812      *
813      * @return a {@code Period} based on this period with excess months normalized to years, not null
814      * @throws ArithmeticException if numeric overflow occurs
815      */
816     public Period normalized() {
817         long totalMonths = toTotalMonths();
818         long splitYears = totalMonths / 12;
819         int splitMonths = cast(int) (totalMonths % 12);  // no overflow
820         if (splitYears == years && splitMonths == months) {
821             return this;
822         }
823         return create(MathHelper.toIntExact(splitYears), splitMonths, days);
824     }
825 
826     /**
827      * Gets the total number of months _in this period.
828      * !(p)
829      * This returns the total number of months _in the period by multiplying the
830      * number of years by 12 and adding the number of months.
831      * !(p)
832      * This instance is immutable and unaffected by this method call.
833      *
834      * @return the total number of months _in the period, may be negative
835      */
836     public long toTotalMonths() {
837         return years * 12L + months;  // no overflow
838     }
839 
840     //-------------------------------------------------------------------------
841     /**
842      * Adds this period to the specified temporal object.
843      * !(p)
844      * This returns a temporal object of the same observable type as the input
845      * with this period added.
846      * If the temporal has a chronology, it must be the ISO chronology.
847      * !(p)
848      * In most cases, it is clearer to reverse the calling pattern by using
849      * {@link Temporal#plus(TemporalAmount)}.
850      * !(pre)
851      *   // these two lines are equivalent, but the second approach is recommended
852      *   dateTime = thisPeriod.addTo(dateTime);
853      *   dateTime = dateTime.plus(thisPeriod);
854      * </pre>
855      * !(p)
856      * The calculation operates as follows.
857      * First, the chronology of the temporal is checked to ensure it is ISO chronology or null.
858      * Second, if the months are zero, the years are added if non-zero, otherwise
859      * the combination of years and months is added if non-zero.
860      * Finally, any days are added.
861      * !(p)
862      * This approach ensures that a partial period can be added to a partial date.
863      * For example, a period of years and/or months can be added to a {@code YearMonth},
864      * but a period including days cannot.
865      * The approach also adds years and months together when necessary, which ensures
866      * correct behaviour at the end of the month.
867      * !(p)
868      * This instance is immutable and unaffected by this method call.
869      *
870      * @param temporal  the temporal object to adjust, not null
871      * @return an object of the same type with the adjustment made, not null
872      * @throws DateTimeException if unable to add
873      * @throws ArithmeticException if numeric overflow occurs
874      */
875     override
876     public Temporal addTo(Temporal temporal) {
877         validateChrono(temporal);
878         if (months == 0) {
879             if (years != 0) {
880                 temporal = temporal.plus(years, ChronoUnit.YEARS);
881             }
882         } else {
883             long totalMonths = toTotalMonths();
884             if (totalMonths != 0) {
885                 temporal = temporal.plus(totalMonths, ChronoUnit.MONTHS);
886             }
887         }
888         if (days != 0) {
889             temporal = temporal.plus(days, ChronoUnit.DAYS);
890         }
891         return temporal;
892     }
893 
894     /**
895      * Subtracts this period from the specified temporal object.
896      * !(p)
897      * This returns a temporal object of the same observable type as the input
898      * with this period subtracted.
899      * If the temporal has a chronology, it must be the ISO chronology.
900      * !(p)
901      * In most cases, it is clearer to reverse the calling pattern by using
902      * {@link Temporal#minus(TemporalAmount)}.
903      * !(pre)
904      *   // these two lines are equivalent, but the second approach is recommended
905      *   dateTime = thisPeriod.subtractFrom(dateTime);
906      *   dateTime = dateTime.minus(thisPeriod);
907      * </pre>
908      * !(p)
909      * The calculation operates as follows.
910      * First, the chronology of the temporal is checked to ensure it is ISO chronology or null.
911      * Second, if the months are zero, the years are subtracted if non-zero, otherwise
912      * the combination of years and months is subtracted if non-zero.
913      * Finally, any days are subtracted.
914      * !(p)
915      * This approach ensures that a partial period can be subtracted from a partial date.
916      * For example, a period of years and/or months can be subtracted from a {@code YearMonth},
917      * but a period including days cannot.
918      * The approach also subtracts years and months together when necessary, which ensures
919      * correct behaviour at the end of the month.
920      * !(p)
921      * This instance is immutable and unaffected by this method call.
922      *
923      * @param temporal  the temporal object to adjust, not null
924      * @return an object of the same type with the adjustment made, not null
925      * @throws DateTimeException if unable to subtract
926      * @throws ArithmeticException if numeric overflow occurs
927      */
928     override
929     public Temporal subtractFrom(Temporal temporal) {
930         validateChrono(temporal);
931         if (months == 0) {
932             if (years != 0) {
933                 temporal = temporal.minus(years, ChronoUnit.YEARS);
934             }
935         } else {
936             long totalMonths = toTotalMonths();
937             if (totalMonths != 0) {
938                 temporal = temporal.minus(totalMonths, ChronoUnit.MONTHS);
939             }
940         }
941         if (days != 0) {
942             temporal = temporal.minus(days, ChronoUnit.DAYS);
943         }
944         return temporal;
945     }
946 
947     /**
948      * Validates that the temporal has the correct chronology.
949      */
950     private void validateChrono(TemporalAccessor temporal) {
951         assert(temporal, "temporal");
952         Chronology temporalChrono = QueryHelper.query!Chronology(temporal ,TemporalQueries.chronology());
953         if (temporalChrono !is null && (IsoChronology.INSTANCE == temporalChrono) == false) {
954             throw new DateTimeException("Chronology mismatch, expected: ISO, actual: " ~ temporalChrono.getId());
955         }
956     }
957 
958     //-----------------------------------------------------------------------
959     /**
960      * Checks if this period is equal to another period.
961      * !(p)
962      * The comparison is based on the type {@code Period} and each of the three amounts.
963      * To be equal, the years, months and days units must be individually equal.
964      * Note that this means that a period of "15 Months" is not equal to a period
965      * of "1 Year and 3 Months".
966      *
967      * @param obj  the object to check, null returns false
968      * @return true if this is equal to the other period
969      */
970     override
971     public bool opEquals(Object obj) {
972         if (this is obj) {
973             return true;
974         }
975         if (cast(Period)(obj) !is null) {
976             Period other = cast(Period) obj;
977             return years == other.years &&
978                     months == other.months &&
979                     days == other.days;
980         }
981         return false;
982     }
983 
984     /**
985      * A hash code for this period.
986      *
987      * @return a suitable hash code
988      */
989     override
990     public size_t toHash() @trusted nothrow {
991         try
992         {
993             return years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16);
994         }
995         catch(Exception e){}
996         return int.init;
997     }
998 
999     //-----------------------------------------------------------------------
1000     /**
1001      * Outputs this period as a {@code string}, such as {@code P6Y3M1D}.
1002      * !(p)
1003      * The output will be _in the ISO-8601 period format.
1004      * A zero period will be represented as zero days, 'P0D'.
1005      *
1006      * @return a string representation of this period, not null
1007      */
1008     override
1009     public string toString() {
1010         if (this == ZERO) {
1011             return "P0D";
1012         } else {
1013             StringBuilder buf = new StringBuilder();
1014             buf.append('P');
1015             if (years != 0) {
1016                 buf.append(years).append('Y');
1017             }
1018             if (months != 0) {
1019                 buf.append(months).append('M');
1020             }
1021             if (days != 0) {
1022                 buf.append(days).append('D');
1023             }
1024             return buf.toString();
1025         }
1026     }
1027 
1028     //-----------------------------------------------------------------------
1029     /**
1030      * Writes the object using a
1031      * <a href="{@docRoot}/serialized-form.html#hunt.time.Ser">dedicated serialized form</a>.
1032      * @serialData
1033      * !(pre)
1034      *  _out.writeByte(14);  // identifies a Period
1035      *  _out.writeInt(years);
1036      *  _out.writeInt(months);
1037      *  _out.writeInt(days);
1038      * </pre>
1039      *
1040      * @return the instance of {@code Ser}, not null
1041      */
1042     private Object writeReplace() {
1043         return new Ser(Ser.PERIOD_TYPE, this);
1044     }
1045 
1046     /**
1047      * Defend against malicious streams.
1048      *
1049      * @param s the stream to read
1050      * @throws java.io.InvalidObjectException always
1051      */
1052      ///@gxc
1053     // private void readObject(ObjectInputStream s) /*throws InvalidObjectException*/ {
1054     //     throw new InvalidObjectException("Deserialization via serialization delegate");
1055     // }
1056 
1057     void writeExternal(DataOutput _out) /*throws IOException*/ {
1058         _out.writeInt(years);
1059         _out.writeInt(months);
1060         _out.writeInt(days);
1061     }
1062 
1063     static Period readExternal(DataInput _in) /*throws IOException*/ {
1064         int years = _in.readInt();
1065         int months = _in.readInt();
1066         int days = _in.readInt();
1067         return Period.of(years, months, days);
1068     }
1069 
1070 }