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.temporal.IsoFields;
13 
14 import hunt.time.DayOfWeek;
15 import hunt.time.temporal.ChronoField;
16 import hunt.time.temporal.ChronoUnit;
17 
18 import hunt.time.Exceptions;
19 import hunt.time.Duration;
20 import hunt.time.LocalDate;
21 import hunt.time.chrono.ChronoLocalDate;
22 import hunt.time.chrono.Chronology;
23 import hunt.time.chrono.IsoChronology;
24 import hunt.time.format.ResolverStyle;
25 import hunt.time.temporal.TemporalField;
26 import hunt.time.temporal.ValueRange;
27 import hunt.time.temporal.TemporalAccessor;
28 import hunt.time.temporal.TemporalUnit;
29 import hunt.time.temporal.Temporal;
30 import hunt.time.util.Common;
31 
32 import hunt.Assert;
33 import hunt.collection.Map;
34 import hunt.Integer;
35 import hunt.Long;
36 import hunt.math.Helper;
37 import hunt.Exceptions;
38 
39 import hunt.util.Comparator;
40 import hunt.util.Locale;
41 // import hunt.util.ResourceBundle;
42 
43 // import sun.util.locale.provider.CalendarDataUtility;
44 // import sun.util.locale.provider.LocaleProviderAdapter;
45 // import sun.util.locale.provider.LocaleResources;
46 
47 /**
48  * Fields and units specific to the ISO-8601 calendar system,
49  * including quarter-of-year and week-based-year.
50  * !(p)
51  * This class defines fields and units that are specific to the ISO calendar system.
52  *
53  * !(h3)Quarter of year</h3>
54  * The ISO-8601 standard is based on the standard civic 12 month year.
55  * This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4.
56  * !(p)
57  * January, February and March are _in Q1.
58  * April, May and June are _in Q2.
59  * July, August and September are _in Q3.
60  * October, November and December are _in Q4.
61  * !(p)
62  * The complete date is expressed using three fields:
63  * !(ul)
64  * !(li){@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92
65  * !(li){@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the quarter within the year, from 1 to 4
66  * !(li){@link ChronoField#YEAR YEAR} - the standard ISO year
67  * </ul>
68  *
69  * !(h3)Week based years</h3>
70  * The ISO-8601 standard was originally intended as a data interchange format,
71  * defining a string format for dates and times. However, it also defines an
72  * alternate way of expressing the date, based on the concept of week-based-year.
73  * !(p)
74  * The date is expressed using three fields:
75  * !(ul)
76  * !(li){@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the
77  *  day-of-week from Monday (1) to Sunday (7)
78  * !(li){@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year
79  * !(li){@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year
80  * </ul>
81  * The week-based-year itself is defined relative to the standard ISO proleptic year.
82  * It differs from the standard year _in that it always starts on a Monday.
83  * !(p)
84  * The first week of a week-based-year is the first Monday-based week of the standard
85  * ISO year that has at least 4 days _in the new year.
86  * !(ul)
87  * !(li)If January 1st is Monday then week 1 starts on January 1st
88  * !(li)If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year
89  * !(li)If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year
90  * !(li)If January 1st is Thursday then week 1 starts on December 29th of the previous standard year
91  * !(li)If January 1st is Friday then week 1 starts on January 4th
92  * !(li)If January 1st is Saturday then week 1 starts on January 3rd
93  * !(li)If January 1st is Sunday then week 1 starts on January 2nd
94  * </ul>
95  * There are 52 weeks _in most week-based years, however on occasion there are 53 weeks.
96  * !(p)
97  * For example:
98  *
99  * <table class=striped style="text-align: left">
100  * !(caption)Examples of Week based Years</caption>
101  * !(thead)
102  * !(tr)<th scope="col">Date</th><th scope="col">Day-of-week</th><th scope="col">Field values</th></tr>
103  * </thead>
104  * !(tbody)
105  * !(tr)<th scope="row">2008-12-28</th>!(td)Sunday</td>!(td)Week 52 of week-based-year 2008</td></tr>
106  * !(tr)<th scope="row">2008-12-29</th>!(td)Monday</td>!(td)Week 1 of week-based-year 2009</td></tr>
107  * !(tr)<th scope="row">2008-12-31</th>!(td)Wednesday</td>!(td)Week 1 of week-based-year 2009</td></tr>
108  * !(tr)<th scope="row">2009-01-01</th>!(td)Thursday</td>!(td)Week 1 of week-based-year 2009</td></tr>
109  * !(tr)<th scope="row">2009-01-04</th>!(td)Sunday</td>!(td)Week 1 of week-based-year 2009</td></tr>
110  * !(tr)<th scope="row">2009-01-05</th>!(td)Monday</td>!(td)Week 2 of week-based-year 2009</td></tr>
111  * </tbody>
112  * </table>
113  *
114  * @implSpec
115  * !(p)
116  * This class is immutable and thread-safe.
117  *
118  * @since 1.8
119  */
120 public final class IsoFields
121 {
122 
123     /**
124      * The field that represents the day-of-quarter.
125      * !(p)
126      * This field allows the day-of-quarter value to be queried and set.
127      * The day-of-quarter has values from 1 to 90 _in Q1 of a standard year, from 1 to 91
128      * _in Q1 of a leap year, from 1 to 91 _in Q2 and from 1 to 92 _in Q3 and Q4.
129      * !(p)
130      * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year
131      * are available.
132      * !(p)
133      * When setting this field, the value is allowed to be partially lenient, taking any
134      * value from 1 to 92. If the quarter has less than 92 days, then day 92, and
135      * potentially day 91, is _in the following quarter.
136      * !(p)
137      * In the resolving phase of parsing, a date can be created from a year,
138      * quarter-of-year and day-of-quarter.
139      * !(p)
140      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
141      * validated against their range of valid values. The day-of-quarter field
142      * is validated from 1 to 90, 91 or 92 depending on the year and quarter.
143      * !(p)
144      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
145      * validated against their range of valid values. The day-of-quarter field is
146      * validated between 1 and 92, ignoring the actual range based on the year and quarter.
147      * If the day-of-quarter exceeds the actual range by one day, then the resulting date
148      * is one day later. If the day-of-quarter exceeds the actual range by two days,
149      * then the resulting date is two days later.
150      * !(p)
151      * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is validated
152      * against the range of valid values. The resulting date is calculated equivalent to
153      * the following three stage approach. First, create a date on the first of January
154      * _in the requested year. Then take the quarter-of-year, subtract one, and add the
155      * amount _in quarters to the date. Finally, take the day-of-quarter, subtract one,
156      * and add the amount _in days to the date.
157      * !(p)
158      * This unit is an immutable and thread-safe singleton.
159      */
160     //public __gshared TemporalField DAY_OF_QUARTER;
161     /**
162      * The field that represents the quarter-of-year.
163      * !(p)
164      * This field allows the quarter-of-year value to be queried and set.
165      * The quarter-of-year has values from 1 to 4.
166      * !(p)
167      * The quarter-of-year can only be calculated if the month-of-year is available.
168      * !(p)
169      * In the resolving phase of parsing, a date can be created from a year,
170      * quarter-of-year and day-of-quarter.
171      * See {@link #DAY_OF_QUARTER} for details.
172      * !(p)
173      * This unit is an immutable and thread-safe singleton.
174      */
175     //public __gshared TemporalField QUARTER_OF_YEAR;
176     /**
177      * The field that represents the week-of-week-based-year.
178      * !(p)
179      * This field allows the week of the week-based-year value to be queried and set.
180      * The week-of-week-based-year has values from 1 to 52, or 53 if the
181      * week-based-year has 53 weeks.
182      * !(p)
183      * In the resolving phase of parsing, a date can be created from a
184      * week-based-year, week-of-week-based-year and day-of-week.
185      * !(p)
186      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
187      * validated against their range of valid values. The week-of-week-based-year
188      * field is validated from 1 to 52 or 53 depending on the week-based-year.
189      * !(p)
190      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
191      * validated against their range of valid values. The week-of-week-based-year
192      * field is validated between 1 and 53, ignoring the week-based-year.
193      * If the week-of-week-based-year is 53, but the week-based-year only has
194      * 52 weeks, then the resulting date is _in week 1 of the following week-based-year.
195      * !(p)
196      * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the week-based-year
197      * is validated against the range of valid values. If the day-of-week is outside
198      * the range 1 to 7, then the resulting date is adjusted by a suitable number of
199      * weeks to reduce the day-of-week to the range 1 to 7. If the week-of-week-based-year
200      * value is outside the range 1 to 52, then any excess weeks are added or subtracted
201      * from the resulting date.
202      * !(p)
203      * This unit is an immutable and thread-safe singleton.
204      */
205     //public __gshared TemporalField WEEK_OF_WEEK_BASED_YEAR;
206     /**
207      * The field that represents the week-based-year.
208      * !(p)
209      * This field allows the week-based-year value to be queried and set.
210      * !(p)
211      * The field has a range that matches {@link LocalDate#MAX} and {@link LocalDate#MIN}.
212      * !(p)
213      * In the resolving phase of parsing, a date can be created from a
214      * week-based-year, week-of-week-based-year and day-of-week.
215      * See {@link #WEEK_OF_WEEK_BASED_YEAR} for details.
216      * !(p)
217      * This unit is an immutable and thread-safe singleton.
218      */
219     //public __gshared TemporalField WEEK_BASED_YEAR;
220     /**
221      * The unit that represents week-based-years for the purpose of addition and subtraction.
222      * !(p)
223      * This allows a number of week-based-years to be added to, or subtracted from, a date.
224      * The unit is equal to either 52 or 53 weeks.
225      * The estimated duration of a week-based-year is the same as that of a standard ISO
226      * year at {@code 365.2425 Days}.
227      * !(p)
228      * The rules for addition add the number of week-based-years to the existing value
229      * for the week-based-year field. If the resulting week-based-year only has 52 weeks,
230      * then the date will be _in week 1 of the following week-based-year.
231      * !(p)
232      * This unit is an immutable and thread-safe singleton.
233      */
234     //public __gshared TemporalUnit WEEK_BASED_YEARS;
235     /**
236      * Unit that represents the concept of a quarter-year.
237      * For the ISO calendar system, it is equal to 3 months.
238      * The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}.
239      * !(p)
240      * This unit is an immutable and thread-safe singleton.
241      */
242     //public __gshared TemporalUnit QUARTER_YEARS;
243 
244     // shared static this()
245     // {
246         // DAY_OF_QUARTER = Field.DAY_OF_QUARTER;
247         mixin(MakeGlobalVar!(TemporalField)("DAY_OF_QUARTER",`Field.DAY_OF_QUARTER`));
248         // QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR;
249         mixin(MakeGlobalVar!(TemporalField)("QUARTER_OF_YEAR",`Field.QUARTER_OF_YEAR`));
250 
251         // WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR;
252         mixin(MakeGlobalVar!(TemporalField)("WEEK_OF_WEEK_BASED_YEAR",`Field.WEEK_OF_WEEK_BASED_YEAR`));
253 
254         mixin(MakeGlobalVar!(TemporalField)("WEEK_BASED_YEAR",`Field.WEEK_BASED_YEAR`));
255 
256 
257         // WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS;
258         mixin(MakeGlobalVar!(TemporalUnit)("WEEK_BASED_YEARS",`Unit.WEEK_BASED_YEARS`));
259 
260         // QUARTER_YEARS = Unit.QUARTER_YEARS;
261         mixin(MakeGlobalVar!(TemporalUnit)("QUARTER_YEARS",` Unit.QUARTER_YEARS`));
262 
263 
264     // }
265 
266     /**
267      * Restricted constructor.
268      */
269     private this()
270     {
271         throw new AssertionError("Not instantiable");
272     }
273 
274     //-----------------------------------------------------------------------
275     /**
276      * Implementation of the field.
277      */
278     static class Field : TemporalField
279     {
280         // static Field DAY_OF_QUARTER;
281         // static Field QUARTER_OF_YEAR;
282         // static Field WEEK_OF_WEEK_BASED_YEAR;
283         // static Field WEEK_BASED_YEAR;
284         string getDisplayName(Locale locale){ return null;}
285 
286         TemporalUnit getBaseUnit(){ return null;}
287 
288         TemporalUnit getRangeUnit(){ return null;}
289 
290         ValueRange range(){ return null;}
291 
292         // bool isDateBased(){ return false;}
293 
294         // bool isTimeBased(){ return false;}
295 
296         bool isSupportedBy(TemporalAccessor temporal){ return false;}
297 
298         // ValueRange rangeRefinedBy(TemporalAccessor temporal){ return null;}
299 
300         long getFrom(TemporalAccessor temporal){ return long.init;}
301 
302         Temporal adjustInto(Temporal temporal, long newValue){ return null;}
303 
304         TemporalAccessor resolve(Map!(TemporalField, Long) fieldValues,
305                 TemporalAccessor partialTemporal, ResolverStyle resolverStyle){ return null;}
306 
307         override string toString(){return super.toString();}
308 
309         // int opCmp(Object o){return 0;}
310 
311         int opCmp(TemporalField o){return 0;}
312         // shared static this()
313         // {
314         //     DAY_OF_QUARTER = new class Field
315         //     {
316         //         override
317         //         public TemporalUnit getBaseUnit()
318         //         {
319         //             return ChronoUnit.DAYS;
320         //         }
321         //         override
322         //         public TemporalUnit getRangeUnit()
323         //         {
324         //             return QUARTER_YEARS;
325         //         }
326         //         override
327         //         public ValueRange range()
328         //         {
329         //             return ValueRange.of(1, 90, 92);
330         //         }
331         //         override
332         //         public bool isSupportedBy(TemporalAccessor temporal)
333         //         {
334         //             return temporal.isSupported(ChronoField.DAY_OF_YEAR)
335         //                 && temporal.isSupported(ChronoField.MONTH_OF_YEAR)
336         //                 && temporal.isSupported(ChronoField.YEAR) && isIso(temporal);
337         //         }
338 
339         //         override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
340         //         {
341         //             if (isSupportedBy(temporal) == false)
342         //             {
343         //                 throw new Exception("Unsupported field: DayOfQuarter");
344         //             }
345         //             long qoy = temporal.getLong(QUARTER_OF_YEAR);
346         //             if (qoy == 1)
347         //             {
348         //                 long year = temporal.getLong(ChronoField.YEAR);
349         //                 return (IsoChronology.INSTANCE.isLeapYear(year)
350         //                         ? ValueRange.of(1, 91) : ValueRange.of(1, 90));
351         //             }
352         //             else if (qoy == 2)
353         //             {
354         //                 return ValueRange.of(1, 91);
355         //             }
356         //             else if (qoy == 3 || qoy == 4)
357         //             {
358         //                 return ValueRange.of(1, 92);
359         //             } // else value not from 1 to 4, so drop through
360         //             return range();
361         //         }
362         //         override
363         //         public long getFrom(TemporalAccessor temporal)
364         //         {
365         //             if (isSupportedBy(temporal) == false)
366         //             {
367         //                 throw new Exception("Unsupported field: DayOfQuarter");
368         //             }
369         //             int doy = temporal.get(ChronoField.DAY_OF_YEAR);
370         //             int moy = temporal.get(ChronoField.MONTH_OF_YEAR);
371         //             long year = temporal.getLong(ChronoField.YEAR);
372         //             return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year)
373         //                     ? 4 : 0)];
374         //         }
375         //         /*@SuppressWarnings("unchecked")*/
376         //         override
377         //         public Temporal adjustInto(Temporal temporal, long newValue)
378         //                 /* if (is(R : Temporal)) */
379         //         {
380         //             // calls getFrom() to check if supported
381         //             long curValue = getFrom(temporal);
382         //             range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check
383         //             return cast(Temporal) temporal._with(ChronoField.DAY_OF_YEAR,
384         //                     temporal.getLong(ChronoField.DAY_OF_YEAR) + (newValue - curValue));
385         //         }
386         //         override
387         //         public ChronoLocalDate resolve(Map!(TemporalField, Long) fieldValues,
388         //                 TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
389         //         {
390         //             Long yearLong = fieldValues.get(ChronoField.YEAR);
391         //             Long qoyLong = fieldValues.get(QUARTER_OF_YEAR);
392         //             if (yearLong is null || qoyLong is null)
393         //             {
394         //                 return null;
395         //             }
396         //             int y = ChronoField.YEAR.checkValidIntValue(yearLong.longValue()); // always validate
397         //             long doq = fieldValues.get(DAY_OF_QUARTER).longValue();
398         //             ensureIso(partialTemporal);
399         //             LocalDate date;
400         //             if (resolverStyle == ResolverStyle.LENIENT)
401         //             {
402         //                 date = LocalDate.of(y, 1, 1)
403         //                     .plusMonths(MathHelper.multiplyExact(MathHelper.subtractExact(cast(int)(qoyLong.longValue()),
404         //                             1), 3));
405         //                 doq = MathHelper.subtractExact(doq, 1);
406         //             }
407         //             else
408         //             {
409         //                 int qoy = QUARTER_OF_YEAR.range()
410         //                     .checkValidIntValue(qoyLong.longValue(), QUARTER_OF_YEAR); // validated
411         //                 date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1);
412         //                 if (doq < 1 || doq > 90)
413         //                 {
414         //                     if (resolverStyle == ResolverStyle.STRICT)
415         //                     {
416         //                         rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range
417         //                     }
418         //                     else
419         //                     { // SMART
420         //                         range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter
421         //                     }
422         //                 }
423         //                 doq--;
424         //             }
425         //             fieldValues.remove(this);
426         //             fieldValues.remove(ChronoField.YEAR);
427         //             fieldValues.remove(QUARTER_OF_YEAR);
428         //             return date.plusDays(doq);
429         //         }
430 
431         //         override
432         //         string getDisplayName(Locale locale)
433         //         {
434         //             assert(locale, "locale");
435         //             return toString();
436         //         }
437 
438         //         override public string toString()
439         //         {
440         //             return "DayOfQuarter";
441         //         }
442 
443         //         override
444         //         int opCmp(TemporalField obj)
445         //         {
446         //             if (cast(Field)(obj) !is null)
447         //             {
448         //                 Field other = cast(Field) obj;
449         //                 return compare(toString(), other.toString());
450         //             }
451         //             return 0;
452         //         }
453         //     };
454 
455             mixin(MakeGlobalVar!(Field)("DAY_OF_QUARTER",`new class Field
456             {
457                 override
458                 public TemporalUnit getBaseUnit()
459                 {
460                     return ChronoUnit.DAYS;
461                 }
462                 override
463                 public TemporalUnit getRangeUnit()
464                 {
465                     return QUARTER_YEARS;
466                 }
467                 override
468                 public ValueRange range()
469                 {
470                     return ValueRange.of(1, 90, 92);
471                 }
472                 override
473                 public bool isSupportedBy(TemporalAccessor temporal)
474                 {
475                     return temporal.isSupported(ChronoField.DAY_OF_YEAR)
476                         && temporal.isSupported(ChronoField.MONTH_OF_YEAR)
477                         && temporal.isSupported(ChronoField.YEAR) && isIso(temporal);
478                 }
479 
480                 override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
481                 {
482                     if (isSupportedBy(temporal) == false)
483                     {
484                         throw new Exception("Unsupported field: DayOfQuarter");
485                     }
486                     long qoy = temporal.getLong(QUARTER_OF_YEAR);
487                     if (qoy == 1)
488                     {
489                         long year = temporal.getLong(ChronoField.YEAR);
490                         return (IsoChronology.INSTANCE.isLeapYear(year)
491                                 ? ValueRange.of(1, 91) : ValueRange.of(1, 90));
492                     }
493                     else if (qoy == 2)
494                     {
495                         return ValueRange.of(1, 91);
496                     }
497                     else if (qoy == 3 || qoy == 4)
498                     {
499                         return ValueRange.of(1, 92);
500                     } // else value not from 1 to 4, so drop through
501                     return range();
502                 }
503                 override
504                 public long getFrom(TemporalAccessor temporal)
505                 {
506                     if (isSupportedBy(temporal) == false)
507                     {
508                         throw new Exception("Unsupported field: DayOfQuarter");
509                     }
510                     int doy = temporal.get(ChronoField.DAY_OF_YEAR);
511                     int moy = temporal.get(ChronoField.MONTH_OF_YEAR);
512                     long year = temporal.getLong(ChronoField.YEAR);
513                     return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year)
514                             ? 4 : 0)];
515                 }
516                 /*@SuppressWarnings("unchecked")*/
517                 override
518                 public Temporal adjustInto(Temporal temporal, long newValue)
519                         /* if (is(R : Temporal)) */
520                 {
521                     // calls getFrom() to check if supported
522                     long curValue = getFrom(temporal);
523                     range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check
524                     return cast(Temporal) temporal._with(ChronoField.DAY_OF_YEAR,
525                             temporal.getLong(ChronoField.DAY_OF_YEAR) + (newValue - curValue));
526                 }
527                 override
528                 public ChronoLocalDate resolve(Map!(TemporalField, Long) fieldValues,
529                         TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
530                 {
531                     Long yearLong = fieldValues.get(ChronoField.YEAR);
532                     Long qoyLong = fieldValues.get(QUARTER_OF_YEAR);
533                     if (yearLong is null || qoyLong is null)
534                     {
535                         return null;
536                     }
537                     int y = ChronoField.YEAR.checkValidIntValue(yearLong.longValue()); // always validate
538                     long doq = fieldValues.get(DAY_OF_QUARTER).longValue();
539                     ensureIso(partialTemporal);
540                     LocalDate date;
541                     if (resolverStyle == ResolverStyle.LENIENT)
542                     {
543                         date = LocalDate.of(y, 1, 1)
544                             .plusMonths(MathHelper.multiplyExact(MathHelper.subtractExact(cast(int)(qoyLong.longValue()),
545                                     1), 3));
546                         doq = MathHelper.subtractExact(doq, 1);
547                     }
548                     else
549                     {
550                         int qoy = QUARTER_OF_YEAR.range()
551                             .checkValidIntValue(qoyLong.longValue(), QUARTER_OF_YEAR); // validated
552                         date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1);
553                         if (doq < 1 || doq > 90)
554                         {
555                             if (resolverStyle == ResolverStyle.STRICT)
556                             {
557                                 rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range
558                             }
559                             else
560                             { // SMART
561                                 range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter
562                             }
563                         }
564                         doq--;
565                     }
566                     fieldValues.remove(this);
567                     fieldValues.remove(ChronoField.YEAR);
568                     fieldValues.remove(QUARTER_OF_YEAR);
569                     return date.plusDays(doq);
570                 }
571 
572                 override
573                 string getDisplayName(Locale locale)
574                 {
575                     assert(locale, "locale");
576                     return toString();
577                 }
578 
579                 override public string toString()
580                 {
581                     return "DayOfQuarter";
582                 }
583 
584                 override
585                 int opCmp(TemporalField obj)
586                 {
587                     if (cast(Field)(obj) !is null)
588                     {
589                         Field other = cast(Field) obj;
590                         return compare(toString(), other.toString());
591                     }
592                     return 0;
593                 }
594             }`));
595             // QUARTER_OF_YEAR = new class Field
596             // {
597             //     override
598             //     public TemporalUnit getBaseUnit()
599             //     {
600             //         return QUARTER_YEARS;
601             //     }
602             //     override
603             //     public TemporalUnit getRangeUnit()
604             //     {
605             //         return ChronoUnit.YEARS;
606             //     }
607             //     override
608             //     public ValueRange range()
609             //     {
610             //         return ValueRange.of(1, 4);
611             //     }
612             //     override
613             //     public bool isSupportedBy(TemporalAccessor temporal)
614             //     {
615             //         return temporal.isSupported(ChronoField.MONTH_OF_YEAR) && isIso(temporal);
616             //     }
617             //     override
618             //     public long getFrom(TemporalAccessor temporal)
619             //     {
620             //         if (isSupportedBy(temporal) == false)
621             //         {
622             //             throw new Exception("Unsupported field: QuarterOfYear");
623             //         }
624             //         long moy = temporal.getLong(ChronoField.MONTH_OF_YEAR);
625             //         return ((moy + 2) / 3);
626             //     }
627 
628             //     override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
629             //     {
630             //         if (isSupportedBy(temporal) == false)
631             //         {
632             //             throw new Exception("Unsupported field: QuarterOfYear");
633             //         }
634             //         return range()/* super.rangeRefinedBy(temporal) */;
635             //     }
636             //     /*@SuppressWarnings("unchecked")*/
637             //     override public Temporal adjustInto(Temporal temporal, long newValue)
638             //             /* if (is(R : Temporal)) */
639             //     {
640             //         // calls getFrom() to check if supported
641             //         long curValue = getFrom(temporal);
642             //         range().checkValidValue(newValue, this); // strictly check from 1 to 4
643             //         return cast(Temporal) temporal._with(ChronoField.MONTH_OF_YEAR,
644             //                 temporal.getLong(ChronoField.MONTH_OF_YEAR) + (newValue - curValue) * 3);
645             //     }
646 
647             //     override
648             //     string getDisplayName(Locale locale)
649             //     {
650             //         assert(locale, "locale");
651             //         return toString();
652             //     }
653 
654             //     override public string toString()
655             //     {
656             //         return "QuarterOfYear";
657             //     }
658 
659             //     override TemporalAccessor resolve(Map!(TemporalField, Long) fieldValues,
660             //             TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
661             //     {
662             //         return null;
663             //     }
664             //     override
665             //     int opCmp(TemporalField obj)
666             //     {
667             //         if (cast(Field)(obj) !is null)
668             //         {
669             //             Field other = cast(Field) obj;
670             //             return compare(toString(), other.toString());
671             //         }
672             //         return 0;
673             //     }
674             // };
675             mixin(MakeGlobalVar!(Field)("QUARTER_OF_YEAR",`new class Field
676             {
677                 override
678                 public TemporalUnit getBaseUnit()
679                 {
680                     return QUARTER_YEARS;
681                 }
682                 override
683                 public TemporalUnit getRangeUnit()
684                 {
685                     return ChronoUnit.YEARS;
686                 }
687                 override
688                 public ValueRange range()
689                 {
690                     return ValueRange.of(1, 4);
691                 }
692                 override
693                 public bool isSupportedBy(TemporalAccessor temporal)
694                 {
695                     return temporal.isSupported(ChronoField.MONTH_OF_YEAR) && isIso(temporal);
696                 }
697                 override
698                 public long getFrom(TemporalAccessor temporal)
699                 {
700                     if (isSupportedBy(temporal) == false)
701                     {
702                         throw new Exception("Unsupported field: QuarterOfYear");
703                     }
704                     long moy = temporal.getLong(ChronoField.MONTH_OF_YEAR);
705                     return ((moy + 2) / 3);
706                 }
707 
708                 override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
709                 {
710                     if (isSupportedBy(temporal) == false)
711                     {
712                         throw new Exception("Unsupported field: QuarterOfYear");
713                     }
714                     return range()/* super.rangeRefinedBy(temporal) */;
715                 }
716                 /*@SuppressWarnings("unchecked")*/
717                 override public Temporal adjustInto(Temporal temporal, long newValue)
718                         /* if (is(R : Temporal)) */
719                 {
720                     // calls getFrom() to check if supported
721                     long curValue = getFrom(temporal);
722                     range().checkValidValue(newValue, this); // strictly check from 1 to 4
723                     return cast(Temporal) temporal._with(ChronoField.MONTH_OF_YEAR,
724                             temporal.getLong(ChronoField.MONTH_OF_YEAR) + (newValue - curValue) * 3);
725                 }
726 
727                 override
728                 string getDisplayName(Locale locale)
729                 {
730                     assert(locale, "locale");
731                     return toString();
732                 }
733 
734                 override public string toString()
735                 {
736                     return "QuarterOfYear";
737                 }
738 
739                 override TemporalAccessor resolve(Map!(TemporalField, Long) fieldValues,
740                         TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
741                 {
742                     return null;
743                 }
744                 override
745                 int opCmp(TemporalField obj)
746                 {
747                     if (cast(Field)(obj) !is null)
748                     {
749                         Field other = cast(Field) obj;
750                         return compare(toString(), other.toString());
751                     }
752                     return 0;
753                 }
754             }`));
755 
756             // WEEK_OF_WEEK_BASED_YEAR = new class Field
757             // {
758             //     override
759             //     public string getDisplayName(Locale locale)
760             //     {
761             //         ///@gxc
762             //         // assert(locale, "locale");
763             //         // LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
764             //         //                             .getLocaleResources(
765             //         //                                 CalendarDataUtility
766             //         //                                     .findRegionOverride(locale));
767             //         // ResourceBundle rb = lr.getJavaTimeFormatData();
768             //         // return rb.containsKey("field.week") ? rb.getString("field.week") : toString();
769             //         implementationMissing();
770             //         return null;
771             //     }
772 
773             //     override
774             //     public TemporalUnit getBaseUnit()
775             //     {
776             //         return ChronoUnit.WEEKS;
777             //     }
778             //     override
779             //     public TemporalUnit getRangeUnit()
780             //     {
781             //         return WEEK_BASED_YEARS;
782             //     }
783             //     override
784             //     public ValueRange range()
785             //     {
786             //         return ValueRange.of(1, 52, 53);
787             //     }
788             //     override
789             //     public bool isSupportedBy(TemporalAccessor temporal)
790             //     {
791             //         return temporal.isSupported(ChronoField.EPOCH_DAY) && isIso(temporal);
792             //     }
793 
794             //     override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
795             //     {
796             //         if (isSupportedBy(temporal) == false)
797             //         {
798             //             throw new Exception("Unsupported field: WeekOfWeekBasedYear");
799             //         }
800             //         return getWeekRange(LocalDate.from(temporal));
801             //     }
802             //     override
803             //     public long getFrom(TemporalAccessor temporal)
804             //     {
805             //         if (isSupportedBy(temporal) == false)
806             //         {
807             //             throw new Exception("Unsupported field: WeekOfWeekBasedYear");
808             //         }
809             //         return getWeek(LocalDate.from(temporal));
810             //     }
811             //     /*@SuppressWarnings("unchecked")*/
812             //     override public Temporal adjustInto(Temporal temporal, long newValue)
813             //             /* if (is(R : Temporal)) */
814             //     {
815             //         // calls getFrom() to check if supported
816             //         range().checkValidValue(newValue, this); // lenient range
817             //         return cast(Temporal) temporal.plus(MathHelper.subtractExact(newValue,
818             //                 getFrom(temporal)), ChronoUnit.WEEKS);
819             //     }
820             //     override
821             //     public ChronoLocalDate resolve(Map!(TemporalField, Long) fieldValues,
822             //             TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
823             //     {
824             //         Long wbyLong = fieldValues.get(WEEK_BASED_YEAR);
825             //         Long dowLong = fieldValues.get(ChronoField.DAY_OF_WEEK);
826             //         if (wbyLong is null || dowLong is null)
827             //         {
828             //             return null;
829             //         }
830             //         int wby = WEEK_BASED_YEAR.range()
831             //             .checkValidIntValue(wbyLong.longValue(), WEEK_BASED_YEAR); // always validate
832             //         long wowby = fieldValues.get(WEEK_OF_WEEK_BASED_YEAR).longValue();
833             //         ensureIso(partialTemporal);
834             //         LocalDate date = LocalDate.of(wby, 1, 4);
835             //         if (resolverStyle == ResolverStyle.LENIENT)
836             //         {
837             //             long dow = dowLong.longValue(); // unvalidated
838             //             if (dow > 7)
839             //             {
840             //                 date = date.plusWeeks((dow - 1) / 7);
841             //                 dow = ((dow - 1) % 7) + 1;
842             //             }
843             //             else if (dow < 1)
844             //             {
845             //                 date = date.plusWeeks(MathHelper.subtractExact(dow, 7) / 7);
846             //                 dow = ((dow + 6) % 7) + 1;
847             //             }
848             //             date = date.plusWeeks(MathHelper.subtractExact(wowby, 1))
849             //                 ._with(ChronoField.DAY_OF_WEEK, dow);
850             //         }
851             //         else
852             //         {
853             //             int dow = ChronoField.DAY_OF_WEEK.checkValidIntValue(dowLong.longValue()); // validated
854             //             if (wowby < 1 || wowby > 52)
855             //             {
856             //                 if (resolverStyle == ResolverStyle.STRICT)
857             //                 {
858             //                     getWeekRange(date).checkValidValue(wowby, this); // only allow exact range
859             //                 }
860             //                 else
861             //                 { // SMART
862             //                     range().checkValidValue(wowby, this); // allow 1-53 rolling into next year
863             //                 }
864             //             }
865             //             date = date.plusWeeks(wowby - 1)._with(ChronoField.DAY_OF_WEEK, dow);
866             //         }
867             //         fieldValues.remove(this);
868             //         fieldValues.remove(WEEK_BASED_YEAR);
869             //         fieldValues.remove(ChronoField.DAY_OF_WEEK);
870             //         return date;
871             //     }
872 
873             //     /* override */
874             //     // string getDisplayName(Locale locale)
875             //     // {
876             //     //     assert(locale, "locale");
877             //     //     return toString();
878             //     // }
879 
880             //     override public string toString()
881             //     {
882             //         return "WeekOfWeekBasedYear";
883             //     }
884 
885             //     override int opCmp(TemporalField obj)
886             //     {
887             //         if (cast(Field)(obj) !is null)
888             //         {
889             //             Field other = cast(Field) obj;
890             //             return compare(toString(), other.toString());
891             //         }
892             //         return 0;
893             //     }
894             // };
895 
896             mixin(MakeGlobalVar!(Field)("WEEK_OF_WEEK_BASED_YEAR",`new class Field
897             {
898                 override
899                 public string getDisplayName(Locale locale)
900                 {
901                     ///@gxc
902                     // assert(locale, "locale");
903                     // LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
904                     //                             .getLocaleResources(
905                     //                                 CalendarDataUtility
906                     //                                     .findRegionOverride(locale));
907                     // ResourceBundle rb = lr.getJavaTimeFormatData();
908                     // return rb.containsKey("field.week") ? rb.getString("field.week") : toString();
909                     implementationMissing();
910                     return null;
911                 }
912 
913                 override
914                 public TemporalUnit getBaseUnit()
915                 {
916                     return ChronoUnit.WEEKS;
917                 }
918                 override
919                 public TemporalUnit getRangeUnit()
920                 {
921                     return IsoFields.WEEK_BASED_YEARS;
922                 }
923                 override
924                 public ValueRange range()
925                 {
926                     return ValueRange.of(1, 52, 53);
927                 }
928                 override
929                 public bool isSupportedBy(TemporalAccessor temporal)
930                 {
931                     return temporal.isSupported(ChronoField.EPOCH_DAY) && isIso(temporal);
932                 }
933 
934                 override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
935                 {
936                     if (isSupportedBy(temporal) == false)
937                     {
938                         throw new Exception("Unsupported field: WeekOfWeekBasedYear");
939                     }
940                     return getWeekRange(LocalDate.from(temporal));
941                 }
942                 override
943                 public long getFrom(TemporalAccessor temporal)
944                 {
945                     if (isSupportedBy(temporal) == false)
946                     {
947                         throw new Exception("Unsupported field: WeekOfWeekBasedYear");
948                     }
949                     return getWeek(LocalDate.from(temporal));
950                 }
951                 /*@SuppressWarnings("unchecked")*/
952                 override public Temporal adjustInto(Temporal temporal, long newValue)
953                         /* if (is(R : Temporal)) */
954                 {
955                     // calls getFrom() to check if supported
956                     range().checkValidValue(newValue, this); // lenient range
957                     return cast(Temporal) temporal.plus(MathHelper.subtractExact(newValue,
958                             getFrom(temporal)), ChronoUnit.WEEKS);
959                 }
960                 override
961                 public ChronoLocalDate resolve(Map!(TemporalField, Long) fieldValues,
962                         TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
963                 {
964                     Long wbyLong = fieldValues.get(WEEK_BASED_YEAR);
965                     Long dowLong = fieldValues.get(ChronoField.DAY_OF_WEEK);
966                     if (wbyLong is null || dowLong is null)
967                     {
968                         return null;
969                     }
970                     int wby = WEEK_BASED_YEAR.range()
971                         .checkValidIntValue(wbyLong.longValue(), WEEK_BASED_YEAR); // always validate
972                     long wowby = fieldValues.get(WEEK_OF_WEEK_BASED_YEAR).longValue();
973                     ensureIso(partialTemporal);
974                     LocalDate date = LocalDate.of(wby, 1, 4);
975                     if (resolverStyle == ResolverStyle.LENIENT)
976                     {
977                         long dow = dowLong.longValue(); // unvalidated
978                         if (dow > 7)
979                         {
980                             date = date.plusWeeks((dow - 1) / 7);
981                             dow = ((dow - 1) % 7) + 1;
982                         }
983                         else if (dow < 1)
984                         {
985                             date = date.plusWeeks(MathHelper.subtractExact(dow, 7) / 7);
986                             dow = ((dow + 6) % 7) + 1;
987                         }
988                         date = date.plusWeeks(MathHelper.subtractExact(wowby, 1))
989                             ._with(ChronoField.DAY_OF_WEEK, dow);
990                     }
991                     else
992                     {
993                         int dow = ChronoField.DAY_OF_WEEK.checkValidIntValue(dowLong.longValue()); // validated
994                         if (wowby < 1 || wowby > 52)
995                         {
996                             if (resolverStyle == ResolverStyle.STRICT)
997                             {
998                                 getWeekRange(date).checkValidValue(wowby, this); // only allow exact range
999                             }
1000                             else
1001                             { // SMART
1002                                 range().checkValidValue(wowby, this); // allow 1-53 rolling into next year
1003                             }
1004                         }
1005                         date = date.plusWeeks(wowby - 1)._with(ChronoField.DAY_OF_WEEK, dow);
1006                     }
1007                     fieldValues.remove(this);
1008                     fieldValues.remove(WEEK_BASED_YEAR);
1009                     fieldValues.remove(ChronoField.DAY_OF_WEEK);
1010                     return date;
1011                 }
1012 
1013                 /* override */
1014                 // string getDisplayName(Locale locale)
1015                 // {
1016                 //     assert(locale, "locale");
1017                 //     return toString();
1018                 // }
1019 
1020                 override public string toString()
1021                 {
1022                     return "WeekOfWeekBasedYear";
1023                 }
1024 
1025                 override int opCmp(TemporalField obj)
1026                 {
1027                     if (cast(Field)(obj) !is null)
1028                     {
1029                         Field other = cast(Field) obj;
1030                         return compare(toString(), other.toString());
1031                     }
1032                     return 0;
1033                 }
1034             }`));
1035 
1036             // WEEK_BASED_YEAR = new class Field
1037             // {
1038             //     override
1039             //     public TemporalUnit getBaseUnit()
1040             //     {
1041             //         return IsoFields.WEEK_BASED_YEARS;
1042             //     }
1043             //     override
1044             //     public TemporalUnit getRangeUnit()
1045             //     {
1046             //         return ChronoUnit.FOREVER;
1047             //     }
1048             //     override
1049             //     public ValueRange range()
1050             //     {
1051             //         return ChronoField.YEAR.range();
1052             //     }
1053             //     override
1054             //     public bool isSupportedBy(TemporalAccessor temporal)
1055             //     {
1056             //         return temporal.isSupported(ChronoField.EPOCH_DAY) && isIso(temporal);
1057             //     }
1058             //     override
1059             //     public long getFrom(TemporalAccessor temporal)
1060             //     {
1061             //         if (isSupportedBy(temporal) == false)
1062             //         {
1063             //             throw new Exception("Unsupported field: WeekBasedYear");
1064             //         }
1065             //         return getWeekBasedYear(LocalDate.from(temporal));
1066             //     }
1067 
1068             //     override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
1069             //     {
1070             //         if (isSupportedBy(temporal) == false)
1071             //         {
1072             //             throw new Exception("Unsupported field: WeekBasedYear");
1073             //         }
1074             //         return range()/* super.rangeRefinedBy(temporal) */;
1075             //     }
1076             //     /*@SuppressWarnings("unchecked")*/
1077             //     override public Temporal adjustInto(Temporal temporal, long newValue)
1078             //             /* if (is(R : Temporal)) */
1079             //     {
1080             //         if (isSupportedBy(temporal) == false)
1081             //         {
1082             //             throw new Exception("Unsupported field: WeekBasedYear");
1083             //         }
1084             //         int newWby = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check
1085             //         LocalDate date = LocalDate.from(temporal);
1086             //         int dow = date.get(ChronoField.DAY_OF_WEEK);
1087             //         int week = getWeek(date);
1088             //         if (week == 53 && getWeekRange(newWby) == 52)
1089             //         {
1090             //             week = 52;
1091             //         }
1092             //         LocalDate resolved = LocalDate.of(newWby, 1, 4); // 4th is guaranteed to be _in week one
1093             //         int days = (dow - resolved.get(ChronoField.DAY_OF_WEEK)) + ((week - 1) * 7);
1094             //         resolved = resolved.plusDays(days);
1095             //         return cast(Temporal) temporal._with(resolved);
1096             //     }
1097 
1098             //     override
1099             //     string getDisplayName(Locale locale)
1100             //     {
1101             //         assert(locale, "locale");
1102             //         return toString();
1103             //     }
1104 
1105             //     override public string toString()
1106             //     {
1107             //         return "WeekBasedYear";
1108             //     }
1109 
1110             //     override TemporalAccessor resolve(Map!(TemporalField, Long) fieldValues,
1111             //             TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
1112             //     {
1113             //         return null;
1114             //     }
1115 
1116             //     override int opCmp(TemporalField obj)
1117             //     {
1118             //         if (cast(Field)(obj) !is null)
1119             //         {
1120             //             Field other = cast(Field) obj;
1121             //             return compare(toString(), other.toString());
1122             //         }
1123             //         return 0;
1124             //     }
1125             // };
1126             mixin(MakeGlobalVar!(Field)("WEEK_BASED_YEAR",`new class Field
1127             {
1128                 override
1129                 public TemporalUnit getBaseUnit()
1130                 {
1131                     return IsoFields.WEEK_BASED_YEARS;
1132                 }
1133                 override
1134                 public TemporalUnit getRangeUnit()
1135                 {
1136                     return ChronoUnit.FOREVER;
1137                 }
1138                 override
1139                 public ValueRange range()
1140                 {
1141                     return ChronoField.YEAR.range();
1142                 }
1143                 override
1144                 public bool isSupportedBy(TemporalAccessor temporal)
1145                 {
1146                     return temporal.isSupported(ChronoField.EPOCH_DAY) && isIso(temporal);
1147                 }
1148                 override
1149                 public long getFrom(TemporalAccessor temporal)
1150                 {
1151                     if (isSupportedBy(temporal) == false)
1152                     {
1153                         throw new Exception("Unsupported field: WeekBasedYear");
1154                     }
1155                     return getWeekBasedYear(LocalDate.from(temporal));
1156                 }
1157 
1158                 override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
1159                 {
1160                     if (isSupportedBy(temporal) == false)
1161                     {
1162                         throw new Exception("Unsupported field: WeekBasedYear");
1163                     }
1164                     return range()/* super.rangeRefinedBy(temporal) */;
1165                 }
1166                 /*@SuppressWarnings("unchecked")*/
1167                 override public Temporal adjustInto(Temporal temporal, long newValue)
1168                         /* if (is(R : Temporal)) */
1169                 {
1170                     if (isSupportedBy(temporal) == false)
1171                     {
1172                         throw new Exception("Unsupported field: WeekBasedYear");
1173                     }
1174                     int newWby = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check
1175                     LocalDate date = LocalDate.from(temporal);
1176                     int dow = date.get(ChronoField.DAY_OF_WEEK);
1177                     int week = getWeek(date);
1178                     if (week == 53 && getWeekRange(newWby) == 52)
1179                     {
1180                         week = 52;
1181                     }
1182                     LocalDate resolved = LocalDate.of(newWby, 1, 4); // 4th is guaranteed to be _in week one
1183                     int days = (dow - resolved.get(ChronoField.DAY_OF_WEEK)) + ((week - 1) * 7);
1184                     resolved = resolved.plusDays(days);
1185                     return cast(Temporal) temporal._with(resolved);
1186                 }
1187 
1188                 override
1189                 string getDisplayName(Locale locale)
1190                 {
1191                     assert(locale, "locale");
1192                     return toString();
1193                 }
1194 
1195                 override public string toString()
1196                 {
1197                     return "WeekBasedYear";
1198                 }
1199 
1200                 override TemporalAccessor resolve(Map!(TemporalField, Long) fieldValues,
1201                         TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
1202                 {
1203                     return null;
1204                 }
1205 
1206                 override int opCmp(TemporalField obj)
1207                 {
1208                     if (cast(Field)(obj) !is null)
1209                     {
1210                         Field other = cast(Field) obj;
1211                         return compare(toString(), other.toString());
1212                     }
1213                     return 0;
1214                 }
1215             }`));
1216         // }
1217 
1218         override public bool isDateBased()
1219         {
1220             return true;
1221         }
1222 
1223         override public bool isTimeBased()
1224         {
1225             return false;
1226         }
1227 
1228         override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
1229         {
1230             return range();
1231         }
1232 
1233         //-------------------------------------------------------------------------
1234         enum int[] QUARTER_DAYS = [0, 90, 181, 273, 0, 91, 182, 274];
1235 
1236          static void ensureIso(TemporalAccessor temporal)
1237         {
1238             if (isIso(temporal) == false)
1239             {
1240                 throw new DateTimeException("Resolve requires IsoChronology");
1241             }
1242         }
1243 
1244          static ValueRange getWeekRange(LocalDate date)
1245         {
1246             int wby = getWeekBasedYear(date);
1247             return ValueRange.of(1, getWeekRange(wby));
1248         }
1249 
1250          static int getWeekRange(int wby)
1251         {
1252             LocalDate date = LocalDate.of(wby, 1, 1);
1253             // 53 weeks if standard year starts on Thursday, or Wed _in a leap year
1254             if (date.getDayOfWeek() == DayOfWeek.THURSDAY
1255                     || (date.getDayOfWeek() == DayOfWeek.WEDNESDAY && date.isLeapYear()))
1256             {
1257                 return 53;
1258             }
1259             return 52;
1260         }
1261 
1262          static int getWeek(LocalDate date)
1263         {
1264             int dow0 = date.getDayOfWeek().ordinal();
1265             int doy0 = date.getDayOfYear() - 1;
1266             int doyThu0 = doy0 + (3 - dow0); // adjust to mid-week Thursday (which is 3 indexed from zero)
1267             int alignedWeek = doyThu0 / 7;
1268             int firstThuDoy0 = doyThu0 - (alignedWeek * 7);
1269             int firstMonDoy0 = firstThuDoy0 - 3;
1270             if (firstMonDoy0 < -3)
1271             {
1272                 firstMonDoy0 += 7;
1273             }
1274             if (doy0 < firstMonDoy0)
1275             {
1276                 return cast(int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum();
1277             }
1278             int week = ((doy0 - firstMonDoy0) / 7) + 1;
1279             if (week == 53)
1280             {
1281                 if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false)
1282                 {
1283                     week = 1;
1284                 }
1285             }
1286             return week;
1287         }
1288 
1289         static int getWeekBasedYear(LocalDate date)
1290         {
1291             int year = date.getYear();
1292             int doy = date.getDayOfYear();
1293             if (doy <= 3)
1294             {
1295                 int dow = date.getDayOfWeek().ordinal();
1296                 if (doy - dow < -2)
1297                 {
1298                     year--;
1299                 }
1300             }
1301             else if (doy >= 363)
1302             {
1303                 int dow = date.getDayOfWeek().ordinal();
1304                 doy = doy - 363 - (date.isLeapYear() ? 1 : 0);
1305                 if (doy - dow >= 0)
1306                 {
1307                     year++;
1308                 }
1309             }
1310             return year;
1311         }
1312     }
1313 
1314     //-----------------------------------------------------------------------
1315     /**
1316      * Implementation of the unit.
1317      */
1318     static class Unit : TemporalUnit
1319     {
1320 
1321         /**
1322          * Unit that represents the concept of a week-based-year.
1323          */
1324         // static Unit WEEK_BASED_YEARS;
1325         /**
1326          * Unit that represents the concept of a quarter-year.
1327          */
1328         // static Unit QUARTER_YEARS;
1329 
1330         // shared static this()
1331         // {
1332             // WEEK_BASED_YEARS = new Unit("WeekBasedYears", Duration.ofSeconds(31556952L));
1333             mixin(MakeGlobalVar!(Unit)("WEEK_BASED_YEARS",`new Unit("WeekBasedYears", 0, Duration.ofSeconds(31556952L))`));
1334             // QUARTER_YEARS = new Unit("QuarterYears", Duration.ofSeconds(31556952L / 4));
1335             mixin(MakeGlobalVar!(Unit)("QUARTER_YEARS",`new Unit("QuarterYears", 1, Duration.ofSeconds(31556952L / 4))`));
1336 
1337         // }
1338 
1339         // private string name;
1340         private Duration duration;
1341 
1342         protected this(string name, int ordinal, Duration estimatedDuration)
1343         {
1344             super(name, ordinal);
1345             this.duration = estimatedDuration;
1346         }
1347 
1348         override public Duration getDuration()
1349         {
1350             return duration;
1351         }
1352 
1353         override public bool isDurationEstimated()
1354         {
1355             return true;
1356         }
1357 
1358         override public bool isDateBased()
1359         {
1360             return true;
1361         }
1362 
1363         override public bool isTimeBased()
1364         {
1365             return false;
1366         }
1367 
1368         override public bool isSupportedBy(Temporal temporal)
1369         {
1370             return temporal.isSupported((ChronoField.EPOCH_DAY)) && isIso(temporal);
1371         }
1372 
1373         /*@SuppressWarnings("unchecked")*/
1374         override public Temporal addTo(Temporal temporal, long amount) /* if (is(R : Temporal)) */
1375         {
1376             auto name = this.toString();
1377             {
1378             if(name ==Unit.WEEK_BASED_YEARS.toString)
1379                 return cast(Temporal) temporal._with(WEEK_BASED_YEAR,
1380                         MathHelper.addExact(temporal.get(WEEK_BASED_YEAR), amount));
1381             if(name ==Unit.QUARTER_YEARS.toString)
1382                 return cast(Temporal) temporal.plus(amount / 4, ChronoUnit.YEARS)
1383                     .plus((amount % 4) * 3, ChronoUnit.MONTHS);
1384             throw new IllegalStateException("Unreachable");
1385             }
1386         }
1387 
1388         override public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive)
1389         {
1390             if (typeid(temporal1Inclusive) != typeid(temporal2Exclusive))
1391             {
1392                 return temporal1Inclusive.until(temporal2Exclusive, this);
1393             }
1394             auto name = this.toString();
1395             {
1396                 if (name == WEEK_BASED_YEARS.toString)
1397                     return MathHelper.subtractExact(temporal2Exclusive.getLong(WEEK_BASED_YEAR),
1398                             temporal1Inclusive.getLong(WEEK_BASED_YEAR));
1399                 if (name == QUARTER_YEARS.toString)
1400                     return temporal1Inclusive.until(temporal2Exclusive, ChronoUnit.MONTHS) / 3;
1401 
1402                 throw new IllegalStateException("Unreachable");
1403             }
1404         }
1405 
1406         // override public string toString()
1407         // {
1408         //     return name;
1409         // }
1410 
1411     }
1412 
1413     static bool isIso(TemporalAccessor temporal)
1414     {
1415         return Chronology.from(temporal) == (IsoChronology.INSTANCE);
1416     }
1417 }