1 | /*
|
2 | * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
|
3 | * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
|
4 | * @license BSD-3-Clause (see LICENSE in the root directory of this source tree)
|
5 | */
|
6 |
|
7 | import {requireNonNull} from '../assert';
|
8 | import {IllegalStateException} from '../errors';
|
9 |
|
10 | import {TemporalAdjuster} from './TemporalAdjuster';
|
11 | import {ChronoField} from '../temporal/ChronoField';
|
12 | import {ChronoUnit} from '../temporal/ChronoUnit';
|
13 | import {MathUtil} from '../MathUtil';
|
14 |
|
15 | /**
|
16 | * Common implementations of {@link TemporalAdjuster}.
|
17 | *
|
18 | * This class provides common implementations of {@link TemporalAdjuster}.
|
19 | * They are especially useful to document the intent of business logic and
|
20 | * often link well to requirements.
|
21 | * For example, these two pieces of code do the same thing, but the second
|
22 | * one is clearer (assuming that there is a static import of this class):
|
23 | * <pre>
|
24 | * // direct manipulation
|
25 | * date.withDayOfMonth(1).plusMonths(1).minusDays(1);
|
26 | * // use of an adjuster from this class
|
27 | * date.with(lastDayOfMonth());
|
28 | * </pre>
|
29 | * There are two equivalent ways of using a {@link TemporalAdjuster}.
|
30 | * The first is to invoke the method on the interface directly.
|
31 | * The second is to use {@link Temporal#with}:
|
32 | * <pre>
|
33 | * // these two lines are equivalent, but the second approach is recommended
|
34 | * dateTime = adjuster.adjustInto(dateTime);
|
35 | * dateTime = dateTime.with(adjuster);
|
36 | * </pre>
|
37 | * It is recommended to use the second approach, {@link with},
|
38 | * as it is a lot clearer to read in code.
|
39 | *
|
40 | * ### Specification for implementors
|
41 | *
|
42 | * This is a thread-safe utility class.
|
43 | * All returned adjusters are immutable and thread-safe.
|
44 | *
|
45 | * The JDK 8 ofDateAdjuster(UnaryOperator) method is not backported.
|
46 | */
|
47 | export class TemporalAdjusters {
|
48 |
|
49 | //-----------------------------------------------------------------------
|
50 | /**
|
51 | * Returns the 'first day of month' adjuster, which returns a new date set to
|
52 | * the first day of the current month.
|
53 | *
|
54 | * The ISO calendar system behaves as follows:
|
55 | *
|
56 | * * The input 2011-01-15 will return 2011-01-01.
|
57 | * * The input 2011-02-15 will return 2011-02-01.
|
58 | *
|
59 | * The behavior is suitable for use with most calendar systems.
|
60 | * It is equivalent to:
|
61 | * <pre>
|
62 | * temporal.with(DAY_OF_MONTH, 1);
|
63 | * </pre>
|
64 | *
|
65 | * @return {TemporalAdjuster} the first day-of-month adjuster, not null
|
66 | */
|
67 | static firstDayOfMonth() {
|
68 | return Impl.FIRST_DAY_OF_MONTH;
|
69 | }
|
70 |
|
71 | /**
|
72 | * Returns the 'last day of month' adjuster, which returns a new date set to
|
73 | * the last day of the current month.
|
74 | *
|
75 | * The ISO calendar system behaves as follows:
|
76 | *
|
77 | * * The input 2011-01-15 will return 2011-01-31.
|
78 | * * The input 2011-02-15 will return 2011-02-28.
|
79 | * * The input 2012-02-15 will return 2012-02-29 (leap year).
|
80 | * * The input 2011-04-15 will return 2011-04-30.
|
81 | *
|
82 | * The behavior is suitable for use with most calendar systems.
|
83 | * It is equivalent to:
|
84 | * <pre>
|
85 | * long lastDay = temporal.range(DAY_OF_MONTH).getMaximum();
|
86 | * temporal.with(DAY_OF_MONTH, lastDay);
|
87 | * </pre>
|
88 | *
|
89 | * @return {TemporalAdjuster} the last day-of-month adjuster, not null
|
90 | */
|
91 | static lastDayOfMonth() {
|
92 | return Impl.LAST_DAY_OF_MONTH;
|
93 | }
|
94 |
|
95 | /**
|
96 | * Returns the 'first day of next month' adjuster, which returns a new date set to
|
97 | * the first day of the next month.
|
98 | *
|
99 | * The ISO calendar system behaves as follows:
|
100 | *
|
101 | * * The input 2011-01-15 will return 2011-02-01.
|
102 | * * The input 2011-02-15 will return 2011-03-01.
|
103 | *
|
104 | * The behavior is suitable for use with most calendar systems.
|
105 | * It is equivalent to:
|
106 | * <pre>
|
107 | * temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS);
|
108 | * </pre>
|
109 | *
|
110 | * @return {TemporalAdjuster} the first day of next month adjuster, not null
|
111 | */
|
112 | static firstDayOfNextMonth() {
|
113 | return Impl.FIRST_DAY_OF_NEXT_MONTH;
|
114 | }
|
115 |
|
116 | //-----------------------------------------------------------------------
|
117 | /**
|
118 | * Returns the 'first day of year' adjuster, which returns a new date set to
|
119 | * the first day of the current year.
|
120 | *
|
121 | * The ISO calendar system behaves as follows:
|
122 | *
|
123 | * * The input 2011-01-15 will return 2011-01-01.
|
124 | * * The input 2011-02-15 will return 2011-01-01.
|
125 | *
|
126 | * The behavior is suitable for use with most calendar systems.
|
127 | * It is equivalent to:
|
128 | * <pre>
|
129 | * temporal.with(DAY_OF_YEAR, 1);
|
130 | * </pre>
|
131 | *
|
132 | * @return {TemporalAdjuster} the first day-of-year adjuster, not null
|
133 | */
|
134 | static firstDayOfYear() {
|
135 | return Impl.FIRST_DAY_OF_YEAR;
|
136 | }
|
137 |
|
138 | /**
|
139 | * Returns the 'last day of year' adjuster, which returns a new date set to
|
140 | * the last day of the current year.
|
141 | *
|
142 | * The ISO calendar system behaves as follows:
|
143 | *
|
144 | * * The input 2011-01-15 will return 2011-12-31.
|
145 | * * The input 2011-02-15 will return 2011-12-31.
|
146 | *
|
147 | * The behavior is suitable for use with most calendar systems.
|
148 | * It is equivalent to:
|
149 | * <pre>
|
150 | * long lastDay = temporal.range(DAY_OF_YEAR).getMaximum();
|
151 | * temporal.with(DAY_OF_YEAR, lastDay);
|
152 | * </pre>
|
153 | *
|
154 | * @return {TemporalAdjuster} the last day-of-year adjuster, not null
|
155 | */
|
156 | static lastDayOfYear() {
|
157 | return Impl.LAST_DAY_OF_YEAR;
|
158 | }
|
159 |
|
160 | /**
|
161 | * Returns the 'first day of next year' adjuster, which returns a new date set to
|
162 | * the first day of the next year.
|
163 | *
|
164 | * The ISO calendar system behaves as follows:
|
165 | *
|
166 | * * The input 2011-01-15 will return 2012-01-01.
|
167 | *
|
168 | * The behavior is suitable for use with most calendar systems.
|
169 | * It is equivalent to:
|
170 | * <pre>
|
171 | * temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS);
|
172 | * </pre>
|
173 | *
|
174 | * @return {TemporalAdjuster} the first day of next month adjuster, not null
|
175 | */
|
176 | static firstDayOfNextYear() {
|
177 | return Impl.FIRST_DAY_OF_NEXT_YEAR;
|
178 | }
|
179 |
|
180 | //-----------------------------------------------------------------------
|
181 | /**
|
182 | * Returns the first in month adjuster, which returns a new date
|
183 | * in the same month with the first matching day-of-week.
|
184 | * This is used for expressions like 'first Tuesday in March'.
|
185 | *
|
186 | * The ISO calendar system behaves as follows:
|
187 | *
|
188 | * * The input 2011-12-15 for (MONDAY) will return 2011-12-05.
|
189 | * * The input 2011-12-15 for (FRIDAY) will return 2011-12-02.
|
190 | *
|
191 | * The behavior is suitable for use with most calendar systems.
|
192 | * It uses the {@link DAY_OF_WEEK} and {@link DAY_OF_MONTH} fields
|
193 | * and the {@link DAYS} unit, and assumes a seven day week.
|
194 | *
|
195 | * @param {DayOfWeek} dayOfWeek the day-of-week, not null
|
196 | * @return {TemporalAdjuster} the first in month adjuster, not null
|
197 | */
|
198 | static firstInMonth(dayOfWeek) {
|
199 | requireNonNull(dayOfWeek, 'dayOfWeek');
|
200 | return new DayOfWeekInMonth(1, dayOfWeek);
|
201 | }
|
202 |
|
203 | /**
|
204 | * Returns the last in month adjuster, which returns a new date
|
205 | * in the same month with the last matching day-of-week.
|
206 | * This is used for expressions like 'last Tuesday in March'.
|
207 | *
|
208 | * The ISO calendar system behaves as follows:
|
209 | *
|
210 | * * The input 2011-12-15 for (MONDAY) will return 2011-12-26.
|
211 | * * The input 2011-12-15 for (FRIDAY) will return 2011-12-30.
|
212 | *
|
213 | * The behavior is suitable for use with most calendar systems.
|
214 | * It uses the {@link DAY_OF_WEEK} and {@link DAY_OF_MONTH} fields
|
215 | * and the {@link DAYS} unit, and assumes a seven day week.
|
216 | *
|
217 | * @param {DayOfWeek} dayOfWeek the day-of-week, not null
|
218 | * @return {TemporalAdjuster} the first in month adjuster, not null
|
219 | */
|
220 | static lastInMonth(dayOfWeek) {
|
221 | requireNonNull(dayOfWeek, 'dayOfWeek');
|
222 | return new DayOfWeekInMonth(-1, dayOfWeek);
|
223 | }
|
224 |
|
225 | /**
|
226 | * Returns the day-of-week in month adjuster, which returns a new date
|
227 | * in the same month with the ordinal day-of-week.
|
228 | * This is used for expressions like the 'second Tuesday in March'.
|
229 | *
|
230 | * The ISO calendar system behaves as follows:
|
231 | *
|
232 | * * The input 2011-12-15 for (1,TUESDAY) will return 2011-12-06.
|
233 | * * The input 2011-12-15 for (2,TUESDAY) will return 2011-12-13.
|
234 | * * The input 2011-12-15 for (3,TUESDAY) will return 2011-12-20.
|
235 | * * The input 2011-12-15 for (4,TUESDAY) will return 2011-12-27.
|
236 | * * The input 2011-12-15 for (5,TUESDAY) will return 2012-01-03.
|
237 | * * The input 2011-12-15 for (-1,TUESDAY) will return 2011-12-27 (last in month).
|
238 | * * The input 2011-12-15 for (-4,TUESDAY) will return 2011-12-06 (3 weeks before last in month).
|
239 | * * The input 2011-12-15 for (-5,TUESDAY) will return 2011-11-29 (4 weeks before last in month).
|
240 | * * The input 2011-12-15 for (0,TUESDAY) will return 2011-11-29 (last in previous month).
|
241 | *
|
242 | * For a positive or zero ordinal, the algorithm is equivalent to finding the first
|
243 | * day-of-week that matches within the month and then adding a number of weeks to it.
|
244 | * For a negative ordinal, the algorithm is equivalent to finding the last
|
245 | * day-of-week that matches within the month and then subtracting a number of weeks to it.
|
246 | * The ordinal number of weeks is not validated and is interpreted leniently
|
247 | * according to this algorithm. This definition means that an ordinal of zero finds
|
248 | * the last matching day-of-week in the previous month.
|
249 | *
|
250 | * The behavior is suitable for use with most calendar systems.
|
251 | * It uses the {@link DAY_OF_WEEK} and {@link DAY_OF_MONTH} fields
|
252 | * and the {@link DAYS} unit, and assumes a seven day week.
|
253 | *
|
254 | * @param {Number} ordinal the week within the month, unbounded but typically from -5 to 5
|
255 | * @param {DayOfWeek} dayOfWeek the day-of-week, not null
|
256 | * @return {TemporalAdjuster} the day-of-week in month adjuster, not null
|
257 | */
|
258 | static dayOfWeekInMonth(ordinal, dayOfWeek) {
|
259 | requireNonNull(dayOfWeek, 'dayOfWeek');
|
260 | return new DayOfWeekInMonth(ordinal, dayOfWeek);
|
261 | }
|
262 |
|
263 | //-----------------------------------------------------------------------
|
264 | /**
|
265 | * Returns the next day-of-week adjuster, which adjusts the date to the
|
266 | * first occurrence of the specified day-of-week after the date being adjusted.
|
267 | *
|
268 | * The ISO calendar system behaves as follows:
|
269 | *
|
270 | * * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
|
271 | * * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
|
272 | * * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-22 (seven days later).
|
273 | *
|
274 | * The behavior is suitable for use with most calendar systems.
|
275 | * It uses the {@link DAY_OF_WEEK} field and the {@link DAYS} unit,
|
276 | * and assumes a seven day week.
|
277 | *
|
278 | * @param {DayOfWeek} dayOfWeek the day-of-week to move the date to, not null
|
279 | * @return {TemporalAdjuster} the next day-of-week adjuster, not null
|
280 | */
|
281 | static next(dayOfWeek) {
|
282 | return new RelativeDayOfWeek(2, dayOfWeek);
|
283 | }
|
284 |
|
285 | /**
|
286 | * Returns the next-or-same day-of-week adjuster, which adjusts the date to the
|
287 | * first occurrence of the specified day-of-week after the date being adjusted
|
288 | * unless it is already on that day in which case the same object is returned.
|
289 | *
|
290 | * The ISO calendar system behaves as follows:
|
291 | *
|
292 | * * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
|
293 | * * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
|
294 | * * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input).
|
295 | *
|
296 | * The behavior is suitable for use with most calendar systems.
|
297 | * It uses the {@link DAY_OF_WEEK} field and the {@link DAYS} unit,
|
298 | * and assumes a seven day week.
|
299 | *
|
300 | * @param {DayOfWeek} dayOfWeek the day-of-week to check for or move the date to, not null
|
301 | * @return {TemporalAdjuster} the next-or-same day-of-week adjuster, not null
|
302 | */
|
303 | static nextOrSame(dayOfWeek) {
|
304 | return new RelativeDayOfWeek(0, dayOfWeek);
|
305 | }
|
306 |
|
307 | /**
|
308 | * Returns the previous day-of-week adjuster, which adjusts the date to the
|
309 | * first occurrence of the specified day-of-week before the date being adjusted.
|
310 | *
|
311 | * The ISO calendar system behaves as follows:
|
312 | *
|
313 | * * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
|
314 | * * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
|
315 | * * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-08 (seven days earlier).
|
316 | *
|
317 | * The behavior is suitable for use with most calendar systems.
|
318 | * It uses the {@link DAY_OF_WEEK} field and the {@link DAYS} unit,
|
319 | * and assumes a seven day week.
|
320 | *
|
321 | * @param {DayOfWeek} dayOfWeek the day-of-week to move the date to, not null
|
322 | * @return {TemporalAdjuster} the previous day-of-week adjuster, not null
|
323 | */
|
324 | static previous(dayOfWeek) {
|
325 | return new RelativeDayOfWeek(3, dayOfWeek);
|
326 | }
|
327 |
|
328 | /**
|
329 | * Returns the previous-or-same day-of-week adjuster, which adjusts the date to the
|
330 | * first occurrence of the specified day-of-week before the date being adjusted
|
331 | * unless it is already on that day in which case the same object is returned.
|
332 | *
|
333 | * The ISO calendar system behaves as follows:
|
334 | *
|
335 | * * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
|
336 | * * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
|
337 | * * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input).
|
338 | *
|
339 | * The behavior is suitable for use with most calendar systems.
|
340 | * It uses the {@link DAY_OF_WEEK} field and the {@link DAYS} unit,
|
341 | * and assumes a seven day week.
|
342 | *
|
343 | * @param {DayOfWeek} dayOfWeek the day-of-week to check for or move the date to, not null
|
344 | * @return {TemporalAdjuster} the previous-or-same day-of-week adjuster, not null
|
345 | */
|
346 | static previousOrSame(dayOfWeek) {
|
347 | return new RelativeDayOfWeek(1, dayOfWeek);
|
348 | }
|
349 |
|
350 | }
|
351 |
|
352 | //-----------------------------------------------------------------------
|
353 | /**
|
354 | * Enum implementing the adjusters.
|
355 | */
|
356 | class Impl extends TemporalAdjuster {
|
357 |
|
358 | /**
|
359 | *
|
360 | * @param ordinal
|
361 | * @private
|
362 | */
|
363 | constructor(ordinal) {
|
364 | super();
|
365 | this._ordinal = ordinal;
|
366 | }
|
367 |
|
368 | adjustInto(temporal) {
|
369 | switch (this._ordinal) {
|
370 | case 0: return temporal.with(ChronoField.DAY_OF_MONTH, 1);
|
371 | case 1: return temporal.with(ChronoField.DAY_OF_MONTH, temporal.range(ChronoField.DAY_OF_MONTH).maximum());
|
372 | case 2: return temporal.with(ChronoField.DAY_OF_MONTH, 1).plus(1, ChronoUnit.MONTHS);
|
373 | case 3: return temporal.with(ChronoField.DAY_OF_YEAR, 1);
|
374 | case 4: return temporal.with(ChronoField.DAY_OF_YEAR, temporal.range(ChronoField.DAY_OF_YEAR).maximum());
|
375 | case 5: return temporal.with(ChronoField.DAY_OF_YEAR, 1).plus(1, ChronoUnit.YEARS);
|
376 | }
|
377 | throw new IllegalStateException('Unreachable');
|
378 | }
|
379 |
|
380 | }
|
381 |
|
382 | /** First day of month adjuster. */
|
383 | Impl.FIRST_DAY_OF_MONTH = new Impl(0);
|
384 | /** Last day of month adjuster. */
|
385 | Impl.LAST_DAY_OF_MONTH = new Impl(1);
|
386 | /** First day of next month adjuster. */
|
387 | Impl.FIRST_DAY_OF_NEXT_MONTH = new Impl(2);
|
388 | /** First day of year adjuster. */
|
389 | Impl.FIRST_DAY_OF_YEAR = new Impl(3);
|
390 | /** Last day of year adjuster. */
|
391 | Impl.LAST_DAY_OF_YEAR = new Impl(4);
|
392 | /** First day of next month adjuster. */
|
393 | Impl.FIRST_DAY_OF_NEXT_YEAR = new Impl(5);
|
394 |
|
395 |
|
396 | /**
|
397 | * Class implementing day-of-week in month adjuster.
|
398 | */
|
399 | class DayOfWeekInMonth extends TemporalAdjuster {
|
400 |
|
401 | /**
|
402 | *
|
403 | * @param ordinal
|
404 | * @param dow
|
405 | * @private
|
406 | */
|
407 | constructor(ordinal, dow) {
|
408 | super();
|
409 | this._ordinal = ordinal;
|
410 | this._dowValue = dow.value();
|
411 | }
|
412 |
|
413 | adjustInto(temporal) {
|
414 | if (this._ordinal >= 0) {
|
415 | const temp = temporal.with(ChronoField.DAY_OF_MONTH, 1);
|
416 | const curDow = temp.get(ChronoField.DAY_OF_WEEK);
|
417 | let dowDiff = MathUtil.intMod((this._dowValue - curDow + 7), 7);
|
418 | dowDiff += (this._ordinal - 1) * 7; // safe from overflow
|
419 | return temp.plus(dowDiff, ChronoUnit.DAYS);
|
420 | } else {
|
421 | const temp = temporal.with(ChronoField.DAY_OF_MONTH, temporal.range(ChronoField.DAY_OF_MONTH).maximum());
|
422 | const curDow = temp.get(ChronoField.DAY_OF_WEEK);
|
423 | let daysDiff = this._dowValue - curDow;
|
424 | daysDiff = (daysDiff === 0 ? 0 : (daysDiff > 0 ? daysDiff - 7 : daysDiff));
|
425 | daysDiff -= (-this._ordinal - 1) * 7; // safe from overflow
|
426 | return temp.plus(daysDiff, ChronoUnit.DAYS);
|
427 | }
|
428 | }
|
429 | }
|
430 |
|
431 | /**
|
432 | * Implementation of next, previous or current day-of-week.
|
433 | */
|
434 | class RelativeDayOfWeek extends TemporalAdjuster {
|
435 |
|
436 | /**
|
437 | *
|
438 | * @param relative
|
439 | * @param dayOfWeek
|
440 | * @private
|
441 | */
|
442 | constructor(relative, dayOfWeek) {
|
443 | super();
|
444 | requireNonNull(dayOfWeek, 'dayOfWeek');
|
445 | /** Whether the current date is a valid answer. */
|
446 | this._relative = relative;
|
447 | /** The day-of-week value, from 1 to 7. */
|
448 | this._dowValue = dayOfWeek.value();
|
449 | }
|
450 |
|
451 | adjustInto(temporal) {
|
452 | const calDow = temporal.get(ChronoField.DAY_OF_WEEK);
|
453 | if (this._relative < 2 && calDow === this._dowValue) {
|
454 | return temporal;
|
455 | }
|
456 | if ((this._relative & 1) === 0) {
|
457 | const daysDiff = calDow - this._dowValue;
|
458 | return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS);
|
459 | } else {
|
460 | const daysDiff = this._dowValue - calDow;
|
461 | return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, ChronoUnit.DAYS);
|
462 | }
|
463 | }
|
464 | }
|