@naturalcycles/js-lib 14.243.0 → 14.243.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/localDate.js +6 -6
- package/dist/datetime/localTime.d.ts +1 -2
- package/dist/datetime/localTime.js +61 -35
- package/dist/datetime/timeInterval.js +2 -2
- package/dist-esm/datetime/localDate.js +6 -6
- package/dist-esm/datetime/localTime.js +61 -35
- package/dist-esm/datetime/timeInterval.js +2 -2
- package/package.json +2 -1
- package/src/datetime/localDate.ts +6 -6
- package/src/datetime/localTime.ts +66 -37
- package/src/datetime/timeInterval.ts +2 -5
|
@@ -7,7 +7,7 @@ const is_util_1 = require("../is.util");
|
|
|
7
7
|
const iterable2_1 = require("../iter/iterable2");
|
|
8
8
|
const localTime_1 = require("./localTime");
|
|
9
9
|
const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
10
|
-
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)
|
|
10
|
+
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)/;
|
|
11
11
|
/**
|
|
12
12
|
* LocalDate represents a date without time.
|
|
13
13
|
* It is timezone-independent.
|
|
@@ -452,12 +452,12 @@ class LocalDateFactory {
|
|
|
452
452
|
if (d instanceof Date) {
|
|
453
453
|
return this.fromDate(d);
|
|
454
454
|
}
|
|
455
|
-
const
|
|
456
|
-
if (!
|
|
455
|
+
const m = typeof d === 'string' && DATE_REGEX.exec(d);
|
|
456
|
+
if (!m)
|
|
457
457
|
return null;
|
|
458
|
-
const year = Number(
|
|
459
|
-
const month = Number(
|
|
460
|
-
const day = Number(
|
|
458
|
+
const year = Number(m[1]);
|
|
459
|
+
const month = Number(m[2]);
|
|
460
|
+
const day = Number(m[3]);
|
|
461
461
|
if (!year ||
|
|
462
462
|
!month ||
|
|
463
463
|
month < 1 ||
|
|
@@ -228,8 +228,7 @@ declare class LocalTimeFactory {
|
|
|
228
228
|
* Returns null if invalid
|
|
229
229
|
*/
|
|
230
230
|
parseOrNull(d: LocalTimeInputNullable): LocalTime | null;
|
|
231
|
-
|
|
232
|
-
parseToUnixTimestamp(d: LocalTimeInput): UnixTimestampNumber;
|
|
231
|
+
private parseStringToDateOrNull;
|
|
233
232
|
isValid(d: LocalTimeInputNullable): boolean;
|
|
234
233
|
/**
|
|
235
234
|
* Returns the IANA timezone e.g `Europe/Stockholm`.
|
|
@@ -23,6 +23,10 @@ const SECONDS_IN_DAY = 86400;
|
|
|
23
23
|
// const MILLISECONDS_IN_DAY = 86400000
|
|
24
24
|
// const MILLISECONDS_IN_MINUTE = 60000
|
|
25
25
|
const VALID_DAYS_OF_WEEK = new Set([1, 2, 3, 4, 5, 6, 7]);
|
|
26
|
+
// It supports 2 forms:
|
|
27
|
+
// 1. 2023-03-03
|
|
28
|
+
// 2. 2023-03-03T05:10:02
|
|
29
|
+
const DATE_TIME_REGEX = /^(\d{4})-(\d{2})-(\d{2})([T\s](\d{2}):(\d{2}):(\d{2}))?/;
|
|
26
30
|
class LocalTime {
|
|
27
31
|
constructor($date) {
|
|
28
32
|
this.$date = $date;
|
|
@@ -266,7 +270,7 @@ class LocalTime {
|
|
|
266
270
|
return Math.abs(this.diff(other, unit));
|
|
267
271
|
}
|
|
268
272
|
diff(other, unit) {
|
|
269
|
-
const date2 = exports.localTime.
|
|
273
|
+
const date2 = exports.localTime.of(other).$date;
|
|
270
274
|
const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000;
|
|
271
275
|
if (!secDiff)
|
|
272
276
|
return 0;
|
|
@@ -448,7 +452,7 @@ class LocalTime {
|
|
|
448
452
|
*/
|
|
449
453
|
cmp(d) {
|
|
450
454
|
const t1 = this.$date.valueOf();
|
|
451
|
-
const t2 = exports.localTime.
|
|
455
|
+
const t2 = exports.localTime.of(d).$date.valueOf();
|
|
452
456
|
if (t1 === t2)
|
|
453
457
|
return 0;
|
|
454
458
|
return t1 < t2 ? -1 : 1;
|
|
@@ -474,7 +478,7 @@ class LocalTime {
|
|
|
474
478
|
};
|
|
475
479
|
}
|
|
476
480
|
fromNow(now = new Date()) {
|
|
477
|
-
const msDiff = exports.localTime.
|
|
481
|
+
const msDiff = exports.localTime.of(now).$date.valueOf() - this.$date.valueOf();
|
|
478
482
|
if (msDiff === 0)
|
|
479
483
|
return 'now';
|
|
480
484
|
if (msDiff >= 0) {
|
|
@@ -619,41 +623,63 @@ class LocalTimeFactory {
|
|
|
619
623
|
return null;
|
|
620
624
|
}
|
|
621
625
|
else {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
date = new Date(d.slice(0, 19));
|
|
626
|
-
// We used to slice to remove the timezone information, now we don't
|
|
627
|
-
// date = new Date(d)
|
|
628
|
-
}
|
|
629
|
-
// validation
|
|
630
|
-
if (isNaN(date.getDate())) {
|
|
631
|
-
// throw new TypeError(`Cannot parse "${d}" into LocalTime`)
|
|
632
|
-
return null;
|
|
626
|
+
date = this.parseStringToDateOrNull(d);
|
|
627
|
+
if (date === null)
|
|
628
|
+
return null;
|
|
633
629
|
}
|
|
634
630
|
return new LocalTime(date);
|
|
635
631
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
632
|
+
parseStringToDateOrNull(s) {
|
|
633
|
+
// Slicing removes the "timezone component", and makes the date "local"
|
|
634
|
+
// e.g 2022-04-06T23:15:00+09:00
|
|
635
|
+
// becomes 2022-04-06T23:15:00
|
|
636
|
+
// date = new Date(d.slice(0, 19))
|
|
637
|
+
// Parsing is inspired by how Day.js does it
|
|
638
|
+
// Specifically, it ensures that `localTime('2023-03-03')` returns the expected Date, and not a day before
|
|
639
|
+
// Because `new Date('2023-03-03')` in NewYork gives you '2023-03-02 19:00:00 GMT-0500'
|
|
640
|
+
const m = s.match(DATE_TIME_REGEX);
|
|
641
|
+
// Validate in 3 ways:
|
|
642
|
+
// 1. Should match Regex.
|
|
643
|
+
// In some ways it's stricter than Date constructor, e.g it doesn't allow 2023/05/05
|
|
644
|
+
// In other ways it's looser, e.g it allows `2023-05-05T`, while Date constructor doesn't.
|
|
645
|
+
// 2. Date constructor (of Node/v8 implementation, which we know is different from e.g WebKit/Safari)
|
|
646
|
+
// should not return `Invalid Date`.
|
|
647
|
+
// 3. Year, month and day should be valid, e.g 2023-01-32 should not be allowed.
|
|
648
|
+
// UPD: Actually, 3 can be skipped, because 2 is catching it already
|
|
649
|
+
// UPD: 2 is skipped, 1 and 3 are kept
|
|
650
|
+
// if (!m || isNaN(new Date(s).getDate())) return null
|
|
651
|
+
if (!m)
|
|
652
|
+
return null;
|
|
653
|
+
const year = Number(m[1]);
|
|
654
|
+
const month = Number(m[2]);
|
|
655
|
+
const day = Number(m[3]);
|
|
656
|
+
const hour = Number(m[5]);
|
|
657
|
+
const minute = Number(m[6]);
|
|
658
|
+
const second = Number(m[7]);
|
|
659
|
+
// Validation for just the Date part
|
|
660
|
+
if (!year ||
|
|
661
|
+
!month ||
|
|
662
|
+
month < 1 ||
|
|
663
|
+
month > 12 ||
|
|
664
|
+
!day ||
|
|
665
|
+
day < 1 ||
|
|
666
|
+
day > localDate_1.localDate.getMonthLength(year, month)) {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
// Validation for Date+Time string, since the string is longer than YYYY-MM-DD
|
|
670
|
+
if (s.length > 10 &&
|
|
671
|
+
(isNaN(hour) ||
|
|
672
|
+
isNaN(minute) ||
|
|
673
|
+
isNaN(second) ||
|
|
674
|
+
hour < 0 ||
|
|
675
|
+
hour > 23 ||
|
|
676
|
+
minute < 0 ||
|
|
677
|
+
minute > 59 ||
|
|
678
|
+
second < 0 ||
|
|
679
|
+
second > 59)) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
return new Date(year, month - 1, day, hour || 0, minute || 0, second || 0, 0);
|
|
657
683
|
}
|
|
658
684
|
isValid(d) {
|
|
659
685
|
return this.parseOrNull(d) !== null;
|
|
@@ -14,7 +14,7 @@ class TimeInterval {
|
|
|
14
14
|
this.$end = $end;
|
|
15
15
|
}
|
|
16
16
|
static of(start, end) {
|
|
17
|
-
return new TimeInterval(localTime_1.localTime.
|
|
17
|
+
return new TimeInterval(localTime_1.localTime.of(start).unix(), localTime_1.localTime.of(end).unix());
|
|
18
18
|
}
|
|
19
19
|
get start() {
|
|
20
20
|
return this.$start;
|
|
@@ -58,7 +58,7 @@ class TimeInterval {
|
|
|
58
58
|
return this.cmp(d) >= 0;
|
|
59
59
|
}
|
|
60
60
|
includes(d, incl = '[)') {
|
|
61
|
-
d = localTime_1.localTime.
|
|
61
|
+
d = localTime_1.localTime.of(d).unix();
|
|
62
62
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
63
63
|
if (d < this.$start || (d === this.$start && incl[0] === '('))
|
|
64
64
|
return false;
|
|
@@ -3,7 +3,7 @@ import { _isTruthy } from '../is.util';
|
|
|
3
3
|
import { Iterable2 } from '../iter/iterable2';
|
|
4
4
|
import { localTime } from './localTime';
|
|
5
5
|
const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
6
|
-
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)
|
|
6
|
+
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)/;
|
|
7
7
|
/**
|
|
8
8
|
* LocalDate represents a date without time.
|
|
9
9
|
* It is timezone-independent.
|
|
@@ -447,12 +447,12 @@ class LocalDateFactory {
|
|
|
447
447
|
if (d instanceof Date) {
|
|
448
448
|
return this.fromDate(d);
|
|
449
449
|
}
|
|
450
|
-
const
|
|
451
|
-
if (!
|
|
450
|
+
const m = typeof d === 'string' && DATE_REGEX.exec(d);
|
|
451
|
+
if (!m)
|
|
452
452
|
return null;
|
|
453
|
-
const year = Number(
|
|
454
|
-
const month = Number(
|
|
455
|
-
const day = Number(
|
|
453
|
+
const year = Number(m[1]);
|
|
454
|
+
const month = Number(m[2]);
|
|
455
|
+
const day = Number(m[3]);
|
|
456
456
|
if (!year ||
|
|
457
457
|
!month ||
|
|
458
458
|
month < 1 ||
|
|
@@ -19,6 +19,10 @@ const SECONDS_IN_DAY = 86400;
|
|
|
19
19
|
// const MILLISECONDS_IN_DAY = 86400000
|
|
20
20
|
// const MILLISECONDS_IN_MINUTE = 60000
|
|
21
21
|
const VALID_DAYS_OF_WEEK = new Set([1, 2, 3, 4, 5, 6, 7]);
|
|
22
|
+
// It supports 2 forms:
|
|
23
|
+
// 1. 2023-03-03
|
|
24
|
+
// 2. 2023-03-03T05:10:02
|
|
25
|
+
const DATE_TIME_REGEX = /^(\d{4})-(\d{2})-(\d{2})([T\s](\d{2}):(\d{2}):(\d{2}))?/;
|
|
22
26
|
export class LocalTime {
|
|
23
27
|
constructor($date) {
|
|
24
28
|
this.$date = $date;
|
|
@@ -262,7 +266,7 @@ export class LocalTime {
|
|
|
262
266
|
return Math.abs(this.diff(other, unit));
|
|
263
267
|
}
|
|
264
268
|
diff(other, unit) {
|
|
265
|
-
const date2 = localTime.
|
|
269
|
+
const date2 = localTime.of(other).$date;
|
|
266
270
|
const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000;
|
|
267
271
|
if (!secDiff)
|
|
268
272
|
return 0;
|
|
@@ -444,7 +448,7 @@ export class LocalTime {
|
|
|
444
448
|
*/
|
|
445
449
|
cmp(d) {
|
|
446
450
|
const t1 = this.$date.valueOf();
|
|
447
|
-
const t2 = localTime.
|
|
451
|
+
const t2 = localTime.of(d).$date.valueOf();
|
|
448
452
|
if (t1 === t2)
|
|
449
453
|
return 0;
|
|
450
454
|
return t1 < t2 ? -1 : 1;
|
|
@@ -470,7 +474,7 @@ export class LocalTime {
|
|
|
470
474
|
};
|
|
471
475
|
}
|
|
472
476
|
fromNow(now = new Date()) {
|
|
473
|
-
const msDiff = localTime.
|
|
477
|
+
const msDiff = localTime.of(now).$date.valueOf() - this.$date.valueOf();
|
|
474
478
|
if (msDiff === 0)
|
|
475
479
|
return 'now';
|
|
476
480
|
if (msDiff >= 0) {
|
|
@@ -614,41 +618,63 @@ class LocalTimeFactory {
|
|
|
614
618
|
return null;
|
|
615
619
|
}
|
|
616
620
|
else {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
date = new Date(d.slice(0, 19));
|
|
621
|
-
// We used to slice to remove the timezone information, now we don't
|
|
622
|
-
// date = new Date(d)
|
|
623
|
-
}
|
|
624
|
-
// validation
|
|
625
|
-
if (isNaN(date.getDate())) {
|
|
626
|
-
// throw new TypeError(`Cannot parse "${d}" into LocalTime`)
|
|
627
|
-
return null;
|
|
621
|
+
date = this.parseStringToDateOrNull(d);
|
|
622
|
+
if (date === null)
|
|
623
|
+
return null;
|
|
628
624
|
}
|
|
629
625
|
return new LocalTime(date);
|
|
630
626
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
627
|
+
parseStringToDateOrNull(s) {
|
|
628
|
+
// Slicing removes the "timezone component", and makes the date "local"
|
|
629
|
+
// e.g 2022-04-06T23:15:00+09:00
|
|
630
|
+
// becomes 2022-04-06T23:15:00
|
|
631
|
+
// date = new Date(d.slice(0, 19))
|
|
632
|
+
// Parsing is inspired by how Day.js does it
|
|
633
|
+
// Specifically, it ensures that `localTime('2023-03-03')` returns the expected Date, and not a day before
|
|
634
|
+
// Because `new Date('2023-03-03')` in NewYork gives you '2023-03-02 19:00:00 GMT-0500'
|
|
635
|
+
const m = s.match(DATE_TIME_REGEX);
|
|
636
|
+
// Validate in 3 ways:
|
|
637
|
+
// 1. Should match Regex.
|
|
638
|
+
// In some ways it's stricter than Date constructor, e.g it doesn't allow 2023/05/05
|
|
639
|
+
// In other ways it's looser, e.g it allows `2023-05-05T`, while Date constructor doesn't.
|
|
640
|
+
// 2. Date constructor (of Node/v8 implementation, which we know is different from e.g WebKit/Safari)
|
|
641
|
+
// should not return `Invalid Date`.
|
|
642
|
+
// 3. Year, month and day should be valid, e.g 2023-01-32 should not be allowed.
|
|
643
|
+
// UPD: Actually, 3 can be skipped, because 2 is catching it already
|
|
644
|
+
// UPD: 2 is skipped, 1 and 3 are kept
|
|
645
|
+
// if (!m || isNaN(new Date(s).getDate())) return null
|
|
646
|
+
if (!m)
|
|
647
|
+
return null;
|
|
648
|
+
const year = Number(m[1]);
|
|
649
|
+
const month = Number(m[2]);
|
|
650
|
+
const day = Number(m[3]);
|
|
651
|
+
const hour = Number(m[5]);
|
|
652
|
+
const minute = Number(m[6]);
|
|
653
|
+
const second = Number(m[7]);
|
|
654
|
+
// Validation for just the Date part
|
|
655
|
+
if (!year ||
|
|
656
|
+
!month ||
|
|
657
|
+
month < 1 ||
|
|
658
|
+
month > 12 ||
|
|
659
|
+
!day ||
|
|
660
|
+
day < 1 ||
|
|
661
|
+
day > localDate.getMonthLength(year, month)) {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
// Validation for Date+Time string, since the string is longer than YYYY-MM-DD
|
|
665
|
+
if (s.length > 10 &&
|
|
666
|
+
(isNaN(hour) ||
|
|
667
|
+
isNaN(minute) ||
|
|
668
|
+
isNaN(second) ||
|
|
669
|
+
hour < 0 ||
|
|
670
|
+
hour > 23 ||
|
|
671
|
+
minute < 0 ||
|
|
672
|
+
minute > 59 ||
|
|
673
|
+
second < 0 ||
|
|
674
|
+
second > 59)) {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
return new Date(year, month - 1, day, hour || 0, minute || 0, second || 0, 0);
|
|
652
678
|
}
|
|
653
679
|
isValid(d) {
|
|
654
680
|
return this.parseOrNull(d) !== null;
|
|
@@ -11,7 +11,7 @@ export class TimeInterval {
|
|
|
11
11
|
this.$end = $end;
|
|
12
12
|
}
|
|
13
13
|
static of(start, end) {
|
|
14
|
-
return new TimeInterval(localTime.
|
|
14
|
+
return new TimeInterval(localTime.of(start).unix(), localTime.of(end).unix());
|
|
15
15
|
}
|
|
16
16
|
get start() {
|
|
17
17
|
return this.$start;
|
|
@@ -55,7 +55,7 @@ export class TimeInterval {
|
|
|
55
55
|
return this.cmp(d) >= 0;
|
|
56
56
|
}
|
|
57
57
|
includes(d, incl = '[)') {
|
|
58
|
-
d = localTime.
|
|
58
|
+
d = localTime.of(d).unix();
|
|
59
59
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
60
60
|
if (d < this.$start || (d === this.$start && incl[0] === '('))
|
|
61
61
|
return false;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/js-lib",
|
|
3
|
-
"version": "14.243.
|
|
3
|
+
"version": "14.243.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"prepare": "husky",
|
|
6
6
|
"build-prod": "build-prod-esm-cjs",
|
|
7
7
|
"test-tz1": "TZ=Europe/Stockholm yarn test local",
|
|
8
8
|
"test-tz2": "TZ=JST-9 yarn test local",
|
|
9
|
+
"test-ny": "TZ=GMT-0500 yarn test localTime",
|
|
9
10
|
"docs-dev": "vitepress dev docs --open",
|
|
10
11
|
"docs-build": "vitepress build docs",
|
|
11
12
|
"docs-preview": "vitepress preview docs"
|
|
@@ -16,7 +16,7 @@ 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
|
-
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)
|
|
19
|
+
const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)/
|
|
20
20
|
|
|
21
21
|
export type LocalDateInput = LocalDate | Date | IsoDateString
|
|
22
22
|
export type LocalDateInputNullable = LocalDateInput | null | undefined
|
|
@@ -526,12 +526,12 @@ class LocalDateFactory {
|
|
|
526
526
|
return this.fromDate(d)
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
const
|
|
530
|
-
if (!
|
|
529
|
+
const m = typeof (d as any) === 'string' && DATE_REGEX.exec(d)
|
|
530
|
+
if (!m) return null
|
|
531
531
|
|
|
532
|
-
const year = Number(
|
|
533
|
-
const month = Number(
|
|
534
|
-
const day = Number(
|
|
532
|
+
const year = Number(m[1])
|
|
533
|
+
const month = Number(m[2])
|
|
534
|
+
const day = Number(m[3])
|
|
535
535
|
|
|
536
536
|
if (
|
|
537
537
|
!year ||
|
|
@@ -51,6 +51,10 @@ const SECONDS_IN_DAY = 86400
|
|
|
51
51
|
// const MILLISECONDS_IN_DAY = 86400000
|
|
52
52
|
// const MILLISECONDS_IN_MINUTE = 60000
|
|
53
53
|
const VALID_DAYS_OF_WEEK = new Set([1, 2, 3, 4, 5, 6, 7])
|
|
54
|
+
// It supports 2 forms:
|
|
55
|
+
// 1. 2023-03-03
|
|
56
|
+
// 2. 2023-03-03T05:10:02
|
|
57
|
+
const DATE_TIME_REGEX = /^(\d{4})-(\d{2})-(\d{2})([T\s](\d{2}):(\d{2}):(\d{2}))?/
|
|
54
58
|
|
|
55
59
|
export class LocalTime {
|
|
56
60
|
constructor(public $date: Date) {}
|
|
@@ -337,7 +341,7 @@ export class LocalTime {
|
|
|
337
341
|
}
|
|
338
342
|
|
|
339
343
|
diff(other: LocalTimeInput, unit: LocalTimeUnit): number {
|
|
340
|
-
const date2 = localTime.
|
|
344
|
+
const date2 = localTime.of(other).$date
|
|
341
345
|
|
|
342
346
|
const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000
|
|
343
347
|
if (!secDiff) return 0
|
|
@@ -531,7 +535,7 @@ export class LocalTime {
|
|
|
531
535
|
*/
|
|
532
536
|
cmp(d: LocalTimeInput): -1 | 0 | 1 {
|
|
533
537
|
const t1 = this.$date.valueOf()
|
|
534
|
-
const t2 = localTime.
|
|
538
|
+
const t2 = localTime.of(d).$date.valueOf()
|
|
535
539
|
if (t1 === t2) return 0
|
|
536
540
|
return t1 < t2 ? -1 : 1
|
|
537
541
|
}
|
|
@@ -560,7 +564,7 @@ export class LocalTime {
|
|
|
560
564
|
}
|
|
561
565
|
|
|
562
566
|
fromNow(now: LocalTimeInput = new Date()): string {
|
|
563
|
-
const msDiff = localTime.
|
|
567
|
+
const msDiff = localTime.of(now).$date.valueOf() - this.$date.valueOf()
|
|
564
568
|
|
|
565
569
|
if (msDiff === 0) return 'now'
|
|
566
570
|
|
|
@@ -729,47 +733,72 @@ class LocalTimeFactory {
|
|
|
729
733
|
// unexpected type, e.g Function or something
|
|
730
734
|
return null
|
|
731
735
|
} else {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
// becomes 2022-04-06T23:15:00
|
|
735
|
-
date = new Date(d.slice(0, 19))
|
|
736
|
-
// We used to slice to remove the timezone information, now we don't
|
|
737
|
-
// date = new Date(d)
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// validation
|
|
741
|
-
if (isNaN(date.getDate())) {
|
|
742
|
-
// throw new TypeError(`Cannot parse "${d}" into LocalTime`)
|
|
743
|
-
return null
|
|
736
|
+
date = this.parseStringToDateOrNull(d)
|
|
737
|
+
if (date === null) return null
|
|
744
738
|
}
|
|
745
739
|
|
|
746
740
|
return new LocalTime(date)
|
|
747
741
|
}
|
|
748
742
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
743
|
+
private parseStringToDateOrNull(s: string): Date | null {
|
|
744
|
+
// Slicing removes the "timezone component", and makes the date "local"
|
|
745
|
+
// e.g 2022-04-06T23:15:00+09:00
|
|
746
|
+
// becomes 2022-04-06T23:15:00
|
|
747
|
+
// date = new Date(d.slice(0, 19))
|
|
748
|
+
|
|
749
|
+
// Parsing is inspired by how Day.js does it
|
|
750
|
+
// Specifically, it ensures that `localTime('2023-03-03')` returns the expected Date, and not a day before
|
|
751
|
+
// Because `new Date('2023-03-03')` in NewYork gives you '2023-03-02 19:00:00 GMT-0500'
|
|
752
|
+
|
|
753
|
+
const m = s.match(DATE_TIME_REGEX)
|
|
754
|
+
// Validate in 3 ways:
|
|
755
|
+
// 1. Should match Regex.
|
|
756
|
+
// In some ways it's stricter than Date constructor, e.g it doesn't allow 2023/05/05
|
|
757
|
+
// In other ways it's looser, e.g it allows `2023-05-05T`, while Date constructor doesn't.
|
|
758
|
+
// 2. Date constructor (of Node/v8 implementation, which we know is different from e.g WebKit/Safari)
|
|
759
|
+
// should not return `Invalid Date`.
|
|
760
|
+
// 3. Year, month and day should be valid, e.g 2023-01-32 should not be allowed.
|
|
761
|
+
// UPD: Actually, 3 can be skipped, because 2 is catching it already
|
|
762
|
+
// UPD: 2 is skipped, 1 and 3 are kept
|
|
763
|
+
// if (!m || isNaN(new Date(s).getDate())) return null
|
|
764
|
+
if (!m) return null
|
|
765
|
+
const year = Number(m[1])
|
|
766
|
+
const month = Number(m[2])
|
|
767
|
+
const day = Number(m[3])
|
|
768
|
+
const hour = Number(m[5])
|
|
769
|
+
const minute = Number(m[6])
|
|
770
|
+
const second = Number(m[7])
|
|
771
|
+
|
|
772
|
+
// Validation for just the Date part
|
|
773
|
+
if (
|
|
774
|
+
!year ||
|
|
775
|
+
!month ||
|
|
776
|
+
month < 1 ||
|
|
777
|
+
month > 12 ||
|
|
778
|
+
!day ||
|
|
779
|
+
day < 1 ||
|
|
780
|
+
day > localDate.getMonthLength(year, month)
|
|
781
|
+
) {
|
|
782
|
+
return null
|
|
783
|
+
}
|
|
767
784
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
785
|
+
// Validation for Date+Time string, since the string is longer than YYYY-MM-DD
|
|
786
|
+
if (
|
|
787
|
+
s.length > 10 &&
|
|
788
|
+
(isNaN(hour) ||
|
|
789
|
+
isNaN(minute) ||
|
|
790
|
+
isNaN(second) ||
|
|
791
|
+
hour < 0 ||
|
|
792
|
+
hour > 23 ||
|
|
793
|
+
minute < 0 ||
|
|
794
|
+
minute > 59 ||
|
|
795
|
+
second < 0 ||
|
|
796
|
+
second > 59)
|
|
797
|
+
) {
|
|
798
|
+
return null
|
|
799
|
+
}
|
|
771
800
|
|
|
772
|
-
return
|
|
801
|
+
return new Date(year, month - 1, day, hour || 0, minute || 0, second || 0, 0)
|
|
773
802
|
}
|
|
774
803
|
|
|
775
804
|
isValid(d: LocalTimeInputNullable): boolean {
|
|
@@ -17,10 +17,7 @@ export class TimeInterval {
|
|
|
17
17
|
) {}
|
|
18
18
|
|
|
19
19
|
static of(start: LocalTimeInput, end: LocalTimeInput): TimeInterval {
|
|
20
|
-
return new TimeInterval(
|
|
21
|
-
localTime.parseToUnixTimestamp(start),
|
|
22
|
-
localTime.parseToUnixTimestamp(end),
|
|
23
|
-
)
|
|
20
|
+
return new TimeInterval(localTime.of(start).unix(), localTime.of(end).unix())
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
get start(): UnixTimestampNumber {
|
|
@@ -77,7 +74,7 @@ export class TimeInterval {
|
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
includes(d: LocalTimeInput, incl: Inclusiveness = '[)'): boolean {
|
|
80
|
-
d = localTime.
|
|
77
|
+
d = localTime.of(d).unix()
|
|
81
78
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
82
79
|
if (d < this.$start || (d === this.$start && incl[0] === '(')) return false
|
|
83
80
|
if (d > this.$end || (d === this.$end && incl[1] === ')')) return false
|