@naturalcycles/js-lib 14.242.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.
@@ -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 matches = typeof d === 'string' && DATE_REGEX.exec(d.slice(0, 10));
456
- if (!matches)
455
+ const m = typeof d === 'string' && DATE_REGEX.exec(d);
456
+ if (!m)
457
457
  return null;
458
- const year = Number(matches[1]);
459
- const month = Number(matches[2]);
460
- const day = Number(matches[3]);
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
- parseToDate(d: LocalTimeInput): Date;
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.parseToDate(other);
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.parseToDate(d).valueOf();
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.parseToDate(now).valueOf() - this.$date.valueOf();
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
- // Slicing removes the "timezone component", and makes the date "local"
623
- // e.g 2022-04-06T23:15:00+09:00
624
- // becomes 2022-04-06T23:15:00
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
- parseToDate(d) {
637
- if (d instanceof LocalTime)
638
- return d.$date;
639
- if (d instanceof Date)
640
- return d;
641
- const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d);
642
- (0, assert_1._assert)(!isNaN(date.getDate()), `Cannot parse "${d}" to Date`, {
643
- input: d,
644
- });
645
- return date;
646
- }
647
- parseToUnixTimestamp(d) {
648
- if (typeof d === 'number')
649
- return d;
650
- if (d instanceof LocalTime)
651
- return d.unix();
652
- const date = d instanceof Date ? d : new Date(d);
653
- (0, assert_1._assert)(!isNaN(date.getDate()), `Cannot parse "${d}" to UnixTimestamp`, {
654
- input: d,
655
- });
656
- return date.valueOf() / 1000;
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.parseToUnixTimestamp(start), localTime_1.localTime.parseToUnixTimestamp(end));
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.parseToUnixTimestamp(d);
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;
package/dist/types.d.ts CHANGED
@@ -180,10 +180,18 @@ export type NumberOfHours = number;
180
180
  export type NumberOfMinutes = number;
181
181
  export type NumberOfSeconds = number;
182
182
  export type NumberOfMilliseconds = number;
183
+ /**
184
+ * Integer between 0 and 100 (inclusive).
185
+ */
186
+ export type NumberOfPercent = number;
183
187
  /**
184
188
  * Same as `number`, but with semantic meaning that it's an Integer.
185
189
  */
186
190
  export type Integer = number;
191
+ export type PositiveInteger = number;
192
+ export type NonNegativeInteger = number;
193
+ export type PositiveNumber = number;
194
+ export type NonNegativeNumber = number;
187
195
  /**
188
196
  * Convenience type alias, that allows to write this:
189
197
  *
@@ -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 matches = typeof d === 'string' && DATE_REGEX.exec(d.slice(0, 10));
451
- if (!matches)
450
+ const m = typeof d === 'string' && DATE_REGEX.exec(d);
451
+ if (!m)
452
452
  return null;
453
- const year = Number(matches[1]);
454
- const month = Number(matches[2]);
455
- const day = Number(matches[3]);
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.parseToDate(other);
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.parseToDate(d).valueOf();
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.parseToDate(now).valueOf() - this.$date.valueOf();
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
- // Slicing removes the "timezone component", and makes the date "local"
618
- // e.g 2022-04-06T23:15:00+09:00
619
- // becomes 2022-04-06T23:15:00
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
- parseToDate(d) {
632
- if (d instanceof LocalTime)
633
- return d.$date;
634
- if (d instanceof Date)
635
- return d;
636
- const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d);
637
- _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to Date`, {
638
- input: d,
639
- });
640
- return date;
641
- }
642
- parseToUnixTimestamp(d) {
643
- if (typeof d === 'number')
644
- return d;
645
- if (d instanceof LocalTime)
646
- return d.unix();
647
- const date = d instanceof Date ? d : new Date(d);
648
- _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to UnixTimestamp`, {
649
- input: d,
650
- });
651
- return date.valueOf() / 1000;
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.parseToUnixTimestamp(start), localTime.parseToUnixTimestamp(end));
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.parseToUnixTimestamp(d);
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.242.0",
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 matches = typeof (d as any) === 'string' && DATE_REGEX.exec(d.slice(0, 10))
530
- if (!matches) return null
529
+ const m = typeof (d as any) === 'string' && DATE_REGEX.exec(d)
530
+ if (!m) return null
531
531
 
532
- const year = Number(matches[1])
533
- const month = Number(matches[2])
534
- const day = Number(matches[3])
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.parseToDate(other)
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.parseToDate(d).valueOf()
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.parseToDate(now).valueOf() - this.$date.valueOf()
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
- // Slicing removes the "timezone component", and makes the date "local"
733
- // e.g 2022-04-06T23:15:00+09:00
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
- parseToDate(d: LocalTimeInput): Date {
750
- if (d instanceof LocalTime) return d.$date
751
- if (d instanceof Date) return d
752
-
753
- const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d)
754
-
755
- _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to Date`, {
756
- input: d,
757
- })
758
-
759
- return date
760
- }
761
-
762
- parseToUnixTimestamp(d: LocalTimeInput): UnixTimestampNumber {
763
- if (typeof d === 'number') return d
764
- if (d instanceof LocalTime) return d.unix()
765
-
766
- const date = d instanceof Date ? d : new Date(d)
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
- _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to UnixTimestamp`, {
769
- input: d,
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 date.valueOf() / 1000
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.parseToUnixTimestamp(d)
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
package/src/types.ts CHANGED
@@ -237,11 +237,19 @@ export type NumberOfHours = number
237
237
  export type NumberOfMinutes = number
238
238
  export type NumberOfSeconds = number
239
239
  export type NumberOfMilliseconds = number
240
+ /**
241
+ * Integer between 0 and 100 (inclusive).
242
+ */
243
+ export type NumberOfPercent = number
240
244
 
241
245
  /**
242
246
  * Same as `number`, but with semantic meaning that it's an Integer.
243
247
  */
244
248
  export type Integer = number
249
+ export type PositiveInteger = number
250
+ export type NonNegativeInteger = number
251
+ export type PositiveNumber = number
252
+ export type NonNegativeNumber = number
245
253
 
246
254
  /**
247
255
  * Convenience type alias, that allows to write this: