@bgord/tools 0.15.1 → 0.16.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 (122) hide show
  1. package/dist/basename.vo.d.ts +11 -2
  2. package/dist/basename.vo.js +22 -13
  3. package/dist/date-calculator.service.d.ts +2 -2
  4. package/dist/date-calculator.service.js +10 -11
  5. package/dist/date-range.vo.d.ts +1 -0
  6. package/dist/date-range.vo.js +2 -1
  7. package/dist/day-iso-id.vo.d.ts +3 -0
  8. package/dist/day-iso-id.vo.js +4 -4
  9. package/dist/day.vo.js +12 -10
  10. package/dist/directory-path-absolute.vo.d.ts +5 -0
  11. package/dist/directory-path-absolute.vo.js +11 -5
  12. package/dist/directory-path-relative.vo.d.ts +6 -0
  13. package/dist/directory-path-relative.vo.js +12 -6
  14. package/dist/dll.service.js +37 -31
  15. package/dist/extension.vo.d.ts +6 -2
  16. package/dist/extension.vo.js +11 -9
  17. package/dist/file-path-absolute-schema.vo.d.ts +5 -0
  18. package/dist/file-path-absolute-schema.vo.js +12 -6
  19. package/dist/file-path-relative-schema.vo.d.ts +6 -1
  20. package/dist/file-path-relative-schema.vo.js +10 -6
  21. package/dist/file-path.vo.js +4 -4
  22. package/dist/filename-from-string.vo.d.ts +6 -4
  23. package/dist/filename-from-string.vo.js +15 -14
  24. package/dist/filename-suffix.vo.d.ts +4 -2
  25. package/dist/filename-suffix.vo.js +5 -3
  26. package/dist/filename.vo.d.ts +2 -2
  27. package/dist/filename.vo.js +9 -9
  28. package/dist/height.vo.d.ts +6 -4
  29. package/dist/height.vo.js +56 -51
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.js +0 -1
  32. package/dist/language.vo.d.ts +1 -1
  33. package/dist/language.vo.js +1 -2
  34. package/dist/mime.vo.d.ts +3 -1
  35. package/dist/mime.vo.js +8 -6
  36. package/dist/month-iso-id.vo.d.ts +3 -0
  37. package/dist/month-iso-id.vo.js +7 -12
  38. package/dist/month.vo.js +15 -13
  39. package/dist/object-key.vo.d.ts +5 -0
  40. package/dist/object-key.vo.js +16 -6
  41. package/dist/package-version.vo.d.ts +3 -0
  42. package/dist/package-version.vo.js +12 -34
  43. package/dist/pagination.service.d.ts +1 -1
  44. package/dist/pagination.service.js +11 -11
  45. package/dist/quarter-iso-id.vo.d.ts +3 -0
  46. package/dist/quarter-iso-id.vo.js +8 -7
  47. package/dist/rate-limiter.service.d.ts +3 -2
  48. package/dist/rate-limiter.service.js +4 -2
  49. package/dist/reordering.service.d.ts +20 -2
  50. package/dist/reordering.service.js +49 -29
  51. package/dist/revision.vo.d.ts +8 -3
  52. package/dist/revision.vo.js +13 -6
  53. package/dist/rounding.adapter.js +1 -2
  54. package/dist/size.vo.d.ts +1 -0
  55. package/dist/size.vo.js +4 -7
  56. package/dist/streak-calculator.service.d.ts +3 -4
  57. package/dist/streak-calculator.service.js +11 -17
  58. package/dist/time-zone-offset-value.vo.d.ts +1 -1
  59. package/dist/time-zone-offset-value.vo.js +1 -7
  60. package/dist/time.service.d.ts +11 -6
  61. package/dist/time.service.js +31 -18
  62. package/dist/timezone.vo.js +1 -3
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/dist/week-iso-id.vo.d.ts +3 -0
  65. package/dist/week-iso-id.vo.js +4 -4
  66. package/dist/week.vo.js +1 -1
  67. package/dist/weekday.vo.d.ts +7 -6
  68. package/dist/weekday.vo.js +20 -13
  69. package/dist/weight.vo.d.ts +12 -0
  70. package/dist/weight.vo.js +37 -27
  71. package/dist/year-iso-id.vo.d.ts +3 -0
  72. package/dist/year-iso-id.vo.js +4 -6
  73. package/dist/year.vo.d.ts +2 -0
  74. package/dist/year.vo.js +4 -2
  75. package/package.json +1 -1
  76. package/readme.md +0 -1
  77. package/src/basename.vo.ts +25 -14
  78. package/src/clock.vo.ts +1 -0
  79. package/src/date-calculator.service.ts +10 -15
  80. package/src/date-range.vo.ts +3 -1
  81. package/src/day-iso-id.vo.ts +9 -10
  82. package/src/day.vo.ts +17 -10
  83. package/src/directory-path-absolute.vo.ts +12 -5
  84. package/src/directory-path-relative.vo.ts +13 -6
  85. package/src/dll.service.ts +45 -43
  86. package/src/extension.vo.ts +14 -12
  87. package/src/file-path-absolute-schema.vo.ts +15 -6
  88. package/src/file-path-relative-schema.vo.ts +13 -6
  89. package/src/file-path.vo.ts +15 -11
  90. package/src/filename-from-string.vo.ts +20 -15
  91. package/src/filename-suffix.vo.ts +8 -4
  92. package/src/filename.vo.ts +14 -15
  93. package/src/height.vo.ts +71 -53
  94. package/src/index.ts +0 -1
  95. package/src/language.vo.ts +1 -2
  96. package/src/mime.vo.ts +10 -7
  97. package/src/month-iso-id.vo.ts +10 -20
  98. package/src/month.vo.ts +19 -13
  99. package/src/object-key.vo.ts +21 -7
  100. package/src/outlier-detector.service.ts +1 -0
  101. package/src/package-version.vo.ts +18 -47
  102. package/src/pagination.service.ts +15 -13
  103. package/src/quarter-iso-id.vo.ts +11 -13
  104. package/src/quarter.vo.ts +3 -0
  105. package/src/rate-limiter.service.ts +7 -7
  106. package/src/reordering.service.ts +52 -38
  107. package/src/revision.vo.ts +17 -8
  108. package/src/rounding.adapter.ts +1 -3
  109. package/src/size.vo.ts +6 -16
  110. package/src/streak-calculator.service.ts +12 -17
  111. package/src/time-zone-offset-value.vo.ts +2 -7
  112. package/src/time.service.ts +43 -45
  113. package/src/timezone.vo.ts +1 -3
  114. package/src/week-iso-id.vo.ts +13 -14
  115. package/src/week.vo.ts +4 -2
  116. package/src/weekday.vo.ts +27 -13
  117. package/src/weight.vo.ts +49 -30
  118. package/src/year-iso-id.vo.ts +6 -9
  119. package/src/year.vo.ts +12 -2
  120. package/dist/stepper.service.d.ts +0 -23
  121. package/dist/stepper.service.js +0 -33
  122. package/src/stepper.service.ts +0 -43
