@naturalcycles/js-lib 14.244.0 → 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.
Files changed (37) hide show
  1. package/dist/datetime/dateInterval.d.ts +1 -1
  2. package/dist/datetime/localDate.d.ts +62 -43
  3. package/dist/datetime/localDate.js +153 -123
  4. package/dist/datetime/localTime.d.ts +57 -28
  5. package/dist/datetime/localTime.js +205 -138
  6. package/dist/datetime/timeInterval.d.ts +2 -2
  7. package/dist/datetime/timeInterval.js +2 -2
  8. package/dist/error/error.util.d.ts +1 -1
  9. package/dist/index.d.ts +37 -37
  10. package/dist/index.js +37 -37
  11. package/dist/json-schema/from-data/generateJsonSchemaFromData.d.ts +1 -1
  12. package/dist/json-schema/jsonSchemaBuilder.d.ts +1 -1
  13. package/dist/object/object.util.d.ts +1 -1
  14. package/dist/time/time.util.js +4 -2
  15. package/dist/zod/zod.util.d.ts +1 -1
  16. package/dist-esm/datetime/localDate.js +153 -122
  17. package/dist-esm/datetime/localTime.js +205 -137
  18. package/dist-esm/datetime/timeInterval.js +2 -2
  19. package/dist-esm/decorators/logMethod.decorator.js +1 -1
  20. package/dist-esm/index.js +37 -37
  21. package/dist-esm/json-schema/jsonSchemaBuilder.js +1 -1
  22. package/dist-esm/time/time.util.js +4 -2
  23. package/package.json +1 -1
  24. package/src/datetime/dateInterval.ts +1 -1
  25. package/src/datetime/localDate.ts +157 -133
  26. package/src/datetime/localTime.ts +204 -149
  27. package/src/datetime/timeInterval.ts +4 -4
  28. package/src/decorators/logMethod.decorator.ts +1 -1
  29. package/src/define.ts +1 -1
  30. package/src/error/error.util.ts +3 -3
  31. package/src/http/fetcher.ts +1 -1
  32. package/src/index.ts +37 -37
  33. package/src/json-schema/from-data/generateJsonSchemaFromData.ts +1 -1
  34. package/src/json-schema/jsonSchemaBuilder.ts +2 -2
  35. package/src/object/object.util.ts +1 -1
  36. package/src/time/time.util.ts +4 -1
  37. package/src/zod/zod.util.ts +1 -1
@@ -10,14 +10,21 @@ import type {
10
10
  UnixTimestampMillisNumber,
11
11
  UnixTimestampNumber,
12
12
  } from '../types'
13
- import { DateObject, ISODayOfWeek, localTime, LocalTime } from './localTime'
13
+ import { DateObject, ISODayOfWeek, LocalTime, localTime } from './localTime'
14
14
 
15
15
  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
- // Regex is open-ended (no $ at the end) to support e.g Date+Time string to be parsed (time part will be dropped)
20
- const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)/
19
+ /**
20
+ * Regex is open-ended (no $ at the end) to support e.g Date+Time string to be parsed (time part will be dropped)
21
+ */
22
+ const DATE_REGEX_LOOSE = /^(\d\d\d\d)-(\d\d)-(\d\d)/
23
+ /**
24
+ * Strict version.
25
+ */
26
+ const DATE_REGEX_STRICT = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
27
+ const COMPACT_DATE_REGEX = /^(\d\d\d\d)(\d\d)(\d\d)$/
21
28
 
22
29
  export type LocalDateInput = LocalDate | Date | IsoDateString
23
30
  export type LocalDateInputNullable = LocalDateInput | null | undefined
@@ -67,7 +74,7 @@ export class LocalDate {
67
74
  }
68
75
 
69
76
  isSame(d: LocalDateInput): boolean {
70
- d = localDate.from(d)
77
+ d = localDate.fromInput(d)
71
78
  return this.day === d.day && this.month === d.month && this.year === d.year
72
79
  }
73
80
 
@@ -108,14 +115,14 @@ export class LocalDate {
108
115
  * Third argument allows to override "today".
109
116
  */
110
117
  isOlderThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
111
- return this.isBefore(localDate.from(today || new Date()).plus(-n, unit))
118
+ return this.isBefore(localDate.fromInput(today || new Date()).plus(-n, unit))
112
119
  }
113
120
 
114
121
  /**
115
122
  * Checks if this localDate is same or older (<=) than "today" by X units.
116
123
  */
