import * as dayjs from 'dayjs'; import { Dayjs } from 'dayjs'; import * as quarterOfYear from 'dayjs/plugin/quarterOfYear'; import * as timezone from 'dayjs/plugin/timezone'; import { DurationUnitEnum } from '../enums/duration-unit.enum'; import { TicketTypeEnum } from '../enums/ticket-type.enum'; import { ValidationBehaviour } from '../enums/validation-behaviour.enum'; import { ComputeValidationDateParams } from '../interfaces/validation/compute-validation-date.params'; export class VanillaDurationCalculationService { constructor() { dayjs.extend(quarterOfYear); dayjs.extend(timezone); dayjs.tz.setDefault('Europe/Paris'); } public computeValidationDate(computeValidationDateParams: ComputeValidationDateParams): { validFrom: Date; validUntil: Date; } { const { fromDate, timeUnit, timeValue, ticketType, validityDateFrom, validityDateUntil, overrideUnit, overrideEndingHour, validationDateBehavior, } = computeValidationDateParams; let formattedFromDate: Date = dayjs(fromDate).toDate(); // handle "start of period" => force validation date to be at start of period (for calendar to be equal to ticket.dateFrom) if (validationDateBehavior === ValidationBehaviour.START_OF_PERIOD) { if ([TicketTypeEnum.DYNAMIC_CALENDAR].includes(ticketType)) { formattedFromDate = this.findBeginningOfPeriod( dayjs(fromDate), overrideUnit ?? timeUnit, ).toDate(); } else if ([TicketTypeEnum.CALENDAR].includes(ticketType)) { formattedFromDate = dayjs(validityDateFrom).toDate(); } } return { validFrom: formattedFromDate, validUntil: this.addDurationBoundedByValidity( dayjs(formattedFromDate), timeUnit, timeValue, validityDateUntil, overrideEndingHour, ).toDate(), }; } public addDurationBoundedByValidity( dateFrom: Dayjs, timeUnit: DurationUnitEnum, timeValue: number, validityDateUntil?: Date, overrideEndingHour?: string, ): Dayjs { // We subtract 1 second at each duration to have a 23:59 to avoid confusion at display in the front-end app let currentValidationUntilDate: Dayjs = this.subtractOneSecond( this.addDuration(dateFrom, timeUnit, timeValue), ); if (validityDateUntil) { const validityDateUntilAsDayJs: Dayjs = dayjs(validityDateUntil); if (currentValidationUntilDate.isAfter(validityDateUntilAsDayJs)) { currentValidationUntilDate = validityDateUntilAsDayJs; } } if (overrideEndingHour) { currentValidationUntilDate = this.overrideEndingHour( currentValidationUntilDate, overrideEndingHour, ); } return currentValidationUntilDate; } public findBeginningOfPeriod(dateFrom: Dayjs, periodUnit: DurationUnitEnum): Dayjs { switch (periodUnit) { case DurationUnitEnum.QUARTER: return dateFrom.startOf('quarter'); case DurationUnitEnum.YEAR: return dateFrom.startOf('year'); case DurationUnitEnum.MONTH: return dateFrom.startOf('month'); case DurationUnitEnum.WEEK: return dateFrom.startOf('week'); case DurationUnitEnum.DAY: return dateFrom.startOf('day'); case DurationUnitEnum.HOUR: return dateFrom.startOf('hour'); case DurationUnitEnum.MINUTE: return dateFrom.startOf('minute'); case DurationUnitEnum.SECOND: return dateFrom.startOf('second'); } } public addDuration(dateFrom: Dayjs, timeUnit: DurationUnitEnum, timeValue: number): Dayjs { switch (timeUnit) { case DurationUnitEnum.QUARTER: return dateFrom.add(timeValue, 'quarter'); case DurationUnitEnum.YEAR: return dateFrom.add(timeValue, 'year'); case DurationUnitEnum.MONTH: return dateFrom.add(timeValue, 'month'); case DurationUnitEnum.WEEK: return dateFrom.add(timeValue, 'week'); case DurationUnitEnum.DAY: return dateFrom.add(timeValue, 'day'); case DurationUnitEnum.HOUR: return dateFrom.add(timeValue, 'hour'); case DurationUnitEnum.MINUTE: return dateFrom.add(timeValue, 'minute'); case DurationUnitEnum.SECOND: return dateFrom.add(timeValue, 'second'); } } public overrideEndingHour(date: Dayjs, endingHour: string): Dayjs { const [hour, minutes] = endingHour.split(':'); date = date.hour(parseInt(hour)).minute(parseInt(minutes)).second(0).millisecond(0); return date; } public subtractOneSecond(date: Dayjs): Dayjs { return date.subtract(1, 'second'); } }