@@ -12,75 +12,74 @@ interface TimeResultInterface {
12
12
 
13
13
  isAfter(another: TimeResultInterface): boolean;
14
14
  isBefore(another: TimeResultInterface): boolean;
15
+
16
+ add(another: TimeResultInterface): TimeResultInterface;
17
+ subtract(another: TimeResultInterface): TimeResultInterface;
15
18
  }
16
19
 
17
20
  export class TimeResult implements TimeResultInterface {
18
- constructor(
19
- readonly days: number,
20
- readonly hours: number,
21
- readonly minutes: number,
22
- readonly seconds: number,
23
- readonly ms: TimestampType,
24
- ) {}
21
+ private readonly valueMs: TimestampType;
22
+
23
+ constructor(ms: TimestampType) {
24
+ this.valueMs = ms;
25
+ }
26
+
27
+ get days(): number {
28
+ return rounding.round(this.valueMs / 86_400_000);
29
+ }
30
+
31
+ get hours(): number {
32
+ return rounding.round(this.valueMs / 3_600_000);
33
+ }
34
+
35
+ get minutes(): number {
36
+ return rounding.round(this.valueMs / 60_000);
37
+ }
38
+
39
+ get seconds(): number {
40
+ return rounding.round(this.valueMs / 1_000);
41
+ }
42
+
43
+ get ms(): TimestampType {
44
+ return this.valueMs;
45
+ }
25
46
 
26
47
  isAfter(another: TimeResultInterface): boolean {
27
- return this.ms > another.ms;
48
+ return this.valueMs > another.ms;
28
49
  }
29
50
 
30
51
  isBefore(another: TimeResultInterface): boolean {
31
- return this.ms < another.ms;
52
+ return this.valueMs < another.ms;
53
+ }
54
+
55
+ add(another: TimeResultInterface): TimeResultInterface {
56
+ return new TimeResult((this.valueMs + another.ms) as TimestampType);
57
+ }
58
+
59
+ subtract(another: TimeResultInterface): TimeResultInterface {
60
+ return new TimeResult((this.valueMs - another.ms) as TimestampType);
32
61
  }
33
62
  }
