UNPKG

18.3 kBJavaScriptView Raw
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
7import {requireNonNull} from '../assert';
8import {IllegalStateException} from '../errors';
9
10import {TemporalAdjuster} from './TemporalAdjuster';
11import {ChronoField} from '../temporal/ChronoField';
12import {ChronoUnit} from '../temporal/ChronoUnit';
13import {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 */
47export 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 */
356class 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. */
383Impl.FIRST_DAY_OF_MONTH = new Impl(0);
384/** Last day of month adjuster. */
385Impl.LAST_DAY_OF_MONTH = new Impl(1);
386/** First day of next month adjuster. */
387Impl.FIRST_DAY_OF_NEXT_MONTH = new Impl(2);
388/** First day of year adjuster. */
389Impl.FIRST_DAY_OF_YEAR = new Impl(3);
390/** Last day of year adjuster. */
391Impl.LAST_DAY_OF_YEAR = new Impl(4);
392/** First day of next month adjuster. */
393Impl.FIRST_DAY_OF_NEXT_YEAR = new Impl(5);
394
395
396/**
397 * Class implementing day-of-week in month adjuster.
398 */
399class 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 */
434class 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}