1 import { differenceInMinutes } from 'date-fns';
3 import isTruthy from '@proton/utils/isTruthy';
6 VcalDateOrDateTimeProperty,
10 } from '../../interfaces/calendar';
11 import { propertyToUTCDate } from '../vcalConverter';
12 import { getIsPropertyAllDay } from '../vcalHelper';
15 const HOUR = 60 * MINUTE;
16 const DAY = 24 * HOUR;
19 export const transformBeforeAt = (at: Date) => {
20 const minutes = 60 - (at.getMinutes() || 60);
21 const hours = 24 - (at.getHours() || 24) - (minutes > 0 ? 1 : 0);
22 return new Date(at.getFullYear(), at.getMonth(), at.getDate(), hours, minutes);
25 export const getIsAbsoluteTrigger = (trigger: VcalTriggerProperty): trigger is VcalDateTimeProperty => {
26 return (trigger as VcalDateTimeProperty).parameters?.type === 'date-time';
29 export const absoluteToRelativeTrigger = (trigger: VcalDateTimeProperty, dtstart: VcalDateOrDateTimeProperty) => {
30 const utcStartDate = propertyToUTCDate(dtstart);
31 const triggerDate = propertyToUTCDate(trigger);
32 const durationInMinutes = differenceInMinutes(utcStartDate, triggerDate);
33 const duration = Math.abs(durationInMinutes * MINUTE);
34 const weeks = Math.floor(duration / WEEK);
35 const days = Math.floor((duration % WEEK) / DAY);
36 const hours = Math.floor((duration % DAY) / HOUR);
37 const minutes = Math.floor((duration % HOUR) / MINUTE);
39 return { weeks, days, hours, minutes, seconds: 0, isNegative: durationInMinutes >= 0 };
43 * If you import this function, notice unit has to be in seconds, not milliseconds
45 export const normalizeDurationToUnit = (duration: Partial<VcalDurationValue>, unit: number) => {
46 const normalizedUnits = [
47 Math.floor(((duration.weeks || 0) * WEEK) / unit),
48 Math.floor(((duration.days || 0) * DAY) / unit),
49 Math.floor(((duration.hours || 0) * HOUR) / unit),
50 Math.floor(((duration.minutes || 0) * MINUTE) / unit),
51 Math.floor((duration.seconds || 0) / unit),
53 return normalizedUnits.reduce((acc, curr) => acc + curr, 0);
56 export const normalizeRelativeTrigger = (duration: VcalDurationValue, isAllDay: boolean) => {
57 const { minutes, hours, weeks, days, isNegative } = duration;
59 // the API admits all trigger components for all-day events,
60 // but we do not support arbitrary combinations of non-zero values for weeks and days
61 const isMidNightAlarm = hours === 0 && minutes === 0;
62 const mustKeepWeeks = !isNegative || isMidNightAlarm ? days === 0 : days === 6;
64 ? { ...duration, seconds: 0 }
65 : { ...duration, weeks: 0, days: days + 7 * weeks, seconds: 0 };
67 // We only admit one trigger component for part-day events
68 // If that's the case already, no need to normalize
69 if ([minutes, hours, weeks, days].filter(isTruthy).length <= 1) {
70 return { ...duration, seconds: 0 };
72 // Otherwise, normalize to a single component
73 const result = { weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0, isNegative };
74 const totalMinutes = normalizeDurationToUnit(duration, MINUTE);
75 if (totalMinutes % 60 !== 0) {
76 return { ...result, minutes: totalMinutes };
78 const totalHours = Math.floor(totalMinutes / 60);
79 if (totalHours % 24 !== 0) {
80 return { ...result, hours: totalHours };
82 const totalDays = Math.floor(totalHours / 24);
83 if (totalDays % 7 !== 0) {
84 return { ...result, days: totalDays };
86 const totalWeeks = Math.floor(totalDays / 7);
87 return { ...result, weeks: totalWeeks };
90 export const normalizeTrigger = (trigger: VcalTriggerProperty, dtstart: VcalDateOrDateTimeProperty) => {
91 const duration = getIsAbsoluteTrigger(trigger) ? absoluteToRelativeTrigger(trigger, dtstart) : trigger.value;
92 return normalizeRelativeTrigger(duration, getIsPropertyAllDay(dtstart));