34
63
 
35
64
  export class Time {
36
65
  static Days(value: number): TimeResultInterface {
37
- return new TimeResult(
38
- value,
39
- value * 24,
40
- value * 24 * 60,
41
- value * 24 * 60 * 60,
42
- (value * 24 * 60 * 60 * 1000) as TimestampType,
43
- );
66
+ return new TimeResult((value * 86_400_000) as TimestampType);
44
67
  }
45
68
 
46
69
  static Hours(value: number): TimeResultInterface {
47
- return new TimeResult(
48
- rounding.round(value / 24),
49
- value,
50
- value * 60,
51
- value * 60 * 60,
52
- (value * 60 * 60 * 1000) as TimestampType,
53
- );
70
+ return new TimeResult((value * 3_600_000) as TimestampType);
54
71
  }
55
72
 
56
73
  static Minutes(value: number): TimeResultInterface {
57
- return new TimeResult(
58
- rounding.round(value / 60 / 24),
59
- rounding.round(value / 60),
60
- value,
61
- value * 60,
62
- (value * 60 * 1000) as TimestampType,
63
- );
74
+ return new TimeResult((value * 60_000) as TimestampType);
64
75
  }
65
76
 
66
77
  static Seconds(value: number): TimeResultInterface {
67
- return new TimeResult(
68
- rounding.round(value / 60 / 60 / 24),
69
- rounding.round(value / 60 / 60),
70
- rounding.round(value / 60),
71
- value,
72
- (value * 1000) as TimestampType,
73
- );
78
+ return new TimeResult((value * 1_000) as TimestampType);
74
79
  }
75
80
 
76
81
  static Ms(value: number): TimeResultInterface {
77
- return new TimeResult(
78
- rounding.round(value / 1000 / 60 / 60 / 24),
79
- rounding.round(value / 1000 / 60 / 60),
80
- rounding.round(value / 1000 / 60),
81
- rounding.round(value / 1000),
82
- value as TimestampType,
83
- );
82
+ return new TimeResult(value as TimestampType);
84
83
  }
85
84
 
86
85
  static Now(now: TimestampType) {
@@ -88,7 +87,6 @@ export class Time {
88
87
  Minus(time: TimeResultInterface): TimeResultInterface {
89
88
  return Time.Ms(now - time.ms);
90
89
  },
91
-
92
90
  Add(time: TimeResultInterface): TimeResultInterface {
93
91
  return Time.Ms(now + time.ms);
94
92
  },
@@ -7,9 +7,7 @@ export const Timezone = z
7
7
  .min(1, TimezoneError)
8
8
  .refine((value) => {
9
9
  try {
10
- const date = new Date();
11
- const formatter = new Intl.DateTimeFormat("en-US", { timeZone: value });
12
- formatter.format(date);
10
+ new Intl.DateTimeFormat("en-US", { timeZone: value }).format(new Date());
13
11
  return true;
14
12
  } catch (_error) {
15
13
  return false;
@@ -1,23 +1,22 @@
1
1
  import { getISOWeeksInYear } from "date-fns";
2
2
  import { z } from "zod/v4";
3
3
 
4
+ export const WeekIsoIdError = { error: "week-iso-id.invalid" } as const;
5
+
4
6
  export const WeekIsoId = z
5
- .string()
6
- .regex(/^\d{4}-W\d{2}$/)
7
- .refine(
8
- (value) => {
9
- const [yearPart, weekPart] = value.split("-W");
7
+ .string(WeekIsoIdError)
8
+ .regex(/^\d{4}-W\d{2}$/, WeekIsoIdError)
9
+ .refine((value) => {
10
+ const [yearPart, weekPart] = value.split("-W");
11
+
12
+ const year = Number(yearPart);
13
+ const week = Number(weekPart);
10
14
 
11
- const year = Number(yearPart);
12
- const week = Number(weekPart);
15
+ if (!(Number.isInteger(year) && Number.isInteger(week)) || week < 1) return false;
13
16
 
14
- if (!(Number.isInteger(year) && Number.isInteger(week)) || week < 1) return false;
17
+ const weeksInYear = getISOWeeksInYear(new Date(Date.UTC(year, 0, 4)));
15
18
 
16
- // Does this ISO week-year actually have that many weeks?
17
- const weeksInYear = getISOWeeksInYear(new Date(Date.UTC(year, 0, 4)));
19
+ return week <= weeksInYear;
20
+ }, WeekIsoIdError);
18
21
 
19
- return week <= weeksInYear;
20
- },
21
- { message: "week-iso-id.invalid" },
22
- );
23
22
  export type WeekIsoIdType = z.infer<typeof WeekIsoId>;
package/src/week.vo.ts CHANGED
@@ -8,21 +8,24 @@ export class Week extends DateRange {
8
8
  const year = getISOWeekYear(this.getStart());
9
9
  const week = getISOWeek(this.getStart()).toString().padStart(2, "0");
10
10
 
11
- return `${year}-W${week}`;
11
+ return WeekIsoId.parse(`${year}-W${week}`);
12
12
  }
13
13
 
14
14
  previous(): Week {
15
15
  const shifted = addWeeks(new Date(this.getStart()), -1).getTime();
16
+
16
17
  return Week.fromTimestamp(Timestamp.parse(shifted));
17
18
  }
18
19
 
19
20
  next(): Week {
20
21
  const shifted = addWeeks(new Date(this.getStart()), 1).getTime();
22
+
21
23
  return Week.fromTimestamp(Timestamp.parse(shifted));
22
24
  }
23
25
 
24
26
  shift(count: number): Week {
25
27
  const shifted = addWeeks(new Date(this.getStart()), count).getTime();
28
+
26
29
  return Week.fromTimestamp(Timestamp.parse(shifted));
27
30
  }
28
31
 
@@ -39,7 +42,6 @@ export class Week extends DateRange {
39
42
 
40
43
  static fromIsoId(isoId: WeekIsoIdType): Week {
41
44
  const [yearPart, weekPart] = WeekIsoId.parse(isoId).split("-W");
42
-
43
45
  const year = Number(yearPart);
44
46
  const week = Number(weekPart);
45
47
 
package/src/weekday.vo.ts CHANGED
@@ -9,6 +9,8 @@ export enum WeekdayFormatterEnum {
9
9
  ZERO_BASED_NUMBER = "ZERO_BASED_NUMBER", // Sunday=0 ... Saturday=6 (JS)
10
10
  }
11
11
 
12
+ export const WeekdayValueError = "invalid.weekday" as const;
13
+
12
14
  const FULL_NAMES: readonly string[] = [
13
15
  "Sunday",
14
16
  "Monday",
@@ -29,8 +31,10 @@ export const WeekdayFormatters: Record<WeekdayFormatterEnum, WeekdayFormatter> =
29
31
  } as const;
30
32
 
31
33
  export class Weekday {
34
+ // 0..6 (Sun..Sat)
32
35
  private readonly value: number;
33
36
 
37
+ // default formatter used by toString()/format() when no runtime formatter given
34
38
  private readonly formatter: WeekdayFormatter;
35
39
 
36
40
  static readonly SUNDAY = new Weekday(0);
@@ -42,28 +46,35 @@ export class Weekday {
42
46
  static readonly SATURDAY = new Weekday(6);
43
47
 
44
48
  constructor(candidate: number, formatter?: WeekdayFormatter) {
45
- if (!Number.isInteger(candidate)) throw new Error("Invalid weekday");
46
- if (candidate < 0) throw new Error("Invalid weekday");
47
- if (candidate > 6) throw new Error("Invalid weekday");
49
+ if (!Number.isInteger(candidate) || candidate < 0 || candidate > 6) throw new Error(WeekdayValueError);
48
50
 
49
51
  this.value = candidate;
50
- this.formatter = (formatter as WeekdayFormatter) ?? WeekdayFormatters.FULL;
52
+ this.formatter = formatter ?? WeekdayFormatters.FULL;
51
53
  }
52
54
 
53
55
  static fromUtcTimestamp(timestamp: TimestampType, formatter?: WeekdayFormatter): Weekday {
54
- const day = new Date(timestamp).getUTCDay(); // 0..6
55
- return new Weekday(day, formatter);
56
+ const dayZeroBased = new Date(timestamp).getUTCDay(); // 0..6
57
+ return new Weekday(dayZeroBased, formatter);
58
+ }
59
+
60
+ get(): number {
61
+ return this.value;
56
62
  }
57
63
 
58
- get(formatter?: WeekdayFormatter) {
59
- const format = formatter ?? this.formatter;
60
- return { raw: this.value, formatted: format(this.value) };
64
+ format(formatter?: WeekdayFormatter): string {
65
+ const chosen = formatter ?? this.formatter;
66
+ return chosen(this.value);
67
+ }
68
+
69
+ toString(): string {
70
+ return this.format(WeekdayFormatters.FULL);
61
71
  }
62
72
 
63
73
  equals(another: Weekday): boolean {
64
- return this.value === another.get().raw;
74
+ return this.value === another.value;
65
75
  }
66
76
 
77
+ /** ISO-8601 weekday number: Monday=1 ... Sunday=7 */
67
78
  toIsoNumber(): number {
68
79
  return this.value === 0 ? 7 : this.value;
69
80
  }
@@ -90,12 +101,15 @@ export class Weekday {
90
101
  return this.value === 0;
91
102
  }
92
103
 
93
- static list(formatter?: WeekdayFormatter): Weekday[] {
94
- return Array.from({ length: 7 }).map((_, index) => new Weekday(index, formatter));
104
+ static list(formatter?: WeekdayFormatter): readonly Weekday[] {
105
+ const chosen = formatter ?? undefined;
106
+
107
+ return Array.from({ length: 7 }, (_, index) => new Weekday(index, chosen));
95
108
  }
96
109
 
97
- static listMondayFirst(formatter?: WeekdayFormatter): Weekday[] {
110
+ static listMondayFirst(formatter?: WeekdayFormatter): readonly Weekday[] {
98
111
  const days = Weekday.list(formatter);
112
+
99
113
  return [...days.slice(1), days[0]];
100
114
  }
101
115
  }
package/src/weight.vo.ts CHANGED
@@ -2,18 +2,31 @@ import { z } from "zod/v4";
2
2
  import { RoundToDecimal } from "./rounding.adapter";
3
3
  import type { RoundingPort } from "./rounding.port";
4
4
 
5
- const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
6
- const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
7
- const PositiveNumericValue = FiniteNumericValue.gt(0, { message: "Must be greater than 0" });
8
- const NonNegativeIntegerGrams = FiniteNumericValue.int().min(0, {
9
- message: "Grams must be an integer greater than or equal to 0",
10
- });
11
-
12
5
  export enum WeightUnit {
13
6
  kg = "kg",
14
7
  lb = "lb",
15
8
  }
16
9
 
10
+ export const WeightNonFiniteError = { error: "weight.non_finite" } as const;
11
+ export const WeightNegativeError = { error: "weight.negative" } as const;
12
+ export const WeightNonPositiveError = { error: "weight.non_positive" } as const;
13
+ export const WeightGramsNonNegativeError = { error: "weight.grams_non_negative" } as const;
14
+
15
+ const WeightQuantityNumber = z
16
+ .number(WeightNonFiniteError)
17
+ .refine(Number.isFinite, WeightNonFiniteError)
18
+ .min(0, WeightNegativeError);
19
+
20
+ const DivisionScalarNumber = z
21
+ .number(WeightNonFiniteError)
22
+ .refine(Number.isFinite, WeightNonFiniteError)
23
+ .gt(0, WeightNonPositiveError);
24
+
25
+ const CanonicalGramsInteger = z
26
+ .number(WeightGramsNonNegativeError)
27
+ .int(WeightGramsNonNegativeError)
28
+ .min(0, WeightGramsNonNegativeError);
29
+
17
30
  export class Weight {
18
31
  private static readonly GRAMS_PER_KILOGRAM = 1_000;
19
32
  private static readonly POUNDS_PER_KILOGRAM = 2.2046226218487757;
@@ -22,24 +35,27 @@ export class Weight {
22
35
  private constructor(private readonly grams: number) {}
23
36
 
24
37
  static fromKilograms(kilograms: number): Weight {
25
- NonNegativeNumericValue.parse(kilograms);
26
- const gramsRounded = Math.round(kilograms * Weight.GRAMS_PER_KILOGRAM);
27
- NonNegativeIntegerGrams.parse(gramsRounded);
28
- return new Weight(gramsRounded);
38
+ const kilogramsParsed = WeightQuantityNumber.parse(kilograms);
39
+ const gramsRounded = Math.round(kilogramsParsed * Weight.GRAMS_PER_KILOGRAM);
40
+ const grams = CanonicalGramsInteger.parse(gramsRounded);
41
+
42
+ return new Weight(grams);
29
43
  }
30
44
 
31
45
  static fromPounds(pounds: number): Weight {
32
- NonNegativeNumericValue.parse(pounds);
33
- const gramsRounded = Math.round(pounds * Weight.KILOGRAMS_PER_POUND * Weight.GRAMS_PER_KILOGRAM);
34
- NonNegativeIntegerGrams.parse(gramsRounded);
35
- return new Weight(gramsRounded);
46
+ const poundsParsed = WeightQuantityNumber.parse(pounds);
47
+ const gramsRounded = Math.round(poundsParsed * Weight.KILOGRAMS_PER_POUND * Weight.GRAMS_PER_KILOGRAM);
48
+ const grams = CanonicalGramsInteger.parse(gramsRounded);
49
+
50
+ return new Weight(grams);
36
51
  }
37
52
 
38
53
  static fromGrams(grams: number): Weight {
39
- NonNegativeNumericValue.parse(grams);
40
- const gramsRounded = Math.round(grams);
41
- NonNegativeIntegerGrams.parse(gramsRounded);
42
- return new Weight(gramsRounded);
54
+ const gramsParsed = WeightQuantityNumber.parse(grams);
55
+ const gramsRounded = Math.round(gramsParsed);
56
+ const integerGrams = CanonicalGramsInteger.parse(gramsRounded);
57
+
58
+ return new Weight(integerGrams);
43
59
  }
44
60
 
45
61
  static zero(): Weight {
@@ -52,18 +68,18 @@ export class Weight {
52
68
 
53
69
  toKilograms(rounding?: RoundingPort): number {
54
70
  const kilograms = this.grams / Weight.GRAMS_PER_KILOGRAM;
71
+
55
72
  return rounding ? rounding.round(kilograms) : kilograms;
56
73
  }
57
74
 
58
75
  toPounds(rounding?: RoundingPort): number {
59
76
  const pounds = (this.grams / Weight.GRAMS_PER_KILOGRAM) * Weight.POUNDS_PER_KILOGRAM;
77
+
60
78
  return rounding ? rounding.round(pounds) : pounds;
61
79
  }
62
80
 
63
81
  format(unit: WeightUnit, rounding: RoundingPort = new RoundToDecimal(2)): string {
64
- const value = { [WeightUnit.kg]: this.toKilograms(rounding), [WeightUnit.lb]: this.toPounds(rounding) }[
65
- unit
66
- ];
82
+ const value = unit === WeightUnit.kg ? this.toKilograms(rounding) : this.toPounds(rounding);
67
83
 
68
84
  return `${value.toString()} ${unit}`;
69
85
  }
@@ -74,21 +90,24 @@ export class Weight {
74
90
 
75
91
  subtract(other: Weight): Weight {
76
92
  const result = this.grams - other.grams;
93
+
77
94
  return new Weight(result < 0 ? 0 : result);
78
95
  }
79
96
 
80
97
  multiply(factor: number): Weight {
81
- NonNegativeNumericValue.parse(factor);
82
- const gramsRounded = Math.round(this.grams * factor);
83
- NonNegativeIntegerGrams.parse(gramsRounded);
84
- return new Weight(gramsRounded);
98
+ const factorParsed = WeightQuantityNumber.parse(factor);
99
+ const gramsRounded = Math.round(this.grams * factorParsed);
100
+ const grams = CanonicalGramsInteger.parse(gramsRounded);
101
+
102
+ return new Weight(grams);
85
103
  }
86
104
 
87
105
  divideByScalar(divisor: number): Weight {
88
- PositiveNumericValue.parse(divisor);
89
- const gramsRounded = Math.round(this.grams / divisor);
90
- NonNegativeIntegerGrams.parse(gramsRounded);
91
- return new Weight(gramsRounded);
106
+ const divisorParsed = DivisionScalarNumber.parse(divisor);
107
+ const gramsRounded = Math.round(this.grams / divisorParsed);
108
+ const grams = CanonicalGramsInteger.parse(gramsRounded);
109
+
110
+ return new Weight(grams);
92
111
  }
93
112
 
94
113
  equals(other: Weight): boolean {
@@ -1,13 +1,10 @@
1
1
  import { z } from "zod/v4";
2
2
 
3
+ export const YearIsoIdError = { error: "year-iso-id.invalid" } as const;
4
+
3
5
  export const YearIsoId = z
4
- .string()
5
- .regex(/^\d{4}$/)
6
- .refine(
7
- (value) => {
8
- const year = Number(value);
9
- return value.length === 4 && Number.isInteger(year);
10
- },
11
- { message: "year-iso-id.invalid" },
12
- );
6
+ .string(YearIsoIdError)
7
+ .regex(/^\d{4}$/, YearIsoIdError)
8
+ .refine((value) => Number.isInteger(Number(value)), YearIsoIdError);
9
+
13
10
  export type YearIsoIdType = z.infer<typeof YearIsoId>;
package/src/year.vo.ts CHANGED
@@ -3,6 +3,9 @@ import { DateRange } from "./date-range.vo";
3
3
  import { Timestamp, type TimestampType } from "./timestamp.vo";
4
4
  import { YearIsoId, type YearIsoIdType } from "./year-iso-id.vo";
5
5
 
6
+ export const YearInvalidIntegerError = "year.invalid_integer" as const;
7
+ export const YearOutOfRangeError = "year.out_of_range" as const;
8
+
6
9
  export class Year extends DateRange {
7
10
  toIsoId(): YearIsoIdType {
8
11
  return String(getYear(this.getStart())) as YearIsoIdType;
@@ -10,27 +13,32 @@ export class Year extends DateRange {
10
13
 
11
14
  isLeapYear(): boolean {
12
15
  const year = getYear(this.getStart());
16
+
13
17
  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
14
18
  }
15
19
 
16
20
  previous(): Year {
17
21
  const shifted = addYears(new Date(this.getStart()), -1).getTime();
22
+
18
23
  return Year.fromTimestamp(Timestamp.parse(shifted));
19
24
  }
20
25
 
21
26
  next(): Year {
22
27
  const shifted = addYears(new Date(this.getStart()), 1).getTime();
28
+
23
29
  return Year.fromTimestamp(Timestamp.parse(shifted));
24
30
  }
25
31
 
26
32
  shift(count: number): Year {
27
33
  const shifted = addYears(new Date(this.getStart()), count).getTime();
34
+
28
35
  return Year.fromTimestamp(Timestamp.parse(shifted));
29
36
  }
30
37
 
31
38
  static fromTimestamp(timestamp: TimestampType): Year {
32
39
  const start = Timestamp.parse(startOfYear(timestamp).getTime());
33
40
  const end = Timestamp.parse(endOfYear(timestamp).getTime());
41
+
34
42
  return new Year(start, end);
35
43
  }
36
44
 
@@ -39,9 +47,11 @@ export class Year extends DateRange {
39
47
  }
40
48
 
41
49
  static fromNumber(value: number): Year {
42
- if (!Number.isInteger(value)) throw new Error("year.invalid_integer");
43
- if (value < 0 || value > 9999) throw new Error("year.out_of_range");
50
+ if (!Number.isInteger(value)) throw new Error(YearInvalidIntegerError);
51
+ if (value < 0 || value > 9999) throw new Error(YearOutOfRangeError);
52
+
44
53
  const reference = Timestamp.parse(Date.UTC(value, 0, 1, 0, 0, 0, 0));
54
+
45
55
  return Year.fromTimestamp(reference);
46
56
  }
47
57
 
@@ -1,23 +0,0 @@
1
- type StepType = number;
2
- type StepperConfigType = {
3
- total: StepType;
4
- };
5
- export declare class Stepper {
6
- static readonly DEFAULT_CURRENT = 1;
7
- private current;
8
- private readonly total;
9
- constructor(config: StepperConfigType);
10
- continue(): Stepper;
11
- read(): {
12
- finished: boolean;
13
- raw: {
14
- current: number;
15
- total: number;
16
- };
17
- formatted: string;
18
- };
19
- reset(): Stepper;
20
- format(): string;
21
- isFinished(): boolean;
22
- }
23
- export {};
@@ -1,33 +0,0 @@
1
- export class Stepper {
2
- static DEFAULT_CURRENT = 1;
3
- current = Stepper.DEFAULT_CURRENT;
4
- total;
5
- constructor(config) {
6
- if (!Number.isInteger(config.total))
7
- throw new Error("Total value is not an integer");
8
- if (config.total <= Stepper.DEFAULT_CURRENT)
9
- throw new Error("Total value should be greater than one");
10
- this.total = config.total;
11
- }
12
- continue() {
13
- this.current = Math.min(this.current + 1, this.total);
14
- return this;
15
- }
16
- read() {
17
- return {
18
- finished: this.isFinished(),
19
- raw: { current: this.current, total: this.total },
20
- formatted: `${this.current}/${this.total}`,
21
- };
22
- }
23
- reset() {
24
- this.current = Stepper.DEFAULT_CURRENT;
25
- return this;
26
- }
27
- format() {
28
- return this.read().formatted;
29
- }
30
- isFinished() {
31
- return this.current === this.total;
32
- }
33
- }
@@ -1,43 +0,0 @@
1
- type StepType = number;
2
- type StepperConfigType = { total: StepType };
3
-
4
- export class Stepper {
5
- static readonly DEFAULT_CURRENT = 1;
6
-
7
- private current: StepType = Stepper.DEFAULT_CURRENT;
8
-
9
- private readonly total: StepType;
10
-
11
- constructor(config: StepperConfigType) {
12
- if (!Number.isInteger(config.total)) throw new Error("Total value is not an integer");
13
- if (config.total <= Stepper.DEFAULT_CURRENT) throw new Error("Total value should be greater than one");
14
- this.total = config.total;
15
- }
16
-
17
- continue(): Stepper {
18
- this.current = Math.min(this.current + 1, this.total);
19
-
20
- return this;
21
- }
22
-
23
- read() {
24
- return {
25
- finished: this.isFinished(),
26
- raw: { current: this.current, total: this.total },
27
- formatted: `${this.current}/${this.total}`,
28
- };
29
- }
30
-
31
- reset(): Stepper {
32
- this.current = Stepper.DEFAULT_CURRENT;
33
- return this;
34
- }
35
-
36
- format(): string {
37
- return this.read().formatted;
38
- }
39
-
40
- isFinished(): boolean {
41
- return this.current === this.total;
42
- }
43
- }