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