@naturalcycles/js-lib 14.243.0 → 14.244.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.
- package/dist/datetime/dateInterval.js +1 -1
- package/dist/datetime/localDate.d.ts +31 -26
- package/dist/datetime/localDate.js +180 -154
- package/dist/datetime/localTime.d.ts +43 -41
- package/dist/datetime/localTime.js +178 -118
- package/dist/datetime/timeInterval.js +2 -2
- package/dist/env/buildInfo.js +2 -2
- package/dist-esm/datetime/dateInterval.js +1 -1
- package/dist-esm/datetime/localDate.js +180 -154
- package/dist-esm/datetime/localTime.js +178 -118
- package/dist-esm/datetime/timeInterval.js +2 -2
- package/dist-esm/env/buildInfo.js +2 -2
- package/package.json +2 -1
- package/src/datetime/dateInterval.ts +1 -1
- package/src/datetime/localDate.ts +188 -168
- package/src/datetime/localTime.ts +191 -149
- package/src/datetime/timeInterval.ts +2 -5
- package/src/env/buildInfo.ts +2 -2
|
@@ -16,7 +16,8 @@ export type LocalDateUnit = LocalDateUnitStrict | 'week'
|
|
|
16
16
|
export type LocalDateUnitStrict = 'year' | 'month' | 'day'
|
|
17
17
|
|
|
18
18
|
const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
19
|
-
|
|
19
|
+
// Regex is open-ended (no $ at the end) to support e.g Date+Time string to be parsed (time part will be dropped)
|
|
20
|
+
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)/
|
|
20
21
|
|
|
21
22
|
export type LocalDateInput = LocalDate | Date | IsoDateString
|
|
22
23
|
export type LocalDateInputNullable = LocalDateInput | null | undefined
|
|
@@ -28,77 +29,71 @@ export type LocalDateFormatter = (ld: LocalDate) => string
|
|
|
28
29
|
*/
|
|
29
30
|
export class LocalDate {
|
|
30
31
|
constructor(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
public year: number,
|
|
33
|
+
public month: number,
|
|
34
|
+
public day: number,
|
|
34
35
|
) {}
|
|
35
36
|
|
|
36
37
|
get(unit: LocalDateUnitStrict): number {
|
|
37
|
-
return unit === 'year' ? this
|
|
38
|
+
return unit === 'year' ? this.year : unit === 'month' ? this.month : this.day
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
set(unit: LocalDateUnitStrict, v: number, mutate = false): LocalDate {
|
|
41
42
|
const t = mutate ? this : this.clone()
|
|
42
43
|
|
|
43
44
|
if (unit === 'year') {
|
|
44
|
-
t
|
|
45
|
+
t.year = v
|
|
45
46
|
} else if (unit === 'month') {
|
|
46
|
-
t
|
|
47
|
+
t.month = v
|
|
47
48
|
} else {
|
|
48
|
-
t
|
|
49
|
+
t.day = v
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
return t
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
year(v?: number): number | LocalDate {
|
|
57
|
-
return v === undefined ? this.$year : this.set('year', v)
|
|
55
|
+
setYear(v: number): LocalDate {
|
|
56
|
+
return this.set('year', v)
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
month(v?: number): number | LocalDate {
|
|
62
|
-
return v === undefined ? this.$month : this.set('month', v)
|
|
58
|
+
setMonth(v: number): LocalDate {
|
|
59
|
+
return this.set('month', v)
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
day(v?: number): number | LocalDate {
|
|
67
|
-
return v === undefined ? this.$day : this.set('day', v)
|
|
61
|
+
setDay(v: number): LocalDate {
|
|
62
|
+
return this.set('day', v)
|
|
68
63
|
}
|
|
69
64
|
|
|
70
|
-
dayOfWeek(): ISODayOfWeek {
|
|
65
|
+
get dayOfWeek(): ISODayOfWeek {
|
|
71
66
|
return (this.toDate().getDay() || 7) as ISODayOfWeek
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
isSame(d: LocalDateInput): boolean {
|
|
75
|
-
d = localDate.
|
|
76
|
-
return this
|
|
70
|
+
d = localDate.from(d)
|
|
71
|
+
return this.day === d.day && this.month === d.month && this.year === d.year
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
isBefore(d: LocalDateInput, inclusive = false): boolean {
|
|
80
|
-
const r = this.
|
|
75
|
+
const r = this.compare(d)
|
|
81
76
|
return r === -1 || (r === 0 && inclusive)
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
isSameOrBefore(d: LocalDateInput): boolean {
|
|
85
|
-
return this.
|
|
80
|
+
return this.compare(d) <= 0
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
isAfter(d: LocalDateInput, inclusive = false): boolean {
|
|
89
|
-
const r = this.
|
|
84
|
+
const r = this.compare(d)
|
|
90
85
|
return r === 1 || (r === 0 && inclusive)
|
|
91
86
|
}
|
|
92
87
|
|
|
93
88
|
isSameOrAfter(d: LocalDateInput): boolean {
|
|
94
|
-
return this.
|
|
89
|
+
return this.compare(d) >= 0
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
isBetween(min: LocalDateInput, max: LocalDateInput, incl: Inclusiveness = '[)'): boolean {
|
|
98
|
-
let r = this.
|
|
93
|
+
let r = this.compare(min)
|
|
99
94
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
100
95
|
if (r < 0 || (r === 0 && incl[0] === '(')) return false
|
|
101
|
-
r = this.
|
|
96
|
+
r = this.compare(max)
|
|
102
97
|
if (r > 0 || (r === 0 && incl[1] === ')')) return false
|
|
103
98
|
return true
|
|
104
99
|
}
|
|
@@ -113,14 +108,14 @@ export class LocalDate {
|
|
|
113
108
|
* Third argument allows to override "today".
|
|
114
109
|
*/
|
|
115
110
|
isOlderThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
|
|
116
|
-
return this.isBefore(localDate.
|
|
111
|
+
return this.isBefore(localDate.from(today || new Date()).plus(-n, unit))
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
/**
|
|
120
115
|
* Checks if this localDate is same or older (<=) than "today" by X units.
|
|
121
116
|
*/
|
|
122
117
|
isSameOrOlderThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
|
|
123
|
-
return this.isSameOrBefore(localDate.
|
|
118
|
+
return this.isSameOrBefore(localDate.from(today || new Date()).plus(-n, unit))
|
|
124
119
|
}
|
|
125
120
|
|
|
126
121
|
/**
|
|
@@ -133,14 +128,14 @@ export class LocalDate {
|
|
|
133
128
|
* Third argument allows to override "today".
|
|
134
129
|
*/
|
|
135
130
|
isYoungerThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
|
|
136
|
-
return this.isAfter(localDate.
|
|
131
|
+
return this.isAfter(localDate.from(today || new Date()).plus(-n, unit))
|
|
137
132
|
}
|
|
138
133
|
|
|
139
134
|
/**
|
|
140
135
|
* Checks if this localDate is same or younger (>=) than "today" by X units.
|
|
141
136
|
*/
|
|
142
137
|
isSameOrYoungerThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
|
|
143
|
-
return this.isSameOrAfter(localDate.
|
|
138
|
+
return this.isSameOrAfter(localDate.from(today || new Date()).plus(-n, unit))
|
|
144
139
|
}
|
|
145
140
|
|
|
146
141
|
getAgeInYears(today?: LocalDateInput): number {
|
|
@@ -153,7 +148,7 @@ export class LocalDate {
|
|
|
153
148
|
return this.getAgeIn('day', today)
|
|
154
149
|
}
|
|
155
150
|
getAgeIn(unit: LocalDateUnit, today?: LocalDateInput): number {
|
|
156
|
-
return localDate.
|
|
151
|
+
return localDate.from(today || new Date()).diff(this, unit)
|
|
157
152
|
}
|
|
158
153
|
|
|
159
154
|
/**
|
|
@@ -161,14 +156,14 @@ export class LocalDate {
|
|
|
161
156
|
* returns 0 if they are equal
|
|
162
157
|
* returns -1 if this < d
|
|
163
158
|
*/
|
|
164
|
-
|
|
165
|
-
d = localDate.
|
|
166
|
-
if (this
|
|
167
|
-
if (this
|
|
168
|
-
if (this
|
|
169
|
-
if (this
|
|
170
|
-
if (this
|
|
171
|
-
if (this
|
|
159
|
+
compare(d: LocalDateInput): -1 | 0 | 1 {
|
|
160
|
+
d = localDate.from(d)
|
|
161
|
+
if (this.year < d.year) return -1
|
|
162
|
+
if (this.year > d.year) return 1
|
|
163
|
+
if (this.month < d.month) return -1
|
|
164
|
+
if (this.month > d.month) return 1
|
|
165
|
+
if (this.day < d.day) return -1
|
|
166
|
+
if (this.day > d.day) return 1
|
|
172
167
|
return 0
|
|
173
168
|
}
|
|
174
169
|
|
|
@@ -185,24 +180,24 @@ export class LocalDate {
|
|
|
185
180
|
* a.diff(b) means "a minus b"
|
|
186
181
|
*/
|
|
187
182
|
diff(d: LocalDateInput, unit: LocalDateUnit): number {
|
|
188
|
-
d = localDate.
|
|
183
|
+
d = localDate.from(d)
|
|
189
184
|
|
|
190
|
-
const sign = this.
|
|
185
|
+
const sign = this.compare(d)
|
|
191
186
|
if (!sign) return 0
|
|
192
187
|
|
|
193
188
|
// Put items in descending order: "big minus small"
|
|
194
189
|
const [big, small] = sign === 1 ? [this, d] : [d, this]
|
|
195
190
|
|
|
196
191
|
if (unit === 'year') {
|
|
197
|
-
let years = big
|
|
192
|
+
let years = big.year - small.year
|
|
198
193
|
|
|
199
194
|
if (
|
|
200
|
-
big
|
|
201
|
-
(big
|
|
202
|
-
big
|
|
195
|
+
big.month < small.month ||
|
|
196
|
+
(big.month === small.month &&
|
|
197
|
+
big.day < small.day &&
|
|
203
198
|
!(
|
|
204
|
-
big
|
|
205
|
-
small
|
|
199
|
+
big.day === localDate.getMonthLength(big.year, big.month) &&
|
|
200
|
+
small.day === localDate.getMonthLength(small.year, small.month)
|
|
206
201
|
))
|
|
207
202
|
) {
|
|
208
203
|
years--
|
|
@@ -212,10 +207,10 @@ export class LocalDate {
|
|
|
212
207
|
}
|
|
213
208
|
|
|
214
209
|
if (unit === 'month') {
|
|
215
|
-
let months = (big
|
|
216
|
-
if (big
|
|
217
|
-
const bigMonthLen = localDate.getMonthLength(big
|
|
218
|
-
if (big
|
|
210
|
+
let months = (big.year - small.year) * 12 + (big.month - small.month)
|
|
211
|
+
if (big.day < small.day) {
|
|
212
|
+
const bigMonthLen = localDate.getMonthLength(big.year, big.month)
|
|
213
|
+
if (big.day !== bigMonthLen || small.day < bigMonthLen) {
|
|
219
214
|
months--
|
|
220
215
|
}
|
|
221
216
|
}
|
|
@@ -223,21 +218,21 @@ export class LocalDate {
|
|
|
223
218
|
}
|
|
224
219
|
|
|
225
220
|
// unit is 'day' or 'week'
|
|
226
|
-
let days = big
|
|
221
|
+
let days = big.day - small.day
|
|
227
222
|
|
|
228
223
|
// If small date is after 1st of March - next year's "leapness" should be used
|
|
229
|
-
const offsetYear = small
|
|
230
|
-
for (let year = small
|
|
224
|
+
const offsetYear = small.month >= 3 ? 1 : 0
|
|
225
|
+
for (let year = small.year; year < big.year; year++) {
|
|
231
226
|
days += localDate.getYearLength(year + offsetYear)
|
|
232
227
|
}
|
|
233
228
|
|
|
234
|
-
if (small
|
|
235
|
-
for (let month = small
|
|
236
|
-
days += localDate.getMonthLength(big
|
|
229
|
+
if (small.month < big.month) {
|
|
230
|
+
for (let month = small.month; month < big.month; month++) {
|
|
231
|
+
days += localDate.getMonthLength(big.year, month)
|
|
237
232
|
}
|
|
238
|
-
} else if (big
|
|
239
|
-
for (let month = big
|
|
240
|
-
days -= localDate.getMonthLength(big
|
|
233
|
+
} else if (big.month < small.month) {
|
|
234
|
+
for (let month = big.month; month < small.month; month++) {
|
|
235
|
+
days -= localDate.getMonthLength(big.year, month)
|
|
241
236
|
}
|
|
242
237
|
}
|
|
243
238
|
|
|
@@ -274,7 +269,7 @@ export class LocalDate {
|
|
|
274
269
|
}
|
|
275
270
|
|
|
276
271
|
plus(num: number, unit: LocalDateUnit, mutate = false): LocalDate {
|
|
277
|
-
let {
|
|
272
|
+
let { day, month, year } = this
|
|
278
273
|
|
|
279
274
|
if (unit === 'week') {
|
|
280
275
|
num *= 7
|
|
@@ -282,65 +277,65 @@ export class LocalDate {
|
|
|
282
277
|
}
|
|
283
278
|
|
|
284
279
|
if (unit === 'day') {
|
|
285
|
-
|
|
280
|
+
day += num
|
|
286
281
|
} else if (unit === 'month') {
|
|
287
|
-
|
|
282
|
+
month += num
|
|
288
283
|
} else if (unit === 'year') {
|
|
289
|
-
|
|
284
|
+
year += num
|
|
290
285
|
}
|
|
291
286
|
|
|
292
287
|
// check month overflow
|
|
293
|
-
while (
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
while (month > 12) {
|
|
289
|
+
year += 1
|
|
290
|
+
month -= 12
|
|
296
291
|
}
|
|
297
|
-
while (
|
|
298
|
-
|
|
299
|
-
|
|
292
|
+
while (month < 1) {
|
|
293
|
+
year -= 1
|
|
294
|
+
month += 12
|
|
300
295
|
}
|
|
301
296
|
|
|
302
297
|
// check day overflow
|
|
303
298
|
// Applies not only for 'day' unit, but also e.g 2022-05-31 plus 1 month should be 2022-06-30 (not 31!)
|
|
304
|
-
if (
|
|
305
|
-
while (
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
299
|
+
if (day < 1) {
|
|
300
|
+
while (day < 1) {
|
|
301
|
+
month -= 1
|
|
302
|
+
if (month < 1) {
|
|
303
|
+
year -= 1
|
|
304
|
+
month += 12
|
|
310
305
|
}
|
|
311
306
|
|
|
312
|
-
|
|
307
|
+
day += localDate.getMonthLength(year, month)
|
|
313
308
|
}
|
|
314
309
|
} else {
|
|
315
|
-
let monLen = localDate.getMonthLength(
|
|
310
|
+
let monLen = localDate.getMonthLength(year, month)
|
|
316
311
|
|
|
317
312
|
if (unit !== 'day') {
|
|
318
|
-
if (
|
|
313
|
+
if (day > monLen) {
|
|
319
314
|
// Case of 2022-05-31 plus 1 month should be 2022-06-30, not 31
|
|
320
|
-
|
|
315
|
+
day = monLen
|
|
321
316
|
}
|
|
322
317
|
} else {
|
|
323
|
-
while (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
318
|
+
while (day > monLen) {
|
|
319
|
+
day -= monLen
|
|
320
|
+
month += 1
|
|
321
|
+
if (month > 12) {
|
|
322
|
+
year += 1
|
|
323
|
+
month -= 12
|
|
329
324
|
}
|
|
330
325
|
|
|
331
|
-
monLen = localDate.getMonthLength(
|
|
326
|
+
monLen = localDate.getMonthLength(year, month)
|
|
332
327
|
}
|
|
333
328
|
}
|
|
334
329
|
}
|
|
335
330
|
|
|
336
331
|
if (mutate) {
|
|
337
|
-
this
|
|
338
|
-
this
|
|
339
|
-
this
|
|
332
|
+
this.year = year
|
|
333
|
+
this.month = month
|
|
334
|
+
this.day = day
|
|
340
335
|
return this
|
|
341
336
|
}
|
|
342
337
|
|
|
343
|
-
return new LocalDate(
|
|
338
|
+
return new LocalDate(year, month, day)
|
|
344
339
|
}
|
|
345
340
|
|
|
346
341
|
minus(num: number, unit: LocalDateUnit, mutate = false): LocalDate {
|
|
@@ -349,33 +344,29 @@ export class LocalDate {
|
|
|
349
344
|
|
|
350
345
|
startOf(unit: LocalDateUnitStrict): LocalDate {
|
|
351
346
|
if (unit === 'day') return this
|
|
352
|
-
if (unit === 'month') return new LocalDate(this
|
|
347
|
+
if (unit === 'month') return new LocalDate(this.year, this.month, 1)
|
|
353
348
|
// year
|
|
354
|
-
return new LocalDate(this
|
|
349
|
+
return new LocalDate(this.year, 1, 1)
|
|
355
350
|
}
|
|
356
351
|
|
|
357
352
|
endOf(unit: LocalDateUnitStrict): LocalDate {
|
|
358
353
|
if (unit === 'day') return this
|
|
359
354
|
if (unit === 'month')
|
|
360
|
-
return new LocalDate(
|
|
361
|
-
this.$year,
|
|
362
|
-
this.$month,
|
|
363
|
-
localDate.getMonthLength(this.$year, this.$month),
|
|
364
|
-
)
|
|
355
|
+
return new LocalDate(this.year, this.month, localDate.getMonthLength(this.year, this.month))
|
|
365
356
|
// year
|
|
366
|
-
return new LocalDate(this
|
|
357
|
+
return new LocalDate(this.year, 12, 31)
|
|
367
358
|
}
|
|
368
359
|
|
|
369
360
|
/**
|
|
370
361
|
* Returns how many days are in the current month.
|
|
371
362
|
* E.g 31 for January.
|
|
372
363
|
*/
|
|
373
|
-
daysInMonth(): number {
|
|
374
|
-
return localDate.getMonthLength(this
|
|
364
|
+
get daysInMonth(): number {
|
|
365
|
+
return localDate.getMonthLength(this.year, this.month)
|
|
375
366
|
}
|
|
376
367
|
|
|
377
368
|
clone(): LocalDate {
|
|
378
|
-
return new LocalDate(this
|
|
369
|
+
return new LocalDate(this.year, this.month, this.day)
|
|
379
370
|
}
|
|
380
371
|
|
|
381
372
|
/**
|
|
@@ -385,7 +376,7 @@ export class LocalDate {
|
|
|
385
376
|
* Timezone will match local timezone.
|
|
386
377
|
*/
|
|
387
378
|
toDate(): Date {
|
|
388
|
-
return new Date(this
|
|
379
|
+
return new Date(this.year, this.month - 1, this.day)
|
|
389
380
|
}
|
|
390
381
|
|
|
391
382
|
/**
|
|
@@ -398,9 +389,9 @@ export class LocalDate {
|
|
|
398
389
|
|
|
399
390
|
toDateObject(): DateObject {
|
|
400
391
|
return {
|
|
401
|
-
year: this
|
|
402
|
-
month: this
|
|
403
|
-
day: this
|
|
392
|
+
year: this.year,
|
|
393
|
+
month: this.month,
|
|
394
|
+
day: this.day,
|
|
404
395
|
}
|
|
405
396
|
}
|
|
406
397
|
|
|
@@ -409,7 +400,7 @@ export class LocalDate {
|
|
|
409
400
|
* LocalTime's Date will be in local timezone.
|
|
410
401
|
*/
|
|
411
402
|
toLocalTime(): LocalTime {
|
|
412
|
-
return localTime.
|
|
403
|
+
return localTime.fromDate(this.toDate())
|
|
413
404
|
}
|
|
414
405
|
|
|
415
406
|
/**
|
|
@@ -417,9 +408,9 @@ export class LocalDate {
|
|
|
417
408
|
*/
|
|
418
409
|
toISODate(): IsoDateString {
|
|
419
410
|
return [
|
|
420
|
-
String(this
|
|
421
|
-
String(this
|
|
422
|
-
String(this
|
|
411
|
+
String(this.year).padStart(4, '0'),
|
|
412
|
+
String(this.month).padStart(2, '0'),
|
|
413
|
+
String(this.day).padStart(2, '0'),
|
|
423
414
|
].join('-')
|
|
424
415
|
}
|
|
425
416
|
|
|
@@ -448,9 +439,9 @@ export class LocalDate {
|
|
|
448
439
|
*/
|
|
449
440
|
toStringCompact(): string {
|
|
450
441
|
return [
|
|
451
|
-
String(this
|
|
452
|
-
String(this
|
|
453
|
-
String(this
|
|
442
|
+
String(this.year).padStart(4, '0'),
|
|
443
|
+
String(this.month).padStart(2, '0'),
|
|
444
|
+
String(this.day).padStart(2, '0'),
|
|
454
445
|
].join('')
|
|
455
446
|
}
|
|
456
447
|
|
|
@@ -464,14 +455,14 @@ export class LocalDate {
|
|
|
464
455
|
/**
|
|
465
456
|
* Returns unix timestamp of 00:00:00 of that date (in UTC, because unix timestamp always reflects UTC).
|
|
466
457
|
*/
|
|
467
|
-
unix(): UnixTimestampNumber {
|
|
458
|
+
get unix(): UnixTimestampNumber {
|
|
468
459
|
return Math.floor(this.toDate().valueOf() / 1000)
|
|
469
460
|
}
|
|
470
461
|
|
|
471
462
|
/**
|
|
472
463
|
* Same as .unix(), but in milliseconds.
|
|
473
464
|
*/
|
|
474
|
-
unixMillis(): UnixTimestampMillisNumber {
|
|
465
|
+
get unixMillis(): UnixTimestampMillisNumber {
|
|
475
466
|
return this.toDate().valueOf()
|
|
476
467
|
}
|
|
477
468
|
|
|
@@ -489,13 +480,6 @@ export class LocalDate {
|
|
|
489
480
|
}
|
|
490
481
|
|
|
491
482
|
class LocalDateFactory {
|
|
492
|
-
/**
|
|
493
|
-
* Create LocalDate from year, month and day components.
|
|
494
|
-
*/
|
|
495
|
-
create(year: number, month: number, day: number): LocalDate {
|
|
496
|
-
return new LocalDate(year, month, day)
|
|
497
|
-
}
|
|
498
|
-
|
|
499
483
|
/**
|
|
500
484
|
* Create LocalDate from LocalDateInput.
|
|
501
485
|
* Input can already be a LocalDate - it is returned as-is in that case.
|
|
@@ -505,33 +489,43 @@ class LocalDateFactory {
|
|
|
505
489
|
*
|
|
506
490
|
* Will throw if it fails to parse/construct LocalDate.
|
|
507
491
|
*/
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
input: d,
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
return t
|
|
492
|
+
from(input: LocalDateInput): LocalDate {
|
|
493
|
+
const ld = this.fromOrNull(input)
|
|
494
|
+
this.assertNotNull(ld, input)
|
|
495
|
+
return ld
|
|
516
496
|
}
|
|
517
497
|
|
|
518
498
|
/**
|
|
519
499
|
* Tries to construct LocalDate from LocalDateInput, returns null otherwise.
|
|
520
500
|
* Does not throw (returns null instead).
|
|
521
501
|
*/
|
|
522
|
-
|
|
502
|
+
fromOrNull(d: LocalDateInputNullable): LocalDate | null {
|
|
523
503
|
if (!d) return null
|
|
524
504
|
if (d instanceof LocalDate) return d
|
|
525
505
|
if (d instanceof Date) {
|
|
526
506
|
return this.fromDate(d)
|
|
527
507
|
}
|
|
508
|
+
if (typeof (d as any) === 'string') {
|
|
509
|
+
return this.fromStringOrNull(d)
|
|
510
|
+
}
|
|
528
511
|
|
|
529
|
-
|
|
530
|
-
|
|
512
|
+
return null
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
fromString(s: string): LocalDate {
|
|
516
|
+
const ld = this.fromStringOrNull(s)
|
|
517
|
+
this.assertNotNull(ld, s)
|
|
518
|
+
return ld
|
|
519
|
+
}
|
|
531
520
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
521
|
+
fromStringOrNull(s: string | undefined | null): LocalDate | null {
|
|
522
|
+
if (!s) return null
|
|
523
|
+
const m = DATE_REGEX.exec(s)
|
|
524
|
+
if (!m) return null
|
|
525
|
+
|
|
526
|
+
const year = Number(m[1])
|
|
527
|
+
const month = Number(m[2])
|
|
528
|
+
const day = Number(m[3])
|
|
535
529
|
|
|
536
530
|
if (
|
|
537
531
|
!year ||
|
|
@@ -552,14 +546,51 @@ class LocalDateFactory {
|
|
|
552
546
|
* Parses "compact iso8601 format", e.g `19840621` into LocalDate.
|
|
553
547
|
* Throws if it fails to do so.
|
|
554
548
|
*/
|
|
555
|
-
|
|
556
|
-
const [year, month, day] = [
|
|
549
|
+
fromCompactString(s: string): LocalDate {
|
|
550
|
+
const [year, month, day] = [s.slice(0, 4), s.slice(4, 6), s.slice(6, 8)].map(Number)
|
|
551
|
+
|
|
552
|
+
_assert(day && month && year, `Cannot parse compact string "${s}" into LocalDate`)
|
|
553
|
+
|
|
554
|
+
return new LocalDate(year, month, day)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Constructs LocalDate from Date.
|
|
559
|
+
* Takes Date as-is, in its timezone - local or UTC.
|
|
560
|
+
*/
|
|
561
|
+
fromDate(d: Date): LocalDate {
|
|
562
|
+
return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Constructs LocalDate from Date.
|
|
567
|
+
* Takes Date's year/month/day components in UTC, using getUTCFullYear, getUTCMonth, getUTCDate.
|
|
568
|
+
*/
|
|
569
|
+
fromDateInUTC(d: Date): LocalDate {
|
|
570
|
+
return new LocalDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate())
|
|
571
|
+
}
|
|
557
572
|
|
|
558
|
-
|
|
573
|
+
/**
|
|
574
|
+
* Create LocalDate from year, month and day components.
|
|
575
|
+
*/
|
|
576
|
+
fromComponents(year: number, month: number, day: number): LocalDate {
|
|
577
|
+
return new LocalDate(year, month, day)
|
|
578
|
+
}
|
|
559
579
|
|
|
580
|
+
fromDateObject(o: DateObject): LocalDate {
|
|
581
|
+
const { year, month, day } = o
|
|
560
582
|
return new LocalDate(year, month, day)
|
|
561
583
|
}
|
|
562
584
|
|
|
585
|
+
private assertNotNull(
|
|
586
|
+
ld: LocalDate | null,
|
|
587
|
+
input: LocalDateInputNullable,
|
|
588
|
+
): asserts ld is LocalDate {
|
|
589
|
+
_assert(ld !== null, `Cannot parse "${input}" into LocalDate`, {
|
|
590
|
+
input,
|
|
591
|
+
})
|
|
592
|
+
}
|
|
593
|
+
|
|
563
594
|
getYearLength(year: number): number {
|
|
564
595
|
return this.isLeapYear(year) ? 366 : 365
|
|
565
596
|
}
|
|
@@ -574,26 +605,17 @@ class LocalDateFactory {
|
|
|
574
605
|
}
|
|
575
606
|
|
|
576
607
|
/**
|
|
577
|
-
*
|
|
578
|
-
* Takes Date as-is, in its timezone - local or UTC.
|
|
579
|
-
*/
|
|
580
|
-
fromDate(d: Date): LocalDate {
|
|
581
|
-
return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Constructs LocalDate from Date.
|
|
586
|
-
* Takes Date's year/month/day components in UTC, using getUTCFullYear, getUTCMonth, getUTCDate.
|
|
608
|
+
* Returns true if input is valid to create LocalDate.
|
|
587
609
|
*/
|
|
588
|
-
|
|
589
|
-
return
|
|
610
|
+
isValid(input: LocalDateInputNullable): boolean {
|
|
611
|
+
return this.fromOrNull(input) !== null
|
|
590
612
|
}
|
|
591
613
|
|
|
592
614
|
/**
|
|
593
615
|
* Returns true if isoString is a valid iso8601 string like `yyyy-mm-dd`.
|
|
594
616
|
*/
|
|
595
|
-
|
|
596
|
-
return this.
|
|
617
|
+
isValidString(isoString: string | undefined | null): boolean {
|
|
618
|
+
return this.fromStringOrNull(isoString) !== null
|
|
597
619
|
}
|
|
598
620
|
|
|
599
621
|
/**
|
|
@@ -615,7 +637,7 @@ class LocalDateFactory {
|
|
|
615
637
|
*/
|
|
616
638
|
sort(items: LocalDate[], dir: SortDirection = 'asc', mutate = false): LocalDate[] {
|
|
617
639
|
const mod = dir === 'desc' ? -1 : 1
|
|
618
|
-
return (mutate ? items : [...items]).sort((a, b) => a.
|
|
640
|
+
return (mutate ? items : [...items]).sort((a, b) => a.compare(b) * mod)
|
|
619
641
|
}
|
|
620
642
|
|
|
621
643
|
/**
|
|
@@ -634,7 +656,7 @@ class LocalDateFactory {
|
|
|
634
656
|
_assert(items2.length, 'localDate.min called on empty array')
|
|
635
657
|
|
|
636
658
|
return items2
|
|
637
|
-
.map(i => this.
|
|
659
|
+
.map(i => this.from(i))
|
|
638
660
|
.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
|
|
639
661
|
}
|
|
640
662
|
|
|
@@ -653,7 +675,9 @@ class LocalDateFactory {
|
|
|
653
675
|
const items2 = items.filter(_isTruthy)
|
|
654
676
|
_assert(items2.length, 'localDate.max called on empty array')
|
|
655
677
|
|
|
656
|
-
return items2
|
|
678
|
+
return items2
|
|
679
|
+
.map(i => this.from(i))
|
|
680
|
+
.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
|
|
657
681
|
}
|
|
658
682
|
|
|
659
683
|
/**
|
|
@@ -686,8 +710,8 @@ class LocalDateFactory {
|
|
|
686
710
|
stepUnit = 'day'
|
|
687
711
|
}
|
|
688
712
|
|
|
689
|
-
const $min = this.
|
|
690
|
-
const $max = this.
|
|
713
|
+
const $min = this.from(min).startOf(stepUnit)
|
|
714
|
+
const $max = this.from(max).startOf(stepUnit)
|
|
691
715
|
|
|
692
716
|
let value = $min
|
|
693
717
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
@@ -718,14 +742,14 @@ class LocalDateFactory {
|
|
|
718
742
|
* Similar to `localDate.orToday`, but that will instead return Today on falsy input.
|
|
719
743
|
*/
|
|
720
744
|
orUndefined(d: LocalDateInputNullable): LocalDate | undefined {
|
|
721
|
-
return d ? this.
|
|
745
|
+
return d ? this.from(d) : undefined
|
|
722
746
|
}
|
|
723
747
|
|
|
724
748
|
/**
|
|
725
749
|
* Creates a LocalDate from the input, unless it's falsy - then returns localDate.today.
|
|
726
750
|
*/
|
|
727
751
|
orToday(d: LocalDateInputNullable): LocalDate {
|
|
728
|
-
return d ? this.
|
|
752
|
+
return d ? this.from(d) : this.today()
|
|
729
753
|
}
|
|
730
754
|
}
|
|
731
755
|
|
|
@@ -735,11 +759,7 @@ interface LocalDateFn extends LocalDateFactory {
|
|
|
735
759
|
|
|
736
760
|
const localDateFactory = new LocalDateFactory()
|
|
737
761
|
|
|
738
|
-
|
|
739
|
-
// return localDateFactory.of(d)
|
|
740
|
-
// }, localDateFactory) as LocalDateFn
|
|
741
|
-
|
|
742
|
-
export const localDate = localDateFactory.of.bind(localDateFactory) as LocalDateFn
|
|
762
|
+
export const localDate = localDateFactory.from.bind(localDateFactory) as LocalDateFn
|
|
743
763
|
|
|
744
764
|
// The line below is the blackest of black magic I have ever written in 2024.
|
|
745
765
|
// And probably 2023 as well.
|