1 import mod from '@proton/utils/mod';
3 import { addDays, isNextDay } from '../date-fns-utc';
5 convertUTCDateTimeToZone,
6 convertZonedDateTimeToUTC,
10 } from '../date/timezone';
11 import { buildMailTo, getEmailTo } from '../helpers/email';
15 VcalDateOrDateTimeProperty,
16 VcalDateOrDateTimeValue,
20 VcalOrganizerProperty,
22 } from '../interfaces/calendar';
23 import { VcalDays } from '../interfaces/calendar';
24 import { getIsPropertyAllDay, getPropertyTzid } from './vcalHelper';
26 export const dateToProperty = ({
34 }): VcalDateProperty => {
36 value: { year, month, day },
37 parameters: { type: 'date' },
41 export const dateTimeToProperty = (
42 { year = 1, month = 1, day = 1, hours = 0, minutes = 0, seconds = 0 }: DateTime,
45 ): VcalDateTimeProperty => {
46 const value = { year, month, day, hours, minutes, seconds, isUTC };
60 export const getDateProperty = ({ year, month, day }: { year: number; month: number; day: number }) => {
61 return dateToProperty({ year, month, day });
64 export const getDateTimeProperty = (zonelessTime: DateTime, tzid = '') => {
65 const isUTC = (tzid || '').toLowerCase().includes('utc');
66 return dateTimeToProperty(zonelessTime, isUTC, isUTC ? undefined : tzid);
69 export const getDateOrDateTimeProperty = (property: VcalDateOrDateTimeProperty, start: Date) => {
70 if (getIsPropertyAllDay(property)) {
71 return getDateProperty(fromUTCDate(start));
73 return getDateTimeProperty(fromUTCDate(start), getPropertyTzid(property));
76 export const propertyToLocalDate = (property: VcalDateOrDateTimeProperty) => {
77 if (getIsPropertyAllDay(property)) {
78 return toLocalDate(property.value);
80 if (property.value.isUTC || !property.parameters?.tzid) {
81 return toUTCDate(property.value);
83 // For dates with a timezone, convert the relative date time to UTC time
84 return toUTCDate(convertZonedDateTimeToUTC(property.value, property.parameters.tzid));
87 export const propertyToUTCDate = (property: VcalDateOrDateTimeProperty) => {
88 if (getIsPropertyAllDay(property) || property.value.isUTC || !property.parameters?.tzid) {
89 return toUTCDate(property.value);
91 // For dates with a timezone, convert the relative date time to UTC time
92 return toUTCDate(convertZonedDateTimeToUTC(property.value, property.parameters.tzid));
95 interface GetDtendPropertyArgs {
96 dtstart: VcalDateOrDateTimeProperty;
97 dtend?: VcalDateOrDateTimeProperty;
99 export const getDtendProperty = ({ dtstart, dtend }: GetDtendPropertyArgs) => {
103 if (getIsPropertyAllDay(dtstart)) {
104 const utcEnd = addDays(toUTCDate(dtstart.value), 1);
105 return getDateProperty(fromUTCDate(utcEnd));
107 return getDateTimeProperty(dtstart.value, getPropertyTzid(dtstart));
110 export const dayToNumericDay = (day: VcalDaysKeys): VcalDays | undefined => {
111 return VcalDays[day];
114 export const numericDayToDay = (number: VcalDays): VcalDaysKeys => {
115 if (number in VcalDays) {
116 return VcalDays[number] as VcalDaysKeys;
118 return VcalDays[mod(number, 7)] as VcalDaysKeys;
121 export const getDateTimePropertyInDifferentTimezone = (
122 property: VcalDateOrDateTimeProperty,
126 if (isAllDay === true || getIsPropertyAllDay(property)) {
127 return getDateProperty(property.value);
129 const utcDate = propertyToUTCDate(property);
130 const zonedDate = convertUTCDateTimeToZone(fromUTCDate(utcDate), tzid);
131 return getDateTimeProperty(zonedDate, tzid);
134 export const getAllDayInfo = (dtstart: VcalDateOrDateTimeProperty, dtend?: VcalDateOrDateTimeProperty) => {
135 const isAllDay = getIsPropertyAllDay(dtstart);
137 return { isAllDay: false, isSingleAllDay: false };
140 return { isAllDay: true, isSingleAllDay: true };
142 // For all-day events, we need fake UTC dates to determine if the event lasts a single day
143 const fakeUTCStart = toUTCDate(dtstart.value);
144 const fakeUTCEnd = toUTCDate(dtend.value);
145 // account for non-RFC-compliant all-day events with DTSTART = DTEND
146 return { isAllDay: true, isSingleAllDay: isNextDay(fakeUTCStart, fakeUTCEnd) || +fakeUTCStart === +fakeUTCEnd };
149 export interface UntilDateArgument {
154 export const getUntilProperty = (
155 untilDateTime: UntilDateArgument,
158 ): VcalDateOrDateTimeValue => {
159 // According to the RFC, we should use UTC dates if and only if the event is not all-day.
161 // we should use a floating date in this case
163 year: untilDateTime.year,
164 month: untilDateTime.month,
165 day: untilDateTime.day,
168 // Pick end of day in the event start date timezone
169 const zonedEndOfDay = { ...untilDateTime, hours: 23, minutes: 59, seconds: 59 };
170 const utcEndOfDay = convertZonedDateTimeToUTC(zonedEndOfDay, tzid);
171 return { ...utcEndOfDay, isUTC: true };
174 export const extractEmailAddress = ({ value, parameters }: VcalAttendeeProperty | VcalOrganizerProperty) => {
175 const email = value || parameters?.cn;
176 return email && getEmailTo(email);
179 export const buildVcalOrganizer = (email: string, cn?: string) => {
181 value: buildMailTo(email),
188 export const buildVcalAttendee = (email: string) => {
190 value: buildMailTo(email),
197 export const getHasModifiedDtstamp = (newVevent: VcalVeventComponent, oldVevent: VcalVeventComponent) => {
198 const { dtstamp: newDtstamp } = newVevent;
199 const { dtstamp: oldDtstamp } = oldVevent;
200 if (!newDtstamp || !oldDtstamp) {
203 return +propertyToUTCDate(newDtstamp) !== +propertyToUTCDate(oldDtstamp);
206 export const getHasStartChanged = (newVevent: VcalVeventComponent, oldVevent: VcalVeventComponent) =>
207 +propertyToUTCDate(oldVevent.dtstart) !== +propertyToUTCDate(newVevent.dtstart);
209 export const getHasModifiedDateTimes = (newVevent: VcalVeventComponent, oldVevent: VcalVeventComponent) => {
210 const isStartPreserved = !getHasStartChanged(newVevent, oldVevent);
211 const isEndPreserved =
212 +propertyToUTCDate(getDtendProperty(newVevent)) === +propertyToUTCDate(getDtendProperty(oldVevent));
213 return !isStartPreserved || !isEndPreserved;
216 export const getIsEquivalentOrganizer = (newOrganizer: VcalOrganizerProperty, oldOrganizer: VcalOrganizerProperty) => {
217 if (newOrganizer.value !== oldOrganizer.value) {
220 if (newOrganizer.parameters?.cn !== oldOrganizer.parameters?.cn) {
226 export const getIsEquivalentAttendee = (newAttendee: VcalAttendeeProperty, oldAttendee: VcalAttendeeProperty) => {
227 if (newAttendee.value !== oldAttendee.value) {
230 if (newAttendee.parameters?.partstat !== oldAttendee.parameters?.partstat) {
233 if (newAttendee.parameters?.role !== oldAttendee.parameters?.role) {