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.chrono.IsoChronology;
13 
14 import hunt.time.temporal.ChronoField;
15 
16 
17 import std.conv;
18 import hunt.stream.Common;
19 import hunt.time.Clock;
20 import hunt.time.Exceptions;
21 import hunt.time.Instant;
22 import hunt.time.LocalDate;
23 import hunt.time.LocalDateTime;
24 import hunt.time.Month;
25 import hunt.time.Period;
26 import hunt.time.Year;
27 import hunt.time.ZonedDateTime;
28 import hunt.time.ZoneId;
29 import hunt.time.ZoneOffset;
30 import hunt.time.format.ResolverStyle;
31 import hunt.time.format.TextStyle;
32 // import hunt.time.format.DateTimeFormatterBuilder;
33 import hunt.time.temporal.ChronoField;
34 import hunt.time.temporal.TemporalAccessor;
35 import hunt.time.temporal.TemporalField;
36 import hunt.time.temporal.ValueRange;
37 import hunt.time.temporal.TemporalQuery;
38 import hunt.time.Exceptions;
39 import hunt.collection.List;
40 // import hunt.time.util.Locale;
41 import hunt.collection.Map;
42 import hunt.time.chrono.AbstractChronology;
43 import hunt.time.chrono.Era;
44 import hunt.time.chrono.IsoEra;
45 import hunt.time.chrono.Chronology;
46 import hunt.time.chrono.ChronoLocalDateTime;
47 import hunt.time.chrono.ChronoLocalDateTimeImpl;
48 import hunt.time.chrono.ChronoZonedDateTime;
49 import hunt.time.chrono.ChronoZonedDateTimeImpl;
50 import hunt.time.chrono.ChronoLocalDate;
51 import hunt.time.LocalTime;
52 import hunt.time.util.QueryHelper;
53 import hunt.time.util.Common;
54 
55 import hunt.Long;
56 import hunt.math.Helper;
57 import hunt.Exceptions;
58 import hunt.collection;
59 
60 import hunt.util.Common;
61 import hunt.util.Comparator;
62 // import hunt.serialization.JsonSerializer;
63 
64 import std.concurrency : initOnce;
65 
66 /**
67  * The ISO calendar system.
68  * !(p)
69  * This chronology defines the rules of the ISO calendar system.
70  * This calendar system is based on the ISO-8601 standard, which is the
71  * !(i)de facto</i> world calendar.
72  * !(p)
73  * The fields are defined as follows:
74  * !(ul)
75  * !(li)era - There are two eras, 'Current Era' (CE) and 'Before Current Era' (BCE).
76  * !(li)year-of-era - The year-of-era is the same as the proleptic-year for the current CE era.
77  *  For the BCE era before the ISO epoch the year increases from 1 upwards as time goes backwards.
78  * !(li)proleptic-year - The proleptic year is the same as the year-of-era for the
79  *  current era. For the previous era, years have zero, then negative values.
80  * !(li)month-of-year - There are 12 months _in an ISO year, numbered from 1 to 12.
81  * !(li)day-of-month - There are between 28 and 31 days _in each of the ISO month, numbered from 1 to 31.
82  *  Months 4, 6, 9 and 11 have 30 days, Months 1, 3, 5, 7, 8, 10 and 12 have 31 days.
83  *  Month 2 has 28 days, or 29 _in a leap year.
84  * !(li)day-of-year - There are 365 days _in a standard ISO year and 366 _in a leap year.
85  *  The days are numbered from 1 to 365 or 1 to 366.
86  * !(li)leap-year - Leap years occur every 4 years, except where the year is divisble by 100 and not divisble by 400.
87  * </ul>
88  *
89  * @implSpec
90  * This class is immutable and thread-safe.
91  *
92  * @since 1.8
93  */
94 public final class IsoChronology : AbstractChronology { // , Serializable 
95 
96     /**
97      * Singleton instance of the ISO chronology.
98      */
99     static IsoChronology INSTANCE() {
100         __gshared IsoChronology _INSTANCE;
101         return initOnce!(_INSTANCE)(new IsoChronology());
102     }    
103 
104     private enum long DAYS_0000_TO_1970 = (146097 * 5L) - (30L * 365L + 7L); // taken from LocalDate
105 
106  
107     /**
108      * Restricted constructor.
109      */
110     this() {
111     }
112 
113     //-----------------------------------------------------------------------
114     /**
115      * Gets the ID of the chronology - 'ISO'.
116      * !(p)
117      * The ID uniquely identifies the {@code Chronology}.
118      * It can be used to lookup the {@code Chronology} using {@link Chronology#of(string)}.
119      *
120      * @return the chronology ID - 'ISO'
121      * @see #getCalendarType()
122      */
123     // override
124     public string getId() {
125         return "ISO";
126     }
127 
128     /**
129      * Gets the calendar type of the underlying calendar system - 'iso8601'.
130      * !(p)
131      * The calendar type is an identifier defined by the
132      * !(em)Unicode Locale Data Markup Language (LDML)</em> specification.
133      * It can be used to lookup the {@code Chronology} using {@link Chronology#of(string)}.
134      * It can also be used as part of a locale, accessible via
135      * {@link Locale#getUnicodeLocaleType(string)} with the key 'ca'.
136      *
137      * @return the calendar system type - 'iso8601'
138      * @see #getId()
139      */
140     // override
141     public string getCalendarType() {
142         return "iso8601";
143     }
144 
145     //-----------------------------------------------------------------------
146     /**
147      * Obtains an ISO local date from the era, year-of-era, month-of-year
148      * and day-of-month fields.
149      *
150      * @param era  the ISO era, not null
151      * @param yearOfEra  the ISO year-of-era
152      * @param month  the ISO month-of-year
153      * @param dayOfMonth  the ISO day-of-month
154      * @return the ISO local date, not null
155      * @throws DateTimeException if unable to create the date
156      * @throws ClassCastException if the type of {@code era} is not {@code IsoEra}
157      */
158     // override  // override with covariant return type
159     public LocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) {
160         return date(prolepticYear(era, yearOfEra), month, dayOfMonth);
161     }
162 
163     /**
164      * Obtains an ISO local date from the proleptic-year, month-of-year
165      * and day-of-month fields.
166      * !(p)
167      * This is equivalent to {@link LocalDate#of(int, int, int)}.
168      *
169      * @param prolepticYear  the ISO proleptic-year
170      * @param month  the ISO month-of-year
171      * @param dayOfMonth  the ISO day-of-month
172      * @return the ISO local date, not null
173      * @throws DateTimeException if unable to create the date
174      */
175     // override  // override with covariant return type
176     public LocalDate date(int prolepticYear, int month, int dayOfMonth) {
177         return LocalDate.of(prolepticYear, month, dayOfMonth);
178     }
179 
180     /**
181      * Obtains an ISO local date from the era, year-of-era and day-of-year fields.
182      *
183      * @param era  the ISO era, not null
184      * @param yearOfEra  the ISO year-of-era
185      * @param dayOfYear  the ISO day-of-year
186      * @return the ISO local date, not null
187      * @throws DateTimeException if unable to create the date
188      */
189     // override  // override with covariant return type
190     public LocalDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {
191         return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);
192     }
193 
194     /**
195      * Obtains an ISO local date from the proleptic-year and day-of-year fields.
196      * !(p)
197      * This is equivalent to {@link LocalDate#ofYearDay(int, int)}.
198      *
199      * @param prolepticYear  the ISO proleptic-year
200      * @param dayOfYear  the ISO day-of-year
201      * @return the ISO local date, not null
202      * @throws DateTimeException if unable to create the date
203      */
204     // override  // override with covariant return type
205     public LocalDate dateYearDay(int prolepticYear, int dayOfYear) {
206         return LocalDate.ofYearDay(prolepticYear, dayOfYear);
207     }
208 
209     /**
210      * Obtains an ISO local date from the epoch-day.
211      * !(p)
212      * This is equivalent to {@link LocalDate#ofEpochDay(long)}.
213      *
214      * @param epochDay  the epoch day
215      * @return the ISO local date, not null
216      * @throws DateTimeException if unable to create the date
217      */
218     // override  // override with covariant return type
219     public LocalDate dateEpochDay(long epochDay) {
220         return LocalDate.ofEpochDay(epochDay);
221     }
222 
223     //-----------------------------------------------------------------------
224     /**
225      * Obtains an ISO local date from another date-time object.
226      * !(p)
227      * This is equivalent to {@link LocalDate#from(TemporalAccessor)}.
228      *
229      * @param temporal  the date-time object to convert, not null
230      * @return the ISO local date, not null
231      * @throws DateTimeException if unable to create the date
232      */
233     // override  // override with covariant return type
234     public LocalDate date(TemporalAccessor temporal) {
235         return LocalDate.from(temporal);
236     }
237 
238     //-----------------------------------------------------------------------
239     /**
240      * Gets the number of seconds from the epoch of 1970-01-01T00:00:00Z.
241      * !(p)
242      * The number of seconds is calculated using the year,
243      * month, day-of-month, hour, minute, second, and zoneOffset.
244      *
245      * @param prolepticYear  the year, from MIN_YEAR to MAX_YEAR
246      * @param month  the month-of-year, from 1 to 12
247      * @param dayOfMonth  the day-of-month, from 1 to 31
248      * @param hour  the hour-of-day, from 0 to 23
249      * @param minute  the minute-of-hour, from 0 to 59
250      * @param second  the second-of-minute, from 0 to 59
251      * @param zoneOffset the zone offset, not null
252      * @return the number of seconds relative to 1970-01-01T00:00:00Z, may be negative
253      * @throws DateTimeException if the value of any argument is _out of range,
254      *         or if the day-of-month is invalid for the month-of-year
255      * @since 9
256      */
257     // override
258     public long epochSecond(int prolepticYear, int month, int dayOfMonth,
259                             int hour, int minute, int second, ZoneOffset zoneOffset) {
260         ChronoField.YEAR.checkValidValue(prolepticYear);
261         ChronoField.MONTH_OF_YEAR.checkValidValue(month);
262         ChronoField.DAY_OF_MONTH.checkValidValue(dayOfMonth);
263         ChronoField.HOUR_OF_DAY.checkValidValue(hour);
264         ChronoField.MINUTE_OF_HOUR.checkValidValue(minute);
265         ChronoField.SECOND_OF_MINUTE.checkValidValue(second);
266         assert(zoneOffset, "zoneOffset");
267         if (dayOfMonth > 28) {
268             int dom = numberOfDaysOfMonth(prolepticYear, month);
269             if (dayOfMonth > dom) {
270                 if (dayOfMonth == 29) {
271                     throw new DateTimeException("Invalid date 'February 29' as '" ~ prolepticYear.to!string ~ "' is not a leap year");
272                 } else {
273                     throw new DateTimeException("Invalid date '" ~ Month.of(month).name() ~ " " ~ dayOfMonth.to!string ~ "'");
274                 }
275             }
276         }
277 
278         long totalDays = 0;
279         int timeinSec = 0;
280         totalDays += 365L * prolepticYear;
281         if (prolepticYear >= 0) {
282             totalDays += (prolepticYear + 3L) / 4 - (prolepticYear + 99L) / 100 + (prolepticYear + 399L) / 400;
283         } else {
284             totalDays -= prolepticYear / -4 - prolepticYear / -100 + prolepticYear / -400;
285         }
286         totalDays += (367 * month - 362) / 12;
287         totalDays += dayOfMonth - 1;
288         if (month > 2) {
289             totalDays--;
290             if (IsoChronology.INSTANCE.isLeapYear(prolepticYear) == false) {
291                 totalDays--;
292             }
293         }
294         totalDays -= DAYS_0000_TO_1970;
295         timeinSec = (hour * 60 + minute ) * 60 + second;
296         return MathHelper.addExact(MathHelper.multiplyExact(totalDays, 86400L), timeinSec - zoneOffset.getTotalSeconds());
297      }
298 
299      long epochSecond(Era era, int yearOfEra, int month, int dayOfMonth,
300                                     int hour, int minute, int second, ZoneOffset zoneOffset) {
301         assert(era, "era");
302         return epochSecond(prolepticYear(era, yearOfEra), month, dayOfMonth, hour, minute, second, zoneOffset);
303     }
304 
305     /**
306      * Gets the number of days for the given month _in the given year.
307      *
308      * @param year the year to represent, from MIN_YEAR to MAX_YEAR
309      * @param month the month-of-year to represent, from 1 to 12
310      * @return the number of days for the given month _in the given year
311      */
312     private int numberOfDaysOfMonth(int year, int month) {
313         int dom;
314         switch (month) {
315             case 2:
316                 dom = (IsoChronology.INSTANCE.isLeapYear(year) ? 29 : 28);
317                 break;
318             case 4:
319             case 6:
320             case 9:
321             case 11:
322                 dom = 30;
323                 break;
324             default:
325                 dom = 31;
326                 break;
327         }
328         return dom;
329     }
330 
331 
332     /**
333      * Obtains an ISO local date-time from another date-time object.
334      * !(p)
335      * This is equivalent to {@link LocalDateTime#from(TemporalAccessor)}.
336      *
337      * @param temporal  the date-time object to convert, not null
338      * @return the ISO local date-time, not null
339      * @throws DateTimeException if unable to create the date-time
340      */
341     // override  // override with covariant return type
342     public ChronoLocalDateTime!(ChronoLocalDate) localDateTime(TemporalAccessor temporal) {
343         return cast(ChronoLocalDateTime!(ChronoLocalDate))(LocalDateTime.from(temporal));
344     }
345 
346     /**
347      * Obtains an ISO zoned date-time from another date-time object.
348      * !(p)
349      * This is equivalent to {@link ZonedDateTime#from(TemporalAccessor)}.
350      *
351      * @param temporal  the date-time object to convert, not null
352      * @return the ISO zoned date-time, not null
353      * @throws DateTimeException if unable to create the date-time
354      */
355     // override  // override with covariant return type
356     public ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(TemporalAccessor temporal) {
357         return cast(ChronoZonedDateTime!(ChronoLocalDate))(ZonedDateTime.from(temporal));
358     }
359 
360     /**
361      * Obtains an ISO zoned date-time _in this chronology from an {@code Instant}.
362      * !(p)
363      * This is equivalent to {@link ZonedDateTime#ofInstant(Instant, ZoneId)}.
364      *
365      * @param instant  the instant to create the date-time from, not null
366      * @param zone  the time-zone, not null
367      * @return the zoned date-time, not null
368      * @throws DateTimeException if the result exceeds the supported range
369      */
370     // override
371     public ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(Instant instant, ZoneId zone) {
372         return cast(ChronoZonedDateTime!(ChronoLocalDate))(ZonedDateTime.ofInstant(instant, zone));
373     }
374 
375     //-----------------------------------------------------------------------
376     /**
377      * Obtains the current ISO local date from the system clock _in the default time-zone.
378      * !(p)
379      * This will query the {@link Clock#systemDefaultZone() system clock} _in the default
380      * time-zone to obtain the current date.
381      * !(p)
382      * Using this method will prevent the ability to use an alternate clock for testing
383      * because the clock is hard-coded.
384      *
385      * @return the current ISO local date using the system clock and default time-zone, not null
386      * @throws DateTimeException if unable to create the date
387      */
388     // override  // override with covariant return type
389     public LocalDate dateNow() {
390         return dateNow(Clock.systemDefaultZone());
391     }
392 
393     /**
394      * Obtains the current ISO local date from the system clock _in the specified time-zone.
395      * !(p)
396      * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date.
397      * Specifying the time-zone avoids dependence on the default time-zone.
398      * !(p)
399      * Using this method will prevent the ability to use an alternate clock for testing
400      * because the clock is hard-coded.
401      *
402      * @return the current ISO local date using the system clock, not null
403      * @throws DateTimeException if unable to create the date
404      */
405     // override  // override with covariant return type
406     public LocalDate dateNow(ZoneId zone) {
407         return dateNow(Clock.system(zone));
408     }
409 
410     /**
411      * Obtains the current ISO local date from the specified clock.
412      * !(p)
413      * This will query the specified clock to obtain the current date - today.
414      * Using this method allows the use of an alternate clock for testing.
415      * The alternate clock may be introduced using {@link Clock dependency injection}.
416      *
417      * @param clock  the clock to use, not null
418      * @return the current ISO local date, not null
419      * @throws DateTimeException if unable to create the date
420      */
421     // override  // override with covariant return type
422     public LocalDate dateNow(Clock clock) {
423         assert(clock, "clock");
424         return date(LocalDate.now(clock));
425     }
426 
427     //-----------------------------------------------------------------------
428     /**
429      * Checks if the year is a leap year, according to the ISO proleptic
430      * calendar system rules.
431      * !(p)
432      * This method applies the current rules for leap years across the whole time-line.
433      * In general, a year is a leap year if it is divisible by four without
434      * remainder. However, years divisible by 100, are not leap years, with
435      * the exception of years divisible by 400 which are.
436      * !(p)
437      * For example, 1904 is a leap year it is divisible by 4.
438      * 1900 was not a leap year as it is divisible by 100, however 2000 was a
439      * leap year as it is divisible by 400.
440      * !(p)
441      * The calculation is proleptic - applying the same rules into the far future and far past.
442      * This is historically inaccurate, but is correct for the ISO-8601 standard.
443      *
444      * @param prolepticYear  the ISO proleptic year to check
445      * @return true if the year is leap, false otherwise
446      */
447     // override
448     public bool isLeapYear(long prolepticYear) {
449         return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);
450     }
451 
452     // override
453     public int prolepticYear(Era era, int yearOfEra) {
454         if ((cast(IsoEra)(era) !is null) == false) {
455             throw new ClassCastException("Era must be IsoEra");
456         }
457         return (era == IsoEra.CE ? yearOfEra : 1 - yearOfEra);
458     }
459 
460     // override
461     public IsoEra eraOf(int eraValue) {
462         return IsoEra.of(eraValue);
463     }
464 
465     // override
466     public List!(Era) eras() {
467         auto li = new ArrayList!Era();
468         li.add(IsoEra.BCE);
469         li.add(IsoEra.CE);
470         return li;
471     }
472 
473     //-----------------------------------------------------------------------
474     /**
475      * Resolves parsed {@code ChronoField} values into a date during parsing.
476      * !(p)
477      * Most {@code TemporalField} implementations are resolved using the
478      * resolve method on the field. By contrast, the {@code ChronoField} class
479      * defines fields that only have meaning relative to the chronology.
480      * As such, {@code ChronoField} date fields are resolved here _in the
481      * context of a specific chronology.
482      * !(p)
483      * {@code ChronoField} instances on the ISO calendar system are resolved
484      * as follows.
485      * !(ul)
486      * !(li){@code EPOCH_DAY} - If present, this is converted to a {@code LocalDate}
487      *  and all other date fields are then cross-checked against the date.
488      * !(li){@code PROLEPTIC_MONTH} - If present, then it is split into the
489      *  {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart
490      *  then the field is validated.
491      * !(li){@code YEAR_OF_ERA} and {@code ERA} - If both are present, then they
492      *  are combined to form a {@code YEAR}. In lenient mode, the {@code YEAR_OF_ERA}
493      *  range is not validated, _in smart and strict mode it is. The {@code ERA} is
494      *  validated for range _in all three modes. If only the {@code YEAR_OF_ERA} is
495      *  present, and the mode is smart or lenient, then the current era (CE/AD)
496      *  is assumed. In strict mode, no era is assumed and the {@code YEAR_OF_ERA} is
497      *  left untouched. If only the {@code ERA} is present, then it is left untouched.
498      * !(li){@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} -
499      *  If all three are present, then they are combined to form a {@code LocalDate}.
500      *  In all three modes, the {@code YEAR} is validated. If the mode is smart or strict,
501      *  then the month and day are validated, with the day validated from 1 to 31.
502      *  If the mode is lenient, then the date is combined _in a manner equivalent to
503      *  creating a date on the first of January _in the requested year, then adding
504      *  the difference _in months, then the difference _in days.
505      *  If the mode is smart, and the day-of-month is greater than the maximum for
506      *  the year-month, then the day-of-month is adjusted to the last day-of-month.
507      *  If the mode is strict, then the three fields must form a valid date.
508      * !(li){@code YEAR} and {@code DAY_OF_YEAR} -
509      *  If both are present, then they are combined to form a {@code LocalDate}.
510      *  In all three modes, the {@code YEAR} is validated.
511      *  If the mode is lenient, then the date is combined _in a manner equivalent to
512      *  creating a date on the first of January _in the requested year, then adding
513      *  the difference _in days.
514      *  If the mode is smart or strict, then the two fields must form a valid date.
515      * !(li){@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and
516      *  {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} -
517      *  If all four are present, then they are combined to form a {@code LocalDate}.
518      *  In all three modes, the {@code YEAR} is validated.
519      *  If the mode is lenient, then the date is combined _in a manner equivalent to
520      *  creating a date on the first of January _in the requested year, then adding
521      *  the difference _in months, then the difference _in weeks, then _in days.
522      *  If the mode is smart or strict, then the all four fields are validated to
523      *  their outer ranges. The date is then combined _in a manner equivalent to
524      *  creating a date on the first day of the requested year and month, then adding
525      *  the amount _in weeks and days to reach their values. If the mode is strict,
526      *  the date is additionally validated to check that the day and week adjustment
527      *  did not change the month.
528      * !(li){@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and
529      *  {@code DAY_OF_WEEK} - If all four are present, then they are combined to
530      *  form a {@code LocalDate}. The approach is the same as described above for
531      *  years, months and weeks _in {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}.
532      *  The day-of-week is adjusted as the next or same matching day-of-week once
533      *  the years, months and weeks have been handled.
534      * !(li){@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} -
535      *  If all three are present, then they are combined to form a {@code LocalDate}.
536      *  In all three modes, the {@code YEAR} is validated.
537      *  If the mode is lenient, then the date is combined _in a manner equivalent to
538      *  creating a date on the first of January _in the requested year, then adding
539      *  the difference _in weeks, then _in days.
540      *  If the mode is smart or strict, then the all three fields are validated to
541      *  their outer ranges. The date is then combined _in a manner equivalent to
542      *  creating a date on the first day of the requested year, then adding
543      *  the amount _in weeks and days to reach their values. If the mode is strict,
544      *  the date is additionally validated to check that the day and week adjustment
545      *  did not change the year.
546      * !(li){@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code DAY_OF_WEEK} -
547      *  If all three are present, then they are combined to form a {@code LocalDate}.
548      *  The approach is the same as described above for years and weeks _in
549      *  {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}. The day-of-week is adjusted as the
550      *  next or same matching day-of-week once the years and weeks have been handled.
551      * </ul>
552      *
553      * @param fieldValues  the map of fields to values, which can be updated, not null
554      * @param resolverStyle  the requested type of resolve, not null
555      * @return the resolved date, null if insufficient information to create a date
556      * @throws DateTimeException if the date cannot be resolved, typically
557      *  because of a conflict _in the input data
558      */
559     override  // override for performance
560     public LocalDate resolveDate(Map!(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) {
561         return cast(LocalDate) super.resolveDate(fieldValues, resolverStyle);
562     }
563 
564     override  // override for better proleptic algorithm
565     void resolveProlepticMonth(Map!(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) {
566         Long pMonth = fieldValues.remove(ChronoField.PROLEPTIC_MONTH);
567         if (pMonth !is null) {
568             if (resolverStyle != ResolverStyle.LENIENT) {
569                 ChronoField.PROLEPTIC_MONTH.checkValidValue(pMonth.longValue());
570             }
571             addFieldValue(fieldValues, ChronoField.MONTH_OF_YEAR, MathHelper.floorMod(pMonth.longValue(), 12) + 1);
572             addFieldValue(fieldValues, ChronoField.YEAR, MathHelper.floorDiv(pMonth.longValue(), 12));
573         }
574     }
575 
576     override  // override for enhanced behaviour
577     LocalDate resolveYearOfEra(Map!(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) {
578         Long yoeLong = fieldValues.remove(ChronoField.YEAR_OF_ERA);
579         if (yoeLong !is null) {
580             if (resolverStyle != ResolverStyle.LENIENT) {
581                 ChronoField.YEAR_OF_ERA.checkValidValue(yoeLong.longValue());
582             }
583             Long era = fieldValues.remove(ChronoField.ERA);
584             if (era is null) {
585                 Long year = fieldValues.get(ChronoField.YEAR);
586                 if (resolverStyle == ResolverStyle.STRICT) {
587                     // do not invent era if strict, but do cross-check with year
588                     if (year !is null) {
589                         addFieldValue(fieldValues, ChronoField.YEAR, (year > 0 ? yoeLong.longValue(): MathHelper.subtractExact(1, yoeLong.longValue())));
590                     } else {
591                         // reinstate the field removed earlier, no cross-check issues
592                         fieldValues.put(ChronoField.YEAR_OF_ERA, yoeLong);
593                     }
594                 } else {
595                     // invent era
596                     addFieldValue(fieldValues, ChronoField.YEAR, (year is null || year > 0 ? yoeLong.longValue(): MathHelper.subtractExact(1, yoeLong.longValue())));
597                 }
598             } else if (era.longValue() == 1L) {
599                 addFieldValue(fieldValues, ChronoField.YEAR, yoeLong.longValue());
600             } else if (era.longValue() == 0L) {
601                 addFieldValue(fieldValues, ChronoField.YEAR, MathHelper.subtractExact(1, yoeLong.longValue()));
602             } else {
603                 throw new DateTimeException("Invalid value for era: " ~ era.longValue().to!string);
604             }
605         } else if (fieldValues.containsKey(ChronoField.ERA)) {
606             ChronoField.ERA.checkValidValue(fieldValues.get(ChronoField.ERA).longValue());  // always validated
607         }
608         return null;
609     }
610 
611     override  // override for performance
612     LocalDate resolveYMD(Map !(TemporalField, Long) fieldValues, ResolverStyle resolverStyle) {
613         int y = ChronoField.YEAR.checkValidIntValue(fieldValues.remove(ChronoField.YEAR).longValue());
614         if (resolverStyle == ResolverStyle.LENIENT) {
615             long months = MathHelper.subtractExact(fieldValues.remove(ChronoField.MONTH_OF_YEAR).longValue(), 1);
616             long days = MathHelper.subtractExact(fieldValues.remove(ChronoField.DAY_OF_MONTH).longValue(), 1);
617             return LocalDate.of(y, 1, 1).plusMonths(months).plusDays(days);
618         }
619         int moy = ChronoField.MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(ChronoField.MONTH_OF_YEAR).longValue());
620         int dom = ChronoField.DAY_OF_MONTH.checkValidIntValue(fieldValues.remove(ChronoField.DAY_OF_MONTH).longValue());
621         if (resolverStyle == ResolverStyle.SMART) {  // previous valid
622             if (moy == 4 || moy == 6 || moy == 9 || moy == 11) {
623                 dom = MathHelper.min(dom, 30);
624             } else if (moy == 2) {
625                 dom = MathHelper.min(dom, Month.FEBRUARY.length(Year.isLeap(y)));
626 
627             }
628         }
629         return LocalDate.of(y, moy, dom);
630     }
631 
632     //-----------------------------------------------------------------------
633     // override
634     public ValueRange range(ChronoField field) {
635         return field.range();
636     }
637 
638     //-----------------------------------------------------------------------
639     /**
640      * Obtains a period for this chronology based on years, months and days.
641      * !(p)
642      * This returns a period tied to the ISO chronology using the specified
643      * years, months and days. See {@link Period} for further details.
644      *
645      * @param years  the number of years, may be negative
646      * @param months  the number of years, may be negative
647      * @param days  the number of years, may be negative
648      * @return the ISO period, not null
649      */
650     // override  // override with covariant return type
651     public Period period(int years, int months, int days) {
652         return Period.of(years, months, days);
653     }
654 
655     //-----------------------------------------------------------------------
656     /**
657      * Writes the Chronology using a
658      * <a href="{@docRoot}/serialized-form.html#hunt.time.chrono.Ser">dedicated serialized form</a>.
659      * @serialData
660      * !(pre)
661      *  _out.writeByte(1);     // identifies a Chronology
662      *  _out.writeUTF(getId());
663      * </pre>
664      *
665      * @return the instance of {@code Ser}, not null
666      */
667     override
668     Object writeReplace() {
669         return super.writeReplace();
670     }
671 
672     /**
673      * Defend against malicious streams.
674      *
675      * @param s the stream to read
676      * @throws InvalidObjectException always
677      */
678      ///@gxc
679     // private void readObject(ObjectInputStream s) /*throws InvalidObjectException*/ {
680     //     throw new InvalidObjectException("Deserialization via serialization delegate");
681     // }
682 
683     override
684     public bool opEquals(Object obj) {
685         if (this is obj) {
686            return true;
687         }
688         if (cast(AbstractChronology)(obj) !is null) {
689             return compareTo(cast(AbstractChronology) obj) == 0;
690         }
691         return false;
692     }
693 
694     override
695     public int compareTo(Chronology other) {
696         return getId().compare(other.getId());
697     }
698 
699     // override
700     public int opCmp(Chronology other) {
701         return getId().compare(other.getId());
702     }
703     
704     // mixin SerializationMember!(typeof(this));
705 
706     // override
707 	//  ChronoLocalDateTime!(ChronoLocalDate) localDateTime(TemporalAccessor temporal) {
708     //     try {
709     //         return date(temporal).atTime(LocalTime.from(temporal));
710     //     } catch (DateTimeException ex) {
711     //         throw new DateTimeException("Unable to obtain ChronoLocalDateTime from TemporalAccessor: " ~ typeid(temporal).stringof, ex);
712     //     }
713     // }
714 	
715     // override
716 	//  ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(TemporalAccessor temporal) {
717     //     try {
718     //         ZoneId zone = ZoneId.from(temporal);
719     //         try {
720     //             Instant instant = Instant.from(temporal);
721     //             return zonedDateTime(instant, zone);
722 
723     //         } catch (DateTimeException ex1) {
724     //             ChronoLocalDateTimeImpl!(ChronoLocalDate) cldt = ChronoLocalDateTimeImpl!ChronoLocalDate.ensureValid!ChronoLocalDate(this, localDateTime(temporal));
725     //             return ChronoZonedDateTimeImpl!ChronoLocalDate.ofBest!ChronoLocalDate(cldt, zone, null);
726     //         }
727     //     } catch (DateTimeException ex) {
728     //         throw new DateTimeException("Unable to obtain ChronoZonedDateTime from TemporalAccessor: " ~ typeid(temporal).stringof, ex);
729     //     }
730     // }
731 	
732     // override
733 	//  ChronoZonedDateTime!(ChronoLocalDate) zonedDateTime(Instant instant, ZoneId zone) {
734     //     return ChronoZonedDateTimeImpl.ofInstant(this, instant, zone);
735     // }
736 	
737     // override
738 	//  string getDisplayName(TextStyle style, Locale locale) {
739     //     TemporalAccessor temporal = new AnonymousClass1();
740     //     return new DateTimeFormatterBuilder().appendChronologyText(style).toFormatter(locale).format(temporal);
741     // }
742 	
743     // // override
744 	// public  long epochSecond(int prolepticYear, int month, int dayOfMonth,
745     //                                 int hour, int minute, int second, ZoneOffset zoneOffset) {
746     //     assert(zoneOffset, "zoneOffset");
747     //     ChronoField.HOUR_OF_DAY.checkValidValue(hour);
748     //     ChronoField.MINUTE_OF_HOUR.checkValidValue(minute);
749     //     ChronoField.SECOND_OF_MINUTE.checkValidValue(second);
750     //     long daysInSec = MathHelper.multiplyExact(date(prolepticYear, month, dayOfMonth).toEpochDay(), 86400);
751     //     long timeinSec = (hour * 60 + minute) * 60 + second;
752     //     return MathHelper.addExact(daysInSec, timeinSec - zoneOffset.getTotalSeconds());
753     // }
754 }