import type { IValueObject } from '@gridpilot/shared/domain'; import { NotificationDomainError } from '../errors/NotificationDomainError'; export interface QuietHoursProps { startHour: number; endHour: number; } /** * Value Object: QuietHours * * Encapsulates a daily quiet-hours window using 0-23 hour indices and * provides logic to determine whether a given hour falls within the window. * * Supports both normal ranges (start < end) and overnight ranges (start > end). */ export class QuietHours implements IValueObject { public readonly props: QuietHoursProps; private constructor(startHour: number, endHour: number) { this.props = { startHour, endHour }; } /** * Factory with validation. * - Hours must be integers between 0 and 23. * - Start and end cannot be equal (would mean a 0-length window). */ static create(startHour: number, endHour: number): QuietHours { QuietHours.assertValidHour(startHour, 'Start hour'); QuietHours.assertValidHour(endHour, 'End hour'); if (startHour === endHour) { throw new NotificationDomainError('Quiet hours start and end cannot be the same', 'validation'); } return new QuietHours(startHour, endHour); } private static assertValidHour(value: number, label: string): void { if (!Number.isInteger(value)) { throw new NotificationDomainError(`${label} must be an integer between 0 and 23`, 'validation'); } if (value < 0 || value > 23) { throw new NotificationDomainError(`${label} must be between 0 and 23`, 'validation'); } } /** * Returns true if the given hour (0-23) lies within the quiet window. */ containsHour(hour: number): boolean { QuietHours.assertValidHour(hour, 'Hour'); const { startHour, endHour } = this.props; if (startHour < endHour) { // Normal range (e.g., 22:00 to 23:59 is NOT this case, but 1:00 to 7:00 is) return hour >= startHour && hour < endHour; } // Overnight range (e.g., 22:00 to 07:00) return hour >= startHour || hour < endHour; } equals(other: IValueObject): boolean { return ( this.props.startHour === other.props.startHour && this.props.endHour === other.props.endHour ); } }