117
124
  isSameOrOlderThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
118
- return this.isSameOrBefore(localDate.from(today || new Date()).plus(-n, unit))
125
+ return this.isSameOrBefore(localDate.fromInput(today || new Date()).plus(-n, unit))
119
126
  }
120
127
 
121
128
  /**
@@ -128,14 +135,14 @@ export class LocalDate {
128
135
  * Third argument allows to override "today".
129
136
  */
130
137
  isYoungerThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
131
- return this.isAfter(localDate.from(today || new Date()).plus(-n, unit))
138
+ return this.isAfter(localDate.fromInput(today || new Date()).plus(-n, unit))
132
139
  }
133
140
 
134
141
  /**
135
142
  * Checks if this localDate is same or younger (>=) than "today" by X units.
136
143
  */
137
144
  isSameOrYoungerThan(n: number, unit: LocalDateUnit, today?: LocalDateInput): boolean {
138
- return this.isSameOrAfter(localDate.from(today || new Date()).plus(-n, unit))
145
+ return this.isSameOrAfter(localDate.fromInput(today || new Date()).plus(-n, unit))
139
146
  }
140
147
 
141
148
  getAgeInYears(today?: LocalDateInput): number {
@@ -148,7 +155,7 @@ export class LocalDate {
148
155
  return this.getAgeIn('day', today)
149
156
  }
150
157
  getAgeIn(unit: LocalDateUnit, today?: LocalDateInput): number {
151
- return localDate.from(today || new Date()).diff(this, unit)
158
+ return localDate.fromInput(today || new Date()).diff(this, unit)
152
159
  }
153
160
 
154
161
  /**
@@ -157,7 +164,7 @@ export class LocalDate {
157
164
  * returns -1 if this < d
158
165
  */
159
166
  compare(d: LocalDateInput): -1 | 0 | 1 {
160
- d = localDate.from(d)
167
+ d = localDate.fromInput(d)
161
168
  if (this.year < d.year) return -1
162
169
  if (this.year > d.year) return 1
163
170
  if (this.month < d.month) return -1
@@ -180,7 +187,7 @@ export class LocalDate {
180
187
  * a.diff(b) means "a minus b"
181
188
  */
182
189
  diff(d: LocalDateInput, unit: LocalDateUnit): number {
183
- d = localDate.from(d)
190
+ d = localDate.fromInput(d)
184
191
 
185
192
  const sign = this.compare(d)
186
193
  if (!sign) return 0
@@ -351,8 +358,9 @@ export class LocalDate {
351
358
 
352
359
  endOf(unit: LocalDateUnitStrict): LocalDate {
353
360
  if (unit === 'day') return this
354
- if (unit === 'month')
361
+ if (unit === 'month') {
355
362
  return new LocalDate(this.year, this.month, localDate.getMonthLength(this.year, this.month))
363
+ }
356
364
  // year
357
365
  return new LocalDate(this.year, 12, 31)
358
366
  }
@@ -481,155 +489,181 @@ export class LocalDate {
481
489
 
482
490
  class LocalDateFactory {
483
491
  /**
484
- * Create LocalDate from LocalDateInput.
485
- * Input can already be a LocalDate - it is returned as-is in that case.
486
- * String - will be parsed as yyyy-mm-dd.
487
- * Date - will be converted to LocalDate (as-is, in whatever timezone it is - local or UTC).
488
- * No other formats are supported.
492
+ * Creates a LocalDate from the input, unless it's falsy - then returns undefined.
489
493
  *
490
- * Will throw if it fails to parse/construct LocalDate.
494
+ * Similar to `localDate.orToday`, but that will instead return Today on falsy input.
491
495
  */
492
- from(input: LocalDateInput): LocalDate {
493
- const ld = this.fromOrNull(input)
494
- this.assertNotNull(ld, input)
495
- return ld
496
+ orUndefined(d: LocalDateInputNullable): LocalDate | undefined {
497
+ return d ? this.fromInput(d) : undefined
496
498
  }
497
499
 
498
500
  /**
499
- * Tries to construct LocalDate from LocalDateInput, returns null otherwise.
500
- * Does not throw (returns null instead).
501
+ * Creates a LocalDate from the input, unless it's falsy - then returns localDate.today.
501
502
  */
502
- fromOrNull(d: LocalDateInputNullable): LocalDate | null {
503
- if (!d) return null
504
- if (d instanceof LocalDate) return d
505
- if (d instanceof Date) {
506
- return this.fromDate(d)
507
- }
508
- if (typeof (d as any) === 'string') {
509
- return this.fromStringOrNull(d)
510
- }
511
-
512
- return null
503
+ orToday(d: LocalDateInputNullable): LocalDate {
504
+ return d ? this.fromInput(d) : this.today()
513
505
  }
514
506
 
515
- fromString(s: string): LocalDate {
516
- const ld = this.fromStringOrNull(s)
517
- this.assertNotNull(ld, s)
518
- return ld
507
+ /**
508
+ * Creates LocalDate that represents `today` (in local timezone).
509
+ */
510
+ today(): LocalDate {
511
+ return this.fromDate(new Date())
519
512
  }
520
513
 
521
- fromStringOrNull(s: string | undefined | null): LocalDate | null {
522
- if (!s) return null
523
- const m = DATE_REGEX.exec(s)
524
- if (!m) return null
525
-
526
- const year = Number(m[1])
527
- const month = Number(m[2])
528
- const day = Number(m[3])
529
-
530
- if (
531
- !year ||
532
- !month ||
533
- month < 1 ||
534
- month > 12 ||
535
- !day ||
536
- day < 1 ||
537
- day > this.getMonthLength(year, month)
538
- ) {
539
- return null
540
- }
541
-
542
- return new LocalDate(year, month, day)
514
+ /**
515
+ * Creates LocalDate that represents `today` in UTC.
516
+ */
517
+ todayInUTC(): LocalDate {
518
+ return this.fromDateInUTC(new Date())
543
519
  }
544
520
 
545
521
  /**
546
- * Parses "compact iso8601 format", e.g `19840621` into LocalDate.
547
- * Throws if it fails to do so.
522
+ Convenience function to return current today's IsoDateString representation, e.g `2024-06-21`
548
523
  */
549
- fromCompactString(s: string): LocalDate {
550
- const [year, month, day] = [s.slice(0, 4), s.slice(4, 6), s.slice(6, 8)].map(Number)
551
-
552
- _assert(day && month && year, `Cannot parse compact string "${s}" into LocalDate`)
553
-
554
- return new LocalDate(year, month, day)
524
+ todayString(): IsoDateString {
525
+ return this.fromDate(new Date()).toISODate()
555
526
  }
556
527
 
557
528
  /**
558
- * Constructs LocalDate from Date.
559
- * Takes Date as-is, in its timezone - local or UTC.
529
+ * Create LocalDate from LocalDateInput.
530
+ * Input can already be a LocalDate - it is returned as-is in that case.
531
+ * String - will be parsed as yyyy-mm-dd.
532
+ * Date - will be converted to LocalDate (as-is, in whatever timezone it is - local or UTC).
533
+ * No other formats are supported.
534
+ *
535
+ * Will throw if it fails to parse/construct LocalDate.
560
536
  */
561
- fromDate(d: Date): LocalDate {
562
- return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
537
+ fromInput(input: LocalDateInput): LocalDate {
538
+ if (input instanceof LocalDate) return input
539
+ if (input instanceof Date) {
540
+ return this.fromDate(input)
541
+ }
542
+ // It means it's a string
543
+ return this.fromIsoDateString(input)
563
544
  }
564
545
 
565
546
  /**
566
- * Constructs LocalDate from Date.
567
- * Takes Date's year/month/day components in UTC, using getUTCFullYear, getUTCMonth, getUTCDate.
547
+ * Returns true if input is valid to create LocalDate.
568
548
  */
569
- fromDateInUTC(d: Date): LocalDate {
570
- return new LocalDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate())
549
+ isValid(input: LocalDateInputNullable): boolean {
550
+ if (!input) return false
551
+ if (input instanceof LocalDate) return true
552
+ if (input instanceof Date) return !isNaN(input.getDate())
553
+ return this.isValidString(input)
571
554
  }
572
555
 
573
556
  /**
574
- * Create LocalDate from year, month and day components.
557
+ * Returns true if isoString is a valid iso8601 string like `yyyy-mm-dd`.
575
558
  */
576
- fromComponents(year: number, month: number, day: number): LocalDate {
577
- return new LocalDate(year, month, day)
559
+ isValidString(isoString: string | undefined | null): boolean {
560
+ return !!this.parseToLocalDateOrUndefined(DATE_REGEX_STRICT, isoString)
578
561
  }
579
562
 
580
- fromDateObject(o: DateObject): LocalDate {
581
- const { year, month, day } = o
582
- return new LocalDate(year, month, day)
563
+ /**
564
+ * Tries to convert/parse the input into LocalDate.
565
+ * Uses LOOSE parsing.
566
+ * If invalid - doesn't throw, but returns undefined instead.
567
+ */
568
+ try(input: LocalDateInputNullable): LocalDate | undefined {
569
+ if (!input) return
570
+ if (input instanceof LocalDate) return input
571
+ if (input instanceof Date) {
572
+ if (isNaN(input.getDate())) return
573
+ return new LocalDate(input.getFullYear(), input.getMonth() + 1, input.getDate())
574
+ }
575
+ return this.parseToLocalDateOrUndefined(DATE_REGEX_LOOSE, input)
583
576
  }
584
577
 
585
- private assertNotNull(
586
- ld: LocalDate | null,
587
- input: LocalDateInputNullable,
588
- ): asserts ld is LocalDate {
589
- _assert(ld !== null, `Cannot parse "${input}" into LocalDate`, {
590
- input,
591
- })
578
+ /**
579
+ * Performs STRICT parsing.
580
+ * Only allows IsoDateString input, nothing else.
581
+ */
582
+ fromIsoDateString(s: IsoDateString): LocalDate {
583
+ return this.parseToLocalDate(DATE_REGEX_STRICT, s)
592
584
  }
593
585
 
594
- getYearLength(year: number): number {
595
- return this.isLeapYear(year) ? 366 : 365
586
+ /**
587
+ * Parses "compact iso8601 format", e.g `19840621` into LocalDate.
588
+ * Throws if it fails to do so.
589
+ */
590
+ fromCompactString(s: string): LocalDate {
591
+ return this.parseToLocalDate(COMPACT_DATE_REGEX, s)
596
592
  }
597
593
 
598
- getMonthLength(year: number, month: number): number {
599
- if (month === 2) return this.isLeapYear(year) ? 29 : 28
600
- return MDAYS[month]!
594
+ /**
595
+ * Performs LOOSE parsing.
596
+ * Tries to coerce imprefect/incorrect string input into IsoDateString.
597
+ * Use with caution.
598
+ * Allows to input IsoDateTimeString, will drop the Time part of it.
599
+ */
600
+ parse(s: string): LocalDate {
601
+ return this.parseToLocalDate(DATE_REGEX_LOOSE, String(s))
601
602
  }
602
603
 
603
- isLeapYear(year: number): boolean {
604
- return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
604
+ /**
605
+ * Throws if it fails to parse the input string via Regex and YMD validation.
606
+ */
607
+ private parseToLocalDate(regex: RegExp, s: string): LocalDate {
608
+ const ld = this.parseToLocalDateOrUndefined(regex, s)
609
+ _assert(ld, `Cannot parse "${s}" into LocalDate`)
610
+ return ld
605
611
  }
606
612
 
607
613
  /**
608
- * Returns true if input is valid to create LocalDate.
614
+ * Tries to parse the input string, returns undefined if input is invalid.
609
615
  */
610
- isValid(input: LocalDateInputNullable): boolean {
611
- return this.fromOrNull(input) !== null
616
+ private parseToLocalDateOrUndefined(
617
+ regex: RegExp,
618
+ s: string | undefined | null,
619
+ ): LocalDate | undefined {
620
+ if (!s || typeof (s as any) !== 'string') return
621
+ const m = regex.exec(s)
622
+ if (!m) return
623
+ const year = Number(m[1])
624
+ const month = Number(m[2])
625
+ const day = Number(m[3])
626
+ if (!this.isDateObjectValid({ year, month, day })) return
627
+ return new LocalDate(year, month, day)
612
628
  }
613
629
 
614
630
  /**
615
- * Returns true if isoString is a valid iso8601 string like `yyyy-mm-dd`.
631
+ * Throws on invalid value.
616
632
  */
617
- isValidString(isoString: string | undefined | null): boolean {
618
- return this.fromStringOrNull(isoString) !== null
633
+ private validateDateObject(o: DateObject): void {
634
+ _assert(
635
+ this.isDateObjectValid(o),
636
+ `Cannot construct LocalDate from: ${o.year}-${o.month}-${o.day}`,
637
+ )
638
+ }
639
+
640
+ isDateObjectValid({ year, month, day }: DateObject): boolean {
641
+ return (
642
+ !!year && month >= 1 && month <= 12 && day >= 1 && day <= this.getMonthLength(year, month)
643
+ )
619
644
  }
620
645
 
621
646
  /**
622
- * Creates LocalDate that represents `today` (in local timezone).
647
+ * Constructs LocalDate from Date.
648
+ * Takes Date as-is, in its timezone - local or UTC.
623
649
  */
624
- today(): LocalDate {
625
- return this.fromDate(new Date())
650
+ fromDate(d: Date): LocalDate {
651
+ _assert(!isNaN(d.getDate()), `localDate.fromDate is called on Date object that is invalid`)
652
+ return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
626
653
  }
627
654
 
628
655
  /**
629
- * Creates LocalDate that represents `today` in UTC.
656
+ * Constructs LocalDate from Date.
657
+ * Takes Date's year/month/day components in UTC, using getUTCFullYear, getUTCMonth, getUTCDate.
630
658
  */
631
- todayInUTC(): LocalDate {
632
- return this.fromDateInUTC(new Date())
659
+ fromDateInUTC(d: Date): LocalDate {
660
+ _assert(!isNaN(d.getDate()), `localDate.fromDateInUTC is called on Date object that is invalid`)
661
+ return new LocalDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate())
662
+ }
663
+
664
+ fromDateObject(o: DateObject): LocalDate {
665
+ this.validateDateObject(o)
666
+ return new LocalDate(o.year, o.month, o.day)
633
667
  }
634
668
 
635
669
  /**
@@ -656,7 +690,7 @@ class LocalDateFactory {
656
690
  _assert(items2.length, 'localDate.min called on empty array')
657
691
 
658
692
  return items2
659
- .map(i => this.from(i))
693
+ .map(i => this.fromInput(i))
660
694
  .reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
661
695
  }
662
696
 
@@ -676,7 +710,7 @@ class LocalDateFactory {
676
710
  _assert(items2.length, 'localDate.max called on empty array')
677
711
 
678
712
  return items2
679
- .map(i => this.from(i))
713
+ .map(i => this.fromInput(i))
680
714
  .reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
681
715
  }
682
716
 
@@ -710,8 +744,8 @@ class LocalDateFactory {
710
744
  stepUnit = 'day'
711
745
  }
712
746
 
713
- const $min = this.from(min).startOf(stepUnit)
714
- const $max = this.from(max).startOf(stepUnit)
747
+ const $min = this.fromInput(min).startOf(stepUnit)
748
+ const $max = this.fromInput(max).startOf(stepUnit)
715
749
 
716
750
  let value = $min
717
751
  // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
@@ -736,20 +770,17 @@ class LocalDateFactory {
736
770
  })
737
771
  }
738
772
 
739
- /**
740
- * Creates a LocalDate from the input, unless it's falsy - then returns undefined.
741
- *
742
- * Similar to `localDate.orToday`, but that will instead return Today on falsy input.
743
- */
744
- orUndefined(d: LocalDateInputNullable): LocalDate | undefined {
745
- return d ? this.from(d) : undefined
773
+ getYearLength(year: number): number {
774
+ return this.isLeapYear(year) ? 366 : 365
746
775
  }
747
776
 
748
- /**
749
- * Creates a LocalDate from the input, unless it's falsy - then returns localDate.today.
750
- */
751
- orToday(d: LocalDateInputNullable): LocalDate {
752
- return d ? this.from(d) : this.today()
777
+ getMonthLength(year: number, month: number): number {
778
+ if (month === 2) return this.isLeapYear(year) ? 29 : 28
779
+ return MDAYS[month]!
780
+ }
781
+
782
+ isLeapYear(year: number): boolean {
783
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
753
784
  }
754
785
  }
755
786
 
@@ -759,15 +790,8 @@ interface LocalDateFn extends LocalDateFactory {
759
790
 
760
791
  const localDateFactory = new LocalDateFactory()
761
792
 
762
- export const localDate = localDateFactory.from.bind(localDateFactory) as LocalDateFn
793
+ export const localDate = localDateFactory.fromInput.bind(localDateFactory) as LocalDateFn
763
794
 
764
795
  // The line below is the blackest of black magic I have ever written in 2024.
765
796
  // And probably 2023 as well.
766
797
  Object.setPrototypeOf(localDate, localDateFactory)
767
-
768
- /**
769
- Convenience function to return current today's IsoDateString representation, e.g `2024-06-21`
770
- */
771
- export function todayString(): IsoDateString {
772
- return localDate.fromDate(new Date()).toISODate()
773
- }