Uecko_ERP/packages/rdx-ddd/src/value-objects/utc-date.ts
2026-07-01 09:51:22 +02:00

143 lines
3.3 KiB
TypeScript

import { Result } from "@repo/rdx-utils";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";
interface UtcDateProps {
value: string;
}
const UTC_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
export class UtcDate extends ValueObject<UtcDateProps> {
private readonly timestamp: number;
private constructor(props: UtcDateProps) {
super(props);
this.timestamp = UtcDate.toTimestamp(props.value);
}
static create(dateString: string): Result<UtcDate, Error> {
const validation = UtcDate.validate(dateString);
if (!validation.success) {
return Result.fail(translateZodValidationError("UtcDate creation failed", validation.error));
}
return Result.ok(new UtcDate({ value: validation.data }));
}
static createFromISO(dateString: string): Result<UtcDate, Error> {
return UtcDate.create(dateString);
}
static today(): UtcDate {
const now = new Date();
const year = now.getUTCFullYear();
const month = String(now.getUTCMonth() + 1).padStart(2, "0");
const day = String(now.getUTCDate()).padStart(2, "0");
return new UtcDate({
value: `${year}-${month}-${day}`,
});
}
static validate(dateString: string) {
return z
.string()
.regex(UTC_DATE_REGEX, "Invalid YYYY-MM-DD format")
.superRefine((value, ctx) => {
if (!UtcDate.isValidCalendarDate(value)) {
ctx.addIssue({
code: "custom",
message: "Invalid calendar date",
});
}
})
.safeParse(dateString);
}
private static isValidCalendarDate(value: string): boolean {
const [yearRaw, monthRaw, dayRaw] = value.split("-");
const year = Number(yearRaw);
const month = Number(monthRaw);
const day = Number(dayRaw);
const date = new Date(Date.UTC(year, month - 1, day));
return (
date.getUTCFullYear() === year &&
date.getUTCMonth() === month - 1 &&
date.getUTCDate() === day
);
}
private static toTimestamp(value: string): number {
const [yearRaw, monthRaw, dayRaw] = value.split("-");
const year = Number(yearRaw);
const month = Number(monthRaw);
const day = Number(dayRaw);
return Date.UTC(year, month - 1, day, 0, 0, 0, 0);
}
getProps(): UtcDateProps {
return this.props;
}
toPrimitive(): string {
return this.props.value;
}
toDateString(): string {
return this.props.value;
}
toString(): string {
return this.toDateString();
}
toISOString(): string {
return `${this.props.value}T00:00:00.000Z`;
}
toDate(): Date {
return new Date(this.timestamp);
}
toEuropeanString(): string {
const [year, month, day] = this.props.value.split("-");
return `${day}/${month}/${year}`;
}
equals(other: UtcDate): boolean {
return this.props.value === other.props.value;
}
isFuture(currentDate: UtcDate = UtcDate.today()): boolean {
return this.timestamp > currentDate.timestamp;
}
isPast(currentDate: UtcDate = UtcDate.today()): boolean {
return this.timestamp < currentDate.timestamp;
}
isToday(): boolean {
return this.equals(UtcDate.today());
}
isBefore(other: UtcDate): boolean {
return this.timestamp < other.timestamp;
}
isAfter(other: UtcDate): boolean {
return this.timestamp > other.timestamp;
}
}