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.TemporalAdjusters;
13 
14 import hunt.time.temporal.ChronoField;
15 import hunt.time.temporal.ChronoUnit;
16 import hunt.time.temporal.Temporal;
17 import hunt.time.DayOfWeek;
18 import hunt.time.LocalDate;
19 import hunt.time.temporal.TemporalAdjuster;
20 
21 // import hunt.util.function.UnaryOperator;
22 
23 /**
24  * Common and useful TemporalAdjusters.
25  * !(p)
26  * Adjusters are a key tool for modifying temporal objects.
27  * They exist to externalize the process of adjustment, permitting different
28  * approaches, as per the strategy design pattern.
29  * Examples might be an adjuster that sets the date avoiding weekends, or one that
30  * sets the date to the last day of the month.
31  * !(p)
32  * There are two equivalent ways of using a {@code TemporalAdjuster}.
33  * The first is to invoke the method on the interface directly.
34  * The second is to use {@link Temporal#_with(TemporalAdjuster)}:
35  * !(pre)
36  *   // these two lines are equivalent, but the second approach is recommended
37  *   temporal = thisAdjuster.adjustInto(temporal);
38  *   temporal = temporal._with(thisAdjuster);
39  * </pre>
40  * It is recommended to use the second approach, {@code _with(TemporalAdjuster)},
41  * as it is a lot clearer to read _in code.
42  * !(p)
43  * This class contains a standard set of adjusters, available as static methods.
44  * These include:
45  * !(ul)
46  * !(li)finding the first or last day of the month
47  * !(li)finding the first day of next month
48  * !(li)finding the first or last day of the year
49  * !(li)finding the first day of next year
50  * !(li)finding the first or last day-of-week within a month, such as "first Wednesday _in June"
51  * !(li)finding the next or previous day-of-week, such as "next Thursday"
52  * </ul>
53  *
54  * @implSpec
55  * All the implementations supplied by the static methods are immutable.
56  *
57  * @see TemporalAdjuster
58  * @since 1.8
59  */
60 public final class TemporalAdjusters {
61 
62     /**
63      * Private constructor since this is a utility class.
64      */
65     private this() {
66     }
67 
68     //-----------------------------------------------------------------------
69     /**
70      * Obtains a {@code TemporalAdjuster} that wraps a date adjuster.
71      * !(p)
72      * The {@code TemporalAdjuster} is based on the low level {@code Temporal} interface.
73      * This method allows an adjustment from {@code LocalDate} to {@code LocalDate}
74      * to be wrapped to match the temporal-based interface.
75      * This is provided for convenience to make user-written adjusters simpler.
76      * !(p)
77      * In general, user-written adjusters should be static constants:
78      * !(pre){@code
79      *  static TemporalAdjuster TWO_DAYS_LATER =
80      *       TemporalAdjusters.ofDateAdjuster(date => date.plusDays(2));
81      * }</pre>
82      *
83      * @param dateBasedAdjuster  the date-based adjuster, not null
84      * @return the temporal adjuster wrapping on the date adjuster, not null
85      */
86      ///@gxc
87     // public static TemporalAdjuster ofDateAdjuster(opUnary!(LocalDate) dateBasedAdjuster) {
88     //     assert(dateBasedAdjuster, "dateBasedAdjuster");
89     //     return (temporal) => {
90     //         LocalDate input = LocalDate.from(temporal);
91     //         LocalDate output = dateBasedAdjuster.apply(input);
92     //         return temporal._with(output);
93     //     };
94     // }
95 
96     //-----------------------------------------------------------------------
97     /**
98      * Returns the "first day of month" adjuster, which returns a new date set to
99      * the first day of the current month.
100      * !(p)
101      * The ISO calendar system behaves as follows:!(br)
102      * The input 2011-01-15 will return 2011-01-01.!(br)
103      * The input 2011-02-15 will return 2011-02-01.
104      * !(p)
105      * The behavior is suitable for use with most calendar systems.
106      * It is equivalent to:
107      * !(pre)
108      *  temporal._with(DAY_OF_MONTH, 1);
109      * </pre>
110      *
111      * @return the first day-of-month adjuster, not null
112      */
113     public static TemporalAdjuster firstDayOfMonth() {
114         return new class TemporalAdjuster{
115             Temporal adjustInto(Temporal temporal)
116             {
117                 return temporal._with(ChronoField.DAY_OF_MONTH, 1);
118             }
119         };
120     }
121 
122     /**
123      * Returns the "last day of month" adjuster, which returns a new date set to
124      * the last day of the current month.
125      * !(p)
126      * The ISO calendar system behaves as follows:!(br)
127      * The input 2011-01-15 will return 2011-01-31.!(br)
128      * The input 2011-02-15 will return 2011-02-28.!(br)
129      * The input 2012-02-15 will return 2012-02-29 (leap year).!(br)
130      * The input 2011-04-15 will return 2011-04-30.
131      * !(p)
132      * The behavior is suitable for use with most calendar systems.
133      * It is equivalent to:
134      * !(pre)
135      *  long lastDay = temporal.range(DAY_OF_MONTH).getMaximum();
136      *  temporal._with(DAY_OF_MONTH, lastDay);
137      * </pre>
138      *
139      * @return the last day-of-month adjuster, not null
140      */
141     public static TemporalAdjuster lastDayOfMonth() {
142         return new class TemporalAdjuster{
143             Temporal adjustInto(Temporal temporal)
144             {
145                 return temporal._with(ChronoField.DAY_OF_MONTH, temporal.range(ChronoField.DAY_OF_MONTH).getMaximum());
146             }
147         };
148     }
149 
150     /**
151      * Returns the "first day of next month" adjuster, which returns a new date set to
152      * the first day of the next month.
153      * !(p)
154      * The ISO calendar system behaves as follows:!(br)
155      * The input 2011-01-15 will return 2011-02-01.!(br)
156      * The input 2011-02-15 will return 2011-03-01.
157      * !(p)
158      * The behavior is suitable for use with most calendar systems.
159      * It is equivalent to:
160      * !(pre)
161      *  temporal._with(DAY_OF_MONTH, 1).plus(1, MONTHS);
162      * </pre>
163      *
164      * @return the first day of next month adjuster, not null
165      */
166     public static TemporalAdjuster firstDayOfNextMonth() {
167         return new class TemporalAdjuster{
168             Temporal adjustInto(Temporal temporal)
169             {
170                 return temporal._with(ChronoField.DAY_OF_MONTH, 1).plus(1, ChronoUnit.MONTHS);
171             }
172         };
173     }
174 
175     //-----------------------------------------------------------------------
176     /**
177      * Returns the "first day of year" adjuster, which returns a new date set to
178      * the first day of the current year.
179      * !(p)
180      * The ISO calendar system behaves as follows:!(br)
181      * The input 2011-01-15 will return 2011-01-01.!(br)
182      * The input 2011-02-15 will return 2011-01-01.!(br)
183      * !(p)
184      * The behavior is suitable for use with most calendar systems.
185      * It is equivalent to:
186      * !(pre)
187      *  temporal._with(DAY_OF_YEAR, 1);
188      * </pre>
189      *
190      * @return the first day-of-year adjuster, not null
191      */
192     public static TemporalAdjuster firstDayOfYear() {
193         return new class TemporalAdjuster{
194             Temporal adjustInto(Temporal temporal)
195             {
196                 return temporal._with(ChronoField.DAY_OF_YEAR, 1);
197             }
198         };
199     }
200 
201     /**
202      * Returns the "last day of year" adjuster, which returns a new date set to
203      * the last day of the current year.
204      * !(p)
205      * The ISO calendar system behaves as follows:!(br)
206      * The input 2011-01-15 will return 2011-12-31.!(br)
207      * The input 2011-02-15 will return 2011-12-31.!(br)
208      * !(p)
209      * The behavior is suitable for use with most calendar systems.
210      * It is equivalent to:
211      * !(pre)
212      *  long lastDay = temporal.range(DAY_OF_YEAR).getMaximum();
213      *  temporal._with(DAY_OF_YEAR, lastDay);
214      * </pre>
215      *
216      * @return the last day-of-year adjuster, not null
217      */
218     public static TemporalAdjuster lastDayOfYear() {
219         return new class TemporalAdjuster{
220             Temporal adjustInto(Temporal temporal)
221             {
222                 return temporal._with(ChronoField.DAY_OF_YEAR, temporal.range(ChronoField.DAY_OF_YEAR).getMaximum());
223             }
224         };
225     }
226 
227     /**
228      * Returns the "first day of next year" adjuster, which returns a new date set to
229      * the first day of the next year.
230      * !(p)
231      * The ISO calendar system behaves as follows:!(br)
232      * The input 2011-01-15 will return 2012-01-01.
233      * !(p)
234      * The behavior is suitable for use with most calendar systems.
235      * It is equivalent to:
236      * !(pre)
237      *  temporal._with(DAY_OF_YEAR, 1).plus(1, YEARS);
238      * </pre>
239      *
240      * @return the first day of next month adjuster, not null
241      */
242     public static TemporalAdjuster firstDayOfNextYear() {
243         return new class TemporalAdjuster{
244             Temporal adjustInto(Temporal temporal)
245             {
246                 return temporal._with(ChronoField.DAY_OF_YEAR, 1).plus(1, ChronoUnit.YEARS);
247             }
248         };
249     }
250 
251     //-----------------------------------------------------------------------
252     /**
253      * Returns the first _in month adjuster, which returns a new date
254      * _in the same month with the first matching day-of-week.
255      * This is used for expressions like 'first Tuesday _in March'.
256      * !(p)
257      * The ISO calendar system behaves as follows:!(br)
258      * The input 2011-12-15 for (MONDAY) will return 2011-12-05.!(br)
259      * The input 2011-12-15 for (FRIDAY) will return 2011-12-02.!(br)
260      * !(p)
261      * The behavior is suitable for use with most calendar systems.
262      * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields
263      * and the {@code DAYS} unit, and assumes a seven day week.
264      *
265      * @param dayOfWeek  the day-of-week, not null
266      * @return the first _in month adjuster, not null
267      */
268     public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) {
269         return TemporalAdjusters.dayOfWeekInMonth(1, dayOfWeek);
270     }
271 
272     /**
273      * Returns the last _in month adjuster, which returns a new date
274      * _in the same month with the last matching day-of-week.
275      * This is used for expressions like 'last Tuesday _in March'.
276      * !(p)
277      * The ISO calendar system behaves as follows:!(br)
278      * The input 2011-12-15 for (MONDAY) will return 2011-12-26.!(br)
279      * The input 2011-12-15 for (FRIDAY) will return 2011-12-30.!(br)
280      * !(p)
281      * The behavior is suitable for use with most calendar systems.
282      * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields
283      * and the {@code DAYS} unit, and assumes a seven day week.
284      *
285      * @param dayOfWeek  the day-of-week, not null
286      * @return the first _in month adjuster, not null
287      */
288     public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) {
289         return TemporalAdjusters.dayOfWeekInMonth(-1, dayOfWeek);
290     }
291 
292     /**
293      * Returns the day-of-week _in month adjuster, which returns a new date
294      * with the ordinal day-of-week based on the month.
295      * This is used for expressions like the 'second Tuesday _in March'.
296      * !(p)
297      * The ISO calendar system behaves as follows:!(br)
298      * The input 2011-12-15 for (1,TUESDAY) will return 2011-12-06.!(br)
299      * The input 2011-12-15 for (2,TUESDAY) will return 2011-12-13.!(br)
300      * The input 2011-12-15 for (3,TUESDAY) will return 2011-12-20.!(br)
301      * The input 2011-12-15 for (4,TUESDAY) will return 2011-12-27.!(br)
302      * The input 2011-12-15 for (5,TUESDAY) will return 2012-01-03.!(br)
303      * The input 2011-12-15 for (-1,TUESDAY) will return 2011-12-27 (last _in month).!(br)
304      * The input 2011-12-15 for (-4,TUESDAY) will return 2011-12-06 (3 weeks before last _in month).!(br)
305      * The input 2011-12-15 for (-5,TUESDAY) will return 2011-11-29 (4 weeks before last _in month).!(br)
306      * The input 2011-12-15 for (0,TUESDAY) will return 2011-11-29 (last _in previous month).!(br)
307      * !(p)
308      * For a positive or zero ordinal, the algorithm is equivalent to finding the first
309      * day-of-week that matches within the month and then adding a number of weeks to it.
310      * For a negative ordinal, the algorithm is equivalent to finding the last
311      * day-of-week that matches within the month and then subtracting a number of weeks to it.
312      * The ordinal number of weeks is not validated and is interpreted leniently
313      * according to this algorithm. This definition means that an ordinal of zero finds
314      * the last matching day-of-week _in the previous month.
315      * !(p)
316      * The behavior is suitable for use with most calendar systems.
317      * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields
318      * and the {@code DAYS} unit, and assumes a seven day week.
319      *
320      * @param ordinal  the week within the month, unbounded but typically from -5 to 5
321      * @param dayOfWeek  the day-of-week, not null
322      * @return the day-of-week _in month adjuster, not null
323      */
324     public static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) {
325         assert(dayOfWeek, "dayOfWeek");
326         int dowValue = dayOfWeek.getValue();
327         if (ordinal >= 0) {
328             return new class TemporalAdjuster{
329             Temporal adjustInto(Temporal temporal)
330             {
331                 Temporal temp = temporal._with(ChronoField.DAY_OF_MONTH, 1);
332                 int curDow = temp.get(ChronoField.DAY_OF_WEEK);
333                 int dowDiff = (dowValue - curDow + 7) % 7;
334                 dowDiff += (ordinal - 1L) * 7L;  // safe from overflow
335                 return temp.plus(dowDiff, ChronoUnit.DAYS);
336             }
337             };
338         } else {
339 
340              return new class TemporalAdjuster{
341                 Temporal adjustInto(Temporal temporal)
342                 {
343                     Temporal temp = temporal._with(ChronoField.DAY_OF_MONTH, temporal.range(ChronoField.DAY_OF_MONTH).getMaximum());
344                     int curDow = temp.get(ChronoField.DAY_OF_WEEK);
345                     int daysDiff = dowValue - curDow;
346                     daysDiff = (daysDiff == 0 ? 0 : (daysDiff > 0 ? daysDiff - 7 : daysDiff));
347                     daysDiff -= (-ordinal - 1L) * 7L;  // safe from overflow
348                     return temp.plus(daysDiff, ChronoUnit.DAYS);
349                 }
350             };
351         }
352     }
353 
354     //-----------------------------------------------------------------------
355     /**
356      * Returns the next day-of-week adjuster, which adjusts the date to the
357      * first occurrence of the specified day-of-week after the date being adjusted.
358      * !(p)
359      * The ISO calendar system behaves as follows:!(br)
360      * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).!(br)
361      * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).!(br)
362      * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-22 (seven days later).
363      * !(p)
364      * The behavior is suitable for use with most calendar systems.
365      * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit,
366      * and assumes a seven day week.
367      *
368      * @param dayOfWeek  the day-of-week to move the date to, not null
369      * @return the next day-of-week adjuster, not null
370      */
371     public static TemporalAdjuster next(DayOfWeek dayOfWeek) {
372         int dowValue = dayOfWeek.getValue();
373         return new class TemporalAdjuster{
374                 Temporal adjustInto(Temporal temporal)
375                 {
376                     int calDow = temporal.get(ChronoField.DAY_OF_WEEK);
377                     int daysDiff = calDow - dowValue;
378                     return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS);
379                 }
380             };
381     }
382 
383     /**
384      * Returns the next-or-same day-of-week adjuster, which adjusts the date to the
385      * first occurrence of the specified day-of-week after the date being adjusted
386      * unless it is already on that day _in which case the same object is returned.
387      * !(p)
388      * The ISO calendar system behaves as follows:!(br)
389      * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).!(br)
390      * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).!(br)
391      * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input).
392      * !(p)
393      * The behavior is suitable for use with most calendar systems.
394      * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit,
395      * and assumes a seven day week.
396      *
397      * @param dayOfWeek  the day-of-week to check for or move the date to, not null
398      * @return the next-or-same day-of-week adjuster, not null
399      */
400     public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) {
401         int dowValue = dayOfWeek.getValue();
402         return new class TemporalAdjuster{
403                 Temporal adjustInto(Temporal temporal)
404                 {
405                     int calDow = temporal.get(ChronoField.DAY_OF_WEEK);
406                     if (calDow == dowValue) {
407                         return temporal;
408                     }
409                     int daysDiff = calDow - dowValue;
410                     return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS);
411                 }
412             };
413     }
414 
415     /**
416      * Returns the previous day-of-week adjuster, which adjusts the date to the
417      * first occurrence of the specified day-of-week before the date being adjusted.
418      * !(p)
419      * The ISO calendar system behaves as follows:!(br)
420      * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).!(br)
421      * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).!(br)
422      * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-08 (seven days earlier).
423      * !(p)
424      * The behavior is suitable for use with most calendar systems.
425      * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit,
426      * and assumes a seven day week.
427      *
428      * @param dayOfWeek  the day-of-week to move the date to, not null
429      * @return the previous day-of-week adjuster, not null
430      */
431     public static TemporalAdjuster previous(DayOfWeek dayOfWeek) {
432         int dowValue = dayOfWeek.getValue();
433         return new class TemporalAdjuster{
434                 Temporal adjustInto(Temporal temporal)
435                 {
436                     int calDow = temporal.get(ChronoField.DAY_OF_WEEK);
437                     int daysDiff = dowValue - calDow;
438                     return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS);
439                 }
440             };
441     }
442 
443     /**
444      * Returns the previous-or-same day-of-week adjuster, which adjusts the date to the
445      * first occurrence of the specified day-of-week before the date being adjusted
446      * unless it is already on that day _in which case the same object is returned.
447      * !(p)
448      * The ISO calendar system behaves as follows:!(br)
449      * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).!(br)
450      * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).!(br)
451      * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input).
452      * !(p)
453      * The behavior is suitable for use with most calendar systems.
454      * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit,
455      * and assumes a seven day week.
456      *
457      * @param dayOfWeek  the day-of-week to check for or move the date to, not null
458      * @return the previous-or-same day-of-week adjuster, not null
459      */
460     public static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) {
461         int dowValue = dayOfWeek.getValue();
462          return new class TemporalAdjuster{
463                 Temporal adjustInto(Temporal temporal)
464                 {
465                     int calDow = temporal.get(ChronoField.DAY_OF_WEEK);
466                     if (calDow == dowValue) {
467                         return temporal;
468                     }
469                     int daysDiff = dowValue - calDow;
470                     return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS);
471                 }
472             };
473     }
474 
475 }