121 lines
2.8 KiB
TypeScript
121 lines
2.8 KiB
TypeScript
/**
|
|
* DateRange Value Object
|
|
*
|
|
* Encapsulates ETD/ETA date range with validation
|
|
*
|
|
* Business Rules:
|
|
* - End date must be after start date
|
|
* - Dates cannot be in the past (for new shipments)
|
|
* - Date range is immutable
|
|
*/
|
|
|
|
export class DateRange {
|
|
private readonly startDate: Date;
|
|
private readonly endDate: Date;
|
|
|
|
private constructor(startDate: Date, endDate: Date) {
|
|
this.startDate = startDate;
|
|
this.endDate = endDate;
|
|
}
|
|
|
|
static create(startDate: Date, endDate: Date, allowPastDates = false): DateRange {
|
|
if (!startDate || !endDate) {
|
|
throw new Error('Start date and end date are required.');
|
|
}
|
|
|
|
if (endDate <= startDate) {
|
|
throw new Error('End date must be after start date.');
|
|
}
|
|
|
|
if (!allowPastDates) {
|
|
const now = new Date();
|
|
now.setHours(0, 0, 0, 0); // Reset time to start of day
|
|
|
|
if (startDate < now) {
|
|
throw new Error('Start date cannot be in the past.');
|
|
}
|
|
}
|
|
|
|
return new DateRange(new Date(startDate), new Date(endDate));
|
|
}
|
|
|
|
/**
|
|
* Create from ETD and transit days
|
|
*/
|
|
static fromTransitDays(etd: Date, transitDays: number): DateRange {
|
|
if (transitDays <= 0) {
|
|
throw new Error('Transit days must be positive.');
|
|
}
|
|
|
|
const eta = new Date(etd);
|
|
eta.setDate(eta.getDate() + transitDays);
|
|
|
|
return DateRange.create(etd, eta, true);
|
|
}
|
|
|
|
getStartDate(): Date {
|
|
return new Date(this.startDate);
|
|
}
|
|
|
|
getEndDate(): Date {
|
|
return new Date(this.endDate);
|
|
}
|
|
|
|
getDurationInDays(): number {
|
|
const diffTime = this.endDate.getTime() - this.startDate.getTime();
|
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
}
|
|
|
|
getDurationInHours(): number {
|
|
const diffTime = this.endDate.getTime() - this.startDate.getTime();
|
|
return Math.ceil(diffTime / (1000 * 60 * 60));
|
|
}
|
|
|
|
contains(date: Date): boolean {
|
|
return date >= this.startDate && date <= this.endDate;
|
|
}
|
|
|
|
overlaps(other: DateRange): boolean {
|
|
return (
|
|
this.startDate <= other.endDate && this.endDate >= other.startDate
|
|
);
|
|
}
|
|
|
|
isFutureRange(): boolean {
|
|
const now = new Date();
|
|
return this.startDate > now;
|
|
}
|
|
|
|
isPastRange(): boolean {
|
|
const now = new Date();
|
|
return this.endDate < now;
|
|
}
|
|
|
|
isCurrentRange(): boolean {
|
|
const now = new Date();
|
|
return this.contains(now);
|
|
}
|
|
|
|
equals(other: DateRange): boolean {
|
|
return (
|
|
this.startDate.getTime() === other.startDate.getTime() &&
|
|
this.endDate.getTime() === other.endDate.getTime()
|
|
);
|
|
}
|
|
|
|
toString(): string {
|
|
return `${this.formatDate(this.startDate)} - ${this.formatDate(this.endDate)}`;
|
|
}
|
|
|
|
private formatDate(date: Date): string {
|
|
return date.toISOString().split('T')[0];
|
|
}
|
|
|
|
toObject(): { startDate: Date; endDate: Date } {
|
|
return {
|
|
startDate: new Date(this.startDate),
|
|
endDate: new Date(this.endDate),
|
|
};
|
|
}
|
|
}
|