@js-joda/extra 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,487 @@
1
+ /*
2
+ * @copyright (c) 2022, Philipp Thürwächter & Pattrick Hüper & Michał Sobkiewicz
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 { ChronoField, Clock, DateTimeException, IllegalArgumentException, IsoChronology, LocalDate, Month, MonthDay, TemporalAccessor, TemporalQueries, TemporalQuery, UnsupportedTemporalTypeException, ZoneId } from '@js-joda/core';
8
+
9
+ // TODO: hm... is this a good idea?? copied from joda currently, could we add a js-joda-utils module??
10
+ import { requireInstance, requireNonNull } from './assert';
11
+ import { _ as jodaInternal } from '@js-joda/core';
12
+
13
+ const MathUtil = jodaInternal.MathUtil;
14
+
15
+ /**
16
+ * A day-of-month in the ISO-8601 calendar system.
17
+ *
18
+ * {@link DayOfMonth} is an immutable date-time object that represents a day-of-month.
19
+ * It is a type-safe way of representing a day-of-month in an application.
20
+ * Any field that can be derived from a day-of-month can be obtained.
21
+ *
22
+ * This class does not store or represent a year, month, time or time-zone.
23
+ * For example, the value '21' can be stored in a {@link DayOfMonth} and
24
+ * would represent the 21st day of any month.
25
+ */
26
+ export class DayOfMonth extends TemporalAccessor {
27
+ //-----------------------------------------------------------------------
28
+ /**
29
+ * Function overloading for {@link DayOfMonth.now}:
30
+ * * if called with no arguments, {@link DayOfMonth.now0} is executed;
31
+ * * if called with an instance of {@link ZoneId}, then {@link DayOfMonth.nowZoneId} is executed;
32
+ * * if called with an instance of {@link Clock}, then {@link DayOfMonth.nowClock} is executed;
33
+ * * otherwise {@link IllegalArgumentException} is thrown.
34
+ *
35
+ * @param {?(ZoneId|Clock)} zoneIdOrClock
36
+ * @returns {DayOfMonth}
37
+ */
38
+ static now(zoneIdOrClock) {
39
+ switch (arguments.length) {
40
+ case 0:
41
+ return DayOfMonth.now0();
42
+ case 1:
43
+ requireNonNull(zoneIdOrClock, 'clockOrZone');
44
+ if (zoneIdOrClock instanceof ZoneId) {
45
+ return DayOfMonth.nowZoneId(zoneIdOrClock);
46
+ }
47
+ if (zoneIdOrClock instanceof Clock) {
48
+ return DayOfMonth.nowClock(zoneIdOrClock);
49
+ }
50
+ throw new IllegalArgumentException(`zoneIdOrClock must be an instance of ZoneId or Clock, but is ${zoneIdOrClock.constructor.name}`);
51
+ default:
52
+ throw new IllegalArgumentException(`Invalid number of arguments: ${arguments.length}`);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Obtains the current day-of-month from the system clock in the default time-zone.
58
+ *
59
+ * This will query the {@link Clock.systemDefaultZone} system clock in the default
60
+ * time-zone to obtain the current day-of-month.
61
+ * The zone and offset will be set based on the time-zone in the clock.
62
+ *
63
+ * Using this method will prevent the ability to use an alternate clock for testing
64
+ * because the clock is hard-coded.
65
+ *
66
+ * @return the current day-of-month using the system clock and default time-zone, not null
67
+ * @private
68
+ */
69
+ static now0() {
70
+ return this.now(Clock.systemDefaultZone());
71
+ }
72
+
73
+ /**
74
+ * Obtains the current day-of-month from the system clock in the specified time-zone.
75
+ *
76
+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current day-of-month.
77
+ * Specifying the time-zone avoids dependence on the default time-zone.
78
+ *
79
+ * Using this method will prevent the ability to use an alternate clock for testing
80
+ * because the clock is hard-coded.
81
+ *
82
+ * @param zone the zone ID to use, not null
83
+ * @return the current day-of-month using the system clock, not null
84
+ * @private
85
+ */
86
+ static nowZoneId(zone) {
87
+ return this.now(Clock.system(zone));
88
+ }
89
+
90
+ /**
91
+ * Obtains the current day-of-month from the specified clock.
92
+ *
93
+ * This will query the specified clock to obtain the current day-of-month.
94
+ * Using this method allows the use of an alternate clock for testing.
95
+ * The alternate clock may be introduced using {@link Clock dependency injection}.
96
+ *
97
+ * @param clock the clock to use, not null
98
+ * @return the current day-of-month, not null
99
+ * @private
100
+ */
101
+ static nowClock(clock) {
102
+ const now = LocalDate.now(clock); // called once
103
+ return DayOfMonth.of(now.dayOfMonth());
104
+ }
105
+
106
+ //-----------------------------------------------------------------------
107
+ /**
108
+ * Obtains an instance of {@link DayOfMonth}.
109
+ *
110
+ * A day-of-month object represents one of the 31 days of the month, from 1 to 31.
111
+ *
112
+ * @param dayOfMonth the day-of-month to represent, from 1 to 31
113
+ * @return the day-of-month, not null
114
+ * @throws DateTimeException if the day-of-month is invalid
115
+ */
116
+ static of(dayOfMonth) {
117
+ if (1 <= dayOfMonth && dayOfMonth <= 31) {
118
+ return DayOfMonth.VALUES[dayOfMonth - 1];
119
+ } else {
120
+ throw new DateTimeException(`Invalid value for DayOfMonth: ${dayOfMonth}`);
121
+ }
122
+ }
123
+
124
+ //-----------------------------------------------------------------------
125
+ /**
126
+ * Obtains an instance of {@link DayOfMonth} from a date-time object.
127
+ *
128
+ * This obtains a day-of-month based on the specified temporal.
129
+ * A {@link TemporalAccessor} represents an arbitrary set of date and time information,
130
+ * which this factory converts to an instance of {@link DayOfMonth}.
131
+ *
132
+ * The conversion extracts the {@link ChronoField#DAY_OF_MONTH} day-of-month field.
133
+ * The extraction is only permitted if the temporal object has an ISO
134
+ * chronology, or can be converted to a {@link LocalDate}.
135
+ *
136
+ * This method matches the signature of the functional interface {@link TemporalQuery}
137
+ * allowing it to be used in queries via method reference, {@link DayOfMonth.from}.
138
+ *
139
+ * @param temporal the temporal object to convert, not null
140
+ * @return the day-of-month, not null
141
+ * @throws {DateTimeException} if unable to convert to a {@link DayOfMonth}
142
+ */
143
+ static from(temporal) {
144
+ if (temporal instanceof DayOfMonth) {
145
+ return temporal;
146
+ }
147
+ requireNonNull(temporal, 'temporal');
148
+ try {
149
+ /* TODO: only IsoChronology for now
150
+ if (IsoChronology.INSTANCE.equals(Chronology.from(temporal)) == false) {
151
+ temporal = LocalDate.from(temporal);
152
+ }*/
153
+ return DayOfMonth.of(temporal.get(ChronoField.DAY_OF_MONTH));
154
+ } catch (ex) {
155
+ throw new DateTimeException(`Unable to obtain DayOfMonth from TemporalAccessor: ${temporal} of type ${temporal.constructor.name}`, ex);
156
+ }
157
+ }
158
+
159
+ //-----------------------------------------------------------------------
160
+ /**
161
+ * Constructor, previously validated.
162
+ *
163
+ * @param dayOfMonth the day-of-month to represent, from 1 to 31.
164
+ * @private
165
+ */
166
+ constructor(dayOfMonth) {
167
+ super();
168
+ this._day = MathUtil.safeToInt(dayOfMonth);
169
+ }
170
+
171
+ //-----------------------------------------------------------------------
172
+ /**
173
+ * Gets the day-of-month value.
174
+ *
175
+ * @return the day-of-month, from 1 to 31
176
+ */
177
+ value() {
178
+ return this._day;
179
+ }
180
+
181
+ //-----------------------------------------------------------------------
182
+ /**
183
+ * Checks if the specified field is supported.
184
+ *
185
+ * This checks if this day-of-month can be queried for the specified field.
186
+ * If false, then calling the {@link DayOfMonth.range} range,
187
+ * {@link DayOfMonth.get} get and {@link DayOfMonth.getLong} getLong
188
+ * methods will throw an exception.
189
+ *
190
+ * If the field is a {@link ChronoField} then the query is implemented here.
191
+ * The supported fields are:
192
+ * <ul>
193
+ * <li>{@link ChronoField.DAY_OF_MONTH}
194
+ * </ul>
195
+ * All other {@link ChronoField} instances will return false.
196
+ *
197
+ * If the field is not a {@link ChronoField}, then the result of this method
198
+ * is obtained by invoking {@link TemporalField.isSupportedBy}
199
+ * passing this as the argument.
200
+ * Whether the field is supported is determined by the field.
201
+ *
202
+ * @param field the field to check, null returns false
203
+ * @return true if the field is supported on this day-of-month, false if not
204
+ */
205
+ isSupported(field) {
206
+ if (field instanceof ChronoField) {
207
+ return field === ChronoField.DAY_OF_MONTH;
208
+ }
209
+ return field != null && field.isSupportedBy(this);
210
+ }
211
+
212
+ //-----------------------------------------------------------------------
213
+ /**
214
+ * Gets the range of valid values for the specified field.
215
+ *
216
+ * The range object expresses the minimum and maximum valid values for a field.
217
+ * This day-of-month is used to enhance the accuracy of the returned range.
218
+ * If it is not possible to return the range, because the field is not supported
219
+ * or for some other reason, an exception is thrown.
220
+ *
221
+ * If the field is a {@link ChronoField} then the query is implemented here.
222
+ * The {@link DayOfMonth.isSupported} supported fields will return
223
+ * appropriate range instances.
224
+ * All other {@link ChronoField} instances will throw an {@link UnsupportedTemporalTypeException}.
225
+ *
226
+ * If the field is not a {@link ChronoField}, then the result of this method
227
+ * is obtained by invoking {@link TemporalField.rangeRefinedBy}
228
+ * passing this as the argument.
229
+ * Whether the range can be obtained is determined by the field.
230
+ *
231
+ * @param field the field to query the range for, not null
232
+ * @return the range of valid values for the field, not null
233
+ * @throws {DateTimeException} if the range for the field cannot be obtained
234
+ * @throws {UnsupportedTemporalTypeException} if the field is not supported
235
+ */
236
+ range(field) {
237
+ requireNonNull(field, 'field');
238
+ if (field === ChronoField.DAY_OF_MONTH) {
239
+ return field.range();
240
+ }
241
+ return super.range(field);
242
+ }
243
+
244
+ /**
245
+ * Gets the value of the specified field from this day-of-month as an `int`.
246
+ *
247
+ * This queries this day-of-month for the value for the specified field.
248
+ * The returned value will always be within the valid range of values for the field.
249
+ * If it is not possible to return the value, because the field is not supported
250
+ * or for some other reason, an exception is thrown.
251
+ *
252
+ * If the field is a {@link ChronoField} then the query is implemented here.
253
+ * The {@link DayOfMonth.isSupported} supported fields will return valid
254
+ * values based on this day-of-month.
255
+ * All other {@link ChronoField} instances will throw an {@link UnsupportedTemporalTypeException}.
256
+ *
257
+ * If the field is not a {@link ChronoField}, then the result of this method
258
+ * is obtained by invoking {@link TemporalField.getFrom}
259
+ * passing this as the argument. Whether the value can be obtained,
260
+ * and what the value represents, is determined by the field.
261
+ *
262
+ * @param field the field to get, not null
263
+ * @return the value for the field
264
+ * @throws {DateTimeException} if a value for the field cannot be obtained or
265
+ * the value is outside the range of valid values for the field
266
+ * @throws {UnsupportedTemporalTypeException} if the field is not supported or
267
+ * the range of values exceeds an `int`
268
+ * @throws {ArithmeticException} if numeric overflow occurs
269
+ */
270
+ get(field) {
271
+ return this.range(field).checkValidIntValue(this.getLong(field), field);
272
+ }
273
+
274
+ /**
275
+ * Gets the value of the specified field from this day-of-month as a {@link long}.
276
+ *
277
+ * This queries this day-of-month for the value for the specified field.
278
+ * If it is not possible to return the value, because the field is not supported
279
+ * or for some other reason, an exception is thrown.
280
+ *
281
+ * If the field is a {@link ChronoField} then the query is implemented here.
282
+ * The {@link DayOfMonth.isSupported(TemporalField) supported fields} will return valid
283
+ * values based on this day-of-month.
284
+ * All other {@link ChronoField} instances will throw an {@link UnsupportedTemporalTypeException}.
285
+ *
286
+ * If the field is not a {@link ChronoField}, then the result of this method
287
+ * is obtained by invoking {@link TemporalField.getFrom}
288
+ * passing this as the argument. Whether the value can be obtained,
289
+ * and what the value represents, is determined by the field.
290
+ *
291
+ * @param field the field to get, not null
292
+ * @return the value for the field
293
+ * @throws DateTimeException if a value for the field cannot be obtained
294
+ * @throws UnsupportedTemporalTypeException if the field is not supported
295
+ * @throws ArithmeticException if numeric overflow occurs
296
+ */
297
+ getLong(field) {
298
+ requireNonNull(field, 'field');
299
+ if (field === ChronoField.DAY_OF_MONTH) {
300
+ return this._day;
301
+ } else if (field instanceof ChronoField) {
302
+ throw new UnsupportedTemporalTypeException(`Unsupported field: ${field}`);
303
+ }
304
+ return field.getFrom(this);
305
+ }
306
+
307
+ //-----------------------------------------------------------------------
308
+ /**
309
+ * Checks if the year-month is valid for this year.
310
+ *
311
+ * This method checks whether this day and the input year and month form
312
+ * a valid date.
313
+ *
314
+ * @param yearMonth the year month to validate, null returns false
315
+ * @return true if the year and month are valid for this day
316
+ */
317
+ isValidYearMonth(yearMonth) {
318
+ return yearMonth != null && yearMonth.isValidDay(this._day);
319
+ }
320
+
321
+ //-----------------------------------------------------------------------
322
+ /**
323
+ * Queries this day-of-month using the specified query.
324
+ *
325
+ * This queries this day-of-month using the specified query strategy object.
326
+ * The {@link TemporalQuery} object defines the logic to be used to
327
+ * obtain the result. Read the documentation of the query to understand
328
+ * what the result of this method will be.
329
+ *
330
+ * The result of this method is obtained by invoking the
331
+ * {@link TemporalQuery.queryFrom} method on the
332
+ * specified query passing this as the argument.
333
+ *
334
+ * @param query the query to invoke, not null
335
+ * @return the query result, null may be returned (defined by the query)
336
+ * @throws {DateTimeException} if unable to query (defined by the query)
337
+ * @throws {ArithmeticException} if numeric overflow occurs (defined by the query)
338
+ */
339
+ query(query) {
340
+ requireNonNull(query, 'query');
341
+ requireInstance(query, TemporalQuery, 'query');
342
+ if (query === TemporalQueries.chronology()) {
343
+ return IsoChronology.INSTANCE;
344
+ }
345
+ return super.query(query);
346
+ }
347
+
348
+ /**
349
+ * Adjusts the specified temporal object to have this day-of-month.
350
+ *
351
+ * This returns a temporal object of the same observable type as the input
352
+ * with the day-of-month changed to be the same as this.
353
+ *
354
+ * The adjustment is equivalent to using {@link Temporal.with}
355
+ * passing {@link ChronoField.DAY_OF_MONTH} as the field.
356
+ * If the specified temporal object does not use the ISO calendar system then
357
+ * a {@link DateTimeException} is thrown.
358
+ *
359
+ * In most cases, it is clearer to reverse the calling pattern by using
360
+ * {@link Temporal.with}:
361
+ * ```
362
+ * // these two lines are equivalent, but the second approach is recommended
363
+ * temporal = thisDay.adjustInto(temporal);
364
+ * temporal = temporal.with(thisDay);
365
+ * ```
366
+ *
367
+ * This instance is immutable and unaffected by this method call.
368
+ *
369
+ * @param temporal the target object to be adjusted, not null
370
+ * @return the adjusted object, not null
371
+ * @throws {DateTimeException} if unable to make the adjustment
372
+ * @throws {ArithmeticException} if numeric overflow occurs
373
+ */
374
+ adjustInto(temporal) {
375
+ requireNonNull(temporal, 'temporal');
376
+ /* TODO: only IsoChronology for now
377
+ if (Chronology.from(temporal).equals(IsoChronology.INSTANCE) === false) {
378
+ throw new DateTimeException('Adjustment only supported on ISO date-time');
379
+ }*/
380
+ return temporal.with(ChronoField.DAY_OF_MONTH, this._day);
381
+ }
382
+
383
+ //-----------------------------------------------------------------------
384
+ /**
385
+ * Combines this day-of-month with a month to create a {@link MonthDay}.
386
+ *
387
+ * This returns a {@link MonthDay} formed from this day and the specified month.
388
+ *
389
+ * This method can be used as part of a chain to produce a date:
390
+ * ```
391
+ * LocalDate date = day.atMonth(month).atYear(year);
392
+ * ```
393
+ *
394
+ * If this day-of-month is invalid for the month then it will be changed
395
+ * to the last valid date for the month.
396
+ *
397
+ * @param month the month-of-year to use, from 1 (January) to 12 (December), not null
398
+ * @return the year-month formed from this year and the specified month, not null
399
+ */
400
+ atMonth(month) {
401
+ requireNonNull(month, 'month');
402
+ if (month instanceof Month) {
403
+ return MonthDay.of(month, Math.min(this._day, month.maxLength()));
404
+ } else {
405
+ return MonthDay.of(month, Math.min(this._day, Month.of(month).maxLength()));
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Combines this day-of-month with a year-month to create a {@link LocalDate}.
411
+ *
412
+ * This returns a {@link LocalDate} formed from this year and the specified year-month.
413
+ *
414
+ * If this day-of-month is invalid for the year-month then it will be changed
415
+ * to the last valid date for the month.
416
+ *
417
+ * @param yearMonth the year-month to use, not null
418
+ * @return the local date formed from this year and the specified year-month, not null
419
+ */
420
+ atYearMonth(yearMonth) {
421
+ requireNonNull(yearMonth, 'yearMonth');
422
+ return yearMonth.atDay(Math.min(this._day, yearMonth.lengthOfMonth()));
423
+ }
424
+
425
+ //-----------------------------------------------------------------------
426
+ /**
427
+ * Compares this day-of-month to another.
428
+ *
429
+ * The comparison is based on the value of the day.
430
+ * It is 'consistent with equals', as defined by {@link Comparable}.
431
+ *
432
+ * @param other the other day-of-month instance, not null
433
+ * @return the comparator value, negative if less, positive if greater
434
+ */
435
+ compareTo(other) {
436
+ requireNonNull(other, 'other');
437
+ requireInstance(other, DayOfMonth, 'other');
438
+ return this._day - other._day;
439
+ }
440
+
441
+ //-----------------------------------------------------------------------
442
+ /**
443
+ * Checks if this day-of-month is equal to another day-of-month.
444
+ *
445
+ * @param obj the other day-of-month instance, null returns false
446
+ * @return true if the day-of-month is the same
447
+ */
448
+ equals(obj) {
449
+ if (this === obj) {
450
+ return true;
451
+ }
452
+ if (obj instanceof DayOfMonth) {
453
+ return this._day === obj._day;
454
+ }
455
+ return false;
456
+ }
457
+
458
+ /**
459
+ * A hash code for this day-of-month.
460
+ *
461
+ * @return a suitable hash code
462
+ */
463
+ hashCode() {
464
+ return this._day;
465
+ }
466
+
467
+ //-----------------------------------------------------------------------
468
+ /**
469
+ * Outputs this day-of-month as a {@link String}.
470
+ *
471
+ * @return a string representation of this day-of-month, not null
472
+ */
473
+ toString() {
474
+ return `DayOfMonth:${this._day}`;
475
+ }
476
+
477
+ }
478
+
479
+ export function _init() {
480
+ /**
481
+ * Cache of singleton instances.
482
+ */
483
+ DayOfMonth.VALUES = new Array(31);
484
+ for (let i = 0; i < 31; i++) {
485
+ DayOfMonth.VALUES[i] = new DayOfMonth(i + 1);
486
+ }
487
+ }