import { toPluralString } from '@prefecthq/prefect-design' import { fromZonedTime } from 'date-fns-tz' import { ISchedule } from '@/models' import { IntervalScheduleResponse } from '@/models/api/ScheduleResponse' import { secondsInMinute, minutesInHour, dateFormat, timeNumericFormat } from '@/utilities/dates' import { floor } from '@/utilities/math' import { formatDateInTimezone } from '@/utilities/timezone' export type Intervals = { seconds: number, minutes: number, hours: number, days: number, } export interface IIntervalSchedule extends ISchedule { interval: number, timezone: string | null, anchorDate: Date | null, getIntervals?: () => Intervals, } export class IntervalSchedule implements IIntervalSchedule { public timezone: string | null public interval: number public anchorDate: Date | null public constructor(schedule: Pick) { this.timezone = schedule.timezone this.interval = schedule.interval this.anchorDate = schedule.anchorDate } public get raw(): number { return this.interval } public getIntervals(): Intervals { let remainder = this.interval const intervals = { seconds: 0, minutes: 0, hours: 0, days: 0, } intervals.seconds = remainder % secondsInMinute remainder = floor(remainder / secondsInMinute) intervals.minutes = remainder % minutesInHour remainder = floor(remainder / minutesInHour) intervals.hours = remainder % 24 remainder = floor(remainder / 24) intervals.days = remainder return intervals } public toString({ verbose = false, neat = true }: { neat?: boolean, verbose?: boolean } = {}): string { const { seconds, minutes, hours, days } = this.getIntervals() const strings: string[] = [] if (seconds) { if (neat && seconds === 1 && !minutes && !hours && !days) { strings.push('second') } else { strings.push(`${seconds} ${toPluralString('second', seconds)}`) } } if (minutes) { if (neat && minutes === 1 && !seconds && !hours && !days) { strings.push('minute') } else { strings.push(`${minutes} ${toPluralString('minute', minutes)}`) } } if (hours) { if (neat && hours === 1 && !seconds && !minutes && !days) { strings.push('Hourly') } else { strings.push(`${hours} ${toPluralString('hour', hours)}`) } } if (days) { if (neat && days === 1 && !seconds && !minutes && !hours) { strings.push('Daily') } else { strings.push(`${days} ${toPluralString('day', days)}`) } } let str = strings.reverse().join(', ') if (!str.includes('Every') && !str.includes('Daily') && !str.includes('Hourly')) { str = `Every ${str}` } if (this.anchorDate && verbose) { const date = formatDateInTimezone(this.anchorDate, dateFormat, this.timezone ?? 'UTC') const time = formatDateInTimezone(this.anchorDate, timeNumericFormat, this.timezone ?? 'UTC') str += ` using ${date} at ${time} (${this.timezone ?? 'UTC'}) as the anchor date` } if (str == '') { str = 'None' } return str } public toResponse(): IntervalScheduleResponse { const date = this.anchorDate ?? new Date() const timezone = this.timezone ?? 'UTC' const utcDate = fromZonedTime(date, timezone).toISOString() return { 'interval': this.interval, 'anchor_date': utcDate, 'timezone': timezone, } } }