Merge branch 'fix/sentry-issue' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / calendar / recurrence / rruleEqual.ts
blob22788e00bd1ea5934ae5ef5fe79a59989c32152f
1 import shallowEqual from '@proton/utils/shallowEqual';
3 import { isSameDay } from '../../date-fns-utc';
4 import { toUTCDate } from '../../date/timezone';
5 import isDeepEqual from '../../helpers/isDeepEqual';
6 import { omit } from '../../helpers/object';
7 import type {
8     VcalDateOrDateTimeValue,
9     VcalDaysKeys,
10     VcalRruleProperty,
11     VcalRrulePropertyValue,
12 } from '../../interfaces/calendar/VcalModel';
13 import { VcalDays } from '../../interfaces/calendar/VcalModel';
14 import { FREQUENCY } from '../constants';
15 import { dayToNumericDay } from '../vcalConverter';
16 import { getRruleValue } from './rrule';
17 import { withRruleWkst } from './rruleWkst';
19 const maybeArrayComparisonKeys = [
20     'byday',
21     'bymonthday',
22     'bymonth',
23     'bysecond',
24     'byminute',
25     'byhour',
26     'byyearday',
27     'byweekno',
28 ] as const;
30 const isSingleValue = <T>(arg: T | T[] | undefined) => {
31     if (arg === undefined) {
32         return false;
33     }
34     return !Array.isArray(arg) || arg.length === 1;
37 /**
38  * Remove redundant properties for a given RRULE
39  */
40 const getNormalizedRrule = (rrule: VcalRrulePropertyValue, wkstCalendar = VcalDays.MO) => {
41     const { freq, count, interval, byday, bymonth, bymonthday, wkst: wkstRrule } = rrule;
42     const wkst = wkstRrule ? dayToNumericDay(wkstRrule) : wkstCalendar;
43     const redundantProperties: (keyof VcalRrulePropertyValue)[] = [];
44     if (count && count === 1) {
45         redundantProperties.push('count');
46     }
47     if (interval && interval === 1) {
48         redundantProperties.push('interval');
49     }
50     if (freq === FREQUENCY.WEEKLY) {
51         if (isSingleValue(byday)) {
52             redundantProperties.push('byday');
53         }
54     }
55     if (freq === FREQUENCY.MONTHLY) {
56         if (isSingleValue(bymonthday)) {
57             redundantProperties.push('bymonthday');
58         }
59     }
60     if (freq === FREQUENCY.YEARLY) {
61         if (isSingleValue(byday) && isSingleValue(bymonth)) {
62             redundantProperties.push('byday', 'bymonth');
63         }
64     }
65     return withRruleWkst({ freq, ...omit(rrule, redundantProperties) }, wkst);
68 const isMaybeArrayEqual = (oldValue: any | any[], newValue: any | any[]) => {
69     if (Array.isArray(oldValue) && Array.isArray(newValue)) {
70         return shallowEqual(oldValue.slice().sort(), newValue.slice().sort());
71     }
72     return oldValue === newValue;
75 const isUntilEqual = (oldUntil?: VcalDateOrDateTimeValue, newUntil?: VcalDateOrDateTimeValue) => {
76     if (!oldUntil && !newUntil) {
77         return true;
78     }
79     // If changing an all-day event into a part-day event the until adds the time part, so ignore that here.
80     return oldUntil && newUntil && isSameDay(toUTCDate(oldUntil), toUTCDate(newUntil));
83 /**
84  * Determine if two recurring rules are equal up to re-writing.
85  */
86 export const getIsRruleEqual = (oldRrule?: VcalRruleProperty, newRrule?: VcalRruleProperty, ignoreWkst = false) => {
87     const oldValue = getRruleValue(oldRrule);
88     const newValue = getRruleValue(newRrule);
89     if (ignoreWkst && oldValue && newValue) {
90         // To ignore WKST, we just set it to 'MO' in both RRULEs
91         oldValue.wkst = VcalDays[VcalDays.MO] as VcalDaysKeys;
92         newValue.wkst = VcalDays[VcalDays.MO] as VcalDaysKeys;
93     }
94     if (newValue && oldValue) {
95         // we "normalize" the rrules first (i.e. remove maybeArrayComparisonKeys in case they are redundant)
96         const normalizedOldValue = getNormalizedRrule(oldValue);
97         const normalizedNewValue = getNormalizedRrule(newValue);
98         // Compare array values separately because they can be possibly unsorted...
99         const oldWithoutMaybeArrays = omit(normalizedOldValue, [...maybeArrayComparisonKeys, 'until']);
100         const newWithoutMaybeArrays = omit(normalizedNewValue, [...maybeArrayComparisonKeys, 'until']);
101         if (!isDeepEqual(newWithoutMaybeArrays, oldWithoutMaybeArrays)) {
102             return false;
103         }
104         // Separate comparison for until to handle all-day -> to part-day
105         if (!isUntilEqual(oldValue.until, newValue.until)) {
106             return false;
107         }
108         return !maybeArrayComparisonKeys.some((key) => {
109             return !isMaybeArrayEqual(normalizedOldValue[key], normalizedNewValue[key]);
110         });
111     }
112     return isDeepEqual(oldRrule, newRrule);