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';
8 VcalDateOrDateTimeValue,
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 = [
30 const isSingleValue = <T>(arg: T | T[] | undefined) => {
31 if (arg === undefined) {
34 return !Array.isArray(arg) || arg.length === 1;
38 * Remove redundant properties for a given RRULE
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');
47 if (interval && interval === 1) {
48 redundantProperties.push('interval');
50 if (freq === FREQUENCY.WEEKLY) {
51 if (isSingleValue(byday)) {
52 redundantProperties.push('byday');
55 if (freq === FREQUENCY.MONTHLY) {
56 if (isSingleValue(bymonthday)) {
57 redundantProperties.push('bymonthday');
60 if (freq === FREQUENCY.YEARLY) {
61 if (isSingleValue(byday) && isSingleValue(bymonth)) {
62 redundantProperties.push('byday', 'bymonth');
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());
72 return oldValue === newValue;
75 const isUntilEqual = (oldUntil?: VcalDateOrDateTimeValue, newUntil?: VcalDateOrDateTimeValue) => {
76 if (!oldUntil && !newUntil) {
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));
84 * Determine if two recurring rules are equal up to re-writing.
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;
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)) {
104 // Separate comparison for until to handle all-day -> to part-day
105 if (!isUntilEqual(oldValue.until, newValue.until)) {
108 return !maybeArrayComparisonKeys.some((key) => {
109 return !isMaybeArrayEqual(normalizedOldValue[key], normalizedNewValue[key]);
112 return isDeepEqual(oldRrule, newRrule);