1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import <ExpoModulesCore/EXUtilities.h>
4#import <EXCalendar/EXCalendarConverter.h>
5
6@implementation EXCalendarConverter
7
8+ (NSString *)_recurrenceFrequency:(EKRecurrenceFrequency)frequency
9{
10  if (frequency == EKRecurrenceFrequencyDaily) return @"daily";
11  if (frequency == EKRecurrenceFrequencyWeekly) return @"weekly";
12  if (frequency == EKRecurrenceFrequencyMonthly) return @"monthly";
13  if (frequency == EKRecurrenceFrequencyYearly) return @"yearly";
14  return @"none";
15}
16
17+ (NSString *)_calendarType:(EKCalendarType)type
18{
19  if (type == EKCalendarTypeLocal) return @"local";
20  if (type == EKCalendarTypeCalDAV) return @"caldav";
21  if (type == EKCalendarTypeExchange) return @"exchange";
22  if (type == EKCalendarTypeSubscription) return @"subscribed";
23  if (type == EKCalendarTypeBirthday) return @"birthdays";
24  return @"unknown";
25}
26
27+ (NSString *)_entityType:(EKEntityMask)entityType
28{
29  BOOL allowsEvents = entityType & EKEntityMaskEvent;
30  BOOL allowsReminders = entityType & EKEntityMaskReminder;
31
32  if (allowsEvents && allowsReminders) {
33    return @"both";
34  }
35  if (allowsReminders) {
36    return @"reminder";
37  }
38  if (allowsEvents) {
39    return @"event";
40  }
41  return nil;
42}
43
44+ (NSString *)_eventAvailability:(EKEventAvailability)availability
45{
46  if (availability == EKEventAvailabilityNotSupported) return @"notSupported";
47  if (availability == EKEventAvailabilityBusy) return @"busy";
48  if (availability == EKEventAvailabilityFree) return @"free";
49  if (availability == EKEventAvailabilityTentative) return @"tentative";
50  if (availability == EKEventAvailabilityUnavailable) return @"unavailable";
51  return @"none";
52}
53
54+ (NSString *)_eventStatus:(EKEventStatus)status
55{
56  if (status == EKEventStatusNone) return @"none";
57  if (status == EKEventStatusConfirmed) return @"confirmed";
58  if (status == EKEventStatusTentative) return @"tentative";
59  if (status == EKEventStatusCanceled) return @"canceled";
60  return @"none";
61}
62
63+ (NSString *)_sourceType:(EKSourceType)type
64{
65  if (type == EKSourceTypeLocal) return @"local";
66  if (type == EKSourceTypeExchange) return @"exchange";
67  if (type == EKSourceTypeCalDAV) return @"caldav";
68  if (type == EKSourceTypeMobileMe) return @"mobileme";
69  if (type == EKSourceTypeSubscribed) return @"subscribed";
70  if (type == EKSourceTypeBirthdays) return @"birthdays";
71  return @"none";
72}
73
74+ (NSString *)_participantRole:(EKParticipantRole)role
75{
76  if (role == EKParticipantRoleUnknown) return @"unknown";
77  if (role == EKParticipantRoleRequired) return @"required";
78  if (role == EKParticipantRoleOptional) return @"optional";
79  if (role == EKParticipantRoleChair) return @"chair";
80  if (role == EKParticipantRoleNonParticipant) return @"nonParticipant";
81  return @"none";
82}
83
84+ (NSString *)_participantStatus:(EKParticipantStatus)status
85{
86  if (status == EKParticipantStatusUnknown) return @"unknown";
87  if (status == EKParticipantStatusPending) return @"pending";
88  if (status == EKParticipantStatusAccepted) return @"accepted";
89  if (status == EKParticipantStatusDeclined) return @"declined";
90  if (status == EKParticipantStatusTentative) return @"tentative";
91  if (status == EKParticipantStatusDelegated) return @"delegated";
92  if (status == EKParticipantStatusCompleted) return @"completed";
93  if (status == EKParticipantStatusInProcess) return @"inProcess";
94  return @"none";
95}
96
97+ (NSString *)_participantType:(EKParticipantType)type
98{
99  if (type == EKParticipantTypeUnknown) return @"unknown";
100  if (type == EKParticipantTypePerson) return @"person";
101  if (type == EKParticipantTypeRoom) return @"room";
102  if (type == EKParticipantTypeGroup) return @"group";
103  if (type == EKParticipantTypeResource) return @"resource";
104  return @"none";
105}
106
107+ (NSMutableArray *)_calendarSupportedAvailabilitiesFromMask:(EKCalendarEventAvailabilityMask)types
108{
109  NSMutableArray *availabilitiesStrings = [[NSMutableArray alloc] init];
110
111  if(types & EKCalendarEventAvailabilityBusy) [availabilitiesStrings addObject:@"busy"];
112  if(types & EKCalendarEventAvailabilityFree) [availabilitiesStrings addObject:@"free"];
113  if(types & EKCalendarEventAvailabilityTentative) [availabilitiesStrings addObject:@"tentative"];
114  if(types & EKCalendarEventAvailabilityUnavailable) [availabilitiesStrings addObject:@"unavailable"];
115
116  return availabilitiesStrings;
117}
118
119+ (NSDictionary *)serializeSource:(EKSource *)source
120{
121  if (!source) {
122    return nil;
123  }
124  return @{
125       @"id": source.sourceIdentifier,
126       @"type": [EXCalendarConverter _sourceType:source.sourceType],
127       @"name": EXNullIfNil(source.title)
128       };
129}
130
131+ (NSMutableDictionary *)serializeCalendar:(EKCalendar *)calendar
132{
133  NSMutableDictionary *serializedCalendar = [[NSMutableDictionary alloc] init];
134
135  serializedCalendar[@"id"] = EXNullIfNil(calendar.calendarIdentifier);
136  serializedCalendar[@"title"] = EXNullIfNil(calendar.title);
137  serializedCalendar[@"source"] = EXNullIfNil([EXCalendarConverter serializeSource:calendar.source]);
138  serializedCalendar[@"entityType"] = [EXCalendarConverter _entityType:calendar.allowedEntityTypes];
139  serializedCalendar[@"color"] = calendar.CGColor ? [EXUtilities hexStringWithCGColor:calendar.CGColor] : [NSNull null];
140  serializedCalendar[@"type"] = [EXCalendarConverter _calendarType:calendar.type];
141  serializedCalendar[@"allowsModifications"] = @(calendar.allowsContentModifications);
142  serializedCalendar[@"allowedAvailabilities"] = [EXCalendarConverter _calendarSupportedAvailabilitiesFromMask:calendar.supportedEventAvailabilities];
143
144  return serializedCalendar;
145}
146
147+ (NSArray *)serializeCalendars:(NSArray *)calendars
148{
149  NSMutableArray *serializedCalendars = [[NSMutableArray alloc] init];
150
151  for (EKCalendar *calendar in calendars) {
152    [serializedCalendars addObject:[EXCalendarConverter serializeCalendar:calendar]];
153  }
154
155  return serializedCalendars;
156}
157
158+ (NSMutableDictionary *)_serializeCalendarItem:(EKCalendarItem *)item withDateFormatter:(NSDateFormatter *)dateFormatter
159{
160  NSMutableDictionary *serializedItem = [[NSMutableDictionary alloc] init];
161
162  if (item.calendarItemIdentifier) {
163    serializedItem[@"id"] = item.calendarItemIdentifier;
164  }
165
166  if (item.calendar) {
167    serializedItem[@"calendarId"] = item.calendar.calendarIdentifier;
168  }
169
170  if (item.title) {
171    serializedItem[@"title"] = item.title;
172  }
173
174  if (item.location) {
175    serializedItem[@"location"] = item.location;
176  }
177
178  if (item.creationDate) {
179    serializedItem[@"creationDate"] = [dateFormatter stringFromDate:item.creationDate];
180  }
181
182  if (item.lastModifiedDate) {
183    serializedItem[@"lastModifiedDate"] = [dateFormatter stringFromDate:item.lastModifiedDate];
184  }
185
186  serializedItem[@"timeZone"] = EXNullIfNil(item.timeZone.name);
187  serializedItem[@"url"] = EXNullIfNil(item.URL.absoluteString.stringByRemovingPercentEncoding);
188  serializedItem[@"notes"] = EXNullIfNil(item.notes);
189  serializedItem[@"alarms"] = [EXCalendarConverter _serializeAlarms:item.alarms withDateFormatter:dateFormatter];
190
191  if (item.hasRecurrenceRules) {
192    EKRecurrenceRule *rule = [item.recurrenceRules objectAtIndex:0];
193    NSString *frequencyType = [EXCalendarConverter _recurrenceFrequency:[rule frequency]];
194
195    NSMutableDictionary *recurrenceRule = [NSMutableDictionary dictionaryWithDictionary:@{@"frequency": frequencyType}];
196
197    if ([rule interval]) {
198      recurrenceRule[@"interval"] = @([rule interval]);
199    }
200
201    if ([[rule recurrenceEnd] endDate]) {
202      recurrenceRule[@"endDate"] = [dateFormatter stringFromDate:[[rule recurrenceEnd] endDate]];
203    }
204
205    if ([[rule recurrenceEnd] occurrenceCount]) {
206      recurrenceRule[@"occurrence"] = @([[rule recurrenceEnd] occurrenceCount]);
207    }
208
209    if ([rule daysOfTheWeek]) {
210      NSMutableArray *daysOfTheWeek = [[NSMutableArray alloc] init];
211
212      for (EKRecurrenceDayOfWeek *dayOfTheWeek in [rule daysOfTheWeek]) {
213        [daysOfTheWeek addObject:@{@"dayOfTheWeek":@([dayOfTheWeek dayOfTheWeek]),
214                   @"weekNumber":@([dayOfTheWeek weekNumber])}];
215      }
216      recurrenceRule[@"daysOfTheWeek"] = daysOfTheWeek;
217    }
218
219    if ([rule daysOfTheMonth]) {
220      recurrenceRule[@"daysOfTheMonth"] = [rule daysOfTheMonth];
221    }
222
223    if ([rule daysOfTheYear]) {
224      recurrenceRule[@"daysOfTheYear"] = [rule daysOfTheYear];
225    }
226
227    if ([rule weeksOfTheYear]) {
228      recurrenceRule[@"weeksOfTheYear"] = [rule weeksOfTheYear];
229    }
230
231    if ([rule monthsOfTheYear]) {
232      recurrenceRule[@"monthsOfTheYear"] = [rule monthsOfTheYear];
233    }
234
235    if ([rule setPositions]) {
236      recurrenceRule[@"setPositions"] = [rule setPositions];
237    }
238
239    serializedItem[@"recurrenceRule"] = recurrenceRule;
240  }
241
242  return serializedItem;
243}
244
245+ (NSArray *)serializeCalendarEvents:(NSArray *)calendarEvents
246{
247  NSMutableArray *serializedCalendarEvents = [[NSMutableArray alloc] init];
248
249  for (EKEvent *event in calendarEvents) {
250
251    [serializedCalendarEvents addObject:[EXCalendarConverter serializeCalendarEvent:event]];
252  }
253
254  return serializedCalendarEvents;
255}
256
257+ (NSDictionary *)serializeCalendarEvent:(EKEvent *)event
258{
259  NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
260  NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
261  [dateFormatter setTimeZone:timeZone];
262  [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
263  [dateFormatter setDateFormat: @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z"];
264
265
266  NSMutableDictionary *formedCalendarEvent = [EXCalendarConverter _serializeCalendarItem:event withDateFormatter:dateFormatter];
267
268  if (event.startDate) {
269    formedCalendarEvent[@"startDate"] = [dateFormatter stringFromDate:event.startDate];
270  }
271
272  if (event.endDate) {
273    formedCalendarEvent[@"endDate"] = [dateFormatter stringFromDate:event.endDate];
274  }
275
276  if (event.occurrenceDate) {
277    formedCalendarEvent[@"originalStartDate"] = [dateFormatter stringFromDate:event.occurrenceDate];
278  }
279
280  formedCalendarEvent[@"isDetached"] = [NSNumber numberWithBool:event.isDetached];
281  formedCalendarEvent[@"allDay"] = [NSNumber numberWithBool:event.allDay];
282  formedCalendarEvent[@"availability"] = [EXCalendarConverter _eventAvailability:event.availability];
283  formedCalendarEvent[@"status"] = [EXCalendarConverter _eventStatus:event.status];
284  formedCalendarEvent[@"organizer"] = EXNullIfNil([EXCalendarConverter _serializeAttendee:event.organizer]);
285
286  return formedCalendarEvent;
287}
288
289+ (NSArray *)serializeReminders:(NSArray<EKReminder *> *)reminders
290{
291  NSMutableArray *serializedReminders = [[NSMutableArray alloc] init];
292
293  for (EKReminder *reminder in reminders) {
294    [serializedReminders addObject:[EXCalendarConverter serializeReminder:reminder]];
295  }
296
297  return serializedReminders;
298}
299
300+ (NSDictionary *)serializeReminder:(EKReminder *)reminder
301{
302  NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
303  NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
304  [dateFormatter setTimeZone:timeZone];
305  [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
306  [dateFormatter setDateFormat: @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z"];
307  NSCalendar *currentCalendar = [NSCalendar currentCalendar];
308
309  NSMutableDictionary *formedReminder = [EXCalendarConverter _serializeCalendarItem:reminder withDateFormatter:dateFormatter];
310
311  if (reminder.startDateComponents) {
312    NSDate *startDate = [currentCalendar dateFromComponents:reminder.startDateComponents];
313    formedReminder[@"startDate"] = [dateFormatter stringFromDate:startDate];
314  }
315
316  if (reminder.dueDateComponents) {
317    NSDate *dueDate = [currentCalendar dateFromComponents:reminder.dueDateComponents];
318    formedReminder[@"dueDate"] = [dateFormatter stringFromDate:dueDate];
319  }
320
321  formedReminder[@"completed"] = @(reminder.completed);
322
323  if (reminder.completionDate) {
324    formedReminder[@"completionDate"] = [dateFormatter stringFromDate:reminder.completionDate];
325  }
326
327  return formedReminder;
328}
329
330+ (NSArray *)serializeAttendees:(NSArray<EKParticipant *> *)attendees
331{
332  NSMutableArray *serializedAttendees = [[NSMutableArray alloc] init];
333
334  for (EKParticipant *attendee in attendees) {
335    [serializedAttendees addObject:[EXCalendarConverter _serializeAttendee:attendee]];
336  }
337
338  return serializedAttendees;
339}
340
341+ (NSDictionary *)_serializeAttendee:(EKParticipant *)attendee
342{
343  if (!attendee) {
344    return nil;
345  }
346  NSDictionary *emptyAttendee = @{
347                  @"isCurrentUser": @"",
348                  @"name": @"",
349                  @"role": @"",
350                  @"status": @"",
351                  @"type": @"",
352                  @"url": @""
353                  };
354
355  NSMutableDictionary *formedAttendee = [NSMutableDictionary dictionaryWithDictionary:emptyAttendee];
356
357  formedAttendee[@"isCurrentUser"] = @(attendee.currentUser);
358
359  if (attendee.name) {
360    formedAttendee[@"name"] = attendee.name;
361  }
362
363  if (attendee.participantRole) {
364    formedAttendee[@"role"] = [EXCalendarConverter _participantRole:attendee.participantRole];
365  }
366
367  if (attendee.participantStatus) {
368    formedAttendee[@"status"] = [EXCalendarConverter _participantStatus:attendee.participantStatus];
369  }
370
371  if (attendee.participantType) {
372    formedAttendee[@"type"] = [EXCalendarConverter _participantType:attendee.participantType];
373  }
374
375  if (attendee.URL) {
376    formedAttendee[@"url"] = attendee.URL.absoluteString.stringByRemovingPercentEncoding;
377  }
378
379  return formedAttendee;
380}
381
382+ (NSArray<NSDictionary *> *)_serializeAlarms:(NSArray<EKAlarm *> *)alarms withDateFormatter:(NSDateFormatter *)dateFormatter
383{
384  NSMutableArray *serializedAlarms = [[NSMutableArray alloc] init];
385
386  for (EKAlarm *alarm in alarms) {
387    NSMutableDictionary *formattedAlarm = [[NSMutableDictionary alloc] init];
388
389    if (alarm.absoluteDate) {
390      formattedAlarm[@"absoluteDate"] = [dateFormatter stringFromDate:alarm.absoluteDate];
391    }
392    if (alarm.relativeOffset) {
393      formattedAlarm[@"relativeOffset"] = @(alarm.relativeOffset / 60.0);
394    }
395    if (alarm.structuredLocation) {
396      NSString *proximity = nil;
397      switch (alarm.proximity) {
398        case EKAlarmProximityEnter:
399          proximity = @"enter";
400          break;
401        case EKAlarmProximityLeave:
402          proximity = @"leave";
403          break;
404        default:
405          proximity = @"None";
406          break;
407      }
408      formattedAlarm[@"structuredLocation"] = @{
409                                                @"title": alarm.structuredLocation.title,
410                                                @"proximity": proximity,
411                                                @"radius": @(alarm.structuredLocation.radius),
412                                                @"coords": @{
413                                                    @"latitude": @(alarm.structuredLocation.geoLocation.coordinate.latitude),
414                                                    @"longitude": @(alarm.structuredLocation.geoLocation.coordinate.longitude)
415                                                    }
416                                                };
417
418    }
419    [serializedAlarms addObject:formattedAlarm];
420  }
421  return serializedAlarms;
422}
423
424@end
425