@naturalcycles/js-lib 14.243.1 → 14.245.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.d.ts +1 -1
- package/dist/datetime/dateInterval.js +1 -1
- package/dist/datetime/localDate.d.ts +75 -51
- package/dist/datetime/localDate.js +256 -200
- package/dist/datetime/localTime.d.ts +88 -56
- package/dist/datetime/localTime.js +277 -176
- package/dist/datetime/timeInterval.d.ts +2 -2
- package/dist/datetime/timeInterval.js +2 -2
- package/dist/env/buildInfo.js +2 -2
- package/dist/error/error.util.d.ts +1 -1
- package/dist/index.d.ts +37 -37
- package/dist/index.js +37 -37
- package/dist/json-schema/from-data/generateJsonSchemaFromData.d.ts +1 -1
- package/dist/json-schema/jsonSchemaBuilder.d.ts +1 -1
- package/dist/object/object.util.d.ts +1 -1
- package/dist/time/time.util.js +4 -2
- package/dist/zod/zod.util.d.ts +1 -1
- package/dist-esm/datetime/dateInterval.js +1 -1
- package/dist-esm/datetime/localDate.js +256 -199
- package/dist-esm/datetime/localTime.js +277 -175
- package/dist-esm/datetime/timeInterval.js +2 -2
- package/dist-esm/decorators/logMethod.decorator.js +1 -1
- package/dist-esm/env/buildInfo.js +2 -2
- package/dist-esm/index.js +37 -37
- package/dist-esm/json-schema/jsonSchemaBuilder.js +1 -1
- package/dist-esm/time/time.util.js +4 -2
- package/package.json +1 -1
- package/src/datetime/dateInterval.ts +2 -2
- package/src/datetime/localDate.ts +259 -215
- package/src/datetime/localTime.ts +277 -209
- package/src/datetime/timeInterval.ts +4 -4
- package/src/decorators/logMethod.decorator.ts +1 -1
- package/src/define.ts +1 -1
- package/src/env/buildInfo.ts +2 -2
- package/src/error/error.util.ts +3 -3
- package/src/http/fetcher.ts +1 -1
- package/src/index.ts +37 -37
- package/src/json-schema/from-data/generateJsonSchemaFromData.ts +1 -1
- package/src/json-schema/jsonSchemaBuilder.ts +2 -2
- package/src/object/object.util.ts +1 -1
- package/src/time/time.util.ts +4 -1
- package/src/zod/zod.util.ts +1 -1
|
@@ -19,10 +19,22 @@ 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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
/**
|
|
23
|
+
* It supports 2 forms:
|
|
24
|
+
* 1. 2023-03-03
|
|
25
|
+
* 2. 2023-03-03T05:10:02
|
|
26
|
+
* // todo: make it even looser, like Day.js
|
|
27
|
+
*/
|
|
28
|
+
const DATE_TIME_REGEX_LOOSE = /^(\d{4})-(\d{2})-(\d{2})([Tt\s](\d{2}):?(\d{2})?:?(\d{2})?)?/;
|
|
29
|
+
/**
|
|
30
|
+
* Supports 2 forms:
|
|
31
|
+
* 1. 2023-03-03
|
|
32
|
+
* 2. 2023-03-03T05:10:02
|
|
33
|
+
* Ok, now it allows arbitrary stuff after `:ss`, to allow millis/timezone info,
|
|
34
|
+
* but it will not take it into account.
|
|
35
|
+
*/
|
|
36
|
+
const DATE_TIME_REGEX_STRICT = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/;
|
|
37
|
+
const DATE_REGEX_STRICT = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
|
|
26
38
|
export class LocalTime {
|
|
27
39
|
constructor($date) {
|
|
28
40
|
this.$date = $date;
|
|
@@ -31,14 +43,14 @@ export class LocalTime {
|
|
|
31
43
|
* Returns [cloned] LocalTime that is based on the same unixtimestamp, but in UTC timezone.
|
|
32
44
|
* Opposite of `.local()` method.
|
|
33
45
|
*/
|
|
34
|
-
|
|
46
|
+
toUTC() {
|
|
35
47
|
return new LocalTime(new Date(this.$date.toISOString()));
|
|
36
48
|
}
|
|
37
49
|
/**
|
|
38
50
|
* Returns [cloned] LocalTime that is based on the same unixtimestamp, but in local timezone.
|
|
39
51
|
* Opposite of `.utc()` method.
|
|
40
52
|
*/
|
|
41
|
-
|
|
53
|
+
toLocal() {
|
|
42
54
|
return new LocalTime(new Date(this.$date.getTime()));
|
|
43
55
|
}
|
|
44
56
|
/**
|
|
@@ -160,34 +172,58 @@ export class LocalTime {
|
|
|
160
172
|
}
|
|
161
173
|
return t;
|
|
162
174
|
}
|
|
163
|
-
year(
|
|
164
|
-
return
|
|
175
|
+
get year() {
|
|
176
|
+
return this.$date.getFullYear();
|
|
165
177
|
}
|
|
166
|
-
|
|
167
|
-
return
|
|
178
|
+
setYear(v) {
|
|
179
|
+
return this.set('year', v);
|
|
168
180
|
}
|
|
169
|
-
|
|
170
|
-
return
|
|
181
|
+
get month() {
|
|
182
|
+
return this.$date.getMonth() + 1;
|
|
171
183
|
}
|
|
172
|
-
|
|
173
|
-
return
|
|
184
|
+
setMonth(v) {
|
|
185
|
+
return this.set('month', v);
|
|
174
186
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
187
|
+
get week() {
|
|
188
|
+
return getWeek(this.$date);
|
|
189
|
+
}
|
|
190
|
+
setWeek(v) {
|
|
191
|
+
return this.set('week', v);
|
|
192
|
+
}
|
|
193
|
+
get day() {
|
|
194
|
+
return this.$date.getDate();
|
|
195
|
+
}
|
|
196
|
+
setDay(v) {
|
|
197
|
+
return this.set('day', v);
|
|
198
|
+
}
|
|
199
|
+
get hour() {
|
|
200
|
+
return this.$date.getHours();
|
|
182
201
|
}
|
|
183
|
-
|
|
184
|
-
return
|
|
202
|
+
setHour(v) {
|
|
203
|
+
return this.set('hour', v);
|
|
185
204
|
}
|
|
186
|
-
minute(
|
|
187
|
-
return
|
|
205
|
+
get minute() {
|
|
206
|
+
return this.$date.getMinutes();
|
|
188
207
|
}
|
|
189
|
-
|
|
190
|
-
return
|
|
208
|
+
setMinute(v) {
|
|
209
|
+
return this.set('minute', v);
|
|
210
|
+
}
|
|
211
|
+
get second() {
|
|
212
|
+
return this.$date.getSeconds();
|
|
213
|
+
}
|
|
214
|
+
setSecond(v) {
|
|
215
|
+
return this.set('second', v);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Based on ISO: 1-7 is Mon-Sun.
|
|
219
|
+
*/
|
|
220
|
+
get dayOfWeek() {
|
|
221
|
+
return (this.$date.getDay() || 7);
|
|
222
|
+
}
|
|
223
|
+
setDayOfWeek(v) {
|
|
224
|
+
_assert(VALID_DAYS_OF_WEEK.has(v), `Invalid dayOfWeek: ${v}`);
|
|
225
|
+
const dow = this.$date.getDay() || 7;
|
|
226
|
+
return this.plus(v - dow, 'day');
|
|
191
227
|
}
|
|
192
228
|
setComponents(c, mutate = false) {
|
|
193
229
|
const d = mutate ? this.$date : new Date(this.$date);
|
|
@@ -255,7 +291,7 @@ export class LocalTime {
|
|
|
255
291
|
}
|
|
256
292
|
if (unit === 'year' || unit === 'month') {
|
|
257
293
|
const d = addMonths(this.$date, unit === 'month' ? num : num * 12, mutate);
|
|
258
|
-
return mutate ? this : localTime.
|
|
294
|
+
return mutate ? this : localTime.fromInput(d);
|
|
259
295
|
}
|
|
260
296
|
return this.set(unit, this.get(unit) + num, mutate);
|
|
261
297
|
}
|
|
@@ -266,7 +302,7 @@ export class LocalTime {
|
|
|
266
302
|
return Math.abs(this.diff(other, unit));
|
|
267
303
|
}
|
|
268
304
|
diff(other, unit) {
|
|
269
|
-
const date2 = localTime.
|
|
305
|
+
const date2 = localTime.fromInput(other).$date;
|
|
270
306
|
const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000;
|
|
271
307
|
if (!secDiff)
|
|
272
308
|
return 0;
|
|
@@ -354,32 +390,32 @@ export class LocalTime {
|
|
|
354
390
|
* Returns how many days are in the current month.
|
|
355
391
|
* E.g 31 for January.
|
|
356
392
|
*/
|
|
357
|
-
daysInMonth() {
|
|
393
|
+
get daysInMonth() {
|
|
358
394
|
return localDate.getMonthLength(this.$date.getFullYear(), this.$date.getMonth() + 1);
|
|
359
395
|
}
|
|
360
396
|
isSame(d) {
|
|
361
|
-
return this.
|
|
397
|
+
return this.compare(d) === 0;
|
|
362
398
|
}
|
|
363
399
|
isBefore(d, inclusive = false) {
|
|
364
|
-
const r = this.
|
|
400
|
+
const r = this.compare(d);
|
|
365
401
|
return r === -1 || (r === 0 && inclusive);
|
|
366
402
|
}
|
|
367
403
|
isSameOrBefore(d) {
|
|
368
|
-
return this.
|
|
404
|
+
return this.compare(d) <= 0;
|
|
369
405
|
}
|
|
370
406
|
isAfter(d, inclusive = false) {
|
|
371
|
-
const r = this.
|
|
407
|
+
const r = this.compare(d);
|
|
372
408
|
return r === 1 || (r === 0 && inclusive);
|
|
373
409
|
}
|
|
374
410
|
isSameOrAfter(d) {
|
|
375
|
-
return this.
|
|
411
|
+
return this.compare(d) >= 0;
|
|
376
412
|
}
|
|
377
413
|
isBetween(min, max, incl = '[)') {
|
|
378
|
-
let r = this.
|
|
414
|
+
let r = this.compare(min);
|
|
379
415
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
380
416
|
if (r < 0 || (r === 0 && incl[0] === '('))
|
|
381
417
|
return false;
|
|
382
|
-
r = this.
|
|
418
|
+
r = this.compare(max);
|
|
383
419
|
if (r > 0 || (r === 0 && incl[1] === ')'))
|
|
384
420
|
return false;
|
|
385
421
|
return true;
|
|
@@ -394,13 +430,13 @@ export class LocalTime {
|
|
|
394
430
|
* Third argument allows to override "now".
|
|
395
431
|
*/
|
|
396
432
|
isOlderThan(n, unit, now) {
|
|
397
|
-
return this.isBefore(localTime.
|
|
433
|
+
return this.isBefore(localTime.fromInput(now ?? new Date()).plus(-n, unit));
|
|
398
434
|
}
|
|
399
435
|
/**
|
|
400
436
|
* Checks if this localTime is same or older (<=) than "now" by X units.
|
|
401
437
|
*/
|
|
402
438
|
isSameOrOlderThan(n, unit, now) {
|
|
403
|
-
return this.isSameOrBefore(localTime.
|
|
439
|
+
return this.isSameOrBefore(localTime.fromInput(now ?? new Date()).plus(-n, unit));
|
|
404
440
|
}
|
|
405
441
|
/**
|
|
406
442
|
* Checks if this localTime is younger (>) than "now" by X units.
|
|
@@ -412,13 +448,13 @@ export class LocalTime {
|
|
|
412
448
|
* Third argument allows to override "now".
|
|
413
449
|
*/
|
|
414
450
|
isYoungerThan(n, unit, now) {
|
|
415
|
-
return this.isAfter(localTime.
|
|
451
|
+
return this.isAfter(localTime.fromInput(now ?? new Date()).plus(-n, unit));
|
|
416
452
|
}
|
|
417
453
|
/**
|
|
418
454
|
* Checks if this localTime is same or younger (>=) than "now" by X units.
|
|
419
455
|
*/
|
|
420
456
|
isSameOrYoungerThan(n, unit, now) {
|
|
421
|
-
return this.isSameOrAfter(localTime.
|
|
457
|
+
return this.isSameOrAfter(localTime.fromInput(now ?? new Date()).plus(-n, unit));
|
|
422
458
|
}
|
|
423
459
|
getAgeInYears(now) {
|
|
424
460
|
return this.getAgeIn('year', now);
|
|
@@ -439,42 +475,42 @@ export class LocalTime {
|
|
|
439
475
|
return this.getAgeIn('second', now);
|
|
440
476
|
}
|
|
441
477
|
getAgeIn(unit, now) {
|
|
442
|
-
return localTime.
|
|
478
|
+
return localTime.fromInput(now ?? new Date()).diff(this, unit);
|
|
443
479
|
}
|
|
444
480
|
/**
|
|
445
481
|
* Returns 1 if this > d
|
|
446
482
|
* returns 0 if they are equal
|
|
447
483
|
* returns -1 if this < d
|
|
448
484
|
*/
|
|
449
|
-
|
|
485
|
+
compare(d) {
|
|
450
486
|
const t1 = this.$date.valueOf();
|
|
451
|
-
const t2 = localTime.
|
|
487
|
+
const t2 = localTime.fromInput(d).$date.valueOf();
|
|
452
488
|
if (t1 === t2)
|
|
453
489
|
return 0;
|
|
454
490
|
return t1 < t2 ? -1 : 1;
|
|
455
491
|
}
|
|
456
|
-
|
|
492
|
+
toDateTimeObject() {
|
|
457
493
|
return {
|
|
458
|
-
...this.
|
|
459
|
-
...this.
|
|
494
|
+
...this.toDateObject(),
|
|
495
|
+
...this.toTimeObject(),
|
|
460
496
|
};
|
|
461
497
|
}
|
|
462
|
-
|
|
498
|
+
toDateObject() {
|
|
463
499
|
return {
|
|
464
500
|
year: this.$date.getFullYear(),
|
|
465
501
|
month: this.$date.getMonth() + 1,
|
|
466
502
|
day: this.$date.getDate(),
|
|
467
503
|
};
|
|
468
504
|
}
|
|
469
|
-
|
|
505
|
+
toTimeObject() {
|
|
470
506
|
return {
|
|
471
507
|
hour: this.$date.getHours(),
|
|
472
508
|
minute: this.$date.getMinutes(),
|
|
473
509
|
second: this.$date.getSeconds(),
|
|
474
510
|
};
|
|
475
511
|
}
|
|
476
|
-
|
|
477
|
-
const msDiff = localTime.
|
|
512
|
+
toFromNowString(now = new Date()) {
|
|
513
|
+
const msDiff = localTime.fromInput(now).$date.valueOf() - this.$date.valueOf();
|
|
478
514
|
if (msDiff === 0)
|
|
479
515
|
return 'now';
|
|
480
516
|
if (msDiff >= 0) {
|
|
@@ -482,16 +518,16 @@ export class LocalTime {
|
|
|
482
518
|
}
|
|
483
519
|
return `in ${_ms(msDiff * -1)}`;
|
|
484
520
|
}
|
|
485
|
-
|
|
521
|
+
toDate() {
|
|
486
522
|
return this.$date;
|
|
487
523
|
}
|
|
488
524
|
clone() {
|
|
489
525
|
return new LocalTime(new Date(this.$date));
|
|
490
526
|
}
|
|
491
|
-
unix() {
|
|
527
|
+
get unix() {
|
|
492
528
|
return Math.floor(this.$date.valueOf() / 1000);
|
|
493
529
|
}
|
|
494
|
-
unixMillis() {
|
|
530
|
+
get unixMillis() {
|
|
495
531
|
return this.$date.valueOf();
|
|
496
532
|
}
|
|
497
533
|
valueOf() {
|
|
@@ -523,7 +559,7 @@ export class LocalTime {
|
|
|
523
559
|
* Returns e.g: `1984-06-21`, only the date part of DateTime
|
|
524
560
|
*/
|
|
525
561
|
toISODate() {
|
|
526
|
-
const { year, month, day } = this.
|
|
562
|
+
const { year, month, day } = this.toDateObject();
|
|
527
563
|
return [
|
|
528
564
|
String(year).padStart(4, '0'),
|
|
529
565
|
String(month).padStart(2, '0'),
|
|
@@ -536,7 +572,7 @@ export class LocalTime {
|
|
|
536
572
|
* Returns e.g: `17:03:15` (or `17:03` with seconds=false)
|
|
537
573
|
*/
|
|
538
574
|
toISOTime(seconds = true) {
|
|
539
|
-
const { hour, minute, second } = this.
|
|
575
|
+
const { hour, minute, second } = this.toTimeObject();
|
|
540
576
|
return [
|
|
541
577
|
String(hour).padStart(2, '0'),
|
|
542
578
|
String(minute).padStart(2, '0'),
|
|
@@ -551,7 +587,7 @@ export class LocalTime {
|
|
|
551
587
|
* Returns e.g: `19840621_1705`
|
|
552
588
|
*/
|
|
553
589
|
toStringCompact(seconds = false) {
|
|
554
|
-
const { year, month, day, hour, minute, second } = this.
|
|
590
|
+
const { year, month, day, hour, minute, second } = this.toDateTimeObject();
|
|
555
591
|
return [
|
|
556
592
|
String(year).padStart(4, '0'),
|
|
557
593
|
String(month).padStart(2, '0'),
|
|
@@ -566,7 +602,7 @@ export class LocalTime {
|
|
|
566
602
|
return this.toISODateTime();
|
|
567
603
|
}
|
|
568
604
|
toJSON() {
|
|
569
|
-
return this.unix
|
|
605
|
+
return this.unix;
|
|
570
606
|
}
|
|
571
607
|
toMonthId() {
|
|
572
608
|
return this.toISODate().slice(0, 7);
|
|
@@ -580,105 +616,198 @@ export class LocalTime {
|
|
|
580
616
|
}
|
|
581
617
|
class LocalTimeFactory {
|
|
582
618
|
/**
|
|
583
|
-
*
|
|
584
|
-
*
|
|
619
|
+
* Creates a LocalTime from the input, unless it's falsy - then returns undefined.
|
|
620
|
+
*
|
|
621
|
+
* `localTime` function will instead return LocalTime of `now` for falsy input.
|
|
585
622
|
*/
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
_assert(t !== null, `Cannot parse "${d}" into LocalTime`, {
|
|
589
|
-
input: d,
|
|
590
|
-
});
|
|
591
|
-
return t;
|
|
623
|
+
orUndefined(input) {
|
|
624
|
+
return input || input === 0 ? this.fromInput(input) : undefined;
|
|
592
625
|
}
|
|
593
626
|
/**
|
|
594
|
-
*
|
|
627
|
+
* Creates a LocalTime from the input, unless it's falsy - then returns LocalTime.now
|
|
595
628
|
*/
|
|
596
|
-
|
|
597
|
-
return this.
|
|
629
|
+
orNow(input) {
|
|
630
|
+
return input || input === 0 ? this.fromInput(input) : this.now();
|
|
631
|
+
}
|
|
632
|
+
now() {
|
|
633
|
+
return new LocalTime(new Date());
|
|
598
634
|
}
|
|
599
635
|
/**
|
|
600
|
-
|
|
636
|
+
Convenience function to return current Unix timestamp in seconds.
|
|
637
|
+
Like Date.now(), but in seconds.
|
|
601
638
|
*/
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
return
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// unexpected type, e.g Function or something
|
|
618
|
-
return null;
|
|
639
|
+
nowUnix() {
|
|
640
|
+
return Math.floor(Date.now() / 1000);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Create LocalTime from LocalTimeInput.
|
|
644
|
+
* Input can already be a LocalTime - it is returned as-is in that case.
|
|
645
|
+
* Date - will be used as-is.
|
|
646
|
+
* String - will be parsed as strict `yyyy-mm-ddThh:mm:ss`.
|
|
647
|
+
* Number - will be treated as unix timestamp in seconds.
|
|
648
|
+
*/
|
|
649
|
+
fromInput(input) {
|
|
650
|
+
if (input instanceof LocalTime)
|
|
651
|
+
return input;
|
|
652
|
+
if (input instanceof Date) {
|
|
653
|
+
return this.fromDate(input);
|
|
619
654
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
if (date === null)
|
|
623
|
-
return null;
|
|
655
|
+
if (typeof input === 'number') {
|
|
656
|
+
return this.fromUnix(input);
|
|
624
657
|
}
|
|
658
|
+
// It means it's a string
|
|
659
|
+
// Will parse it STRICTLY
|
|
660
|
+
return this.fromIsoDateTimeString(input);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Returns true if input is valid to create LocalTime.
|
|
664
|
+
*/
|
|
665
|
+
isValid(input) {
|
|
666
|
+
if (!input)
|
|
667
|
+
return false;
|
|
668
|
+
if (input instanceof LocalTime)
|
|
669
|
+
return true;
|
|
670
|
+
if (input instanceof Date)
|
|
671
|
+
return !isNaN(input.getDate());
|
|
672
|
+
// We currently don't validate Unixtimestamp input, treat it as always valid
|
|
673
|
+
if (typeof input === 'number')
|
|
674
|
+
return true;
|
|
675
|
+
return this.isValidString(input);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Returns true if isoString is a valid iso8601 string like `yyyy-mm-ddThh:mm:dd`.
|
|
679
|
+
*/
|
|
680
|
+
isValidString(isoString) {
|
|
681
|
+
return !!this.parseStrictlyOrUndefined(isoString);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Tries to convert/parse the input into LocalTime.
|
|
685
|
+
* Uses LOOSE parsing.
|
|
686
|
+
* If invalid - doesn't throw, but returns undefined instead.
|
|
687
|
+
*/
|
|
688
|
+
try(input) {
|
|
689
|
+
if (input instanceof LocalTime)
|
|
690
|
+
return input;
|
|
691
|
+
if (input instanceof Date) {
|
|
692
|
+
if (isNaN(input.getDate()))
|
|
693
|
+
return;
|
|
694
|
+
return new LocalTime(input);
|
|
695
|
+
}
|
|
696
|
+
if (typeof input === 'number') {
|
|
697
|
+
return this.fromUnix(input);
|
|
698
|
+
}
|
|
699
|
+
if (!input)
|
|
700
|
+
return;
|
|
701
|
+
const date = this.parseLooselyOrUndefined(input);
|
|
702
|
+
return date ? new LocalTime(date) : undefined;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Performs STRICT parsing.
|
|
706
|
+
* Only allows IsoDateTimeString or IsoDateString input, nothing else.
|
|
707
|
+
*/
|
|
708
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
|
|
709
|
+
fromIsoDateTimeString(s) {
|
|
710
|
+
const d = this.parseStrictlyOrUndefined(s);
|
|
711
|
+
_assert(d, `Cannot parse "${s}" into LocalTime`);
|
|
712
|
+
return new LocalTime(d);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Performs LOOSE parsing.
|
|
716
|
+
* Tries to coerce imprefect/incorrect string input into IsoDateTimeString.
|
|
717
|
+
* Use with caution.
|
|
718
|
+
* Allows to input IsoDateString, will set h:m:s to zeros.
|
|
719
|
+
*/
|
|
720
|
+
parse(s) {
|
|
721
|
+
const d = this.parseLooselyOrUndefined(String(s));
|
|
722
|
+
_assert(d, `Cannot parse "${s}" into LocalTime`);
|
|
723
|
+
return new LocalTime(d);
|
|
724
|
+
}
|
|
725
|
+
parseStrictlyOrUndefined(s) {
|
|
726
|
+
if (!s || typeof s !== 'string')
|
|
727
|
+
return;
|
|
728
|
+
let m = DATE_TIME_REGEX_STRICT.exec(s);
|
|
729
|
+
if (!m) {
|
|
730
|
+
// DateTime regex didn't match, try just-Date regex
|
|
731
|
+
m = DATE_REGEX_STRICT.exec(s);
|
|
732
|
+
if (!m)
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const o = {
|
|
736
|
+
year: Number(m[1]),
|
|
737
|
+
month: Number(m[2]),
|
|
738
|
+
day: Number(m[3]),
|
|
739
|
+
hour: Number(m[4]) || 0,
|
|
740
|
+
minute: Number(m[5]) || 0,
|
|
741
|
+
second: Number(m[6]) || 0,
|
|
742
|
+
};
|
|
743
|
+
if (!this.isDateTimeObjectValid(o))
|
|
744
|
+
return;
|
|
745
|
+
return this.createDateFromDateTimeObject(o);
|
|
746
|
+
}
|
|
747
|
+
parseLooselyOrUndefined(s) {
|
|
748
|
+
if (!s || typeof s !== 'string')
|
|
749
|
+
return;
|
|
750
|
+
const m = DATE_TIME_REGEX_LOOSE.exec(s);
|
|
751
|
+
if (!m) {
|
|
752
|
+
if (s.length < 8)
|
|
753
|
+
return;
|
|
754
|
+
// Attempt to parse with Date constructor
|
|
755
|
+
const d = new Date(s);
|
|
756
|
+
return isNaN(d.getDate()) ? undefined : d;
|
|
757
|
+
}
|
|
758
|
+
const o = {
|
|
759
|
+
year: Number(m[1]),
|
|
760
|
+
month: Number(m[2]),
|
|
761
|
+
day: Number(m[3]) || 1,
|
|
762
|
+
// [4] is skipped due to extra regex parentheses group
|
|
763
|
+
hour: Number(m[5]) || 0,
|
|
764
|
+
minute: Number(m[6]) || 0,
|
|
765
|
+
second: Number(m[7]) || 0,
|
|
766
|
+
};
|
|
767
|
+
if (!this.isDateTimeObjectValid(o))
|
|
768
|
+
return;
|
|
769
|
+
return this.createDateFromDateTimeObject(o);
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Throws on invalid value.
|
|
773
|
+
*/
|
|
774
|
+
validateDateTimeObject(o) {
|
|
775
|
+
_assert(this.isDateTimeObjectValid(o), `Cannot construct LocalTime from: ${o.year}-${o.month}-${o.day} ${o.hour}:${o.minute}:${o.second}`);
|
|
776
|
+
}
|
|
777
|
+
isDateTimeObjectValid(o) {
|
|
778
|
+
return localDate.isDateObjectValid(o) && this.isTimeObjectValid(o);
|
|
779
|
+
}
|
|
780
|
+
isTimeObjectValid({ hour, minute, second }) {
|
|
781
|
+
return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59 && second >= 0 && second <= 59;
|
|
782
|
+
}
|
|
783
|
+
fromDate(date) {
|
|
784
|
+
_assert(!isNaN(date.getDate()), `localTime.fromDate is called on Date object that is invalid`);
|
|
625
785
|
return new LocalTime(date);
|
|
626
786
|
}
|
|
627
|
-
|
|
628
|
-
|
|
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);
|
|
678
|
-
}
|
|
679
|
-
isValid(d) {
|
|
680
|
-
return this.parseOrNull(d) !== null;
|
|
787
|
+
fromUnix(ts) {
|
|
788
|
+
return new LocalTime(new Date(ts * 1000));
|
|
681
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Create LocalTime from unixTimestamp in milliseconds (not in seconds).
|
|
792
|
+
*/
|
|
793
|
+
fromMillis(millis) {
|
|
794
|
+
return new LocalTime(new Date(millis));
|
|
795
|
+
}
|
|
796
|
+
fromDateTimeObject(o) {
|
|
797
|
+
// todo: validate?
|
|
798
|
+
return new LocalTime(this.createDateFromDateTimeObject(o));
|
|
799
|
+
}
|
|
800
|
+
createDateFromDateTimeObject(o) {
|
|
801
|
+
return new Date(o.year, o.month - 1, o.day || 1, o.hour || 0, o.minute || 0, o.second || 0);
|
|
802
|
+
}
|
|
803
|
+
// private assertNotNull(
|
|
804
|
+
// lt: LocalTime | null,
|
|
805
|
+
// input: LocalTimeInputNullable,
|
|
806
|
+
// ): asserts lt is LocalTime {
|
|
807
|
+
// _assert(lt !== null, `Cannot parse "${input}" into LocalTime`, {
|
|
808
|
+
// input,
|
|
809
|
+
// })
|
|
810
|
+
// }
|
|
682
811
|
/**
|
|
683
812
|
* Returns the IANA timezone e.g `Europe/Stockholm`.
|
|
684
813
|
* https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
|
@@ -696,26 +825,6 @@ class LocalTimeFactory {
|
|
|
696
825
|
isTimezoneValid(tz) {
|
|
697
826
|
return Intl.supportedValuesOf('timeZone').includes(tz);
|
|
698
827
|
}
|
|
699
|
-
now() {
|
|
700
|
-
return new LocalTime(new Date());
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Creates a LocalTime from the input, unless it's falsy - then returns undefined.
|
|
704
|
-
*
|
|
705
|
-
* `localTime` function will instead return LocalTime of `now` for falsy input.
|
|
706
|
-
*/
|
|
707
|
-
orUndefined(d) {
|
|
708
|
-
return d ? this.of(d) : undefined;
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Creates a LocalTime from the input, unless it's falsy - then returns LocalTime.now
|
|
712
|
-
*/
|
|
713
|
-
orNow(d) {
|
|
714
|
-
return d ? this.of(d) : this.now();
|
|
715
|
-
}
|
|
716
|
-
fromComponents(c) {
|
|
717
|
-
return new LocalTime(new Date(c.year, c.month - 1, c.day || 1, c.hour || 0, c.minute || 0, c.second || 0));
|
|
718
|
-
}
|
|
719
828
|
sort(items, dir = 'asc', mutate = false) {
|
|
720
829
|
const mod = dir === 'desc' ? -1 : 1;
|
|
721
830
|
return (mutate ? items : [...items]).sort((a, b) => {
|
|
@@ -733,7 +842,7 @@ class LocalTimeFactory {
|
|
|
733
842
|
const items2 = items.filter(_isTruthy);
|
|
734
843
|
_assert(items2.length, 'localTime.min called on empty array');
|
|
735
844
|
return items2
|
|
736
|
-
.map(i => this.
|
|
845
|
+
.map(i => this.fromInput(i))
|
|
737
846
|
.reduce((min, item) => (min.$date.valueOf() <= item.$date.valueOf() ? min : item));
|
|
738
847
|
}
|
|
739
848
|
maxOrUndefined(items) {
|
|
@@ -743,7 +852,7 @@ class LocalTimeFactory {
|
|
|
743
852
|
const items2 = items.filter(_isTruthy);
|
|
744
853
|
_assert(items2.length, 'localTime.max called on empty array');
|
|
745
854
|
return items2
|
|
746
|
-
.map(i => this.
|
|
855
|
+
.map(i => this.fromInput(i))
|
|
747
856
|
.reduce((max, item) => (max.$date.valueOf() >= item.$date.valueOf() ? max : item));
|
|
748
857
|
}
|
|
749
858
|
}
|
|
@@ -836,14 +945,7 @@ function differenceInMonths(a, b) {
|
|
|
836
945
|
return -(wholeMonthDiff + ((b.getTime() - anchor) / (anchor2 - anchor)) * sign);
|
|
837
946
|
}
|
|
838
947
|
const localTimeFactory = new LocalTimeFactory();
|
|
839
|
-
export const localTime = localTimeFactory.
|
|
948
|
+
export const localTime = localTimeFactory.fromInput.bind(localTimeFactory);
|
|
840
949
|
// The line below is the blackest of black magic I have ever written in 2024.
|
|
841
950
|
// And probably 2023 as well.
|
|
842
951
|
Object.setPrototypeOf(localTime, localTimeFactory);
|
|
843
|
-
/**
|
|
844
|
-
Convenience function to return current Unix timestamp in seconds.
|
|
845
|
-
Like Date.now(), but in seconds.
|
|
846
|
-
*/
|
|
847
|
-
export function nowUnix() {
|
|
848
|
-
return Math.floor(Date.now() / 1000);
|
|
849
|
-
}
|
|
@@ -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.fromInput(start).unix, localTime.fromInput(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.fromInput(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;
|