1 import truncate from '@proton/utils/truncate';
2 import uniqueBy from '@proton/utils/uniqueBy';
4 import { MINUTE } from '../constants';
5 import { convertUTCDateTimeToZone, fromUTCDate, getTimezoneOffset, toUTCDate } from '../date/timezone';
6 import { omit } from '../helpers/object';
10 VcalValarmRelativeComponent,
12 } from '../interfaces/calendar';
13 import getAlarmMessageText from './alarms/getAlarmMessageText';
14 import { getValarmTrigger } from './alarms/getValarmTrigger';
15 import { normalizeDurationToUnit } from './alarms/trigger';
16 import { NOTIFICATION_TYPE_API, NOTIFICATION_UNITS, NOTIFICATION_WHEN } from './constants';
17 import { getDisplayTitle } from './helper';
18 import { getMillisecondsFromTriggerString } from './vcal';
19 import { propertyToUTCDate } from './vcalConverter';
20 import { getIsAllDay } from './veventHelper';
23 * Given a raw event, (optionally) its starting date, the date now and a timezone id,
24 * generate a notification message for the event
27 component: VcalVeventComponent;
33 export const getAlarmMessage = ({ component, start, now, tzid, formatOptions }: Arguments) => {
34 const { dtstart, summary } = component;
35 const title = truncate(getDisplayTitle(summary?.value), 100);
36 const utcStartDate = start || propertyToUTCDate(dtstart);
38 // To determine if the event is happening in timezoned today, tomorrow, this month or this year,
39 // we pass fake UTC dates to the getAlarmMessage helper
40 const startFakeUTCDate = toUTCDate(convertUTCDateTimeToZone(fromUTCDate(utcStartDate), tzid));
41 const nowFakeUTCDate = toUTCDate(convertUTCDateTimeToZone(fromUTCDate(now), tzid));
42 const isAllDay = getIsAllDay(component);
44 return getAlarmMessageText({
54 * Given the UNIX timestamp for the occurrence of an alarm, and the trigger string of this alarm,
55 * return the timestamp in milliseconds the occurrence of the corresponding event.
56 * The computation must take into account possible DST shifts
63 export const getNextEventTime = ({ Occurrence, Trigger, tzid }: Params) => {
64 const alarmTime = Occurrence * 1000;
65 const eventTime = alarmTime - getMillisecondsFromTriggerString(Trigger);
66 const offsetAlarmTime = getTimezoneOffset(new Date(alarmTime), tzid).offset;
67 const offsetEventTime = getTimezoneOffset(new Date(eventTime), tzid).offset;
68 const offsetDifference = offsetAlarmTime - offsetEventTime;
69 // correct eventTime in case we jumped across an odd number of DST changes
70 return eventTime - offsetDifference * MINUTE;
74 * Filter out future notifications
76 export const filterFutureNotifications = (notifications: NotificationModel[]) => {
77 return notifications.filter(({ when, value }) => {
78 if (when === NOTIFICATION_WHEN.BEFORE) {
85 export const sortNotificationsByAscendingTrigger = (notifications: NotificationModel[]) =>
86 [...notifications].sort((a: NotificationModel, b: NotificationModel) => {
87 const triggerA = getValarmTrigger(a);
88 const triggerB = getValarmTrigger(b);
89 const triggerAMinutes =
90 normalizeDurationToUnit(triggerA, NOTIFICATION_UNITS.MINUTE) * (triggerA.isNegative ? -1 : 1);
91 const triggerBMinutes =
92 normalizeDurationToUnit(triggerB, NOTIFICATION_UNITS.MINUTE) * (triggerB.isNegative ? -1 : 1);
94 return triggerAMinutes - triggerBMinutes;
97 const sortNotificationsByAscendingValue = (a: NotificationModel, b: NotificationModel) =>
98 (a.value || 0) - (b.value || 0);
100 const uniqueNotificationComparator = (notification: NotificationModel) => {
101 const trigger = getValarmTrigger(notification);
103 return `${notification.type}-${
104 normalizeDurationToUnit(trigger, NOTIFICATION_UNITS.MINUTE) * (trigger.isNegative ? -1 : 1)
108 export const dedupeNotifications = (notifications: NotificationModel[]) => {
109 const sortedNotifications = [...notifications].sort(sortNotificationsByAscendingValue);
111 return uniqueBy(sortedNotifications, uniqueNotificationComparator);
114 const getSmallestNonZeroNumericValueFromDurationValue = (object: VcalDurationValue) =>
115 Math.min(...Object.values(omit(object, ['isNegative'])).filter(Boolean));
117 const sortAlarmsByAscendingTriggerValue = (a: VcalValarmRelativeComponent, b: VcalValarmRelativeComponent) => {
118 const aMin = getSmallestNonZeroNumericValueFromDurationValue(a.trigger.value);
119 const bMin = getSmallestNonZeroNumericValueFromDurationValue(b.trigger.value);
124 const uniqueAlarmComparator = (alarm: VcalValarmRelativeComponent) => {
125 const triggerValue = alarm.trigger.value;
126 const isTriggerNegative = 'isNegative' in triggerValue && triggerValue.isNegative;
128 return `${alarm.action.value}-${
129 normalizeDurationToUnit(triggerValue, NOTIFICATION_UNITS.MINUTE) * (isTriggerNegative ? -1 : 1)
135 * This function will deduplicate alarms with any type of relative trigger,
136 * but if you expect it to pick the nicest triggers (i.e. 2 days instead of 1 day and 24 hours)
137 * you must pass normalized triggers
139 export const dedupeAlarmsWithNormalizedTriggers = (alarms: VcalValarmRelativeComponent[]) => {
140 const sortedAlarms = [...alarms].sort(sortAlarmsByAscendingTriggerValue);
142 return uniqueBy(sortedAlarms, uniqueAlarmComparator);
145 export const isEmailNotification = ({ type }: NotificationModel) => type === NOTIFICATION_TYPE_API.EMAIL;