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.DayOfWeek;
13 
14 import hunt.time.temporal.ChronoField;
15 import hunt.time.temporal.ChronoUnit;
16 import hunt.time.format.DateTimeFormatterBuilder;
17 import hunt.time.format.TextStyle;
18 import hunt.time.temporal.ChronoField;
19 import hunt.time.temporal.Temporal;
20 import hunt.time.temporal.TemporalAccessor;
21 import hunt.time.temporal.TemporalAdjuster;
22 import hunt.time.temporal.TemporalField;
23 import hunt.time.temporal.TemporalQueries;
24 import hunt.time.temporal.TemporalQuery;
25 import hunt.time.temporal.ValueRange;
26 import hunt.time.temporal.WeekFields;
27 import hunt.time.Exceptions;
28 import hunt.time.util.Common;
29 
30 import hunt.Enum;
31 import hunt.util.Locale;
32 
33 import std.concurrency : initOnce;
34 import std.conv : to;
35 
36 /**
37  * A day-of-week, such as 'Tuesday'.
38  * !(p)
39  * {@code DayOfWeek} is an enum representing the 7 days of the week -
40  * Monday, Tuesday, Wednesday, Thursday, Friday, Saturday and Sunday.
41  * !(p)
42  * In addition to the textual enum name, each day-of-week has an {@code int} value.
43  * The {@code int} value follows the ISO-8601 standard, from 1 (Monday) to 7 (Sunday).
44  * It is recommended that applications use the enum rather than the {@code int} value
45  * to ensure code clarity.
46  * !(p)
47  * This enum provides access to the localized textual form of the day-of-week.
48  * Some locales also assign different numeric values to the days, declaring
49  * Sunday to have the value 1, however this class provides no support for this.
50  * See {@link WeekFields} for localized week-numbering.
51  * !(p)
52  * !(b)Do not use {@code ordinal()} to obtain the numeric representation of {@code DayOfWeek}.
53  * Use {@code getValue()} instead.</b>
54  * !(p)
55  * This enum represents a common concept that is found _in many calendar systems.
56  * As such, this enum may be used by any calendar system that has the day-of-week
57  * concept defined exactly equivalent to the ISO calendar system.
58  *
59  * @implSpec
60  * This is an immutable and thread-safe enum.
61  *
62  * @since 1.8
63  */
64 final class DayOfWeek : AbstractEnum!DayOfWeek, TemporalAccessor, TemporalAdjuster {
65 
66     /**
67      * The singleton instance for the day-of-week of Monday.
68      * This has the numeric value of {@code 1}.
69      */
70     static DayOfWeek MONDAY() {
71 		__gshared DayOfWeek d;
72 		return initOnce!d(new DayOfWeek("MONDAY", 0));
73 	}
74     /**
75      * The singleton instance for the day-of-week of Tuesday.
76      * This has the numeric value of {@code 2}.
77      */
78     static DayOfWeek TUESDAY() {
79 		__gshared DayOfWeek d;
80 		return initOnce!d(new DayOfWeek("TUESDAY", 1));
81 	}
82     /**
83      * The singleton instance for the day-of-week of Wednesday.
84      * This has the numeric value of {@code 3}.
85      */
86     static DayOfWeek WEDNESDAY() {
87 		__gshared DayOfWeek d;
88 		return initOnce!d(new DayOfWeek("WEDNESDAY", 2));
89 	}
90     /**
91      * The singleton instance for the day-of-week of Thursday.
92      * This has the numeric value of {@code 4}.
93      */
94     static DayOfWeek THURSDAY() {
95 		__gshared DayOfWeek d;
96 		return initOnce!d(new DayOfWeek("THURSDAY", 3));
97 	}
98     /**
99      * The singleton instance for the day-of-week of Friday.
100      * This has the numeric value of {@code 5}.
101      */
102     static DayOfWeek FRIDAY() {
103 		__gshared DayOfWeek d;
104 		return initOnce!d(new DayOfWeek("FRIDAY", 4));
105 	}
106     /**
107      * The singleton instance for the day-of-week of Saturday.
108      * This has the numeric value of {@code 6}.
109      */
110     static DayOfWeek SATURDAY() {
111 		__gshared DayOfWeek d;
112 		return initOnce!d(new DayOfWeek("SATURDAY", 5));
113 	}
114     /**
115      * The singleton instance for the day-of-week of Sunday.
116      * This has the numeric value of {@code 7}.
117      */
118     static DayOfWeek SUNDAY() {
119 		__gshared DayOfWeek d;
120 		return initOnce!d(new DayOfWeek("SUNDAY", 6));
121 	}
122 
123     static DayOfWeek valueOf(string name) {
124         return hunt.Enum.valueOf!(DayOfWeek)(name);
125     }
126 
127     static DayOfWeek[] values() {
128         __gshared DayOfWeek[] d;
129         return initOnce!d({
130             DayOfWeek[] arr;
131             arr ~= DayOfWeek.MONDAY;
132             arr ~= DayOfWeek.TUESDAY;
133 
134             arr ~= DayOfWeek.WEDNESDAY;
135             arr ~= DayOfWeek.THURSDAY;/*  */
136             arr ~= DayOfWeek.FRIDAY;
137             arr ~= DayOfWeek.SATURDAY;
138             arr ~= DayOfWeek.SUNDAY;
139             return arr;
140         }());
141     }
142     
143 
144 	private this(string name, int ordinal) {
145 		super(name, ordinal);
146 	}
147 
148     //-----------------------------------------------------------------------
149     /**
150      * Obtains an instance of {@code DayOfWeek} from an {@code int} value.
151      * !(p)
152      * {@code DayOfWeek} is an enum representing the 7 days of the week.
153      * This factory allows the enum to be obtained from the {@code int} value.
154      * The {@code int} value follows the ISO-8601 standard, from 1 (Monday) to 7 (Sunday).
155      *
156      * @param dayOfWeek  the day-of-week to represent, from 1 (Monday) to 7 (Sunday)
157      * @return the day-of-week singleton, not null
158      * @throws DateTimeException if the day-of-week is invalid
159      */
160     static DayOfWeek of(int dayOfWeek) {
161         if (dayOfWeek < 1 || dayOfWeek > 7) {
162             throw new DateTimeException("Invalid value for DayOfWeek: " ~ dayOfWeek.to!string);
163         }
164         return values[dayOfWeek - 1];
165     }
166 
167     //-----------------------------------------------------------------------
168     /**
169      * Obtains an instance of {@code DayOfWeek} from a temporal object.
170      * !(p)
171      * This obtains a day-of-week based on the specified temporal.
172      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
173      * which this factory converts to an instance of {@code DayOfWeek}.
174      * !(p)
175      * The conversion extracts the {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} field.
176      * !(p)
177      * This method matches the signature of the functional interface {@link TemporalQuery}
178      * allowing it to be used as a query via method reference, {@code DayOfWeek::from}.
179      *
180      * @param temporal  the temporal object to convert, not null
181      * @return the day-of-week, not null
182      * @throws DateTimeException if unable to convert to a {@code DayOfWeek}
183      */
184     static DayOfWeek from(TemporalAccessor temporal) {
185         if (cast(DayOfWeek)(temporal) !is null) {
186             return cast(DayOfWeek) temporal;
187         }
188         try {
189             return of(temporal.get(ChronoField.DAY_OF_WEEK));
190         } catch (DateTimeException ex) {
191             throw new DateTimeException("Unable to obtain DayOfWeek from TemporalAccessor: " ~
192                     typeid(temporal).stringof ~ " of type " ~ typeid(temporal).stringof, ex);
193         }
194     }
195 
196     //-----------------------------------------------------------------------
197     /**
198      * Gets the day-of-week {@code int} value.
199      * !(p)
200      * The values are numbered following the ISO-8601 standard, from 1 (Monday) to 7 (Sunday).
201      * See {@link hunt.time.temporal.WeekFields#dayOfWeek()} for localized week-numbering.
202      *
203      * @return the day-of-week, from 1 (Monday) to 7 (Sunday)
204      */
205     int getValue() {
206         return _ordinal + 1;
207     }
208 
209     //-----------------------------------------------------------------------
210     /**
211      * Gets the textual representation, such as 'Mon' or 'Friday'.
212      * !(p)
213      * This returns the textual name used to identify the day-of-week,
214      * suitable for presentation to the user.
215      * The parameters control the style of the returned text and the locale.
216      * !(p)
217      * If no textual mapping is found then the {@link #getValue() numeric value} is returned.
218      *
219      * @param style  the length of the text required, not null
220      * @param locale  the locale to use, not null
221      * @return the text value of the day-of-week, not null
222      */
223     string getDisplayName(TextStyle style, Locale locale) {
224         return new DateTimeFormatterBuilder().appendText(ChronoField.DAY_OF_WEEK, style).toFormatter(locale).format(this);
225     }
226 
227     //-----------------------------------------------------------------------
228     /**
229      * Checks if the specified field is supported.
230      * !(p)
231      * This checks if this day-of-week can be queried for the specified field.
232      * If false, then calling the {@link #range(TemporalField) range} and
233      * {@link #get(TemporalField) get} methods will throw an exception.
234      * !(p)
235      * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then
236      * this method returns true.
237      * All other {@code ChronoField} instances will return false.
238      * !(p)
239      * If the field is not a {@code ChronoField}, then the result of this method
240      * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}
241      * passing {@code this} as the argument.
242      * Whether the field is supported is determined by the field.
243      *
244      * @param field  the field to check, null returns false
245      * @return true if the field is supported on this day-of-week, false if not
246      */
247     override
248     bool isSupported(TemporalField field) {
249         if (cast(ChronoField)(field) !is null) {
250             return field == ChronoField.DAY_OF_WEEK;
251         }
252         return field !is null && field.isSupportedBy(this);
253     }
254 
255     /**
256      * Gets the range of valid values for the specified field.
257      * !(p)
258      * The range object expresses the minimum and maximum valid values for a field.
259      * This day-of-week is used to enhance the accuracy of the returned range.
260      * If it is not possible to return the range, because the field is not supported
261      * or for some other reason, an exception is thrown.
262      * !(p)
263      * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the
264      * range of the day-of-week, from 1 to 7, will be returned.
265      * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
266      * !(p)
267      * If the field is not a {@code ChronoField}, then the result of this method
268      * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}
269      * passing {@code this} as the argument.
270      * Whether the range can be obtained is determined by the field.
271      *
272      * @param field  the field to query the range for, not null
273      * @return the range of valid values for the field, not null
274      * @throws DateTimeException if the range for the field cannot be obtained
275      * @throws UnsupportedTemporalTypeException if the field is not supported
276      */
277     override
278     ValueRange range(TemporalField field) {
279         if (field == ChronoField.DAY_OF_WEEK) {
280             return field.range();
281         }
282         return /* TemporalAccessor. super.*/range_super(field);
283     }
284 
285     ValueRange range_super(TemporalField field) {
286         if (cast(ChronoField)(field) !is null) {
287             if (isSupported(field)) {
288                 return field.range();
289             }
290             throw new UnsupportedTemporalTypeException("Unsupported field: " ~ typeof(field).stringof);
291         }
292         assert(field, "field");
293         return field.rangeRefinedBy(this);
294     }
295 
296     /**
297      * Gets the value of the specified field from this day-of-week as an {@code int}.
298      * !(p)
299      * This queries this day-of-week for the value of the specified field.
300      * The returned value will always be within the valid range of values for the field.
301      * If it is not possible to return the value, because the field is not supported
302      * or for some other reason, an exception is thrown.
303      * !(p)
304      * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the
305      * value of the day-of-week, from 1 to 7, will be returned.
306      * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
307      * !(p)
308      * If the field is not a {@code ChronoField}, then the result of this method
309      * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
310      * passing {@code this} as the argument. Whether the value can be obtained,
311      * and what the value represents, is determined by the field.
312      *
313      * @param field  the field to get, not null
314      * @return the value for the field, within the valid range of values
315      * @throws DateTimeException if a value for the field cannot be obtained or
316      *         the value is outside the range of valid values for the field
317      * @throws UnsupportedTemporalTypeException if the field is not supported or
318      *         the range of values exceeds an {@code int}
319      * @throws ArithmeticException if numeric overflow occurs
320      */
321     override
322     int get(TemporalField field) {
323         if (field == ChronoField.DAY_OF_WEEK) {
324             return getValue();
325         }
326         return /* TemporalAccessor. super.*/super_get(field);
327     }
328 
329     int super_get(TemporalField field) {
330         ValueRange range = range(field);
331         if (range.isIntValue() == false) {
332             throw new UnsupportedTemporalTypeException("Invalid field " ~ typeof(field).stringof ~ " for get() method, use getLong() instead");
333         }
334         long value = getLong(field);
335         if (range.isValidValue(value) == false) {
336             throw new DateTimeException("Invalid value for " ~ typeof(field).stringof ~ " (valid values " ~ range.toString ~ "): " ~ value.to!string);
337         }
338         return cast(int) value;
339     }
340 
341     /**
342      * Gets the value of the specified field from this day-of-week as a {@code long}.
343      * !(p)
344      * This queries this day-of-week for the value of the specified field.
345      * If it is not possible to return the value, because the field is not supported
346      * or for some other reason, an exception is thrown.
347      * !(p)
348      * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the
349      * value of the day-of-week, from 1 to 7, will be returned.
350      * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.
351      * !(p)
352      * If the field is not a {@code ChronoField}, then the result of this method
353      * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
354      * passing {@code this} as the argument. Whether the value can be obtained,
355      * and what the value represents, is determined by the field.
356      *
357      * @param field  the field to get, not null
358      * @return the value for the field
359      * @throws DateTimeException if a value for the field cannot be obtained
360      * @throws UnsupportedTemporalTypeException if the field is not supported
361      * @throws ArithmeticException if numeric overflow occurs
362      */
363     override
364     long getLong(TemporalField field) {
365         if (field == ChronoField.DAY_OF_WEEK) {
366             return getValue();
367         } else if (cast(ChronoField)(field) !is null) {
368             throw new UnsupportedTemporalTypeException("Unsupported field: " ~ typeid(field).stringof);
369         }
370         return field.getFrom(this);
371     }
372 
373     //-----------------------------------------------------------------------
374     /**
375      * Returns the day-of-week that is the specified number of days after this one.
376      * !(p)
377      * The calculation rolls around the end of the week from Sunday to Monday.
378      * The specified period may be negative.
379      * !(p)
380      * This instance is immutable and unaffected by this method call.
381      *
382      * @param days  the days to add, positive or negative
383      * @return the resulting day-of-week, not null
384      */
385     DayOfWeek plus(long days) {
386         int amount = cast(int) (days % 7);
387         return values[(_ordinal + (amount + 7)) % 7];
388     }
389 
390     /**
391      * Returns the day-of-week that is the specified number of days before this one.
392      * !(p)
393      * The calculation rolls around the start of the year from Monday to Sunday.
394      * The specified period may be negative.
395      * !(p)
396      * This instance is immutable and unaffected by this method call.
397      *
398      * @param days  the days to subtract, positive or negative
399      * @return the resulting day-of-week, not null
400      */
401     DayOfWeek minus(long days) {
402         return plus(-(days % 7));
403     }
404 
405     //-----------------------------------------------------------------------
406     /**
407      * Queries this day-of-week using the specified query.
408      * !(p)
409      * This queries this day-of-week using the specified query strategy object.
410      * The {@code TemporalQuery} object defines the logic to be used to
411      * obtain the result. Read the documentation of the query to understand
412      * what the result of this method will be.
413      * !(p)
414      * The result of this method is obtained by invoking the
415      * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
416      * specified query passing {@code this} as the argument.
417      *
418      * @param !(R) the type of the result
419      * @param query  the query to invoke, not null
420      * @return the query result, null may be returned (defined by the query)
421      * @throws DateTimeException if unable to query (defined by the query)
422      * @throws ArithmeticException if numeric overflow occurs (defined by the query)
423      */
424     /*@SuppressWarnings("unchecked")*/
425     // override
426     R query(R)(TemporalQuery!(R) query) {
427         if (query == TemporalQueries.precision()) {
428             return cast(R) (ChronoUnit.DAYS);
429         }
430         return /* TemporalAccessor. */super_query(query);
431     }
432     R super_query(R)(TemporalQuery!(R) query) {
433          if (query == TemporalQueries.zoneId()
434                  || query == TemporalQueries.chronology()
435                  || query == TemporalQueries.precision()) {
436              return null;
437          }
438          return query.queryFrom(this);
439      }
440     /**
441      * Adjusts the specified temporal object to have this day-of-week.
442      * !(p)
443      * This returns a temporal object of the same observable type as the input
444      * with the day-of-week changed to be the same as this.
445      * !(p)
446      * The adjustment is equivalent to using {@link Temporal#_with(TemporalField, long)}
447      * passing {@link ChronoField#DAY_OF_WEEK} as the field.
448      * Note that this adjusts forwards or backwards within a Monday to Sunday week.
449      * See {@link hunt.time.temporal.WeekFields#dayOfWeek()} for localized week start days.
450      * See {@code TemporalAdjuster} for other adjusters with more control,
451      * such as {@code next(MONDAY)}.
452      * !(p)
453      * In most cases, it is clearer to reverse the calling pattern by using
454      * {@link Temporal#_with(TemporalAdjuster)}:
455      * !(pre)
456      *   // these two lines are equivalent, but the second approach is recommended
457      *   temporal = thisDayOfWeek.adjustInto(temporal);
458      *   temporal = temporal._with(thisDayOfWeek);
459      * </pre>
460      * !(p)
461      * For example, given a date that is a Wednesday, the following are output:
462      * !(pre)
463      *   dateOnWed._with(MONDAY);     // two days earlier
464      *   dateOnWed._with(TUESDAY);    // one day earlier
465      *   dateOnWed._with(WEDNESDAY);  // same date
466      *   dateOnWed._with(THURSDAY);   // one day later
467      *   dateOnWed._with(FRIDAY);     // two days later
468      *   dateOnWed._with(SATURDAY);   // three days later
469      *   dateOnWed._with(SUNDAY);     // four days later
470      * </pre>
471      * !(p)
472      * This instance is immutable and unaffected by this method call.
473      *
474      * @param temporal  the target object to be adjusted, not null
475      * @return the adjusted object, not null
476      * @throws DateTimeException if unable to make the adjustment
477      * @throws ArithmeticException if numeric overflow occurs
478      */
479     override
480     Temporal adjustInto(Temporal temporal) {
481         return temporal._with(ChronoField.DAY_OF_WEEK, getValue());
482     }
483     
484     override string toString() {
485         return super.toString();
486     }
487 }