@bgord/tools 0.15.2 → 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.
- package/dist/basename.vo.d.ts +11 -2
- package/dist/basename.vo.js +22 -13
- package/dist/date-calculator.service.d.ts +2 -2
- package/dist/date-calculator.service.js +10 -11
- package/dist/date-range.vo.d.ts +1 -0
- package/dist/date-range.vo.js +2 -1
- package/dist/day-iso-id.vo.d.ts +3 -0
- package/dist/day-iso-id.vo.js +4 -4
- package/dist/day.vo.js +12 -10
- package/dist/directory-path-absolute.vo.d.ts +5 -0
- package/dist/directory-path-absolute.vo.js +11 -5
- package/dist/directory-path-relative.vo.d.ts +6 -0
- package/dist/directory-path-relative.vo.js +12 -6
- package/dist/dll.service.js +37 -31
- package/dist/extension.vo.d.ts +6 -2
- package/dist/extension.vo.js +11 -9
- package/dist/file-path-absolute-schema.vo.d.ts +5 -0
- package/dist/file-path-absolute-schema.vo.js +12 -6
- package/dist/file-path-relative-schema.vo.d.ts +6 -1
- package/dist/file-path-relative-schema.vo.js +10 -6
- package/dist/file-path.vo.js +4 -4
- package/dist/filename-from-string.vo.d.ts +6 -4
- package/dist/filename-from-string.vo.js +15 -14
- package/dist/filename-suffix.vo.d.ts +4 -2
- package/dist/filename-suffix.vo.js +5 -3
- package/dist/filename.vo.d.ts +2 -2
- package/dist/filename.vo.js +9 -9
- package/dist/height.vo.d.ts +6 -4
- package/dist/height.vo.js +56 -51
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mime.vo.d.ts +3 -1
- package/dist/mime.vo.js +8 -6
- package/dist/month-iso-id.vo.d.ts +3 -0
- package/dist/month-iso-id.vo.js +7 -12
- package/dist/month.vo.js +15 -13
- package/dist/object-key.vo.d.ts +5 -0
- package/dist/object-key.vo.js +16 -6
- package/dist/package-version.vo.d.ts +3 -0
- package/dist/package-version.vo.js +12 -34
- package/dist/pagination.service.d.ts +1 -1
- package/dist/pagination.service.js +11 -11
- package/dist/quarter-iso-id.vo.d.ts +3 -0
- package/dist/quarter-iso-id.vo.js +8 -7
- package/dist/rate-limiter.service.d.ts +3 -2
- package/dist/rate-limiter.service.js +4 -2
- package/dist/reordering.service.d.ts +20 -2
- package/dist/reordering.service.js +49 -29
- package/dist/revision.vo.d.ts +8 -3
- package/dist/revision.vo.js +13 -6
- package/dist/rounding.adapter.js +1 -2
- package/dist/size.vo.d.ts +1 -0
- package/dist/size.vo.js +4 -7
- package/dist/streak-calculator.service.d.ts +3 -4
- package/dist/streak-calculator.service.js +11 -17
- package/dist/time-zone-offset-value.vo.d.ts +1 -1
- package/dist/time-zone-offset-value.vo.js +1 -7
- package/dist/time.service.d.ts +11 -6
- package/dist/time.service.js +31 -18
- package/dist/timezone.vo.js +1 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/week-iso-id.vo.d.ts +3 -0
- package/dist/week-iso-id.vo.js +4 -4
- package/dist/week.vo.js +1 -1
- package/dist/weekday.vo.d.ts +7 -6
- package/dist/weekday.vo.js +20 -13
- package/dist/weight.vo.d.ts +12 -0
- package/dist/weight.vo.js +37 -27
- package/dist/year-iso-id.vo.d.ts +3 -0
- package/dist/year-iso-id.vo.js +4 -6
- package/dist/year.vo.d.ts +2 -0
- package/dist/year.vo.js +4 -2
- package/package.json +1 -1
- package/readme.md +0 -1
- package/src/basename.vo.ts +25 -14
- package/src/clock.vo.ts +1 -0
- package/src/date-calculator.service.ts +10 -15
- package/src/date-range.vo.ts +3 -1
- package/src/day-iso-id.vo.ts +9 -10
- package/src/day.vo.ts +17 -10
- package/src/directory-path-absolute.vo.ts +12 -5
- package/src/directory-path-relative.vo.ts +13 -6
- package/src/dll.service.ts +45 -43
- package/src/extension.vo.ts +14 -12
- package/src/file-path-absolute-schema.vo.ts +15 -6
- package/src/file-path-relative-schema.vo.ts +13 -6
- package/src/file-path.vo.ts +15 -11
- package/src/filename-from-string.vo.ts +20 -15
- package/src/filename-suffix.vo.ts +8 -4
- package/src/filename.vo.ts +14 -15
- package/src/height.vo.ts +71 -53
- package/src/index.ts +0 -1
- package/src/mime.vo.ts +10 -7
- package/src/month-iso-id.vo.ts +10 -20
- package/src/month.vo.ts +19 -13
- package/src/object-key.vo.ts +21 -7
- package/src/outlier-detector.service.ts +1 -0
- package/src/package-version.vo.ts +18 -47
- package/src/pagination.service.ts +15 -13
- package/src/quarter-iso-id.vo.ts +11 -13
- package/src/quarter.vo.ts +3 -0
- package/src/rate-limiter.service.ts +7 -7
- package/src/reordering.service.ts +52 -38
- package/src/revision.vo.ts +17 -8
- package/src/rounding.adapter.ts +1 -3
- package/src/size.vo.ts +6 -16
- package/src/streak-calculator.service.ts +12 -17
- package/src/time-zone-offset-value.vo.ts +2 -7
- package/src/time.service.ts +43 -45
- package/src/timezone.vo.ts +1 -3
- package/src/week-iso-id.vo.ts +13 -14
- package/src/week.vo.ts +4 -2
- package/src/weekday.vo.ts +27 -13
- package/src/weight.vo.ts +49 -30
- package/src/year-iso-id.vo.ts +6 -9
- package/src/year.vo.ts +12 -2
- package/dist/stepper.service.d.ts +0 -23
- package/dist/stepper.service.js +0 -33
- package/src/stepper.service.ts +0 -43
package/src/time.service.ts
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
|
48
|
+
return this.valueMs > another.ms;
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
isBefore(another: TimeResultInterface): boolean {
|
|
31
|
-
return this.
|
|
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
|
},
|
package/src/timezone.vo.ts
CHANGED
|
@@ -7,9 +7,7 @@ export const Timezone = z
|
|
|
7
7
|
.min(1, TimezoneError)
|
|
8
8
|
.refine((value) => {
|
|
9
9
|
try {
|
|
10
|
-
|
|
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;
|
package/src/week-iso-id.vo.ts
CHANGED
|
@@ -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
|
-
(
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
const week = Number(weekPart);
|
|
15
|
+
if (!(Number.isInteger(year) && Number.isInteger(week)) || week < 1) return false;
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
const weeksInYear = getISOWeeksInYear(new Date(Date.UTC(year, 0, 4)));
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
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 =
|
|
52
|
+
this.formatter = formatter ?? WeekdayFormatters.FULL;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
static fromUtcTimestamp(timestamp: TimestampType, formatter?: WeekdayFormatter): Weekday {
|
|
54
|
-
const
|
|
55
|
-
return new Weekday(
|
|
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
|
-
|
|
59
|
-
const
|
|
60
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
const gramsRounded = Math.round(
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
const gramsRounded = Math.round(
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
const gramsRounded = Math.round(
|
|
41
|
-
|
|
42
|
-
|
|
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 =
|
|
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
|
-
|
|
82
|
-
const gramsRounded = Math.round(this.grams *
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
89
|
-
const gramsRounded = Math.round(this.grams /
|
|
90
|
-
|
|
91
|
-
|
|
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 {
|
package/src/year-iso-id.vo.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
43
|
-
if (value < 0 || value > 9999) throw new Error(
|
|
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 {};
|
package/dist/stepper.service.js
DELETED
|
@@ -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
|
-
}
|
package/src/stepper.service.ts
DELETED
|
@@ -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
|
-
}
|