@naturalcycles/js-lib 14.97.0 → 14.97.1
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.d.ts +8 -7
- package/dist/datetime/dateInterval.js +18 -15
- package/dist/datetime/localDate.d.ts +4 -9
- package/dist/datetime/localDate.js +58 -56
- package/dist/datetime/localTime.d.ts +4 -2
- package/dist/datetime/localTime.js +33 -10
- package/dist/datetime/timeInterval.d.ts +38 -0
- package/dist/datetime/timeInterval.js +91 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist-esm/datetime/dateInterval.js +18 -15
- package/dist-esm/datetime/localDate.js +58 -56
- package/dist-esm/datetime/localTime.js +33 -10
- package/dist-esm/datetime/timeInterval.js +87 -0
- package/dist-esm/index.js +1 -0
- package/package.json +1 -1
- package/src/datetime/dateInterval.ts +22 -18
- package/src/datetime/localDate.ts +66 -85
- package/src/datetime/localTime.ts +37 -11
- package/src/datetime/timeInterval.ts +104 -0
- package/src/index.ts +4 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { _assert } from '../error/assert';
|
|
2
|
-
import { Sequence } from '../seq/seq';
|
|
3
|
-
import { END } from '../types';
|
|
4
2
|
import { LocalTime } from './localTime';
|
|
5
|
-
const
|
|
3
|
+
const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
4
|
+
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
|
|
6
5
|
/* eslint-disable no-dupe-class-members */
|
|
7
6
|
/**
|
|
8
7
|
* @experimental
|
|
@@ -48,8 +47,13 @@ export class LocalDate {
|
|
|
48
47
|
return null;
|
|
49
48
|
if (d instanceof LocalDate)
|
|
50
49
|
return d;
|
|
51
|
-
//
|
|
52
|
-
const
|
|
50
|
+
// const [year, month, day] = d.slice(0, 10).split('-').map(Number)
|
|
51
|
+
const matches = DATE_REGEX.exec(d.slice(0, 10));
|
|
52
|
+
if (!matches)
|
|
53
|
+
return null;
|
|
54
|
+
const year = Number(matches[1]);
|
|
55
|
+
const month = Number(matches[2]);
|
|
56
|
+
const day = Number(matches[3]);
|
|
53
57
|
if (!year ||
|
|
54
58
|
!month ||
|
|
55
59
|
month < 1 ||
|
|
@@ -61,6 +65,10 @@ export class LocalDate {
|
|
|
61
65
|
}
|
|
62
66
|
return new LocalDate(year, month, day);
|
|
63
67
|
}
|
|
68
|
+
// Can use just .toString()
|
|
69
|
+
// static parseToString(d: LocalDateConfig): IsoDateString {
|
|
70
|
+
// return typeof d === 'string' ? d : d.toString()
|
|
71
|
+
// }
|
|
64
72
|
static isValid(iso) {
|
|
65
73
|
return this.parseOrNull(iso) !== null;
|
|
66
74
|
}
|
|
@@ -88,32 +96,23 @@ export class LocalDate {
|
|
|
88
96
|
_assert(items.length, 'LocalDate.latest called on empty array');
|
|
89
97
|
return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
|
|
90
98
|
}
|
|
91
|
-
static range(
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const max = LocalDate.of(
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
static range(min, max, incl = '[)', step = 1, stepUnit = 'day') {
|
|
100
|
+
const dates = [];
|
|
101
|
+
const $min = LocalDate.of(min);
|
|
102
|
+
const $max = LocalDate.of(max).startOf(stepUnit);
|
|
103
|
+
let current = $min.startOf(stepUnit);
|
|
104
|
+
if (current.isAfter($min, incl[0] === '[')) {
|
|
105
|
+
// ok
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
current.add(1, stepUnit, true);
|
|
109
|
+
}
|
|
110
|
+
const incl2 = incl[1] === ']';
|
|
111
|
+
while (current.isBefore($max, incl2)) {
|
|
112
|
+
dates.push(current);
|
|
97
113
|
current = current.add(step, stepUnit);
|
|
98
|
-
}
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
static rangeSeq(minIncl, maxExcl, step = 1, stepUnit = 'day') {
|
|
102
|
-
const min = LocalDate.of(minIncl).startOf(stepUnit);
|
|
103
|
-
const max = LocalDate.of(maxExcl).startOf(stepUnit);
|
|
104
|
-
return Sequence.create(min, d => {
|
|
105
|
-
const next = d.add(step, stepUnit);
|
|
106
|
-
return next.isAfter(max) ? END : next;
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
static rangeString(minIncl, maxExcl, step = 1, stepUnit = 'day') {
|
|
110
|
-
return LocalDate.range(minIncl, maxExcl, step, stepUnit).map(ld => ld.toString());
|
|
111
|
-
}
|
|
112
|
-
static rangeIncl(minIncl, maxIncl, step = 1, stepUnit = 'day') {
|
|
113
|
-
return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit);
|
|
114
|
-
}
|
|
115
|
-
static rangeInclString(minIncl, maxIncl, step = 1, stepUnit = 'day') {
|
|
116
|
-
return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit).map(ld => ld.toString());
|
|
114
|
+
}
|
|
115
|
+
return dates;
|
|
117
116
|
}
|
|
118
117
|
get(unit) {
|
|
119
118
|
return unit === 'year' ? this.$year : unit === 'month' ? this.$month : this.$day;
|
|
@@ -144,14 +143,16 @@ export class LocalDate {
|
|
|
144
143
|
d = LocalDate.of(d);
|
|
145
144
|
return this.$day === d.$day && this.$month === d.$month && this.$year === d.$year;
|
|
146
145
|
}
|
|
147
|
-
isBefore(d) {
|
|
148
|
-
|
|
146
|
+
isBefore(d, inclusive = false) {
|
|
147
|
+
const r = this.cmp(d);
|
|
148
|
+
return r === -1 || (r === 0 && inclusive);
|
|
149
149
|
}
|
|
150
150
|
isSameOrBefore(d) {
|
|
151
151
|
return this.cmp(d) <= 0;
|
|
152
152
|
}
|
|
153
|
-
isAfter(d) {
|
|
154
|
-
|
|
153
|
+
isAfter(d, inclusive = false) {
|
|
154
|
+
const r = this.cmp(d);
|
|
155
|
+
return r === 1 || (r === 0 && inclusive);
|
|
155
156
|
}
|
|
156
157
|
isSameOrAfter(d) {
|
|
157
158
|
return this.cmp(d) >= 0;
|
|
@@ -241,24 +242,29 @@ export class LocalDate {
|
|
|
241
242
|
$year += num;
|
|
242
243
|
}
|
|
243
244
|
// check day overflow
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
if (unit === 'day') {
|
|
246
|
+
if ($day < 1) {
|
|
247
|
+
while ($day < 1) {
|
|
248
|
+
$month -= 1;
|
|
249
|
+
if ($month < 1) {
|
|
250
|
+
$year -= 1;
|
|
251
|
+
$month += 12;
|
|
252
|
+
}
|
|
253
|
+
$day += LocalDate.getMonthLength($year, $month);
|
|
254
|
+
}
|
|
251
255
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
256
|
+
else {
|
|
257
|
+
let monLen = LocalDate.getMonthLength($year, $month);
|
|
258
|
+
while ($day > monLen) {
|
|
259
|
+
$day -= monLen;
|
|
260
|
+
$month += 1;
|
|
261
|
+
if ($month > 12) {
|
|
262
|
+
$year += 1;
|
|
263
|
+
$month -= 12;
|
|
264
|
+
}
|
|
265
|
+
monLen = LocalDate.getMonthLength($year, $month);
|
|
266
|
+
}
|
|
260
267
|
}
|
|
261
|
-
monLen = LocalDate.getMonthLength($year, $month);
|
|
262
268
|
}
|
|
263
269
|
// check month overflow
|
|
264
270
|
while ($month > 12) {
|
|
@@ -302,14 +308,10 @@ export class LocalDate {
|
|
|
302
308
|
static getMonthLength(year, month) {
|
|
303
309
|
if (month === 2)
|
|
304
310
|
return this.isLeapYear(year) ? 29 : 28;
|
|
305
|
-
return
|
|
311
|
+
return MDAYS[month];
|
|
306
312
|
}
|
|
307
313
|
static isLeapYear(year) {
|
|
308
|
-
|
|
309
|
-
return false;
|
|
310
|
-
if (year % 100 !== 0)
|
|
311
|
-
return true;
|
|
312
|
-
return year % 400 === 0;
|
|
314
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
313
315
|
}
|
|
314
316
|
clone() {
|
|
315
317
|
return new LocalDate(this.$year, this.$month, this.$day);
|
|
@@ -45,7 +45,7 @@ export class LocalTime {
|
|
|
45
45
|
date = new Date(d * 1000);
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
48
|
-
date = new Date(d);
|
|
48
|
+
date = new Date(d.slice(0, 19));
|
|
49
49
|
}
|
|
50
50
|
// validation
|
|
51
51
|
if (isNaN(date.getDate())) {
|
|
@@ -57,6 +57,28 @@ export class LocalTime {
|
|
|
57
57
|
// }
|
|
58
58
|
return new LocalTime(date, false);
|
|
59
59
|
}
|
|
60
|
+
static parseToDate(d) {
|
|
61
|
+
if (d instanceof LocalTime)
|
|
62
|
+
return d.$date;
|
|
63
|
+
if (d instanceof Date)
|
|
64
|
+
return d;
|
|
65
|
+
const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d);
|
|
66
|
+
if (isNaN(date.getDate())) {
|
|
67
|
+
throw new TypeError(`Cannot parse "${d}" to Date`);
|
|
68
|
+
}
|
|
69
|
+
return date;
|
|
70
|
+
}
|
|
71
|
+
static parseToUnixTimestamp(d) {
|
|
72
|
+
if (typeof d === 'number')
|
|
73
|
+
return d;
|
|
74
|
+
if (d instanceof LocalTime)
|
|
75
|
+
return d.unix();
|
|
76
|
+
const date = d instanceof Date ? d : new Date(d);
|
|
77
|
+
if (isNaN(date.getDate())) {
|
|
78
|
+
throw new TypeError(`Cannot parse "${d}" to UnixTimestamp`);
|
|
79
|
+
}
|
|
80
|
+
return date.valueOf() / 1000;
|
|
81
|
+
}
|
|
60
82
|
static isValid(d) {
|
|
61
83
|
return this.parseOrNull(d) !== null;
|
|
62
84
|
}
|
|
@@ -161,7 +183,7 @@ export class LocalTime {
|
|
|
161
183
|
return Math.abs(this.diff(other, unit));
|
|
162
184
|
}
|
|
163
185
|
diff(other, unit) {
|
|
164
|
-
const date2 = LocalTime.
|
|
186
|
+
const date2 = LocalTime.parseToDate(other);
|
|
165
187
|
if (unit === 'year') {
|
|
166
188
|
return this.$date.getFullYear() - date2.getFullYear();
|
|
167
189
|
}
|
|
@@ -238,14 +260,16 @@ export class LocalTime {
|
|
|
238
260
|
isSame(d) {
|
|
239
261
|
return this.cmp(d) === 0;
|
|
240
262
|
}
|
|
241
|
-
isBefore(d) {
|
|
242
|
-
|
|
263
|
+
isBefore(d, inclusive = false) {
|
|
264
|
+
const r = this.cmp(d);
|
|
265
|
+
return r === -1 || (r === 0 && inclusive);
|
|
243
266
|
}
|
|
244
267
|
isSameOrBefore(d) {
|
|
245
268
|
return this.cmp(d) <= 0;
|
|
246
269
|
}
|
|
247
|
-
isAfter(d) {
|
|
248
|
-
|
|
270
|
+
isAfter(d, inclusive = false) {
|
|
271
|
+
const r = this.cmp(d);
|
|
272
|
+
return r === 1 || (r === 0 && inclusive);
|
|
249
273
|
}
|
|
250
274
|
isSameOrAfter(d) {
|
|
251
275
|
return this.cmp(d) >= 0;
|
|
@@ -266,7 +290,7 @@ export class LocalTime {
|
|
|
266
290
|
*/
|
|
267
291
|
cmp(d) {
|
|
268
292
|
const t1 = this.$date.valueOf();
|
|
269
|
-
const t2 = LocalTime.
|
|
293
|
+
const t2 = LocalTime.parseToDate(d).valueOf();
|
|
270
294
|
if (t1 === t2)
|
|
271
295
|
return 0;
|
|
272
296
|
return t1 < t2 ? -1 : 1;
|
|
@@ -292,8 +316,8 @@ export class LocalTime {
|
|
|
292
316
|
second: this.$date.getSeconds(),
|
|
293
317
|
};
|
|
294
318
|
}
|
|
295
|
-
fromNow(now =
|
|
296
|
-
const msDiff = LocalTime.
|
|
319
|
+
fromNow(now = new Date()) {
|
|
320
|
+
const msDiff = LocalTime.parseToDate(now).valueOf() - this.$date.valueOf();
|
|
297
321
|
if (msDiff === 0)
|
|
298
322
|
return 'now';
|
|
299
323
|
if (msDiff >= 0) {
|
|
@@ -403,4 +427,3 @@ export class LocalTime {
|
|
|
403
427
|
export function localTime(d) {
|
|
404
428
|
return d ? LocalTime.of(d) : LocalTime.now();
|
|
405
429
|
}
|
|
406
|
-
// todo: range
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { LocalTime } from './localTime';
|
|
2
|
+
/**
|
|
3
|
+
* Class that supports an "interval of time" between 2 timestamps - start and end.
|
|
4
|
+
* Example: `1649267185/1649267187`.
|
|
5
|
+
*
|
|
6
|
+
* @experimental
|
|
7
|
+
*/
|
|
8
|
+
export class TimeInterval {
|
|
9
|
+
constructor($start, $end) {
|
|
10
|
+
this.$start = $start;
|
|
11
|
+
this.$end = $end;
|
|
12
|
+
}
|
|
13
|
+
static of(start, end) {
|
|
14
|
+
return new TimeInterval(LocalTime.parseToUnixTimestamp(start), LocalTime.parseToUnixTimestamp(end));
|
|
15
|
+
}
|
|
16
|
+
get start() {
|
|
17
|
+
return this.$start;
|
|
18
|
+
}
|
|
19
|
+
get end() {
|
|
20
|
+
return this.$end;
|
|
21
|
+
}
|
|
22
|
+
get startTime() {
|
|
23
|
+
return LocalTime.of(this.$start);
|
|
24
|
+
}
|
|
25
|
+
get endTime() {
|
|
26
|
+
return LocalTime.of(this.$end);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parses string like `1649267185/1649267187` into a TimeInterval.
|
|
30
|
+
*/
|
|
31
|
+
static parse(d) {
|
|
32
|
+
if (d instanceof TimeInterval)
|
|
33
|
+
return d;
|
|
34
|
+
const [start, end] = d.split('/').map(Number);
|
|
35
|
+
if (!end || !start) {
|
|
36
|
+
throw new Error(`Cannot parse "${d}" into TimeInterval`);
|
|
37
|
+
}
|
|
38
|
+
return new TimeInterval(start, end);
|
|
39
|
+
}
|
|
40
|
+
isSame(d) {
|
|
41
|
+
return this.cmp(d) === 0;
|
|
42
|
+
}
|
|
43
|
+
isBefore(d, inclusive = false) {
|
|
44
|
+
const r = this.cmp(d);
|
|
45
|
+
return r === -1 || (r === 0 && inclusive);
|
|
46
|
+
}
|
|
47
|
+
isSameOrBefore(d) {
|
|
48
|
+
return this.cmp(d) <= 0;
|
|
49
|
+
}
|
|
50
|
+
isAfter(d, inclusive = false) {
|
|
51
|
+
const r = this.cmp(d);
|
|
52
|
+
return r === 1 || (r === 0 && inclusive);
|
|
53
|
+
}
|
|
54
|
+
isSameOrAfter(d) {
|
|
55
|
+
return this.cmp(d) >= 0;
|
|
56
|
+
}
|
|
57
|
+
includes(d, incl = '[)') {
|
|
58
|
+
d = LocalTime.parseToUnixTimestamp(d);
|
|
59
|
+
if (d < this.$start || (d === this.$start && incl[0] === '('))
|
|
60
|
+
return false;
|
|
61
|
+
if (d > this.$end || (d === this.$end && incl[1] === ')'))
|
|
62
|
+
return false;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* TimeIntervals compare by start date.
|
|
67
|
+
* If it's the same - then by end date.
|
|
68
|
+
*/
|
|
69
|
+
cmp(d) {
|
|
70
|
+
d = TimeInterval.parse(d);
|
|
71
|
+
if (this.$start > d.$start)
|
|
72
|
+
return 1;
|
|
73
|
+
if (this.$start < d.$start)
|
|
74
|
+
return -1;
|
|
75
|
+
if (this.$end > d.$end)
|
|
76
|
+
return 1;
|
|
77
|
+
if (this.$end < d.$end)
|
|
78
|
+
return -1;
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
toString() {
|
|
82
|
+
return [this.$start, this.$end].join('/');
|
|
83
|
+
}
|
|
84
|
+
toJSON() {
|
|
85
|
+
return this.toString();
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist-esm/index.js
CHANGED
|
@@ -59,4 +59,5 @@ export * from './string/leven';
|
|
|
59
59
|
export * from './datetime/localDate';
|
|
60
60
|
export * from './datetime/localTime';
|
|
61
61
|
export * from './datetime/dateInterval';
|
|
62
|
+
export * from './datetime/timeInterval';
|
|
62
63
|
export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pRetryFn, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { LocalDate, LocalDateConfig } from './localDate'
|
|
1
|
+
import { Inclusiveness, LocalDate, LocalDateConfig, LocalDateUnit } from './localDate'
|
|
2
2
|
|
|
3
|
-
export type DateIntervalConfig = DateInterval |
|
|
3
|
+
export type DateIntervalConfig = DateInterval | DateIntervalString
|
|
4
4
|
export type DateIntervalString = string
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -34,16 +34,18 @@ export class DateInterval {
|
|
|
34
34
|
return this.cmp(d) === 0
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
isBefore(d: DateIntervalConfig): boolean {
|
|
38
|
-
|
|
37
|
+
isBefore(d: DateIntervalConfig, inclusive = false): boolean {
|
|
38
|
+
const r = this.cmp(d)
|
|
39
|
+
return r === -1 || (r === 0 && inclusive)
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
isSameOrBefore(d: DateIntervalConfig): boolean {
|
|
42
43
|
return this.cmp(d) <= 0
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
isAfter(d: DateIntervalConfig): boolean {
|
|
46
|
-
|
|
46
|
+
isAfter(d: DateIntervalConfig, inclusive = false): boolean {
|
|
47
|
+
const r = this.cmp(d)
|
|
48
|
+
return r === 1 || (r === 0 && inclusive)
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
isSameOrAfter(d: DateIntervalConfig): boolean {
|
|
@@ -53,9 +55,15 @@ export class DateInterval {
|
|
|
53
55
|
/**
|
|
54
56
|
* Ranges of DateInterval (start, end) are INCLUSIVE.
|
|
55
57
|
*/
|
|
56
|
-
includes(d: LocalDateConfig): boolean {
|
|
58
|
+
includes(d: LocalDateConfig, incl: Inclusiveness = '[]'): boolean {
|
|
57
59
|
d = LocalDate.of(d)
|
|
58
|
-
return d.
|
|
60
|
+
return d.isAfter(this.start, incl[0] === '[') && d.isBefore(this.end, incl[1] === ']')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
intersects(int: DateIntervalConfig, inclusive = true): boolean {
|
|
64
|
+
const $int = DateInterval.parse(int)
|
|
65
|
+
const incl = inclusive ? '[]' : '()'
|
|
66
|
+
return this.includes($int.start, incl) || this.includes($int.end, incl)
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
/**
|
|
@@ -67,19 +75,15 @@ export class DateInterval {
|
|
|
67
75
|
return this.start.cmp(d.start) || this.end.cmp(d.end)
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
getDays(incl: Inclusiveness = '[]'): LocalDate[] {
|
|
79
|
+
return LocalDate.range(this.start, this.end, incl, 1, 'day')
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
/**
|
|
71
83
|
* Returns an array of LocalDates that are included in the interval.
|
|
72
|
-
* Ranges are INCLUSIVE.
|
|
73
84
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
let current = this.start
|
|
77
|
-
do {
|
|
78
|
-
days.push(current)
|
|
79
|
-
current = current.add(1, 'day')
|
|
80
|
-
} while (current.isSameOrBefore(this.end))
|
|
81
|
-
|
|
82
|
-
return days
|
|
85
|
+
range(incl: Inclusiveness = '[]', step = 1, stepUnit: LocalDateUnit = 'day'): LocalDate[] {
|
|
86
|
+
return LocalDate.range(this.start, this.end, incl, step, stepUnit)
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
toString(): DateIntervalString {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { _assert } from '../error/assert'
|
|
2
|
-
import {
|
|
3
|
-
import { END, IsoDateString, UnixTimestampNumber } from '../types'
|
|
2
|
+
import { IsoDateString, UnixTimestampNumber } from '../types'
|
|
4
3
|
import { LocalTime } from './localTime'
|
|
5
4
|
|
|
6
5
|
export type LocalDateUnit = 'year' | 'month' | 'day'
|
|
7
6
|
export type Inclusiveness = '()' | '[]' | '[)' | '(]'
|
|
8
7
|
|
|
9
|
-
const
|
|
8
|
+
const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
9
|
+
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
|
|
10
10
|
|
|
11
|
-
export type LocalDateConfig = LocalDate |
|
|
11
|
+
export type LocalDateConfig = LocalDate | IsoDateString
|
|
12
12
|
|
|
13
13
|
/* eslint-disable no-dupe-class-members */
|
|
14
14
|
|
|
@@ -61,8 +61,13 @@ export class LocalDate {
|
|
|
61
61
|
if (!d) return null
|
|
62
62
|
if (d instanceof LocalDate) return d
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
const
|
|
64
|
+
// const [year, month, day] = d.slice(0, 10).split('-').map(Number)
|
|
65
|
+
const matches = DATE_REGEX.exec(d.slice(0, 10))
|
|
66
|
+
if (!matches) return null
|
|
67
|
+
|
|
68
|
+
const year = Number(matches[1])
|
|
69
|
+
const month = Number(matches[2])
|
|
70
|
+
const day = Number(matches[3])
|
|
66
71
|
|
|
67
72
|
if (
|
|
68
73
|
!year ||
|
|
@@ -79,6 +84,11 @@ export class LocalDate {
|
|
|
79
84
|
return new LocalDate(year, month, day)
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
// Can use just .toString()
|
|
88
|
+
// static parseToString(d: LocalDateConfig): IsoDateString {
|
|
89
|
+
// return typeof d === 'string' ? d : d.toString()
|
|
90
|
+
// }
|
|
91
|
+
|
|
82
92
|
static isValid(iso: string | undefined | null): boolean {
|
|
83
93
|
return this.parseOrNull(iso) !== null
|
|
84
94
|
}
|
|
@@ -117,64 +127,30 @@ export class LocalDate {
|
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
static range(
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
min: LocalDateConfig,
|
|
131
|
+
max: LocalDateConfig,
|
|
132
|
+
incl: Inclusiveness = '[)',
|
|
122
133
|
step = 1,
|
|
123
134
|
stepUnit: LocalDateUnit = 'day',
|
|
124
135
|
): LocalDate[] {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const max = LocalDate.of(
|
|
128
|
-
|
|
129
|
-
do {
|
|
130
|
-
days.push(current)
|
|
131
|
-
current = current.add(step, stepUnit)
|
|
132
|
-
} while (current.isBefore(max))
|
|
133
|
-
|
|
134
|
-
return days
|
|
135
|
-
}
|
|
136
|
+
const dates: LocalDate[] = []
|
|
137
|
+
const $min = LocalDate.of(min)
|
|
138
|
+
const $max = LocalDate.of(max).startOf(stepUnit)
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const min = LocalDate.of(minIncl).startOf(stepUnit)
|
|
144
|
-
const max = LocalDate.of(maxExcl).startOf(stepUnit)
|
|
145
|
-
return Sequence.create(min, d => {
|
|
146
|
-
const next = d.add(step, stepUnit)
|
|
147
|
-
return next.isAfter(max) ? END : next
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
static rangeString(
|
|
152
|
-
minIncl: LocalDateConfig,
|
|
153
|
-
maxExcl: LocalDateConfig,
|
|
154
|
-
step = 1,
|
|
155
|
-
stepUnit: LocalDateUnit = 'day',
|
|
156
|
-
): IsoDateString[] {
|
|
157
|
-
return LocalDate.range(minIncl, maxExcl, step, stepUnit).map(ld => ld.toString())
|
|
158
|
-
}
|
|
140
|
+
let current = $min.startOf(stepUnit)
|
|
141
|
+
if (current.isAfter($min, incl[0] === '[')) {
|
|
142
|
+
// ok
|
|
143
|
+
} else {
|
|
144
|
+
current.add(1, stepUnit, true)
|
|
145
|
+
}
|
|
159
146
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
): LocalDate[] {
|
|
166
|
-
return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit)
|
|
167
|
-
}
|
|
147
|
+
const incl2 = incl[1] === ']'
|
|
148
|
+
while (current.isBefore($max, incl2)) {
|
|
149
|
+
dates.push(current)
|
|
150
|
+
current = current.add(step, stepUnit)
|
|
151
|
+
}
|
|
168
152
|
|
|
169
|
-
|
|
170
|
-
minIncl: LocalDateConfig,
|
|
171
|
-
maxIncl: LocalDateConfig,
|
|
172
|
-
step = 1,
|
|
173
|
-
stepUnit: LocalDateUnit = 'day',
|
|
174
|
-
): IsoDateString[] {
|
|
175
|
-
return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit).map(
|
|
176
|
-
ld => ld.toString(),
|
|
177
|
-
)
|
|
153
|
+
return dates
|
|
178
154
|
}
|
|
179
155
|
|
|
180
156
|
get(unit: LocalDateUnit): number {
|
|
@@ -216,16 +192,18 @@ export class LocalDate {
|
|
|
216
192
|
return this.$day === d.$day && this.$month === d.$month && this.$year === d.$year
|
|
217
193
|
}
|
|
218
194
|
|
|
219
|
-
isBefore(d: LocalDateConfig): boolean {
|
|
220
|
-
|
|
195
|
+
isBefore(d: LocalDateConfig, inclusive = false): boolean {
|
|
196
|
+
const r = this.cmp(d)
|
|
197
|
+
return r === -1 || (r === 0 && inclusive)
|
|
221
198
|
}
|
|
222
199
|
|
|
223
200
|
isSameOrBefore(d: LocalDateConfig): boolean {
|
|
224
201
|
return this.cmp(d) <= 0
|
|
225
202
|
}
|
|
226
203
|
|
|
227
|
-
isAfter(d: LocalDateConfig): boolean {
|
|
228
|
-
|
|
204
|
+
isAfter(d: LocalDateConfig, inclusive = false): boolean {
|
|
205
|
+
const r = this.cmp(d)
|
|
206
|
+
return r === 1 || (r === 0 && inclusive)
|
|
229
207
|
}
|
|
230
208
|
|
|
231
209
|
isSameOrAfter(d: LocalDateConfig): boolean {
|
|
@@ -317,26 +295,31 @@ export class LocalDate {
|
|
|
317
295
|
}
|
|
318
296
|
|
|
319
297
|
// check day overflow
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
298
|
+
if (unit === 'day') {
|
|
299
|
+
if ($day < 1) {
|
|
300
|
+
while ($day < 1) {
|
|
301
|
+
$month -= 1
|
|
302
|
+
if ($month < 1) {
|
|
303
|
+
$year -= 1
|
|
304
|
+
$month += 12
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
$day += LocalDate.getMonthLength($year, $month)
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
let monLen = LocalDate.getMonthLength($year, $month)
|
|
311
|
+
|
|
312
|
+
while ($day > monLen) {
|
|
313
|
+
$day -= monLen
|
|
314
|
+
$month += 1
|
|
315
|
+
if ($month > 12) {
|
|
316
|
+
$year += 1
|
|
317
|
+
$month -= 12
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
monLen = LocalDate.getMonthLength($year, $month)
|
|
321
|
+
}
|
|
337
322
|
}
|
|
338
|
-
|
|
339
|
-
monLen = LocalDate.getMonthLength($year, $month)
|
|
340
323
|
}
|
|
341
324
|
|
|
342
325
|
// check month overflow
|
|
@@ -388,13 +371,11 @@ export class LocalDate {
|
|
|
388
371
|
|
|
389
372
|
static getMonthLength(year: number, month: number): number {
|
|
390
373
|
if (month === 2) return this.isLeapYear(year) ? 29 : 28
|
|
391
|
-
return
|
|
374
|
+
return MDAYS[month]!
|
|
392
375
|
}
|
|
393
376
|
|
|
394
377
|
static isLeapYear(year: number): boolean {
|
|
395
|
-
|
|
396
|
-
if (year % 100 !== 0) return true
|
|
397
|
-
return year % 400 === 0
|
|
378
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
|
398
379
|
}
|
|
399
380
|
|
|
400
381
|
clone(): LocalDate {
|