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.WeekFields;
13 
14 import hunt.time.temporal.ChronoField;
15 import hunt.time.temporal.ChronoUnit;
16 
17 import hunt.Exceptions;
18 
19 //import hunt.io.ObjectInputStream;
20 import hunt.time.Exceptions;
21 import hunt.time.DayOfWeek;
22 import hunt.time.chrono.ChronoLocalDate;
23 import hunt.time.chrono.Chronology;
24 import hunt.time.format.ResolverStyle;
25 import hunt.time.temporal.TemporalField;
26 import hunt.time.temporal.TemporalUnit;
27 import hunt.time.temporal.ValueRange;
28 import hunt.time.temporal.Temporal;
29 import hunt.time.temporal.TemporalAccessor;
30 import hunt.time.temporal.IsoFields;
31 import hunt.time.util.Common;
32 
33 import hunt.collection.HashMap;
34 import hunt.collection.Map;
35 import hunt.Exceptions;
36 import hunt.stream.Common;
37 import hunt.Long;
38 import hunt.math.Helper;
39 import hunt.util.Common;
40 import hunt.util.Comparator;
41 import hunt.util.Locale;
42 // import hunt.serialization.JsonSerializer;
43 
44 import std.conv;
45 
46 // import sun.util.locale.provider.CalendarDataUtility;
47 // import sun.util.locale.provider.LocaleProviderAdapter;
48 // import sun.util.locale.provider.LocaleResources;
49 
50 /**
51  * Localized definitions of the day-of-week, week-of-month and week-of-year fields.
52  * !(p)
53  * A standard week is seven days long, but cultures have different definitions for some
54  * other aspects of a week. This class represents the definition of the week, for the
55  * purpose of providing {@link TemporalField} instances.
56  * !(p)
57  * WeekFields provides five fields,
58  * {@link #dayOfWeek()}, {@link #_weekOfMonth()}, {@link #weekOfYear()},
59  * {@link #_weekOfWeekBasedYear()}, and {@link #_weekBasedYear()}
60  * that provide access to the values from any {@linkplain Temporal temporal object}.
61  * !(p)
62  * The computations for day-of-week, week-of-month, and week-of-year are based
63  * on the  {@linkplain ChronoField#YEAR proleptic-year},
64  * {@linkplain ChronoField#MONTH_OF_YEAR month-of-year},
65  * {@linkplain ChronoField#DAY_OF_MONTH day-of-month}, and
66  * {@linkplain ChronoField#DAY_OF_WEEK ISO day-of-week} which are based on the
67  * {@linkplain ChronoField#EPOCH_DAY epoch-day} and the chronology.
68  * The values may not be aligned with the {@linkplain ChronoField#YEAR_OF_ERA year-of-Era}
69  * depending on the Chronology.
70  * !(p)A week is defined by:
71  * !(ul)
72  * !(li)The first day-of-week.
73  * For example, the ISO-8601 standard considers Monday to be the first day-of-week.
74  * !(li)The minimal number of days _in the first week.
75  * For example, the ISO-8601 standard counts the first week as needing at least 4 days.
76  * </ul>
77  * Together these two values allow a year or month to be divided into weeks.
78  *
79  * !(h3)Week of Month</h3>
80  * One field is used: week-of-month.
81  * The calculation ensures that weeks never overlap a month boundary.
82  * The month is divided into periods where each period starts on the defined first day-of-week.
83  * The earliest period is referred to as week 0 if it has less than the minimal number of days
84  * and week 1 if it has at least the minimal number of days.
85  *
86  * <table class=striped style="text-align: left">
87  * !(caption)Examples of WeekFields</caption>
88  * !(thead)
89  * !(tr)<th scope="col">Date</th><th scope="col">Day-of-week</th>
90  *  <th scope="col">First day: Monday!(br)Minimal days: 4</th><th scope="col">First day: Monday!(br)Minimal days: 5</th></tr>
91  * </thead>
92  * !(tbody)
93  * !(tr)<th scope="row">2008-12-31</th>!(td)Wednesday</td>
94  *  !(td)Week 5 of December 2008</td>!(td)Week 5 of December 2008</td></tr>
95  * !(tr)<th scope="row">2009-01-01</th>!(td)Thursday</td>
96  *  !(td)Week 1 of January 2009</td>!(td)Week 0 of January 2009</td></tr>
97  * !(tr)<th scope="row">2009-01-04</th>!(td)Sunday</td>
98  *  !(td)Week 1 of January 2009</td>!(td)Week 0 of January 2009</td></tr>
99  * !(tr)<th scope="row">2009-01-05</th>!(td)Monday</td>
100  *  !(td)Week 2 of January 2009</td>!(td)Week 1 of January 2009</td></tr>
101  * </tbody>
102  * </table>
103  *
104  * !(h3)Week of Year</h3>
105  * One field is used: week-of-year.
106  * The calculation ensures that weeks never overlap a year boundary.
107  * The year is divided into periods where each period starts on the defined first day-of-week.
108  * The earliest period is referred to as week 0 if it has less than the minimal number of days
109  * and week 1 if it has at least the minimal number of days.
110  *
111  * !(h3)Week Based Year</h3>
112  * Two fields are used for week-based-year, one for the
113  * {@link #_weekOfWeekBasedYear() week-of-week-based-year} and one for
114  * {@link #_weekBasedYear() week-based-year}.  In a week-based-year, each week
115  * belongs to only a single year.  Week 1 of a year is the first week that
116  * starts on the first day-of-week and has at least the minimum number of days.
117  * The first and last weeks of a year may contain days from the
118  * previous calendar year or next calendar year respectively.
119  *
120  * <table class=striped style="text-align: left;">
121  * !(caption)Examples of WeekFields for week-based-year</caption>
122  * !(thead)
123  * !(tr)<th scope="col">Date</th><th scope="col">Day-of-week</th>
124  *  <th scope="col">First day: Monday!(br)Minimal days: 4</th><th scope="col">First day: Monday!(br)Minimal days: 5</th></tr>
125  * </thead>
126  * !(tbody)
127  * !(tr)<th scope="row">2008-12-31</th>!(td)Wednesday</td>
128  *  !(td)Week 1 of 2009</td>!(td)Week 53 of 2008</td></tr>
129  * !(tr)<th scope="row">2009-01-01</th>!(td)Thursday</td>
130  *  !(td)Week 1 of 2009</td>!(td)Week 53 of 2008</td></tr>
131  * !(tr)<th scope="row">2009-01-04</th>!(td)Sunday</td>
132  *  !(td)Week 1 of 2009</td>!(td)Week 53 of 2008</td></tr>
133  * !(tr)<th scope="row">2009-01-05</th>!(td)Monday</td>
134  *  !(td)Week 2 of 2009</td>!(td)Week 1 of 2009</td></tr>
135  * </tbody>
136  * </table>
137  *
138  * @implSpec
139  * This class is immutable and thread-safe.
140  *
141  * @since 1.8
142  */
143 public final class WeekFields // : Serializable
144 {
145     // implementation notes
146     // querying week-of-month or week-of-year should return the week value bound within the month/year
147     // however, setting the week value should be lenient (use plus/minus weeks)
148     // allow week-of-month outer _range [0 to 6]
149     // allow week-of-year outer _range [0 to 54]
150     // this is because callers shouldn't be expected to know the details of validity
151 
152     /**
153      * The cache of rules by firstDayOfWeek plus minimalDays.
154      * Initialized first to be available for definition of ISO, etc.
155      */
156     // private static final ConcurrentMap!(string, WeekFields) CACHE = new ConcurrentHashMap!()(4, 0.75f, 2);
157     // __gshared HashMap!(string, WeekFields) CACHE;
158 
159     /**
160      * The ISO-8601 definition, where a week starts on Monday and the first week
161      * has a minimum of 4 days.
162      * !(p)
163      * The ISO-8601 standard defines a calendar system based on weeks.
164      * It uses the week-based-year and week-of-week-based-year concepts to split
165      * up the passage of days instead of the standard year/month/day.
166      * !(p)
167      * Note that the first week may start _in the previous calendar year.
168      * Note also that the first few days of a calendar year may be _in the
169      * week-based-year corresponding to the previous calendar year.
170      */
171     __gshared WeekFields _ISO;
172 
173     /**
174      * The common definition of a week that starts on Sunday and the first week
175      * has a minimum of 1 day.
176      * !(p)
177      * Defined as starting on Sunday and with a minimum of 1 day _in the month.
178      * This week definition is _in use _in the US and other European countries.
179      */
180     // __gshared WeekFields SUNDAY_START;
181 
182     /**
183      * The unit that represents week-based-years for the purpose of addition and subtraction.
184      * !(p)
185      * This allows a number of week-based-years to be added to, or subtracted from, a date.
186      * The unit is equal to either 52 or 53 weeks.
187      * The estimated duration of a week-based-year is the same as that of a standard ISO
188      * year at {@code 365.2425 Days}.
189      * !(p)
190      * The rules for addition add the number of week-based-years to the existing value
191      * for the week-based-year field retaining the week-of-week-based-year
192      * and day-of-week, unless the week number it too large for the target year.
193      * In that case, the week is set to the last week of the year
194      * with the same day-of-week.
195      * !(p)
196      * This unit is an immutable and thread-safe singleton.
197      */
198     // __gshared TemporalUnit WEEK_BASED_YEARS;
199 
200     /**
201      * Serialization version.
202      */
203     private enum long serialVersionUID = -1177360819670808121L;
204 
205     /**
206      * The first day-of-week.
207      */
208     private DayOfWeek firstDayOfWeek;
209     /**
210      * The minimal number of days _in the first week.
211      */
212     private int minimalDays;
213     /**
214      * The field used to access the computed DayOfWeek.
215      */
216     private  /*transient*/ TemporalField _dayOfWeek;
217     /**
218      * The field used to access the computed WeekOfMonth.
219      */
220     private  /*transient*/ TemporalField _weekOfMonth;
221     /**
222      * The field used to access the computed WeekOfYear.
223      */
224     private  /*transient*/ TemporalField _weekOfYear;
225     /**
226      * The field that represents the week-of-week-based-year.
227      * !(p)
228      * This field allows the week of the week-based-year value to be queried and set.
229      * !(p)
230      * This unit is an immutable and thread-safe singleton.
231      */
232     private  /*transient*/ TemporalField _weekOfWeekBasedYear;
233     /**
234      * The field that represents the week-based-year.
235      * !(p)
236      * This field allows the week-based-year value to be queried and set.
237      * !(p)
238      * This unit is an immutable and thread-safe singleton.
239      */
240     private  /*transient*/ TemporalField _weekBasedYear;
241 
242     public void do_init()
243     {
244         _dayOfWeek = ComputedDayOfField.ofDayOfWeekField(this);
245         _weekOfMonth = ComputedDayOfField.ofWeekOfMonthField(this);
246         _weekOfYear = ComputedDayOfField.ofWeekOfYearField(this);
247         _weekOfWeekBasedYear = ComputedDayOfField.ofWeekOfWeekBasedYearField(this);
248         _weekBasedYear = ComputedDayOfField.ofWeekBasedYearField(this);
249     }
250 
251     public static ref WeekFields ISO()
252     {
253         if(_ISO is null)
254         {
255             _ISO = new WeekFields(DayOfWeek.MONDAY, 4);
256             _ISO.do_init();
257         }
258         return _ISO;
259     }
260 
261     // shared static this()
262     // {
263         // CACHE = new HashMap!(string, WeekFields)(4, 0.75f /* , 2 */ );
264         mixin(MakeGlobalVar!(HashMap!(string, WeekFields))("CACHE",` new HashMap!(string, WeekFields)(4, 0.75f /* , 2 */ )`));
265         // ISO = new WeekFields(DayOfWeek.MONDAY, 4);
266         // mixin(MakeGlobalVar!(WeekFields)("ISO",` new WeekFields(DayOfWeek.MONDAY, 4)`));
267 
268         // ISO.do_init();
269         // SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1);
270         mixin(MakeGlobalVar!(WeekFields)("SUNDAY_START",`WeekFields.of(DayOfWeek.SUNDAY, 1)`));
271         // WEEK_BASED_YEARS = IsoFields.WEEK_BASED_YEARS;
272         mixin(MakeGlobalVar!(TemporalUnit)("WEEK_BASED_YEARS",`IsoFields.WEEK_BASED_YEARS`));
273 
274     // }
275     //-----------------------------------------------------------------------
276     /**
277      * Obtains an instance of {@code WeekFields} appropriate for a locale.
278      * !(p)
279      * This will look up appropriate values from the provider of localization data.
280      * If the locale contains "fw" (First day of week) and/or "rg"
281      * (Region Override) <a href="../../util/Locale.html#def_locale_extension">
282      * Unicode extensions</a>, returned instance will reflect the values specified with
283      * those extensions. If both "fw" and "rg" are specified, the value from
284      * the "fw" extension supersedes the implicit one from the "rg" extension.
285      *
286      * @param locale  the locale to use, not null
287      * @return the week-definition, not null
288      */
289     public static WeekFields of(Locale locale)
290     {
291         assert(locale, "locale");
292 
293         ///@gxc
294         // int calDow = CalendarDataUtility.retrieveFirstDayOfWeek(locale);
295         // DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1);
296         // int minDays = CalendarDataUtility.retrieveMinimalDaysInFirstWeek(locale);
297         DayOfWeek dow = DayOfWeek.SUNDAY.plus(2 - 1);
298         int minDays = 7;
299         return WeekFields.of(dow, minDays);
300     }
301 
302     /**
303      * Obtains an instance of {@code WeekFields} from the first day-of-week and minimal days.
304      * !(p)
305      * The first day-of-week defines the ISO {@code DayOfWeek} that is day 1 of the week.
306      * The minimal number of days _in the first week defines how many days must be present
307      * _in a month or year, starting from the first day-of-week, before the week is counted
308      * as the first week. A value of 1 will count the first day of the month or year as part
309      * of the first week, whereas a value of 7 will require the whole seven days to be _in
310      * the new month or year.
311      * !(p)
312      * WeekFields instances are singletons; for each unique combination
313      * of {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek}
314      * the same instance will be returned.
315      *
316      * @param firstDayOfWeek  the first day of the week, not null
317      * @param minimalDaysInFirstWeek  the minimal number of days _in the first week, from 1 to 7
318      * @return the week-definition, not null
319      * @throws IllegalArgumentException if the minimal days value is less than one
320      *      or greater than 7
321      */
322     public static WeekFields of(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek)
323     {
324         string key = firstDayOfWeek.toString() ~ minimalDaysInFirstWeek.to!string;
325         WeekFields rules = CACHE.get(key);
326         if (rules is null)
327         {
328             rules = new WeekFields(firstDayOfWeek, minimalDaysInFirstWeek);
329             rules.do_init();
330             CACHE.putIfAbsent(key, rules);
331             rules = CACHE.get(key);
332         }
333         return rules;
334     }
335 
336     //-----------------------------------------------------------------------
337     /**
338      * Creates an instance of the definition.
339      *
340      * @param firstDayOfWeek  the first day of the week, not null
341      * @param minimalDaysInFirstWeek  the minimal number of days _in the first week, from 1 to 7
342      * @throws IllegalArgumentException if the minimal days value is invalid
343      */
344     this(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek)
345     {
346         assert(firstDayOfWeek, "firstDayOfWeek");
347         if (minimalDaysInFirstWeek < 1 || minimalDaysInFirstWeek > 7)
348         {
349             throw new IllegalArgumentException("Minimal number of days is invalid");
350         }
351         this.firstDayOfWeek = firstDayOfWeek;
352         this.minimalDays = minimalDaysInFirstWeek;
353     }
354 
355     //-----------------------------------------------------------------------
356     /**
357      * Restore the state of a WeekFields from the stream.
358      * Check that the values are valid.
359      *
360      * @param s the stream to read
361      * @throws InvalidObjectException if the serialized object has an invalid
362      *     value for firstDayOfWeek or minimalDays.
363      * @throws ClassNotFoundException if a class cannot be resolved
364      */
365     ///@gxc
366     // private void readObject(ObjectInputStream s)
367     //      /*throws IOException, ClassNotFoundException, InvalidObjectException*/
368     // {
369     //     s.defaultReadObject();
370     //     if (firstDayOfWeek is null) {
371     //         throw new InvalidObjectException("firstDayOfWeek is null");
372     //     }
373 
374     //     if (minimalDays < 1 || minimalDays > 7) {
375     //         throw new InvalidObjectException("Minimal number of days is invalid");
376     //     }
377     // }
378 
379     /**
380      * Return the singleton WeekFields associated with the
381      * {@code firstDayOfWeek} and {@code minimalDays}.
382      * @return the singleton WeekFields for the firstDayOfWeek and minimalDays.
383      * @throws InvalidObjectException if the serialized object has invalid
384      *     values for firstDayOfWeek or minimalDays.
385      */
386     private Object readResolve() /*throws InvalidObjectException*/
387     {
388         try
389         {
390             return WeekFields.of(firstDayOfWeek, minimalDays);
391         }
392         catch (IllegalArgumentException iae)
393         {
394             throw new InvalidObjectException("Invalid serialized WeekFields: " ~ iae.msg);
395         }
396     }
397 
398     //-----------------------------------------------------------------------
399     /**
400      * Gets the first day-of-week.
401      * !(p)
402      * The first day-of-week varies by culture.
403      * For example, the US uses Sunday, while France and the ISO-8601 standard use Monday.
404      * This method returns the first day using the standard {@code DayOfWeek} enum.
405      *
406      * @return the first day-of-week, not null
407      */
408     public DayOfWeek getFirstDayOfWeek()
409     {
410         return firstDayOfWeek;
411     }
412 
413     /**
414      * Gets the minimal number of days _in the first week.
415      * !(p)
416      * The number of days considered to define the first week of a month or year
417      * varies by culture.
418      * For example, the ISO-8601 requires 4 days (more than half a week) to
419      * be present before counting the first week.
420      *
421      * @return the minimal number of days _in the first week of a month or year, from 1 to 7
422      */
423     public int getMinimalDaysInFirstWeek()
424     {
425         return minimalDays;
426     }
427 
428     //-----------------------------------------------------------------------
429     /**
430      * Returns a field to access the day of week based on this {@code WeekFields}.
431      * !(p)
432      * This is similar to {@link ChronoField#DAY_OF_WEEK} but uses values for
433      * the day-of-week based on this {@code WeekFields}.
434      * The days are numbered from 1 to 7 where the
435      * {@link #getFirstDayOfWeek() first day-of-week} is assigned the value 1.
436      * !(p)
437      * For example, if the first day-of-week is Sunday, then that will have the
438      * value 1, with other days ranging from Monday as 2 to Saturday as 7.
439      * !(p)
440      * In the resolving phase of parsing, a localized day-of-week will be converted
441      * to a standardized {@code ChronoField} day-of-week.
442      * The day-of-week must be _in the valid _range 1 to 7.
443      * Other fields _in this class build dates using the standardized day-of-week.
444      *
445      * @return a field providing access to the day-of-week with localized numbering, not null
446      */
447     public TemporalField dayOfWeek()
448     {
449         return _dayOfWeek;
450     }
451 
452     /**
453      * Returns a field to access the week of month based on this {@code WeekFields}.
454      * !(p)
455      * This represents the concept of the count of weeks within the month where weeks
456      * start on a fixed day-of-week, such as Monday.
457      * This field is typically used with {@link WeekFields#dayOfWeek()}.
458      * !(p)
459      * Week one (1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
460      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the month.
461      * Thus, week one may start up to {@code minDays} days before the start of the month.
462      * If the first week starts after the start of the month then the period before is week zero (0).
463      * !(p)
464      * For example:!(br)
465      * - if the 1st day of the month is a Monday, week one starts on the 1st and there is no week zero!(br)
466      * - if the 2nd day of the month is a Monday, week one starts on the 2nd and the 1st is _in week zero!(br)
467      * - if the 4th day of the month is a Monday, week one starts on the 4th and the 1st to 3rd is _in week zero!(br)
468      * - if the 5th day of the month is a Monday, week two starts on the 5th and the 1st to 4th is _in week one!(br)
469      * !(p)
470      * This field can be used with any calendar system.
471      * !(p)
472      * In the resolving phase of parsing, a date can be created from a year,
473      * week-of-month, month-of-year and day-of-week.
474      * !(p)
475      * In {@linkplain ResolverStyle#STRICT strict mode}, all four fields are
476      * validated against their _range of valid values. The week-of-month field
477      * is validated to ensure that the resulting month is the month requested.
478      * !(p)
479      * In {@linkplain ResolverStyle#SMART smart mode}, all four fields are
480      * validated against their _range of valid values. The week-of-month field
481      * is validated from 0 to 6, meaning that the resulting date can be _in a
482      * different month to that specified.
483      * !(p)
484      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
485      * are validated against the _range of valid values. The resulting date is calculated
486      * equivalent to the following four stage approach.
487      * First, create a date on the first day of the first week of January _in the requested year.
488      * Then take the month-of-year, subtract one, and add the amount _in months to the date.
489      * Then take the week-of-month, subtract one, and add the amount _in weeks to the date.
490      * Finally, adjust to the correct day-of-week within the localized week.
491      *
492      * @return a field providing access to the week-of-month, not null
493      */
494     public TemporalField weekOfMonth()
495     {
496         return _weekOfMonth;
497     }
498 
499     /**
500      * Returns a field to access the week of year based on this {@code WeekFields}.
501      * !(p)
502      * This represents the concept of the count of weeks within the year where weeks
503      * start on a fixed day-of-week, such as Monday.
504      * This field is typically used with {@link WeekFields#dayOfWeek()}.
505      * !(p)
506      * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
507      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the year.
508      * Thus, week one may start up to {@code minDays} days before the start of the year.
509      * If the first week starts after the start of the year then the period before is week zero (0).
510      * !(p)
511      * For example:!(br)
512      * - if the 1st day of the year is a Monday, week one starts on the 1st and there is no week zero!(br)
513      * - if the 2nd day of the year is a Monday, week one starts on the 2nd and the 1st is _in week zero!(br)
514      * - if the 4th day of the year is a Monday, week one starts on the 4th and the 1st to 3rd is _in week zero!(br)
515      * - if the 5th day of the year is a Monday, week two starts on the 5th and the 1st to 4th is _in week one!(br)
516      * !(p)
517      * This field can be used with any calendar system.
518      * !(p)
519      * In the resolving phase of parsing, a date can be created from a year,
520      * week-of-year and day-of-week.
521      * !(p)
522      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
523      * validated against their _range of valid values. The week-of-year field
524      * is validated to ensure that the resulting year is the year requested.
525      * !(p)
526      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
527      * validated against their _range of valid values. The week-of-year field
528      * is validated from 0 to 54, meaning that the resulting date can be _in a
529      * different year to that specified.
530      * !(p)
531      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
532      * are validated against the _range of valid values. The resulting date is calculated
533      * equivalent to the following three stage approach.
534      * First, create a date on the first day of the first week _in the requested year.
535      * Then take the week-of-year, subtract one, and add the amount _in weeks to the date.
536      * Finally, adjust to the correct day-of-week within the localized week.
537      *
538      * @return a field providing access to the week-of-year, not null
539      */
540     public TemporalField weekOfYear()
541     {
542         return _weekOfYear;
543     }
544 
545     /**
546      * Returns a field to access the week of a week-based-year based on this {@code WeekFields}.
547      * !(p)
548      * This represents the concept of the count of weeks within the year where weeks
549      * start on a fixed day-of-week, such as Monday and each week belongs to exactly one year.
550      * This field is typically used with {@link WeekFields#dayOfWeek()} and
551      * {@link WeekFields#_weekBasedYear()}.
552      * !(p)
553      * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
554      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the year.
555      * If the first week starts after the start of the year then the period before
556      * is _in the last week of the previous year.
557      * !(p)
558      * For example:!(br)
559      * - if the 1st day of the year is a Monday, week one starts on the 1st!(br)
560      * - if the 2nd day of the year is a Monday, week one starts on the 2nd and
561      *   the 1st is _in the last week of the previous year!(br)
562      * - if the 4th day of the year is a Monday, week one starts on the 4th and
563      *   the 1st to 3rd is _in the last week of the previous year!(br)
564      * - if the 5th day of the year is a Monday, week two starts on the 5th and
565      *   the 1st to 4th is _in week one!(br)
566      * !(p)
567      * This field can be used with any calendar system.
568      * !(p)
569      * In the resolving phase of parsing, a date can be created from a week-based-year,
570      * week-of-year and day-of-week.
571      * !(p)
572      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
573      * validated against their _range of valid values. The week-of-year field
574      * is validated to ensure that the resulting week-based-year is the
575      * week-based-year requested.
576      * !(p)
577      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
578      * validated against their _range of valid values. The week-of-week-based-year field
579      * is validated from 1 to 53, meaning that the resulting date can be _in the
580      * following week-based-year to that specified.
581      * !(p)
582      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
583      * are validated against the _range of valid values. The resulting date is calculated
584      * equivalent to the following three stage approach.
585      * First, create a date on the first day of the first week _in the requested week-based-year.
586      * Then take the week-of-week-based-year, subtract one, and add the amount _in weeks to the date.
587      * Finally, adjust to the correct day-of-week within the localized week.
588      *
589      * @return a field providing access to the week-of-week-based-year, not null
590      */
591     public TemporalField weekOfWeekBasedYear()
592     {
593         return _weekOfWeekBasedYear;
594     }
595 
596     /**
597      * Returns a field to access the year of a week-based-year based on this {@code WeekFields}.
598      * !(p)
599      * This represents the concept of the year where weeks start on a fixed day-of-week,
600      * such as Monday and each week belongs to exactly one year.
601      * This field is typically used with {@link WeekFields#dayOfWeek()} and
602      * {@link WeekFields#_weekOfWeekBasedYear()}.
603      * !(p)
604      * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek}
605      * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days _in the year.
606      * Thus, week one may start before the start of the year.
607      * If the first week starts after the start of the year then the period before
608      * is _in the last week of the previous year.
609      * !(p)
610      * This field can be used with any calendar system.
611      * !(p)
612      * In the resolving phase of parsing, a date can be created from a week-based-year,
613      * week-of-year and day-of-week.
614      * !(p)
615      * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
616      * validated against their _range of valid values. The week-of-year field
617      * is validated to ensure that the resulting week-based-year is the
618      * week-based-year requested.
619      * !(p)
620      * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
621      * validated against their _range of valid values. The week-of-week-based-year field
622      * is validated from 1 to 53, meaning that the resulting date can be _in the
623      * following week-based-year to that specified.
624      * !(p)
625      * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
626      * are validated against the _range of valid values. The resulting date is calculated
627      * equivalent to the following three stage approach.
628      * First, create a date on the first day of the first week _in the requested week-based-year.
629      * Then take the week-of-week-based-year, subtract one, and add the amount _in weeks to the date.
630      * Finally, adjust to the correct day-of-week within the localized week.
631      *
632      * @return a field providing access to the week-based-year, not null
633      */
634     public TemporalField weekBasedYear()
635     {
636         return _weekBasedYear;
637     }
638 
639     //-----------------------------------------------------------------------
640     /**
641      * Checks if this {@code WeekFields} is equal to the specified object.
642      * !(p)
643      * The comparison is based on the entire state of the rules, which is
644      * the first day-of-week and minimal days.
645      *
646      * @param object  the other rules to compare to, null returns false
647      * @return true if this is equal to the specified rules
648      */
649     override public bool opEquals(Object object)
650     {
651         if (this is object)
652         {
653             return true;
654         }
655         if (cast(WeekFields)(object) !is null)
656         {
657             return toHash() == object.toHash();
658         }
659         return false;
660     }
661 
662     /**
663      * A hash code for this {@code WeekFields}.
664      *
665      * @return a suitable hash code
666      */
667     override public size_t toHash() @trusted nothrow
668     {
669         try
670         {
671             return firstDayOfWeek.ordinal() * 7 + minimalDays;
672         }
673         catch (Exception e)
674         {
675         }
676         return int.init;
677     }
678 
679     //-----------------------------------------------------------------------
680     /**
681      * A string representation of this {@code WeekFields} instance.
682      *
683      * @return the string representation, not null
684      */
685     override public string toString()
686     {
687         return "WeekFields[" ~ firstDayOfWeek.toString ~ ',' ~ minimalDays.to!string ~ ']';
688     }
689 
690     // mixin SerializationMember!(typeof(this));
691 
692     //-----------------------------------------------------------------------
693     /**
694      * Field type that computes DayOfWeek, WeekOfMonth, and WeekOfYear
695      * based on a WeekFields.
696      * A separate Field instance is required for each different WeekFields;
697      * combination of start of week and minimum number of days.
698      * Constructors are provided to create fields for DayOfWeek, WeekOfMonth,
699      * and WeekOfYear.
700      */
701     static class ComputedDayOfField : TemporalField
702     {
703 
704         /**
705          * Returns a field to access the day of week,
706          * computed based on a WeekFields.
707          * !(p)
708          * The WeekDefintion of the first day of the week is used with
709          * the ISO DAY_OF_WEEK field to compute week boundaries.
710          */
711         static ComputedDayOfField ofDayOfWeekField(WeekFields weekDef)
712         {
713             return new ComputedDayOfField("DayOfWeek", weekDef,
714                     ChronoUnit.DAYS, ChronoUnit.WEEKS, DAY_OF_WEEK_RANGE);
715         }
716 
717         /**
718          * Returns a field to access the week of month,
719          * computed based on a WeekFields.
720          * @see WeekFields#_weekOfMonth()
721          */
722         static ComputedDayOfField ofWeekOfMonthField(WeekFields weekDef)
723         {
724             return new ComputedDayOfField("WeekOfMonth", weekDef,
725                     ChronoUnit.WEEKS, ChronoUnit.MONTHS, WEEK_OF_MONTH_RANGE);
726         }
727 
728         /**
729          * Returns a field to access the week of year,
730          * computed based on a WeekFields.
731          * @see WeekFields#weekOfYear()
732          */
733         static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef)
734         {
735             return new ComputedDayOfField("WeekOfYear", weekDef,
736                     ChronoUnit.WEEKS, ChronoUnit.YEARS, WEEK_OF_YEAR_RANGE);
737         }
738 
739         /**
740          * Returns a field to access the week of week-based-year,
741          * computed based on a WeekFields.
742          * @see WeekFields#_weekOfWeekBasedYear()
743          */
744         static ComputedDayOfField ofWeekOfWeekBasedYearField(WeekFields weekDef)
745         {
746             return new ComputedDayOfField("WeekOfWeekBasedYear", weekDef,
747                     ChronoUnit.WEEKS, IsoFields.WEEK_BASED_YEARS, WEEK_OF_WEEK_BASED_YEAR_RANGE);
748         }
749 
750         /**
751          * Returns a field to access the week of week-based-year,
752          * computed based on a WeekFields.
753          * @see WeekFields#_weekBasedYear()
754          */
755         static ComputedDayOfField ofWeekBasedYearField(WeekFields weekDef)
756         {
757             return new ComputedDayOfField("WeekBasedYear", weekDef,
758                     IsoFields.WEEK_BASED_YEARS, ChronoUnit.FOREVER, ChronoField.YEAR.range());
759         }
760 
761         /**
762          * Return a new week-based-year date of the Chronology, year, week-of-year,
763          * and dow of week.
764          * @param chrono The chronology of the new date
765          * @param yowby the year of the week-based-year
766          * @param wowby the week of the week-based-year
767          * @param dow the day of the week
768          * @return a ChronoLocalDate for the requested year, week of year, and day of week
769          */
770         private ChronoLocalDate ofWeekBasedYear(Chronology chrono, int yowby, int wowby, int dow)
771         {
772             ChronoLocalDate date = chrono.date(yowby, 1, 1);
773             int ldow = localizedDayOfWeek(date);
774             int offset = startOfWeekOffset(1, ldow);
775 
776             // Clamp the week of year to keep it _in the same year
777             int yearLen = date.lengthOfYear();
778             int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
779             wowby = MathHelper.min(wowby, newYearWeek - 1);
780 
781             int days = -offset + (dow - 1) + (wowby - 1) * 7;
782             return date.plus(days, ChronoUnit.DAYS);
783         }
784 
785         private string name;
786         private WeekFields weekDef;
787         private TemporalUnit baseUnit;
788         private TemporalUnit rangeUnit;
789         private ValueRange _range;
790 
791         this(string name, WeekFields weekDef, TemporalUnit baseUnit,
792                 TemporalUnit rangeUnit, ValueRange _range)
793         {
794             this.name = name;
795             this.weekDef = weekDef;
796             this.baseUnit = baseUnit;
797             this.rangeUnit = rangeUnit;
798             this._range = _range;
799         }
800 
801         //  __gshared ValueRange DAY_OF_WEEK_RANGE;
802         //  __gshared ValueRange WEEK_OF_MONTH_RANGE;
803         //  __gshared ValueRange WEEK_OF_YEAR_RANGE;
804         //  __gshared ValueRange WEEK_OF_WEEK_BASED_YEAR_RANGE;
805 
806         // shared static this()
807         // {
808             // DAY_OF_WEEK_RANGE = ValueRange.of(1, 7);
809             mixin(MakeGlobalVar!(ValueRange)("DAY_OF_WEEK_RANGE",`ValueRange.of(1, 7)`));
810             // WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 6);
811             mixin(MakeGlobalVar!(ValueRange)("WEEK_OF_MONTH_RANGE",`ValueRange.of(0, 1, 4, 6)`));
812 
813             // WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 54);
814             mixin(MakeGlobalVar!(ValueRange)("WEEK_OF_YEAR_RANGE",`ValueRange.of(0, 1, 52, 54)`));
815 
816             // WEEK_OF_WEEK_BASED_YEAR_RANGE = ValueRange.of(1, 52, 53);
817             mixin(MakeGlobalVar!(ValueRange)("WEEK_OF_WEEK_BASED_YEAR_RANGE",`ValueRange.of(1, 52, 53)`));
818 
819         // }
820 
821         override public long getFrom(TemporalAccessor temporal)
822         {
823             if (rangeUnit == ChronoUnit.WEEKS)
824             { // day-of-week
825                 return localizedDayOfWeek(temporal);
826             }
827             else if (rangeUnit == ChronoUnit.MONTHS)
828             { // week-of-month
829                 return localizedWeekOfMonth(temporal);
830             }
831             else if (rangeUnit == ChronoUnit.YEARS)
832             { // week-of-year
833                 return localizedWeekOfYear(temporal);
834             }
835             else if (rangeUnit == WEEK_BASED_YEARS)
836             {
837                 return localizedWeekOfWeekBasedYear(temporal);
838             }
839             else if (rangeUnit == ChronoUnit.FOREVER)
840             {
841                 return localizedWeekBasedYear(temporal);
842             }
843             else
844             {
845                 throw new IllegalStateException(
846                         "unreachable, rangeUnit: " ~ rangeUnit.toString ~ ", this: " ~ this
847                         .toString);
848             }
849         }
850 
851         private int localizedDayOfWeek(TemporalAccessor temporal)
852         {
853             int sow = weekDef.getFirstDayOfWeek().getValue();
854             int isoDow = temporal.get(ChronoField.DAY_OF_WEEK);
855             return MathHelper.floorMod(isoDow - sow, 7) + 1;
856         }
857 
858         private int localizedDayOfWeek(int isoDow)
859         {
860             int sow = weekDef.getFirstDayOfWeek().getValue();
861             return MathHelper.floorMod(isoDow - sow, 7) + 1;
862         }
863 
864         private long localizedWeekOfMonth(TemporalAccessor temporal)
865         {
866             int dow = localizedDayOfWeek(temporal);
867             int dom = temporal.get(ChronoField.DAY_OF_MONTH);
868             int offset = startOfWeekOffset(dom, dow);
869             return computeWeek(offset, dom);
870         }
871 
872         private long localizedWeekOfYear(TemporalAccessor temporal)
873         {
874             int dow = localizedDayOfWeek(temporal);
875             int doy = temporal.get(ChronoField.DAY_OF_YEAR);
876             int offset = startOfWeekOffset(doy, dow);
877             return computeWeek(offset, doy);
878         }
879 
880         /**
881          * Returns the year of week-based-year for the temporal.
882          * The year can be the previous year, the current year, or the next year.
883          * @param temporal a date of any chronology, not null
884          * @return the year of week-based-year for the date
885          */
886         private int localizedWeekBasedYear(TemporalAccessor temporal)
887         {
888             int dow = localizedDayOfWeek(temporal);
889             int year = temporal.get(ChronoField.YEAR);
890             int doy = temporal.get(ChronoField.DAY_OF_YEAR);
891             int offset = startOfWeekOffset(doy, dow);
892             int week = computeWeek(offset, doy);
893             if (week == 0)
894             {
895                 // Day is _in end of week of previous year; return the previous year
896                 return year - 1;
897             }
898             else
899             {
900                 // If getting close to end of year, use higher precision logic
901                 // Check if date of year is _in partial week associated with next year
902                 ValueRange dayRange = temporal.range(ChronoField.DAY_OF_YEAR);
903                 int yearLen = cast(int) dayRange.getMaximum();
904                 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
905                 if (week >= newYearWeek)
906                 {
907                     return year + 1;
908                 }
909             }
910             return year;
911         }
912 
913         /**
914          * Returns the week of week-based-year for the temporal.
915          * The week can be part of the previous year, the current year,
916          * or the next year depending on the week start and minimum number
917          * of days.
918          * @param temporal  a date of any chronology
919          * @return the week of the year
920          * @see #localizedWeekBasedYear(hunt.time.temporal.TemporalAccessor)
921          */
922         private int localizedWeekOfWeekBasedYear(TemporalAccessor temporal)
923         {
924             int dow = localizedDayOfWeek(temporal);
925             int doy = temporal.get(ChronoField.DAY_OF_YEAR);
926             int offset = startOfWeekOffset(doy, dow);
927             int week = computeWeek(offset, doy);
928             if (week == 0)
929             {
930                 // Day is _in end of week of previous year
931                 // Recompute from the last day of the previous year
932                 ChronoLocalDate date = Chronology.from(temporal).date(temporal);
933                 date = date.minus(doy, ChronoUnit.DAYS); // Back down into previous year
934                 return localizedWeekOfWeekBasedYear(date);
935             }
936             else if (week > 50)
937             {
938                 // If getting close to end of year, use higher precision logic
939                 // Check if date of year is _in partial week associated with next year
940                 ValueRange dayRange = temporal.range(ChronoField.DAY_OF_YEAR);
941                 int yearLen = cast(int) dayRange.getMaximum();
942                 int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
943                 if (week >= newYearWeek)
944                 {
945                     // Overlaps with week of following year; reduce to week _in following year
946                     week = week - newYearWeek + 1;
947                 }
948             }
949             return week;
950         }
951 
952         /**
953          * Returns an offset to align week start with a day of month or day of year.
954          *
955          * @param day  the day; 1 through infinity
956          * @param dow  the day of the week of that day; 1 through 7
957          * @return  an offset _in days to align a day with the start of the first 'full' week
958          */
959         private int startOfWeekOffset(int day, int dow)
960         {
961             // offset of first day corresponding to the day of week _in first 7 days (zero origin)
962             int weekStart = MathHelper.floorMod(day - dow, 7);
963             int offset = -weekStart;
964             if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek())
965             {
966                 // The previous week has the minimum days _in the current month to be a 'week'
967                 offset = 7 - weekStart;
968             }
969             return offset;
970         }
971 
972         /**
973          * Returns the week number computed from the reference day and reference dayOfWeek.
974          *
975          * @param offset the offset to align a date with the start of week
976          *     from {@link #startOfWeekOffset}.
977          * @param day  the day for which to compute the week number
978          * @return the week number where zero is used for a partial week and 1 for the first full week
979          */
980         private int computeWeek(int offset, int day)
981         {
982             return ((7 + offset + (day - 1)) / 7);
983         }
984 
985         /*@SuppressWarnings("unchecked")*/
986         override public Temporal adjustInto(Temporal temporal, long newValue)
987                 /* if (is(R : Temporal)) */
988         {
989             // Check the new value and get the old value of the field
990             int newVal = _range.checkValidIntValue(newValue, this); // lenient check _range
991             int currentVal = temporal.get(this);
992             if (newVal == currentVal)
993             {
994                 return temporal;
995             }
996 
997             if (rangeUnit == ChronoUnit.FOREVER)
998             { // replace year of WeekBasedYear
999                 // Create a new date object with the same chronology,
1000                 // the desired year and the same week and dow.
1001                 int idow = temporal.get(weekDef.dayOfWeek);
1002                 int wowby = temporal.get(weekDef._weekOfWeekBasedYear);
1003                 return cast(Temporal) ofWeekBasedYear(Chronology.from(temporal),
1004                         cast(int) newValue, wowby, idow);
1005             }
1006             else
1007             {
1008                 // Compute the difference and add that using the base unit of the field
1009                 return cast(Temporal) temporal.plus(newVal - currentVal, baseUnit);
1010             }
1011         }
1012 
1013         override public ChronoLocalDate resolve(Map!(TemporalField, Long) fieldValues,
1014                 TemporalAccessor partialTemporal, ResolverStyle resolverStyle)
1015         {
1016             long value = fieldValues.get(this).longValue();
1017             int newValue = MathHelper.toIntExact(value); // broad limit makes overflow checking lighter
1018             // first convert localized day-of-week to ISO day-of-week
1019             // doing this first handles case where both ISO and localized were parsed and might mismatch
1020             // day-of-week is always strict as two different day-of-week values makes lenient complex
1021             if (rangeUnit == ChronoUnit.WEEKS)
1022             { // day-of-week
1023                 int checkedValue = _range.checkValidIntValue(value, this); // no leniency as too complex
1024                 int startDow = weekDef.getFirstDayOfWeek().getValue();
1025                 long isoDow = MathHelper.floorMod((startDow - 1) + (checkedValue - 1), 7) + 1;
1026                 fieldValues.remove(this);
1027                 fieldValues.put(ChronoField.DAY_OF_WEEK, new Long(isoDow));
1028                 return null;
1029             }
1030 
1031             // can only build date if ISO day-of-week is present
1032             if (fieldValues.containsKey(ChronoField.DAY_OF_WEEK) == false)
1033             {
1034                 return null;
1035             }
1036             int isoDow = ChronoField.DAY_OF_WEEK.checkValidIntValue(
1037                     fieldValues.get(ChronoField.DAY_OF_WEEK).longValue());
1038             int dow = localizedDayOfWeek(isoDow);
1039 
1040             // build date
1041             Chronology chrono = Chronology.from(partialTemporal);
1042             if (fieldValues.containsKey(ChronoField.YEAR))
1043             {
1044                 int year = ChronoField.YEAR.checkValidIntValue(fieldValues.get(ChronoField.YEAR)
1045                         .longValue()); // validate
1046                 if (rangeUnit == ChronoUnit.MONTHS
1047                         && fieldValues.containsKey(ChronoField.MONTH_OF_YEAR))
1048                 { // week-of-month
1049                     long month = fieldValues.get(ChronoField.MONTH_OF_YEAR).longValue(); // not validated yet
1050                     return resolveWoM(fieldValues, chrono, year, month,
1051                             newValue, dow, resolverStyle);
1052                 }
1053                 if (rangeUnit == ChronoUnit.YEARS)
1054                 { // week-of-year
1055                     return resolveWoY(fieldValues, chrono, year, newValue, dow, resolverStyle);
1056                 }
1057             }
1058             else if ((rangeUnit == WEEK_BASED_YEARS || rangeUnit == ChronoUnit.FOREVER)
1059                     && fieldValues.containsKey(weekDef._weekBasedYear)
1060                     && fieldValues.containsKey(weekDef._weekOfWeekBasedYear))
1061             { // week-of-week-based-year and year-of-week-based-year
1062                 return resolveWBY(fieldValues, chrono, dow, resolverStyle);
1063             }
1064             return null;
1065         }
1066 
1067         private ChronoLocalDate resolveWoM(Map!(TemporalField, Long) fieldValues, Chronology chrono,
1068                 int year, long month, long wom, int localDow, ResolverStyle resolverStyle)
1069         {
1070             ChronoLocalDate date;
1071             if (resolverStyle == ResolverStyle.LENIENT)
1072             {
1073                 date = chrono.date(year, 1, 1).plus(MathHelper.subtractExact(month,
1074                         1), ChronoUnit.MONTHS);
1075                 long weeks = MathHelper.subtractExact(wom, localizedWeekOfMonth(date));
1076                 int days = localDow - localizedDayOfWeek(date); // safe from overflow
1077                 date = date.plus(MathHelper.addExact(MathHelper.multiplyExact(weeks, 7),
1078                         days), ChronoUnit.DAYS);
1079             }
1080             else
1081             {
1082                 int monthValid = ChronoField.MONTH_OF_YEAR.checkValidIntValue(month); // validate
1083                 date = chrono.date(year, monthValid, 1);
1084                 int womInt = _range.checkValidIntValue(wom, this); // validate
1085                 int weeks = cast(int)(womInt - localizedWeekOfMonth(date)); // safe from overflow
1086                 int days = localDow - localizedDayOfWeek(date); // safe from overflow
1087                 date = date.plus(weeks * 7 + days, ChronoUnit.DAYS);
1088                 if (resolverStyle == ResolverStyle.STRICT
1089                         && date.getLong(ChronoField.MONTH_OF_YEAR) != month)
1090                 {
1091                     throw new DateTimeException(
1092                             "Strict mode rejected resolved date as it is _in a different month");
1093                 }
1094             }
1095             fieldValues.remove(this);
1096             fieldValues.remove(ChronoField.YEAR);
1097             fieldValues.remove(ChronoField.MONTH_OF_YEAR);
1098             fieldValues.remove(ChronoField.DAY_OF_WEEK);
1099             return date;
1100         }
1101 
1102         private ChronoLocalDate resolveWoY(Map!(TemporalField, Long) fieldValues,
1103                 Chronology chrono, int year, long woy, int localDow, ResolverStyle resolverStyle)
1104         {
1105             ChronoLocalDate date = chrono.date(year, 1, 1);
1106             if (resolverStyle == ResolverStyle.LENIENT)
1107             {
1108                 long weeks = MathHelper.subtractExact(woy, localizedWeekOfYear(date));
1109                 int days = localDow - localizedDayOfWeek(date); // safe from overflow
1110                 date = date.plus(MathHelper.addExact(MathHelper.multiplyExact(weeks, 7),
1111                         days), ChronoUnit.DAYS);
1112             }
1113             else
1114             {
1115                 int womInt = _range.checkValidIntValue(woy, this); // validate
1116                 int weeks = cast(int)(womInt - localizedWeekOfYear(date)); // safe from overflow
1117                 int days = localDow - localizedDayOfWeek(date); // safe from overflow
1118                 date = date.plus(weeks * 7 + days, ChronoUnit.DAYS);
1119                 if (resolverStyle == ResolverStyle.STRICT && date.getLong(ChronoField.YEAR) != year)
1120                 {
1121                     throw new DateTimeException(
1122                             "Strict mode rejected resolved date as it is _in a different year");
1123                 }
1124             }
1125             fieldValues.remove(this);
1126             fieldValues.remove(ChronoField.YEAR);
1127             fieldValues.remove(ChronoField.DAY_OF_WEEK);
1128             return date;
1129         }
1130 
1131         private ChronoLocalDate resolveWBY(Map!(TemporalField, Long) fieldValues,
1132                 Chronology chrono, int localDow, ResolverStyle resolverStyle)
1133         {
1134             int yowby = weekDef._weekBasedYear.range()
1135                 .checkValidIntValue(fieldValues.get(weekDef._weekBasedYear)
1136                         .longValue(), weekDef._weekBasedYear);
1137             ChronoLocalDate date;
1138             if (resolverStyle == ResolverStyle.LENIENT)
1139             {
1140                 date = ofWeekBasedYear(chrono, yowby, 1, localDow);
1141                 long wowby = fieldValues.get(weekDef._weekOfWeekBasedYear).longValue();
1142                 long weeks = MathHelper.subtractExact(wowby, 1);
1143                 date = date.plus(weeks, ChronoUnit.WEEKS);
1144             }
1145             else
1146             {
1147                 int wowby = weekDef._weekOfWeekBasedYear.range()
1148                     .checkValidIntValue(fieldValues.get(weekDef._weekOfWeekBasedYear)
1149                             .longValue(), weekDef._weekOfWeekBasedYear); // validate
1150                 date = ofWeekBasedYear(chrono, yowby, wowby, localDow);
1151                 if (resolverStyle == ResolverStyle.STRICT && localizedWeekBasedYear(date) != yowby)
1152                 {
1153                     throw new DateTimeException(
1154                             "Strict mode rejected resolved date as it is _in a different week-based-year");
1155                 }
1156             }
1157             fieldValues.remove(this);
1158             fieldValues.remove(weekDef._weekBasedYear);
1159             fieldValues.remove(weekDef._weekOfWeekBasedYear);
1160             fieldValues.remove(ChronoField.DAY_OF_WEEK);
1161             return date;
1162         }
1163 
1164         //-----------------------------------------------------------------------
1165         override public string getDisplayName(Locale locale)
1166         {
1167             assert(locale, "locale");
1168             if (rangeUnit == ChronoUnit.YEARS)
1169             { // only have values for week-of-year
1170                 ///@gxc
1171                 // LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
1172                 //         .getLocaleResources(
1173                 //             CalendarDataUtility.findRegionOverride(locale));
1174                 // ResourceBundle rb = lr.getJavaTimeFormatData();
1175                 // return rb.containsKey("field.week") ? rb.getString("field.week") : name;
1176                 implementationMissing();
1177                 return null;
1178             }
1179             return name;
1180         }
1181 
1182         override public TemporalUnit getBaseUnit()
1183         {
1184             return baseUnit;
1185         }
1186 
1187         override public TemporalUnit getRangeUnit()
1188         {
1189             return rangeUnit;
1190         }
1191 
1192         override public bool isDateBased()
1193         {
1194             return true;
1195         }
1196 
1197         override public bool isTimeBased()
1198         {
1199             return false;
1200         }
1201 
1202         override public ValueRange range()
1203         {
1204             return _range;
1205         }
1206 
1207         //-----------------------------------------------------------------------
1208         override public bool isSupportedBy(TemporalAccessor temporal)
1209         {
1210             if (temporal.isSupported(ChronoField.DAY_OF_WEEK))
1211             {
1212                 if (rangeUnit == ChronoUnit.WEEKS)
1213                 { // day-of-week
1214                     return true;
1215                 }
1216                 else if (rangeUnit == ChronoUnit.MONTHS)
1217                 { // week-of-month
1218                     return temporal.isSupported(ChronoField.DAY_OF_MONTH);
1219                 }
1220                 else if (rangeUnit == ChronoUnit.YEARS)
1221                 { // week-of-year
1222                     return temporal.isSupported(ChronoField.DAY_OF_YEAR);
1223                 }
1224                 else if (rangeUnit == WEEK_BASED_YEARS)
1225                 {
1226                     return temporal.isSupported(ChronoField.DAY_OF_YEAR);
1227                 }
1228                 else if (rangeUnit == ChronoUnit.FOREVER)
1229                 {
1230                     return temporal.isSupported(ChronoField.YEAR);
1231                 }
1232             }
1233             return false;
1234         }
1235 
1236         override public ValueRange rangeRefinedBy(TemporalAccessor temporal)
1237         {
1238             if (rangeUnit == ChronoUnit.WEEKS)
1239             { // day-of-week
1240                 return _range;
1241             }
1242             else if (rangeUnit == ChronoUnit.MONTHS)
1243             { // week-of-month
1244                 return rangeByWeek(temporal, ChronoField.DAY_OF_MONTH);
1245             }
1246             else if (rangeUnit == ChronoUnit.YEARS)
1247             { // week-of-year
1248                 return rangeByWeek(temporal, ChronoField.DAY_OF_YEAR);
1249             }
1250             else if (rangeUnit == WEEK_BASED_YEARS)
1251             {
1252                 return rangeWeekOfWeekBasedYear(temporal);
1253             }
1254             else if (rangeUnit == ChronoUnit.FOREVER)
1255             {
1256                 return ChronoField.YEAR.range();
1257             }
1258             else
1259             {
1260                 throw new IllegalStateException(
1261                         "unreachable, rangeUnit: " ~ rangeUnit.toString ~ ", this: " ~ this
1262                         .toString);
1263             }
1264         }
1265 
1266         /**
1267          * Map the field _range to a week _range
1268          * @param temporal the temporal
1269          * @param field the field to get the _range of
1270          * @return the ValueRange with the _range adjusted to weeks.
1271          */
1272         private ValueRange rangeByWeek(TemporalAccessor temporal, TemporalField field)
1273         {
1274             int dow = localizedDayOfWeek(temporal);
1275             int offset = startOfWeekOffset(temporal.get(field), dow);
1276             ValueRange fieldRange = temporal.range(field);
1277             return ValueRange.of(computeWeek(offset, cast(int) fieldRange.getMinimum()),
1278                     computeWeek(offset, cast(int) fieldRange.getMaximum()));
1279         }
1280 
1281         /**
1282          * Map the field _range to a week _range of a week year.
1283          * @param temporal  the temporal
1284          * @return the ValueRange with the _range adjusted to weeks.
1285          */
1286         private ValueRange rangeWeekOfWeekBasedYear(TemporalAccessor temporal)
1287         {
1288             if (!temporal.isSupported(ChronoField.DAY_OF_YEAR))
1289             {
1290                 return WEEK_OF_YEAR_RANGE;
1291             }
1292             int dow = localizedDayOfWeek(temporal);
1293             int doy = temporal.get(ChronoField.DAY_OF_YEAR);
1294             int offset = startOfWeekOffset(doy, dow);
1295             int week = computeWeek(offset, doy);
1296             if (week == 0)
1297             {
1298                 // Day is _in end of week of previous year
1299                 // Recompute from the last day of the previous year
1300                 ChronoLocalDate date = Chronology.from(temporal).date(temporal);
1301                 date = date.minus(doy + 7, ChronoUnit.DAYS); // Back down into previous year
1302                 return rangeWeekOfWeekBasedYear(date);
1303             }
1304             // Check if day of year is _in partial week associated with next year
1305             ValueRange dayRange = temporal.range(ChronoField.DAY_OF_YEAR);
1306             int yearLen = cast(int) dayRange.getMaximum();
1307             int newYearWeek = computeWeek(offset, yearLen + weekDef.getMinimalDaysInFirstWeek());
1308 
1309             if (week >= newYearWeek)
1310             {
1311                 // Overlaps with weeks of following year; recompute from a week _in following year
1312                 ChronoLocalDate date = Chronology.from(temporal).date(temporal);
1313                 date = date.plus(yearLen - doy + 1 + 7, ChronoUnit.DAYS);
1314                 return rangeWeekOfWeekBasedYear(date);
1315             }
1316             return ValueRange.of(1, newYearWeek - 1);
1317         }
1318 
1319         //-----------------------------------------------------------------------
1320         override public string toString()
1321         {
1322             return name ~ "[" ~ weekDef.toString() ~ "]";
1323         }
1324 
1325         override int opCmp(TemporalField o)
1326         {
1327             if(cast(ComputedDayOfField)o !is null)
1328             {
1329                 auto obj = cast(ComputedDayOfField)o;
1330                 return compare(this.toString,obj.toString);
1331             }
1332             return 0;
1333         }
1334     }
1335 }