@bgord/tools 0.13.3 → 0.14.2
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/age.vo.d.ts +27 -0
- package/dist/age.vo.js +51 -0
- package/dist/day.vo.d.ts +3 -0
- package/dist/day.vo.js +13 -1
- package/dist/height.vo.d.ts +34 -0
- package/dist/height.vo.js +100 -0
- package/dist/image.vo.d.ts +4 -4
- package/dist/image.vo.js +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/month.vo.d.ts +3 -0
- package/dist/month.vo.js +13 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/week.vo.d.ts +3 -0
- package/dist/week.vo.js +13 -1
- package/dist/weight.vo.d.ts +38 -0
- package/dist/weight.vo.js +109 -0
- package/dist/year.vo.d.ts +3 -0
- package/dist/year.vo.js +13 -1
- package/package.json +5 -5
- package/readme.md +3 -0
- package/src/age.vo.ts +65 -0
- package/src/day.vo.ts +16 -1
- package/src/height.vo.ts +121 -0
- package/src/image.vo.ts +4 -4
- package/src/index.ts +3 -0
- package/src/month.vo.ts +16 -1
- package/src/week.vo.ts +16 -1
- package/src/weight.vo.ts +133 -0
- package/src/year.vo.ts +16 -1
package/dist/age.vo.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import type { TimestampType } from "./timestamp.vo";
|
|
3
|
+
export declare const AgeValueError: {
|
|
4
|
+
error: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class Age {
|
|
7
|
+
private readonly value;
|
|
8
|
+
static readonly MIN = 1;
|
|
9
|
+
static readonly MAX = 130;
|
|
10
|
+
static readonly AgeValue: z.core.$ZodBranded<z.ZodNumber, "AgeValue">;
|
|
11
|
+
private constructor();
|
|
12
|
+
get(): number;
|
|
13
|
+
compare(other: Age): -1 | 0 | 1;
|
|
14
|
+
equals(another: Age): boolean;
|
|
15
|
+
isOlderThan(another: Age): boolean;
|
|
16
|
+
isYoungerThan(another: Age): boolean;
|
|
17
|
+
isAdult(minAge: Age): boolean;
|
|
18
|
+
static fromValue(candidate: number): Age;
|
|
19
|
+
static fromBirthdateTimestamp(params: {
|
|
20
|
+
birthdate: TimestampType;
|
|
21
|
+
now: TimestampType;
|
|
22
|
+
}): Age;
|
|
23
|
+
static fromBirthdate(params: {
|
|
24
|
+
birthdate: string;
|
|
25
|
+
now: TimestampType;
|
|
26
|
+
}): Age;
|
|
27
|
+
}
|
package/dist/age.vo.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { differenceInYears } from "date-fns";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
export const AgeValueError = { error: "invalid.age" };
|
|
4
|
+
export class Age {
|
|
5
|
+
value;
|
|
6
|
+
static MIN = 1;
|
|
7
|
+
static MAX = 130;
|
|
8
|
+
static AgeValue = z
|
|
9
|
+
.number(AgeValueError)
|
|
10
|
+
.int(AgeValueError)
|
|
11
|
+
.min(Age.MIN, AgeValueError)
|
|
12
|
+
.max(Age.MAX, AgeValueError)
|
|
13
|
+
.brand("AgeValue");
|
|
14
|
+
constructor(value) {
|
|
15
|
+
this.value = value;
|
|
16
|
+
}
|
|
17
|
+
get() {
|
|
18
|
+
return this.value;
|
|
19
|
+
}
|
|
20
|
+
compare(other) {
|
|
21
|
+
return this.value === other.value ? 0 : this.value < other.value ? -1 : 1;
|
|
22
|
+
}
|
|
23
|
+
equals(another) {
|
|
24
|
+
return this.compare(another) === 0;
|
|
25
|
+
}
|
|
26
|
+
isOlderThan(another) {
|
|
27
|
+
return this.compare(another) === 1;
|
|
28
|
+
}
|
|
29
|
+
isYoungerThan(another) {
|
|
30
|
+
return this.compare(another) === -1;
|
|
31
|
+
}
|
|
32
|
+
isAdult(minAge) {
|
|
33
|
+
return this.equals(minAge) || this.isOlderThan(minAge);
|
|
34
|
+
}
|
|
35
|
+
static fromValue(candidate) {
|
|
36
|
+
return new Age(Age.AgeValue.parse(candidate));
|
|
37
|
+
}
|
|
38
|
+
static fromBirthdateTimestamp(params) {
|
|
39
|
+
if (params.birthdate > params.now)
|
|
40
|
+
throw new Error("invalid.birthdate_in_future");
|
|
41
|
+
const years = differenceInYears(new Date(params.now), new Date(params.birthdate));
|
|
42
|
+
return Age.fromValue(years);
|
|
43
|
+
}
|
|
44
|
+
static fromBirthdate(params) {
|
|
45
|
+
const birthdate = new Date(params.birthdate).getTime();
|
|
46
|
+
if (birthdate > params.now)
|
|
47
|
+
throw new Error("invalid.birthdate_in_future");
|
|
48
|
+
const years = differenceInYears(new Date(params.now), new Date(birthdate));
|
|
49
|
+
return Age.fromValue(years);
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/day.vo.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { type TimestampType } from "./timestamp.vo";
|
|
|
4
4
|
export declare class Day extends DateRange {
|
|
5
5
|
private constructor();
|
|
6
6
|
toIsoId(): DayIsoIdType;
|
|
7
|
+
previous(): Day;
|
|
8
|
+
next(): Day;
|
|
9
|
+
shift(count: number): Day;
|
|
7
10
|
static fromTimestamp(timestamp: TimestampType): Day;
|
|
8
11
|
static fromNow(now: TimestampType): Day;
|
|
9
12
|
static fromIsoId(isoId: DayIsoIdType): Day;
|
package/dist/day.vo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfDay, startOfDay } from "date-fns";
|
|
1
|
+
import { addDays, endOfDay, startOfDay } from "date-fns";
|
|
2
2
|
import { DateRange } from "./date-range.vo";
|
|
3
3
|
import { DayIsoId } from "./day-iso-id.vo";
|
|
4
4
|
import { Time } from "./time.service";
|
|
@@ -10,6 +10,18 @@ export class Day extends DateRange {
|
|
|
10
10
|
toIsoId() {
|
|
11
11
|
return new Date(this.getStart() + Time.Hours(12).ms).toISOString().slice(0, 10);
|
|
12
12
|
}
|
|
13
|
+
previous() {
|
|
14
|
+
const shifted = addDays(new Date(this.getStart()), -1).getTime();
|
|
15
|
+
return Day.fromTimestamp(Timestamp.parse(shifted));
|
|
16
|
+
}
|
|
17
|
+
next() {
|
|
18
|
+
const shifted = addDays(new Date(this.getStart()), 1).getTime();
|
|
19
|
+
return Day.fromTimestamp(Timestamp.parse(shifted));
|
|
20
|
+
}
|
|
21
|
+
shift(count) {
|
|
22
|
+
const shifted = addDays(new Date(this.getStart()), count).getTime();
|
|
23
|
+
return Day.fromTimestamp(Timestamp.parse(shifted));
|
|
24
|
+
}
|
|
13
25
|
static fromTimestamp(timestamp) {
|
|
14
26
|
const start = Timestamp.parse(startOfDay(timestamp).getTime());
|
|
15
27
|
const end = Timestamp.parse(endOfDay(timestamp).getTime());
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type RoundingStrategy } from "./rounding.service";
|
|
2
|
+
export declare enum HeightUnit {
|
|
3
|
+
cm = "cm",
|
|
4
|
+
ft_in = "ft_in"
|
|
5
|
+
}
|
|
6
|
+
export declare class Height {
|
|
7
|
+
private readonly millimeters;
|
|
8
|
+
private static readonly MILLIMETERS_PER_CENTIMETER;
|
|
9
|
+
private static readonly MILLIMETERS_PER_INCH;
|
|
10
|
+
private static readonly INCHES_PER_FOOT;
|
|
11
|
+
private constructor();
|
|
12
|
+
static fromCentimeters(centimeters: number, rounding?: RoundingStrategy): Height;
|
|
13
|
+
static fromFeetInches(feet: number, inches?: number, rounding?: RoundingStrategy): Height;
|
|
14
|
+
static fromMillimeters(millimeters: number, rounding?: RoundingStrategy): Height;
|
|
15
|
+
static zero(): Height;
|
|
16
|
+
toMillimeters(): number;
|
|
17
|
+
toCentimeters(rounding?: RoundingStrategy): number;
|
|
18
|
+
toFeetInches(rounding?: RoundingStrategy): {
|
|
19
|
+
feet: number;
|
|
20
|
+
inches: number;
|
|
21
|
+
};
|
|
22
|
+
format(unit: HeightUnit, roundingStrategy?: RoundingStrategy): string;
|
|
23
|
+
equals(other: Height): boolean;
|
|
24
|
+
compare(other: Height): -1 | 0 | 1;
|
|
25
|
+
greaterThan(other: Height): boolean;
|
|
26
|
+
lessThan(other: Height): boolean;
|
|
27
|
+
isZero(): boolean;
|
|
28
|
+
toJSON(): {
|
|
29
|
+
mm: number;
|
|
30
|
+
};
|
|
31
|
+
static fromJSON(input: {
|
|
32
|
+
mm: number;
|
|
33
|
+
}): Height;
|
|
34
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { RoundToDecimal, RoundToNearest } from "./rounding.service";
|
|
3
|
+
const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
|
|
4
|
+
const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
|
|
5
|
+
const NonNegativeIntegerMillimeters = FiniteNumericValue.int().min(0, {
|
|
6
|
+
message: "Millimeters must be an integer greater than or equal to 0",
|
|
7
|
+
});
|
|
8
|
+
const NonNegativeIntegerValue = FiniteNumericValue.int().min(0, {
|
|
9
|
+
message: "Value must be an integer greater than or equal to 0",
|
|
10
|
+
});
|
|
11
|
+
export var HeightUnit;
|
|
12
|
+
(function (HeightUnit) {
|
|
13
|
+
HeightUnit["cm"] = "cm";
|
|
14
|
+
HeightUnit["ft_in"] = "ft_in";
|
|
15
|
+
})(HeightUnit || (HeightUnit = {}));
|
|
16
|
+
export class Height {
|
|
17
|
+
millimeters;
|
|
18
|
+
static MILLIMETERS_PER_CENTIMETER = 10;
|
|
19
|
+
static MILLIMETERS_PER_INCH = 25.4;
|
|
20
|
+
static INCHES_PER_FOOT = 12;
|
|
21
|
+
constructor(millimeters) {
|
|
22
|
+
this.millimeters = millimeters;
|
|
23
|
+
}
|
|
24
|
+
static fromCentimeters(centimeters, rounding = new RoundToNearest()) {
|
|
25
|
+
NonNegativeNumericValue.parse(centimeters);
|
|
26
|
+
const mmFloat = centimeters * Height.MILLIMETERS_PER_CENTIMETER;
|
|
27
|
+
const mmRounded = rounding.round(mmFloat);
|
|
28
|
+
NonNegativeIntegerMillimeters.parse(mmRounded);
|
|
29
|
+
return new Height(mmRounded);
|
|
30
|
+
}
|
|
31
|
+
static fromFeetInches(feet, inches = 0, rounding = new RoundToNearest()) {
|
|
32
|
+
NonNegativeNumericValue.parse(feet);
|
|
33
|
+
NonNegativeNumericValue.parse(inches);
|
|
34
|
+
const totalInches = feet * Height.INCHES_PER_FOOT + inches;
|
|
35
|
+
const mmFloat = totalInches * Height.MILLIMETERS_PER_INCH;
|
|
36
|
+
const mmRounded = rounding.round(mmFloat);
|
|
37
|
+
NonNegativeIntegerMillimeters.parse(mmRounded);
|
|
38
|
+
return new Height(mmRounded);
|
|
39
|
+
}
|
|
40
|
+
static fromMillimeters(millimeters, rounding = new RoundToNearest()) {
|
|
41
|
+
NonNegativeNumericValue.parse(millimeters);
|
|
42
|
+
const mmRounded = rounding.round(millimeters);
|
|
43
|
+
NonNegativeIntegerMillimeters.parse(mmRounded);
|
|
44
|
+
return new Height(mmRounded);
|
|
45
|
+
}
|
|
46
|
+
static zero() {
|
|
47
|
+
return new Height(0);
|
|
48
|
+
}
|
|
49
|
+
toMillimeters() {
|
|
50
|
+
return this.millimeters;
|
|
51
|
+
}
|
|
52
|
+
toCentimeters(rounding) {
|
|
53
|
+
const cm = this.millimeters / Height.MILLIMETERS_PER_CENTIMETER;
|
|
54
|
+
return rounding ? rounding.round(cm) : cm;
|
|
55
|
+
}
|
|
56
|
+
toFeetInches(rounding = new RoundToNearest()) {
|
|
57
|
+
const totalInchesFloat = this.millimeters / Height.MILLIMETERS_PER_INCH;
|
|
58
|
+
const totalInchesRounded = rounding.round(totalInchesFloat);
|
|
59
|
+
const integerInches = NonNegativeIntegerValue.parse(totalInchesRounded);
|
|
60
|
+
const feet = (integerInches - (integerInches % Height.INCHES_PER_FOOT)) / Height.INCHES_PER_FOOT;
|
|
61
|
+
const inches = integerInches % Height.INCHES_PER_FOOT;
|
|
62
|
+
return { feet, inches };
|
|
63
|
+
}
|
|
64
|
+
format(unit, roundingStrategy) {
|
|
65
|
+
return {
|
|
66
|
+
[HeightUnit.cm]: () => {
|
|
67
|
+
const rounding = roundingStrategy ?? new RoundToDecimal(1);
|
|
68
|
+
return `${this.toCentimeters(rounding)} cm`;
|
|
69
|
+
},
|
|
70
|
+
[HeightUnit.ft_in]: () => {
|
|
71
|
+
const rounding = roundingStrategy ?? new RoundToNearest();
|
|
72
|
+
const { feet, inches } = this.toFeetInches(rounding);
|
|
73
|
+
return `${feet}′${inches}″`;
|
|
74
|
+
},
|
|
75
|
+
}[unit]();
|
|
76
|
+
}
|
|
77
|
+
equals(other) {
|
|
78
|
+
return this.millimeters === other.millimeters;
|
|
79
|
+
}
|
|
80
|
+
compare(other) {
|
|
81
|
+
if (this.equals(other))
|
|
82
|
+
return 0;
|
|
83
|
+
return this.millimeters < other.millimeters ? -1 : 1;
|
|
84
|
+
}
|
|
85
|
+
greaterThan(other) {
|
|
86
|
+
return this.millimeters > other.millimeters;
|
|
87
|
+
}
|
|
88
|
+
lessThan(other) {
|
|
89
|
+
return this.millimeters < other.millimeters;
|
|
90
|
+
}
|
|
91
|
+
isZero() {
|
|
92
|
+
return this.millimeters === 0;
|
|
93
|
+
}
|
|
94
|
+
toJSON() {
|
|
95
|
+
return { mm: this.millimeters };
|
|
96
|
+
}
|
|
97
|
+
static fromJSON(input) {
|
|
98
|
+
return Height.fromMillimeters(input.mm);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/image.vo.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
|
-
export declare const
|
|
3
|
-
export type WidthType = z.infer<typeof
|
|
4
|
-
export declare const
|
|
5
|
-
export type HeightType = z.infer<typeof
|
|
2
|
+
export declare const ImageWidth: z.core.$ZodBranded<z.ZodNumber, "image-width">;
|
|
3
|
+
export type WidthType = z.infer<typeof ImageWidth>;
|
|
4
|
+
export declare const ImageHeight: z.core.$ZodBranded<z.ZodNumber, "image-height">;
|
|
5
|
+
export type HeightType = z.infer<typeof ImageHeight>;
|
package/dist/image.vo.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
2
|
+
export const ImageWidth = z.number().int().positive().max(10000).brand("image-width");
|
|
3
|
+
export const ImageHeight = z.number().int().positive().max(10000).brand("image-height");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./age.vo";
|
|
1
2
|
export * from "./api-key.vo";
|
|
2
3
|
export * from "./basename.vo";
|
|
3
4
|
export * from "./clock.vo";
|
|
@@ -20,6 +21,7 @@ export * from "./file-path-relative-schema.vo";
|
|
|
20
21
|
export * from "./filename.vo";
|
|
21
22
|
export * from "./filename-from-string.vo";
|
|
22
23
|
export * from "./filename-suffix.vo";
|
|
24
|
+
export * from "./height.vo";
|
|
23
25
|
export * from "./hour.vo";
|
|
24
26
|
export * from "./iban.vo";
|
|
25
27
|
export * from "./iban-mask.service";
|
|
@@ -65,6 +67,7 @@ export * from "./visually-unambiguous-characters-generator.service";
|
|
|
65
67
|
export * from "./week.vo";
|
|
66
68
|
export * from "./week-iso-id.vo";
|
|
67
69
|
export * from "./weekday.vo";
|
|
70
|
+
export * from "./weight.vo";
|
|
68
71
|
export * from "./year.vo";
|
|
69
72
|
export * from "./year-iso-id.vo";
|
|
70
73
|
export * from "./z-score.service";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./age.vo";
|
|
1
2
|
export * from "./api-key.vo";
|
|
2
3
|
export * from "./basename.vo";
|
|
3
4
|
export * from "./clock.vo";
|
|
@@ -20,6 +21,7 @@ export * from "./file-path-relative-schema.vo";
|
|
|
20
21
|
export * from "./filename.vo";
|
|
21
22
|
export * from "./filename-from-string.vo";
|
|
22
23
|
export * from "./filename-suffix.vo";
|
|
24
|
+
export * from "./height.vo";
|
|
23
25
|
export * from "./hour.vo";
|
|
24
26
|
export * from "./iban.vo";
|
|
25
27
|
export * from "./iban-mask.service";
|
|
@@ -65,6 +67,7 @@ export * from "./visually-unambiguous-characters-generator.service";
|
|
|
65
67
|
export * from "./week.vo";
|
|
66
68
|
export * from "./week-iso-id.vo";
|
|
67
69
|
export * from "./weekday.vo";
|
|
70
|
+
export * from "./weight.vo";
|
|
68
71
|
export * from "./year.vo";
|
|
69
72
|
export * from "./year-iso-id.vo";
|
|
70
73
|
export * from "./z-score.service";
|
package/dist/month.vo.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { type MonthIsoIdType } from "./month-iso-id.vo";
|
|
|
3
3
|
import { type TimestampType } from "./timestamp.vo";
|
|
4
4
|
export declare class Month extends DateRange {
|
|
5
5
|
toIsoId(): MonthIsoIdType;
|
|
6
|
+
previous(): Month;
|
|
7
|
+
next(): Month;
|
|
8
|
+
shift(count: number): Month;
|
|
6
9
|
static fromTimestamp(timestamp: TimestampType): Month;
|
|
7
10
|
static fromNow(now: TimestampType): Month;
|
|
8
11
|
static fromIsoId(iso: MonthIsoIdType): Month;
|
package/dist/month.vo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfMonth, startOfMonth } from "date-fns";
|
|
1
|
+
import { addMonths, endOfMonth, startOfMonth } from "date-fns";
|
|
2
2
|
import { DateRange } from "./date-range.vo";
|
|
3
3
|
import { MonthIsoId } from "./month-iso-id.vo";
|
|
4
4
|
import { Timestamp } from "./timestamp.vo";
|
|
@@ -6,6 +6,18 @@ export class Month extends DateRange {
|
|
|
6
6
|
toIsoId() {
|
|
7
7
|
return new Date(this.getStart()).toISOString().slice(0, 7);
|
|
8
8
|
}
|
|
9
|
+
previous() {
|
|
10
|
+
const shifted = addMonths(new Date(this.getStart()), -1).getTime();
|
|
11
|
+
return Month.fromTimestamp(Timestamp.parse(shifted));
|
|
12
|
+
}
|
|
13
|
+
next() {
|
|
14
|
+
const shifted = addMonths(new Date(this.getStart()), 1).getTime();
|
|
15
|
+
return Month.fromTimestamp(Timestamp.parse(shifted));
|
|
16
|
+
}
|
|
17
|
+
shift(count) {
|
|
18
|
+
const shifted = addMonths(new Date(this.getStart()), count).getTime();
|
|
19
|
+
return Month.fromTimestamp(Timestamp.parse(shifted));
|
|
20
|
+
}
|
|
9
21
|
static fromTimestamp(timestamp) {
|
|
10
22
|
const start = Timestamp.parse(startOfMonth(timestamp).getTime());
|
|
11
23
|
const end = Timestamp.parse(endOfMonth(timestamp).getTime());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/api-key.vo.ts","../src/basename.vo.ts","../src/clock.vo.ts","../src/date-calculator.service.ts","../src/date-formatter.service.ts","../src/date-range.vo.ts","../src/dates-of-the-week.vo.ts","../src/day-iso-id.vo.ts","../src/day.vo.ts","../src/directory-path-absolute.vo.ts","../src/directory-path-relative.vo.ts","../src/dll.service.ts","../src/email-mask.service.ts","../src/etags.vo.ts","../src/extension.vo.ts","../src/feature-flag.vo.ts","../src/file-path-absolute-schema.vo.ts","../src/file-path-relative-schema.vo.ts","../src/file-path.vo.ts","../src/filename-from-string.vo.ts","../src/filename-suffix.vo.ts","../src/filename.vo.ts","../src/hour.vo.ts","../src/iban-mask.service.ts","../src/iban.vo.ts","../src/image.vo.ts","../src/index.ts","../src/language.vo.ts","../src/mean.service.ts","../src/mime-types.vo.ts","../src/mime.vo.ts","../src/min-max-scaler.service.ts","../src/minute.vo.ts","../src/money.vo.ts","../src/month-iso-id.vo.ts","../src/month.vo.ts","../src/noop.service.ts","../src/notification-template.vo.ts","../src/object-key.vo.ts","../src/outlier-detector.service.ts","../src/package-version.vo.ts","../src/pagination.service.ts","../src/percentage.service.ts","../src/population-standard-deviation.service.ts","../src/quarter-iso-id.vo.ts","../src/quarter.vo.ts","../src/random.service.ts","../src/rate-limiter.service.ts","../src/relative-date.vo.ts","../src/reordering.service.ts","../src/revision.vo.ts","../src/rounding.service.ts","../src/simple-linear-regression.service.ts","../src/size.vo.ts","../src/stepper.service.ts","../src/stopwatch.service.ts","../src/streak-calculator.service.ts","../src/sum.service.ts","../src/thousands-separator.service.ts","../src/time-zone-offset-value.vo.ts","../src/time.service.ts","../src/timestamp.vo.ts","../src/timezone.vo.ts","../src/ts-utils.ts","../src/visually-unambiguous-characters-generator.service.ts","../src/week-iso-id.vo.ts","../src/week.vo.ts","../src/weekday.vo.ts","../src/year-iso-id.vo.ts","../src/year.vo.ts","../src/z-score.service.ts"],"version":"5.9.2"}
|
|
1
|
+
{"root":["../src/age.vo.ts","../src/api-key.vo.ts","../src/basename.vo.ts","../src/clock.vo.ts","../src/date-calculator.service.ts","../src/date-formatter.service.ts","../src/date-range.vo.ts","../src/dates-of-the-week.vo.ts","../src/day-iso-id.vo.ts","../src/day.vo.ts","../src/directory-path-absolute.vo.ts","../src/directory-path-relative.vo.ts","../src/dll.service.ts","../src/email-mask.service.ts","../src/etags.vo.ts","../src/extension.vo.ts","../src/feature-flag.vo.ts","../src/file-path-absolute-schema.vo.ts","../src/file-path-relative-schema.vo.ts","../src/file-path.vo.ts","../src/filename-from-string.vo.ts","../src/filename-suffix.vo.ts","../src/filename.vo.ts","../src/height.vo.ts","../src/hour.vo.ts","../src/iban-mask.service.ts","../src/iban.vo.ts","../src/image.vo.ts","../src/index.ts","../src/language.vo.ts","../src/mean.service.ts","../src/mime-types.vo.ts","../src/mime.vo.ts","../src/min-max-scaler.service.ts","../src/minute.vo.ts","../src/money.vo.ts","../src/month-iso-id.vo.ts","../src/month.vo.ts","../src/noop.service.ts","../src/notification-template.vo.ts","../src/object-key.vo.ts","../src/outlier-detector.service.ts","../src/package-version.vo.ts","../src/pagination.service.ts","../src/percentage.service.ts","../src/population-standard-deviation.service.ts","../src/quarter-iso-id.vo.ts","../src/quarter.vo.ts","../src/random.service.ts","../src/rate-limiter.service.ts","../src/relative-date.vo.ts","../src/reordering.service.ts","../src/revision.vo.ts","../src/rounding.service.ts","../src/simple-linear-regression.service.ts","../src/size.vo.ts","../src/stepper.service.ts","../src/stopwatch.service.ts","../src/streak-calculator.service.ts","../src/sum.service.ts","../src/thousands-separator.service.ts","../src/time-zone-offset-value.vo.ts","../src/time.service.ts","../src/timestamp.vo.ts","../src/timezone.vo.ts","../src/ts-utils.ts","../src/visually-unambiguous-characters-generator.service.ts","../src/week-iso-id.vo.ts","../src/week.vo.ts","../src/weekday.vo.ts","../src/weight.vo.ts","../src/year-iso-id.vo.ts","../src/year.vo.ts","../src/z-score.service.ts"],"version":"5.9.2"}
|
package/dist/week.vo.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { type TimestampType } from "./timestamp.vo";
|
|
|
3
3
|
import { type WeekIsoIdType } from "./week-iso-id.vo";
|
|
4
4
|
export declare class Week extends DateRange {
|
|
5
5
|
toIsoId(): WeekIsoIdType;
|
|
6
|
+
previous(): Week;
|
|
7
|
+
next(): Week;
|
|
8
|
+
shift(count: number): Week;
|
|
6
9
|
static fromTimestamp(timestamp: TimestampType): Week;
|
|
7
10
|
static fromNow(now: TimestampType): Week;
|
|
8
11
|
static fromIsoId(isoId: WeekIsoIdType): Week;
|
package/dist/week.vo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfISOWeek, getISOWeek, getISOWeekYear, setISOWeek, startOfISOWeek } from "date-fns";
|
|
1
|
+
import { addWeeks, endOfISOWeek, getISOWeek, getISOWeekYear, setISOWeek, startOfISOWeek } from "date-fns";
|
|
2
2
|
import { DateRange } from "./date-range.vo";
|
|
3
3
|
import { Timestamp } from "./timestamp.vo";
|
|
4
4
|
import { WeekIsoId } from "./week-iso-id.vo";
|
|
@@ -8,6 +8,18 @@ export class Week extends DateRange {
|
|
|
8
8
|
const week = getISOWeek(this.getStart()).toString().padStart(2, "0");
|
|
9
9
|
return `${year}-W${week}`;
|
|
10
10
|
}
|
|
11
|
+
previous() {
|
|
12
|
+
const shifted = addWeeks(new Date(this.getStart()), -1).getTime();
|
|
13
|
+
return Week.fromTimestamp(Timestamp.parse(shifted));
|
|
14
|
+
}
|
|
15
|
+
next() {
|
|
16
|
+
const shifted = addWeeks(new Date(this.getStart()), 1).getTime();
|
|
17
|
+
return Week.fromTimestamp(Timestamp.parse(shifted));
|
|
18
|
+
}
|
|
19
|
+
shift(count) {
|
|
20
|
+
const shifted = addWeeks(new Date(this.getStart()), count).getTime();
|
|
21
|
+
return Week.fromTimestamp(Timestamp.parse(shifted));
|
|
22
|
+
}
|
|
11
23
|
static fromTimestamp(timestamp) {
|
|
12
24
|
const start = Timestamp.parse(startOfISOWeek(timestamp).getTime());
|
|
13
25
|
const end = Timestamp.parse(endOfISOWeek(timestamp).getTime());
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type RoundingStrategy } from "./rounding.service";
|
|
2
|
+
export declare enum WeightUnit {
|
|
3
|
+
kg = "kg",
|
|
4
|
+
lb = "lb"
|
|
5
|
+
}
|
|
6
|
+
export declare class Weight {
|
|
7
|
+
private readonly grams;
|
|
8
|
+
private static readonly GRAMS_PER_KILOGRAM;
|
|
9
|
+
private static readonly POUNDS_PER_KILOGRAM;
|
|
10
|
+
private static readonly KILOGRAMS_PER_POUND;
|
|
11
|
+
private constructor();
|
|
12
|
+
static fromKilograms(kilograms: number): Weight;
|
|
13
|
+
static fromPounds(pounds: number): Weight;
|
|
14
|
+
static fromGrams(grams: number): Weight;
|
|
15
|
+
static zero(): Weight;
|
|
16
|
+
toGrams(): number;
|
|
17
|
+
toKilograms(rounding?: RoundingStrategy): number;
|
|
18
|
+
toPounds(rounding?: RoundingStrategy): number;
|
|
19
|
+
format(unit: WeightUnit, rounding?: RoundingStrategy): string;
|
|
20
|
+
add(other: Weight): Weight;
|
|
21
|
+
subtract(other: Weight): Weight;
|
|
22
|
+
multiply(factor: number): Weight;
|
|
23
|
+
divideByScalar(divisor: number): Weight;
|
|
24
|
+
equals(other: Weight): boolean;
|
|
25
|
+
compare(other: Weight): -1 | 0 | 1;
|
|
26
|
+
greaterThan(other: Weight): boolean;
|
|
27
|
+
greaterThanOrEqual(other: Weight): boolean;
|
|
28
|
+
lessThan(other: Weight): boolean;
|
|
29
|
+
lessThanOrEqual(other: Weight): boolean;
|
|
30
|
+
isZero(): boolean;
|
|
31
|
+
isPositive(): boolean;
|
|
32
|
+
toJSON(): {
|
|
33
|
+
g: number;
|
|
34
|
+
};
|
|
35
|
+
static fromJSON(input: {
|
|
36
|
+
g: number;
|
|
37
|
+
}): Weight;
|
|
38
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { RoundToDecimal } from "./rounding.service";
|
|
3
|
+
const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
|
|
4
|
+
const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
|
|
5
|
+
const PositiveNumericValue = FiniteNumericValue.gt(0, { message: "Must be greater than 0" });
|
|
6
|
+
const NonNegativeIntegerGrams = FiniteNumericValue.int().min(0, {
|
|
7
|
+
message: "Grams must be an integer greater than or equal to 0",
|
|
8
|
+
});
|
|
9
|
+
export var WeightUnit;
|
|
10
|
+
(function (WeightUnit) {
|
|
11
|
+
WeightUnit["kg"] = "kg";
|
|
12
|
+
WeightUnit["lb"] = "lb";
|
|
13
|
+
})(WeightUnit || (WeightUnit = {}));
|
|
14
|
+
export class Weight {
|
|
15
|
+
grams;
|
|
16
|
+
static GRAMS_PER_KILOGRAM = 1_000;
|
|
17
|
+
static POUNDS_PER_KILOGRAM = 2.2046226218487757;
|
|
18
|
+
static KILOGRAMS_PER_POUND = 1 / Weight.POUNDS_PER_KILOGRAM;
|
|
19
|
+
constructor(grams) {
|
|
20
|
+
this.grams = grams;
|
|
21
|
+
}
|
|
22
|
+
static fromKilograms(kilograms) {
|
|
23
|
+
NonNegativeNumericValue.parse(kilograms);
|
|
24
|
+
const gramsRounded = Math.round(kilograms * Weight.GRAMS_PER_KILOGRAM);
|
|
25
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
26
|
+
return new Weight(gramsRounded);
|
|
27
|
+
}
|
|
28
|
+
static fromPounds(pounds) {
|
|
29
|
+
NonNegativeNumericValue.parse(pounds);
|
|
30
|
+
const gramsRounded = Math.round(pounds * Weight.KILOGRAMS_PER_POUND * Weight.GRAMS_PER_KILOGRAM);
|
|
31
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
32
|
+
return new Weight(gramsRounded);
|
|
33
|
+
}
|
|
34
|
+
static fromGrams(grams) {
|
|
35
|
+
NonNegativeNumericValue.parse(grams);
|
|
36
|
+
const gramsRounded = Math.round(grams);
|
|
37
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
38
|
+
return new Weight(gramsRounded);
|
|
39
|
+
}
|
|
40
|
+
static zero() {
|
|
41
|
+
return new Weight(0);
|
|
42
|
+
}
|
|
43
|
+
toGrams() {
|
|
44
|
+
return this.grams;
|
|
45
|
+
}
|
|
46
|
+
toKilograms(rounding) {
|
|
47
|
+
const kilograms = this.grams / Weight.GRAMS_PER_KILOGRAM;
|
|
48
|
+
return rounding ? rounding.round(kilograms) : kilograms;
|
|
49
|
+
}
|
|
50
|
+
toPounds(rounding) {
|
|
51
|
+
const pounds = (this.grams / Weight.GRAMS_PER_KILOGRAM) * Weight.POUNDS_PER_KILOGRAM;
|
|
52
|
+
return rounding ? rounding.round(pounds) : pounds;
|
|
53
|
+
}
|
|
54
|
+
format(unit, rounding = new RoundToDecimal(2)) {
|
|
55
|
+
const value = { [WeightUnit.kg]: this.toKilograms(rounding), [WeightUnit.lb]: this.toPounds(rounding) }[unit];
|
|
56
|
+
return `${value.toString()} ${unit}`;
|
|
57
|
+
}
|
|
58
|
+
add(other) {
|
|
59
|
+
return new Weight(this.grams + other.grams);
|
|
60
|
+
}
|
|
61
|
+
subtract(other) {
|
|
62
|
+
const result = this.grams - other.grams;
|
|
63
|
+
return new Weight(result < 0 ? 0 : result);
|
|
64
|
+
}
|
|
65
|
+
multiply(factor) {
|
|
66
|
+
NonNegativeNumericValue.parse(factor);
|
|
67
|
+
const gramsRounded = Math.round(this.grams * factor);
|
|
68
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
69
|
+
return new Weight(gramsRounded);
|
|
70
|
+
}
|
|
71
|
+
divideByScalar(divisor) {
|
|
72
|
+
PositiveNumericValue.parse(divisor);
|
|
73
|
+
const gramsRounded = Math.round(this.grams / divisor);
|
|
74
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
75
|
+
return new Weight(gramsRounded);
|
|
76
|
+
}
|
|
77
|
+
equals(other) {
|
|
78
|
+
return this.grams === other.grams;
|
|
79
|
+
}
|
|
80
|
+
compare(other) {
|
|
81
|
+
if (this.grams === other.grams)
|
|
82
|
+
return 0;
|
|
83
|
+
return this.grams < other.grams ? -1 : 1;
|
|
84
|
+
}
|
|
85
|
+
greaterThan(other) {
|
|
86
|
+
return this.grams > other.grams;
|
|
87
|
+
}
|
|
88
|
+
greaterThanOrEqual(other) {
|
|
89
|
+
return this.grams >= other.grams;
|
|
90
|
+
}
|
|
91
|
+
lessThan(other) {
|
|
92
|
+
return this.grams < other.grams;
|
|
93
|
+
}
|
|
94
|
+
lessThanOrEqual(other) {
|
|
95
|
+
return this.grams <= other.grams;
|
|
96
|
+
}
|
|
97
|
+
isZero() {
|
|
98
|
+
return this.grams === 0;
|
|
99
|
+
}
|
|
100
|
+
isPositive() {
|
|
101
|
+
return this.grams > 0;
|
|
102
|
+
}
|
|
103
|
+
toJSON() {
|
|
104
|
+
return { g: this.grams };
|
|
105
|
+
}
|
|
106
|
+
static fromJSON(input) {
|
|
107
|
+
return Weight.fromGrams(input.g);
|
|
108
|
+
}
|
|
109
|
+
}
|
package/dist/year.vo.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { type YearIsoIdType } from "./year-iso-id.vo";
|
|
|
4
4
|
export declare class Year extends DateRange {
|
|
5
5
|
toIsoId(): YearIsoIdType;
|
|
6
6
|
isLeapYear(): boolean;
|
|
7
|
+
previous(): Year;
|
|
8
|
+
next(): Year;
|
|
9
|
+
shift(count: number): Year;
|
|
7
10
|
static fromTimestamp(timestamp: TimestampType): Year;
|
|
8
11
|
static fromNow(now: TimestampType): Year;
|
|
9
12
|
static fromNumber(value: number): Year;
|
package/dist/year.vo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfYear, getYear, startOfYear } from "date-fns";
|
|
1
|
+
import { addYears, endOfYear, getYear, startOfYear } from "date-fns";
|
|
2
2
|
import { DateRange } from "./date-range.vo";
|
|
3
3
|
import { Timestamp } from "./timestamp.vo";
|
|
4
4
|
import { YearIsoId } from "./year-iso-id.vo";
|
|
@@ -10,6 +10,18 @@ export class Year extends DateRange {
|
|
|
10
10
|
const year = getYear(this.getStart());
|
|
11
11
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
12
12
|
}
|
|
13
|
+
previous() {
|
|
14
|
+
const shifted = addYears(new Date(this.getStart()), -1).getTime();
|
|
15
|
+
return Year.fromTimestamp(Timestamp.parse(shifted));
|
|
16
|
+
}
|
|
17
|
+
next() {
|
|
18
|
+
const shifted = addYears(new Date(this.getStart()), 1).getTime();
|
|
19
|
+
return Year.fromTimestamp(Timestamp.parse(shifted));
|
|
20
|
+
}
|
|
21
|
+
shift(count) {
|
|
22
|
+
const shifted = addYears(new Date(this.getStart()), count).getTime();
|
|
23
|
+
return Year.fromTimestamp(Timestamp.parse(shifted));
|
|
24
|
+
}
|
|
13
25
|
static fromTimestamp(timestamp) {
|
|
14
26
|
const start = Timestamp.parse(startOfYear(timestamp).getTime());
|
|
15
27
|
const end = Timestamp.parse(endOfYear(timestamp).getTime());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgord/tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bartosz Gordon",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@biomejs/biome": "2.2.4",
|
|
25
|
-
"@commitlint/cli": "20.
|
|
25
|
+
"@commitlint/cli": "20.1.0",
|
|
26
26
|
"@commitlint/config-conventional": "20.0.0",
|
|
27
|
-
"@types/bun": "1.2.
|
|
27
|
+
"@types/bun": "1.2.23",
|
|
28
28
|
"@types/mime-types": "3.0.1",
|
|
29
29
|
"cspell": "9.2.1",
|
|
30
|
-
"knip": "5.64.
|
|
31
|
-
"lefthook": "1.13.
|
|
30
|
+
"knip": "5.64.1",
|
|
31
|
+
"lefthook": "1.13.6",
|
|
32
32
|
"only-allow": "1.2.1",
|
|
33
33
|
"shellcheck": "4.1.0",
|
|
34
34
|
"typescript": "5.9.2",
|
package/readme.md
CHANGED
|
@@ -24,6 +24,7 @@ Run the tests
|
|
|
24
24
|
|
|
25
25
|
```
|
|
26
26
|
src/
|
|
27
|
+
├── age.vo.ts
|
|
27
28
|
├── api-key.vo.ts
|
|
28
29
|
├── basename.vo.ts
|
|
29
30
|
├── clock.vo.ts
|
|
@@ -46,6 +47,7 @@ src/
|
|
|
46
47
|
├── filename-from-string.vo.ts
|
|
47
48
|
├── filename-suffix.vo.ts
|
|
48
49
|
├── filename.vo.ts
|
|
50
|
+
├── height.vo.ts
|
|
49
51
|
├── hour.vo.ts
|
|
50
52
|
├── iban-mask.service.ts
|
|
51
53
|
├── iban.vo.ts
|
|
@@ -91,6 +93,7 @@ src/
|
|
|
91
93
|
├── week-iso-id.vo.ts
|
|
92
94
|
├── week.vo.ts
|
|
93
95
|
├── weekday.vo.ts
|
|
96
|
+
├── weight.vo.ts
|
|
94
97
|
├── year-iso-id.vo.ts
|
|
95
98
|
├── year.vo.ts
|
|
96
99
|
└── z-score.service.ts
|
package/src/age.vo.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { differenceInYears } from "date-fns";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
import type { TimestampType } from "./timestamp.vo";
|
|
4
|
+
|
|
5
|
+
export const AgeValueError = { error: "invalid.age" };
|
|
6
|
+
|
|
7
|
+
export class Age {
|
|
8
|
+
static readonly MIN = 1;
|
|
9
|
+
static readonly MAX = 130;
|
|
10
|
+
|
|
11
|
+
static readonly AgeValue = z
|
|
12
|
+
.number(AgeValueError)
|
|
13
|
+
.int(AgeValueError)
|
|
14
|
+
.min(Age.MIN, AgeValueError)
|
|
15
|
+
.max(Age.MAX, AgeValueError)
|
|
16
|
+
.brand("AgeValue");
|
|
17
|
+
|
|
18
|
+
private constructor(private readonly value: z.infer<typeof Age.AgeValue>) {}
|
|
19
|
+
|
|
20
|
+
get(): number {
|
|
21
|
+
return this.value as number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
compare(other: Age): -1 | 0 | 1 {
|
|
25
|
+
return this.value === other.value ? 0 : this.value < other.value ? -1 : 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
equals(another: Age): boolean {
|
|
29
|
+
return this.compare(another) === 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isOlderThan(another: Age): boolean {
|
|
33
|
+
return this.compare(another) === 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
isYoungerThan(another: Age): boolean {
|
|
37
|
+
return this.compare(another) === -1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
isAdult(minAge: Age): boolean {
|
|
41
|
+
return this.equals(minAge) || this.isOlderThan(minAge);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static fromValue(candidate: number): Age {
|
|
45
|
+
return new Age(Age.AgeValue.parse(candidate));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static fromBirthdateTimestamp(params: { birthdate: TimestampType; now: TimestampType }): Age {
|
|
49
|
+
if (params.birthdate > params.now) throw new Error("invalid.birthdate_in_future");
|
|
50
|
+
|
|
51
|
+
const years = differenceInYears(new Date(params.now), new Date(params.birthdate));
|
|
52
|
+
|
|
53
|
+
return Age.fromValue(years);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static fromBirthdate(params: { birthdate: string; now: TimestampType }): Age {
|
|
57
|
+
const birthdate = new Date(params.birthdate).getTime();
|
|
58
|
+
|
|
59
|
+
if (birthdate > params.now) throw new Error("invalid.birthdate_in_future");
|
|
60
|
+
|
|
61
|
+
const years = differenceInYears(new Date(params.now), new Date(birthdate));
|
|
62
|
+
|
|
63
|
+
return Age.fromValue(years);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/day.vo.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfDay, startOfDay } from "date-fns";
|
|
1
|
+
import { addDays, endOfDay, startOfDay } from "date-fns";
|
|
2
2
|
import { DateRange } from "./date-range.vo";
|
|
3
3
|
import { DayIsoId, type DayIsoIdType } from "./day-iso-id.vo";
|
|
4
4
|
import { Time } from "./time.service";
|
|
@@ -13,6 +13,21 @@ export class Day extends DateRange {
|
|
|
13
13
|
return new Date(this.getStart() + Time.Hours(12).ms).toISOString().slice(0, 10) as DayIsoIdType;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
previous(): Day {
|
|
17
|
+
const shifted = addDays(new Date(this.getStart()), -1).getTime();
|
|
18
|
+
return Day.fromTimestamp(Timestamp.parse(shifted));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
next(): Day {
|
|
22
|
+
const shifted = addDays(new Date(this.getStart()), 1).getTime();
|
|
23
|
+
return Day.fromTimestamp(Timestamp.parse(shifted));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
shift(count: number): Day {
|
|
27
|
+
const shifted = addDays(new Date(this.getStart()), count).getTime();
|
|
28
|
+
return Day.fromTimestamp(Timestamp.parse(shifted));
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
static fromTimestamp(timestamp: TimestampType): Day {
|
|
17
32
|
const start = Timestamp.parse(startOfDay(timestamp).getTime());
|
|
18
33
|
const end = Timestamp.parse(endOfDay(timestamp).getTime());
|
package/src/height.vo.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { type RoundingStrategy, RoundToDecimal, RoundToNearest } from "./rounding.service";
|
|
3
|
+
|
|
4
|
+
const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
|
|
5
|
+
const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
|
|
6
|
+
const NonNegativeIntegerMillimeters = FiniteNumericValue.int().min(0, {
|
|
7
|
+
message: "Millimeters must be an integer greater than or equal to 0",
|
|
8
|
+
});
|
|
9
|
+
const NonNegativeIntegerValue = FiniteNumericValue.int().min(0, {
|
|
10
|
+
message: "Value must be an integer greater than or equal to 0",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export enum HeightUnit {
|
|
14
|
+
cm = "cm",
|
|
15
|
+
ft_in = "ft_in",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class Height {
|
|
19
|
+
private static readonly MILLIMETERS_PER_CENTIMETER = 10;
|
|
20
|
+
private static readonly MILLIMETERS_PER_INCH = 25.4;
|
|
21
|
+
private static readonly INCHES_PER_FOOT = 12;
|
|
22
|
+
|
|
23
|
+
private constructor(private readonly millimeters: number) {}
|
|
24
|
+
|
|
25
|
+
static fromCentimeters(centimeters: number, rounding: RoundingStrategy = new RoundToNearest()): Height {
|
|
26
|
+
NonNegativeNumericValue.parse(centimeters);
|
|
27
|
+
const mmFloat = centimeters * Height.MILLIMETERS_PER_CENTIMETER;
|
|
28
|
+
const mmRounded = rounding.round(mmFloat);
|
|
29
|
+
NonNegativeIntegerMillimeters.parse(mmRounded);
|
|
30
|
+
return new Height(mmRounded);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static fromFeetInches(feet: number, inches = 0, rounding: RoundingStrategy = new RoundToNearest()): Height {
|
|
34
|
+
NonNegativeNumericValue.parse(feet);
|
|
35
|
+
NonNegativeNumericValue.parse(inches);
|
|
36
|
+
const totalInches = feet * Height.INCHES_PER_FOOT + inches;
|
|
37
|
+
const mmFloat = totalInches * Height.MILLIMETERS_PER_INCH;
|
|
38
|
+
const mmRounded = rounding.round(mmFloat);
|
|
39
|
+
NonNegativeIntegerMillimeters.parse(mmRounded);
|
|
40
|
+
return new Height(mmRounded);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static fromMillimeters(millimeters: number, rounding: RoundingStrategy = new RoundToNearest()): Height {
|
|
44
|
+
NonNegativeNumericValue.parse(millimeters);
|
|
45
|
+
const mmRounded = rounding.round(millimeters);
|
|
46
|
+
NonNegativeIntegerMillimeters.parse(mmRounded);
|
|
47
|
+
return new Height(mmRounded);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static zero(): Height {
|
|
51
|
+
return new Height(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toMillimeters(): number {
|
|
55
|
+
return this.millimeters;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
toCentimeters(rounding?: RoundingStrategy): number {
|
|
59
|
+
const cm = this.millimeters / Height.MILLIMETERS_PER_CENTIMETER;
|
|
60
|
+
return rounding ? rounding.round(cm) : cm;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
toFeetInches(rounding: RoundingStrategy = new RoundToNearest()): {
|
|
64
|
+
feet: number;
|
|
65
|
+
inches: number;
|
|
66
|
+
} {
|
|
67
|
+
const totalInchesFloat = this.millimeters / Height.MILLIMETERS_PER_INCH;
|
|
68
|
+
const totalInchesRounded = rounding.round(totalInchesFloat);
|
|
69
|
+
const integerInches = NonNegativeIntegerValue.parse(totalInchesRounded);
|
|
70
|
+
|
|
71
|
+
const feet = (integerInches - (integerInches % Height.INCHES_PER_FOOT)) / Height.INCHES_PER_FOOT;
|
|
72
|
+
const inches = integerInches % Height.INCHES_PER_FOOT;
|
|
73
|
+
|
|
74
|
+
return { feet, inches };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
format(unit: HeightUnit, roundingStrategy?: RoundingStrategy): string {
|
|
78
|
+
return {
|
|
79
|
+
[HeightUnit.cm]: () => {
|
|
80
|
+
const rounding = roundingStrategy ?? new RoundToDecimal(1);
|
|
81
|
+
|
|
82
|
+
return `${this.toCentimeters(rounding)} cm`;
|
|
83
|
+
},
|
|
84
|
+
[HeightUnit.ft_in]: () => {
|
|
85
|
+
const rounding = roundingStrategy ?? new RoundToNearest();
|
|
86
|
+
const { feet, inches } = this.toFeetInches(rounding);
|
|
87
|
+
|
|
88
|
+
return `${feet}′${inches}″`;
|
|
89
|
+
},
|
|
90
|
+
}[unit]();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
equals(other: Height): boolean {
|
|
94
|
+
return this.millimeters === other.millimeters;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
compare(other: Height): -1 | 0 | 1 {
|
|
98
|
+
if (this.equals(other)) return 0;
|
|
99
|
+
return this.millimeters < other.millimeters ? -1 : 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
greaterThan(other: Height): boolean {
|
|
103
|
+
return this.millimeters > other.millimeters;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
lessThan(other: Height): boolean {
|
|
107
|
+
return this.millimeters < other.millimeters;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
isZero(): boolean {
|
|
111
|
+
return this.millimeters === 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
toJSON(): { mm: number } {
|
|
115
|
+
return { mm: this.millimeters };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static fromJSON(input: { mm: number }): Height {
|
|
119
|
+
return Height.fromMillimeters(input.mm);
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/image.vo.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
export type WidthType = z.infer<typeof
|
|
3
|
+
export const ImageWidth = z.number().int().positive().max(10000).brand("image-width");
|
|
4
|
+
export type WidthType = z.infer<typeof ImageWidth>;
|
|
5
5
|
|
|
6
|
-
export const
|
|
7
|
-
export type HeightType = z.infer<typeof
|
|
6
|
+
export const ImageHeight = z.number().int().positive().max(10000).brand("image-height");
|
|
7
|
+
export type HeightType = z.infer<typeof ImageHeight>;
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from "./age.vo";
|
|
1
2
|
export * from "./api-key.vo";
|
|
2
3
|
export * from "./basename.vo";
|
|
3
4
|
export * from "./clock.vo";
|
|
@@ -20,6 +21,7 @@ export * from "./file-path-relative-schema.vo";
|
|
|
20
21
|
export * from "./filename.vo";
|
|
21
22
|
export * from "./filename-from-string.vo";
|
|
22
23
|
export * from "./filename-suffix.vo";
|
|
24
|
+
export * from "./height.vo";
|
|
23
25
|
export * from "./hour.vo";
|
|
24
26
|
export * from "./iban.vo";
|
|
25
27
|
export * from "./iban-mask.service";
|
|
@@ -65,6 +67,7 @@ export * from "./visually-unambiguous-characters-generator.service";
|
|
|
65
67
|
export * from "./week.vo";
|
|
66
68
|
export * from "./week-iso-id.vo";
|
|
67
69
|
export * from "./weekday.vo";
|
|
70
|
+
export * from "./weight.vo";
|
|
68
71
|
export * from "./year.vo";
|
|
69
72
|
export * from "./year-iso-id.vo";
|
|
70
73
|
export * from "./z-score.service";
|
package/src/month.vo.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfMonth, startOfMonth } from "date-fns";
|
|
1
|
+
import { addMonths, endOfMonth, startOfMonth } from "date-fns";
|
|
2
2
|
import { DateRange } from "./date-range.vo";
|
|
3
3
|
import { MonthIsoId, type MonthIsoIdType } from "./month-iso-id.vo";
|
|
4
4
|
import { Timestamp, type TimestampType } from "./timestamp.vo";
|
|
@@ -8,6 +8,21 @@ export class Month extends DateRange {
|
|
|
8
8
|
return new Date(this.getStart()).toISOString().slice(0, 7) as MonthIsoIdType;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
previous(): Month {
|
|
12
|
+
const shifted = addMonths(new Date(this.getStart()), -1).getTime();
|
|
13
|
+
return Month.fromTimestamp(Timestamp.parse(shifted));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
next(): Month {
|
|
17
|
+
const shifted = addMonths(new Date(this.getStart()), 1).getTime();
|
|
18
|
+
return Month.fromTimestamp(Timestamp.parse(shifted));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
shift(count: number): Month {
|
|
22
|
+
const shifted = addMonths(new Date(this.getStart()), count).getTime();
|
|
23
|
+
return Month.fromTimestamp(Timestamp.parse(shifted));
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
static fromTimestamp(timestamp: TimestampType): Month {
|
|
12
27
|
const start = Timestamp.parse(startOfMonth(timestamp).getTime());
|
|
13
28
|
const end = Timestamp.parse(endOfMonth(timestamp).getTime());
|
package/src/week.vo.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfISOWeek, getISOWeek, getISOWeekYear, setISOWeek, startOfISOWeek } from "date-fns";
|
|
1
|
+
import { addWeeks, endOfISOWeek, getISOWeek, getISOWeekYear, setISOWeek, startOfISOWeek } from "date-fns";
|
|
2
2
|
import { DateRange } from "./date-range.vo";
|
|
3
3
|
import { Timestamp, type TimestampType } from "./timestamp.vo";
|
|
4
4
|
import { WeekIsoId, type WeekIsoIdType } from "./week-iso-id.vo";
|
|
@@ -11,6 +11,21 @@ export class Week extends DateRange {
|
|
|
11
11
|
return `${year}-W${week}`;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
previous(): Week {
|
|
15
|
+
const shifted = addWeeks(new Date(this.getStart()), -1).getTime();
|
|
16
|
+
return Week.fromTimestamp(Timestamp.parse(shifted));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
next(): Week {
|
|
20
|
+
const shifted = addWeeks(new Date(this.getStart()), 1).getTime();
|
|
21
|
+
return Week.fromTimestamp(Timestamp.parse(shifted));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
shift(count: number): Week {
|
|
25
|
+
const shifted = addWeeks(new Date(this.getStart()), count).getTime();
|
|
26
|
+
return Week.fromTimestamp(Timestamp.parse(shifted));
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
static fromTimestamp(timestamp: TimestampType): Week {
|
|
15
30
|
const start = Timestamp.parse(startOfISOWeek(timestamp).getTime());
|
|
16
31
|
const end = Timestamp.parse(endOfISOWeek(timestamp).getTime());
|
package/src/weight.vo.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { type RoundingStrategy, RoundToDecimal } from "./rounding.service";
|
|
3
|
+
|
|
4
|
+
const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
|
|
5
|
+
const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
|
|
6
|
+
const PositiveNumericValue = FiniteNumericValue.gt(0, { message: "Must be greater than 0" });
|
|
7
|
+
const NonNegativeIntegerGrams = FiniteNumericValue.int().min(0, {
|
|
8
|
+
message: "Grams must be an integer greater than or equal to 0",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export enum WeightUnit {
|
|
12
|
+
kg = "kg",
|
|
13
|
+
lb = "lb",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class Weight {
|
|
17
|
+
private static readonly GRAMS_PER_KILOGRAM = 1_000;
|
|
18
|
+
private static readonly POUNDS_PER_KILOGRAM = 2.2046226218487757;
|
|
19
|
+
private static readonly KILOGRAMS_PER_POUND = 1 / Weight.POUNDS_PER_KILOGRAM;
|
|
20
|
+
|
|
21
|
+
private constructor(private readonly grams: number) {}
|
|
22
|
+
|
|
23
|
+
static fromKilograms(kilograms: number): Weight {
|
|
24
|
+
NonNegativeNumericValue.parse(kilograms);
|
|
25
|
+
const gramsRounded = Math.round(kilograms * Weight.GRAMS_PER_KILOGRAM);
|
|
26
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
27
|
+
return new Weight(gramsRounded);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static fromPounds(pounds: number): Weight {
|
|
31
|
+
NonNegativeNumericValue.parse(pounds);
|
|
32
|
+
const gramsRounded = Math.round(pounds * Weight.KILOGRAMS_PER_POUND * Weight.GRAMS_PER_KILOGRAM);
|
|
33
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
34
|
+
return new Weight(gramsRounded);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static fromGrams(grams: number): Weight {
|
|
38
|
+
NonNegativeNumericValue.parse(grams);
|
|
39
|
+
const gramsRounded = Math.round(grams);
|
|
40
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
41
|
+
return new Weight(gramsRounded);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static zero(): Weight {
|
|
45
|
+
return new Weight(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toGrams(): number {
|
|
49
|
+
return this.grams;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toKilograms(rounding?: RoundingStrategy): number {
|
|
53
|
+
const kilograms = this.grams / Weight.GRAMS_PER_KILOGRAM;
|
|
54
|
+
return rounding ? rounding.round(kilograms) : kilograms;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
toPounds(rounding?: RoundingStrategy): number {
|
|
58
|
+
const pounds = (this.grams / Weight.GRAMS_PER_KILOGRAM) * Weight.POUNDS_PER_KILOGRAM;
|
|
59
|
+
return rounding ? rounding.round(pounds) : pounds;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
format(unit: WeightUnit, rounding: RoundingStrategy = new RoundToDecimal(2)): string {
|
|
63
|
+
const value = { [WeightUnit.kg]: this.toKilograms(rounding), [WeightUnit.lb]: this.toPounds(rounding) }[
|
|
64
|
+
unit
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
return `${value.toString()} ${unit}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
add(other: Weight): Weight {
|
|
71
|
+
return new Weight(this.grams + other.grams);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
subtract(other: Weight): Weight {
|
|
75
|
+
const result = this.grams - other.grams;
|
|
76
|
+
return new Weight(result < 0 ? 0 : result);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
multiply(factor: number): Weight {
|
|
80
|
+
NonNegativeNumericValue.parse(factor);
|
|
81
|
+
const gramsRounded = Math.round(this.grams * factor);
|
|
82
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
83
|
+
return new Weight(gramsRounded);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
divideByScalar(divisor: number): Weight {
|
|
87
|
+
PositiveNumericValue.parse(divisor);
|
|
88
|
+
const gramsRounded = Math.round(this.grams / divisor);
|
|
89
|
+
NonNegativeIntegerGrams.parse(gramsRounded);
|
|
90
|
+
return new Weight(gramsRounded);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
equals(other: Weight): boolean {
|
|
94
|
+
return this.grams === other.grams;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
compare(other: Weight): -1 | 0 | 1 {
|
|
98
|
+
if (this.grams === other.grams) return 0;
|
|
99
|
+
return this.grams < other.grams ? -1 : 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
greaterThan(other: Weight): boolean {
|
|
103
|
+
return this.grams > other.grams;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
greaterThanOrEqual(other: Weight): boolean {
|
|
107
|
+
return this.grams >= other.grams;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
lessThan(other: Weight): boolean {
|
|
111
|
+
return this.grams < other.grams;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lessThanOrEqual(other: Weight): boolean {
|
|
115
|
+
return this.grams <= other.grams;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
isZero(): boolean {
|
|
119
|
+
return this.grams === 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
isPositive(): boolean {
|
|
123
|
+
return this.grams > 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
toJSON(): { g: number } {
|
|
127
|
+
return { g: this.grams };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static fromJSON(input: { g: number }): Weight {
|
|
131
|
+
return Weight.fromGrams(input.g);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/year.vo.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endOfYear, getYear, startOfYear } from "date-fns";
|
|
1
|
+
import { addYears, endOfYear, getYear, startOfYear } from "date-fns";
|
|
2
2
|
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";
|
|
@@ -13,6 +13,21 @@ export class Year extends DateRange {
|
|
|
13
13
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
previous(): Year {
|
|
17
|
+
const shifted = addYears(new Date(this.getStart()), -1).getTime();
|
|
18
|
+
return Year.fromTimestamp(Timestamp.parse(shifted));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
next(): Year {
|
|
22
|
+
const shifted = addYears(new Date(this.getStart()), 1).getTime();
|
|
23
|
+
return Year.fromTimestamp(Timestamp.parse(shifted));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
shift(count: number): Year {
|
|
27
|
+
const shifted = addYears(new Date(this.getStart()), count).getTime();
|
|
28
|
+
return Year.fromTimestamp(Timestamp.parse(shifted));
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
static fromTimestamp(timestamp: TimestampType): Year {
|
|
17
32
|
const start = Timestamp.parse(startOfYear(timestamp).getTime());
|
|
18
33
|
const end = Timestamp.parse(endOfYear(timestamp).getTime());
|