UNPKG

expo-calendar

Version:

Provides an API for interacting with the device's system calendars, events, reminders, and associated records.

1,666 lines (1,581 loc) 49.3 kB
import { PermissionResponse, createPermissionHook, UnavailabilityError } from 'expo-modules-core'; import { Platform, processColor } from 'react-native'; import ExpoCalendar from './ExpoCalendar'; export { PermissionResponse, PermissionStatus, PermissionHookOptions, PermissionExpiration, } from 'expo-modules-core'; // @needsAudit /** * @platform ios */ export type RecurringEventOptions = { /** * Whether future events in the recurring series should also be updated. If `true`, will * apply the given changes to the recurring instance specified by `instanceStartDate` and all * future events in the series. If `false`, will only apply the given changes to the instance * specified by `instanceStartDate`. */ futureEvents?: boolean; /** * Date object representing the start time of the desired instance, if looking for a single instance * of a recurring event. If this is not provided and **id** represents a recurring event, the first * instance of that event will be returned by default. */ instanceStartDate?: string | Date; }; type Organizer = { isCurrentUser: boolean; name?: string; role: string; status: string; type: string; url?: string; }; // @needsAudit /** * A calendar record upon which events (or, on iOS, reminders) can be stored. Settings here apply to * the calendar as a whole and how its events are displayed in the OS calendar app. */ export type Calendar = { /** * Internal ID that represents this calendar on the device. */ id: string; /** * Visible name of the calendar. */ title: string; /** * ID of the source to be used for the calendar. Likely the same as the source for any other * locally stored calendars. * @platform ios */ sourceId?: string; /** * Object representing the source to be used for the calendar. */ source: Source; /** * Type of calendar this object represents. * @platform ios */ type?: CalendarType; /** * Color used to display this calendar's events. */ color: string; /** * Whether the calendar is used in the Calendar or Reminders OS app. * @platform ios */ entityType?: EntityTypes; /** * Boolean value that determines whether this calendar can be modified. */ allowsModifications: boolean; /** * Availability types that this calendar supports. */ allowedAvailabilities: Availability[]; /** * Boolean value indicating whether this is the device's primary calendar. * @platform android */ isPrimary?: boolean; /** * Internal system name of the calendar. * @platform android */ name?: string | null; /** * Name for the account that owns this calendar. * @platform android */ ownerAccount?: string; /** * Time zone for the calendar. * @platform android */ timeZone?: string; /** * Alarm methods that this calendar supports. * @platform android */ allowedReminders?: AlarmMethod[]; /** * Attendee types that this calendar supports. * @platform android */ allowedAttendeeTypes?: AttendeeType[]; /** * Indicates whether the OS displays events on this calendar. * @platform android */ isVisible?: boolean; /** * Indicates whether this calendar is synced and its events stored on the device. * Unexpected behavior may occur if this is not set to `true`. * @platform android */ isSynced?: boolean; /** * Level of access that the user has for the calendar. * @platform android */ accessLevel?: CalendarAccessLevel; }; // @needsAudit /** * A source account that owns a particular calendar. Expo apps will typically not need to interact with `Source` objects. */ export type Source = { /** * Internal ID that represents this source on the device. * @platform ios */ id?: string; /** * Type of the account that owns this calendar and was used to sync it to the device. * If `isLocalAccount` is falsy then this must be defined, and must match an account on the device * along with `name`, or the OS will delete the calendar. * On iOS, one of [`SourceType`](#sourcetype)s. */ type: string | SourceType; /** * Name for the account that owns this calendar and was used to sync the calendar to the device. */ name: string; /** * Whether this source is the local phone account. Must be `true` if `type` is `undefined`. * @platform android */ isLocalAccount?: boolean; }; // @needsAudit /** * An event record, or a single instance of a recurring event. On iOS, used in the Calendar app. */ export type Event = { /** * Internal ID that represents this event on the device. */ id: string; /** * ID of the calendar that contains this event. */ calendarId: string; /** * Visible name of the event. */ title: string; /** * Location field of the event. */ location: string | null; /** * Date when the event record was created. * @platform ios */ creationDate?: string | Date; /** * Date when the event record was last modified. * @platform ios */ lastModifiedDate?: string | Date; /** * Time zone the event is scheduled in. */ timeZone: string; /** * Time zone for the event end time. * @platform android */ endTimeZone?: string; /** * URL for the event. * @platform ios */ url?: string; /** * Description or notes saved with the event. */ notes: string; /** * Array of Alarm objects which control automated reminders to the user. */ alarms: Alarm[]; /** * Object representing rules for recurring or repeating events. Set to `null` for one-time events. */ recurrenceRule: RecurrenceRule | null; /** * Date object or string representing the time when the event starts. */ startDate: string | Date; /** * Date object or string representing the time when the event ends. */ endDate: string | Date; /** * For recurring events, the start date for the first (original) instance of the event. * @platform ios */ originalStartDate?: string | Date; /** * Boolean value indicating whether or not the event is a detached (modified) instance of a recurring event. * @platform ios */ isDetached?: boolean; /** * Whether the event is displayed as an all-day event on the calendar */ allDay: boolean; /** * The availability setting for the event. */ availability: Availability; /** * Status of the event. */ status: EventStatus; /** * Organizer of the event. * This property is only available on events associated with calendars that are managed by a service ie. Google Calendar or iCloud. * The organizer is read-only and cannot be set. * * @platform ios */ organizer?: Organizer; /** * Email address of the organizer of the event. * @platform android */ organizerEmail?: string; /** * User's access level for the event. * @platform android */ accessLevel?: EventAccessLevel; /** * Whether invited guests can modify the details of the event. * @platform android */ guestsCanModify?: boolean; /** * Whether invited guests can invite other guests. * @platform android */ guestsCanInviteOthers?: boolean; /** * Whether invited guests can see other guests. * @platform android */ guestsCanSeeGuests?: boolean; /** * For detached (modified) instances of recurring events, the ID of the original recurring event. * @platform android */ originalId?: string; /** * For instances of recurring events, volatile ID representing this instance. Not guaranteed to * always refer to the same instance. * @platform android */ instanceId?: string; }; // @needsAudit /** * A reminder record, used in the iOS Reminders app. No direct analog on Android. * @platform ios */ export type Reminder = { /** * Internal ID that represents this reminder on the device. */ id?: string; /** * ID of the calendar that contains this reminder. */ calendarId?: string; /** * Visible name of the reminder. */ title?: string; /** * Location field of the reminder */ location?: string; /** * Date when the reminder record was created. */ creationDate?: string | Date; /** * Date when the reminder record was last modified. */ lastModifiedDate?: string | Date; /** * Time zone the reminder is scheduled in. */ timeZone?: string; /** * URL for the reminder. */ url?: string; /** * Description or notes saved with the reminder. */ notes?: string; /** * Array of Alarm objects which control automated alarms to the user about the task. */ alarms?: Alarm[]; /** * Object representing rules for recurring or repeated reminders. `null` for one-time tasks. */ recurrenceRule?: RecurrenceRule | null; /** * Date object or string representing the start date of the reminder task. */ startDate?: string | Date; /** * Date object or string representing the time when the reminder task is due. */ dueDate?: string | Date; /** * Indicates whether or not the task has been completed. */ completed?: boolean; /** * Date object or string representing the date of completion, if `completed` is `true`. * Setting this property of a nonnull `Date` will automatically set the reminder's `completed` value to `true`. */ completionDate?: string | Date; }; // @needsAudit /** * A person or entity that is associated with an event by being invited or fulfilling some other role. */ export type Attendee = { /** * Internal ID that represents this attendee on the device. * @platform android */ id?: string; /** * Indicates whether or not this attendee is the current OS user. * @platform ios */ isCurrentUser?: boolean; /** * Displayed name of the attendee. */ name: string; /** * Role of the attendee at the event. */ role: AttendeeRole; /** * Status of the attendee in relation to the event. */ status: AttendeeStatus; /** * Type of the attendee. */ type: AttendeeType; /** * URL for the attendee. * @platform ios */ url?: string; /** * Email address of the attendee. * @platform android */ email?: string; }; // @needsAudit /** * A method for having the OS automatically remind the user about a calendar item. */ export type Alarm = { /** * Date object or string representing an absolute time the alarm should occur. * Overrides `relativeOffset` and `structuredLocation` if specified alongside either. * @platform ios */ absoluteDate?: string; /** * Number of minutes from the `startDate` of the calendar item that the alarm should occur. * Use negative values to have the alarm occur before the `startDate`. */ relativeOffset?: number; // @docsMissing structuredLocation?: AlarmLocation; /** * Method of alerting the user that this alarm should use. On iOS this is always a notification. * @platform android */ method?: AlarmMethod; }; // @docsMissing export type AlarmLocation = { /** * @platform ios */ title?: string; proximity?: string; radius?: number; coords?: { latitude?: number; longitude?: number; }; }; // @docsMissing /** * @platform ios */ export enum DayOfTheWeek { Sunday = 1, Monday = 2, Tuesday = 3, Wednesday = 4, Thursday = 5, Friday = 6, Saturday = 7, } // @docsMissing /** * @platform ios */ export enum MonthOfTheYear { January = 1, February = 2, March = 3, April = 4, May = 5, June = 6, July = 7, August = 8, September = 9, October = 10, November = 11, December = 12, } // @needsAudit /** * A recurrence rule for events or reminders, allowing the same calendar item to recur multiple times. * This type is based on [the iOS interface](https://developer.apple.com/documentation/eventkit/ekrecurrencerule/1507320-initrecurrencewithfrequency) * which is in turn based on [the iCal RFC](https://tools.ietf.org/html/rfc5545#section-3.8.5.3) * so you can refer to those to learn more about this potentially complex interface. * * Not all the combinations make sense. For example, when frequency is `DAILY`, setting `daysOfTheMonth` makes no sense. */ export type RecurrenceRule = { /** * How often the calendar item should recur. */ frequency: Frequency; /** * Interval at which the calendar item should recur. For example, an `interval: 2` with `frequency: DAILY` * would yield an event that recurs every other day. * @default 1 */ interval?: number; /** * Date on which the calendar item should stop recurring; overrides `occurrence` if both are specified. */ endDate?: string | Date; /** * Number of times the calendar item should recur before stopping. */ occurrence?: number; /** * The days of the week the event should recur on. An array of [`DaysOfTheWeek`](#daysoftheweek) object. * @platform ios */ daysOfTheWeek?: DaysOfTheWeek[]; /** * The days of the month this event occurs on. * `-31` to `31` (not including `0`). Negative indicates a value from the end of the range. * This field is only valid for `Calendar.Frequency.Monthly`. * @platform ios */ daysOfTheMonth?: number[]; /** * The months this event occurs on. * This field is only valid for `Calendar.Frequency.Yearly`. * @platform ios */ monthsOfTheYear?: MonthOfTheYear[]; /** * The weeks of the year this event occurs on. * `-53` to `53` (not including `0`). Negative indicates a value from the end of the range. * This field is only valid for `Calendar.Frequency.Yearly`. * @platform ios */ weeksOfTheYear?: number[]; /** * The days of the year this event occurs on. * `-366` to `366` (not including `0`). Negative indicates a value from the end of the range. * This field is only valid for `Calendar.Frequency.Yearly`. * @platform ios */ daysOfTheYear?: number[]; /** * TAn array of numbers that filters which recurrences to include. For example, for an event that * recurs every Monday, passing 2 here will make it recur every other Monday. * `-366` to `366` (not including `0`). Negative indicates a value from the end of the range. * This field is only valid for `Calendar.Frequency.Yearly`. * @platform ios */ setPositions?: number[]; }; // @needsAudit /** * @platform ios */ export type DaysOfTheWeek = { /** * Sunday to Saturday - `DayOfTheWeek` enum. */ dayOfTheWeek: DayOfTheWeek; /** * `-53` to `53` (`0` ignores this field, and a negative indicates a value from the end of the range). */ weekNumber?: number; }; /** * Enum containing all possible user responses to the calendar UI dialogs. Depending on what dialog is presented, a subset of the values applies. * */ export enum CalendarDialogResultActions { /** * On Android, this is the only possible result because the OS doesn't provide enough information to determine the user's action - * the user may have canceled the dialog, modified the event, or deleted it. * * On iOS, this means the user simply closed the dialog. * */ done = 'done', /** * The user canceled or dismissed the dialog. * @platform ios * */ canceled = 'canceled', /** * The user deleted the event. * @platform ios * */ deleted = 'deleted', /** * The user responded to and saved a pending event invitation. * @platform ios * */ responded = 'responded', /** * The user saved a new event or modified an existing one. * @platform ios * */ saved = 'saved', } /** * The result of presenting the calendar dialog for opening (viewing) an event. * */ export type OpenEventDialogResult = { /** * Indicates how user responded to the dialog. * On Android, the `action` is always `done`. * On iOS, it can be `done`, `canceled`, `deleted` or `responded`. * */ action: Extract<CalendarDialogResultActions, 'done' | 'canceled' | 'deleted' | 'responded'>; }; /** * The result of presenting a calendar dialog for creating or editing an event. * */ export type DialogEventResult = { /** * How user responded to the dialog. * On Android, this is always `done` (Android doesn't provide enough information to determine the user's action - * the user may have canceled the dialog, saved or deleted the event). * * On iOS, it can be `saved`, `canceled` or `deleted`. * */ action: Extract<CalendarDialogResultActions, 'done' | 'saved' | 'canceled' | 'deleted'>; /** * The ID of the event that was created or edited. On Android, this is always `null`. * * On iOS, this is a string when user confirms the creation or editing of an event. Otherwise, it's `null`. * */ id: string | null; }; export type PresentationOptions = { /** * Whether to launch the Activity as a new [task](https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_NEW_TASK). * If `true`, the promise resolves with `'done'` action immediately after opening the calendar activity. * @default true * @platform android */ startNewActivityTask?: boolean; }; export type OpenEventPresentationOptions = PresentationOptions & { /** * Whether to allow the user to edit the previewed event. * This property applies only to events in calendars created by the user. * * Note that if the user edits the event, the returned action is the one that user performs last. * For example, when user previews the event, confirms some edits and finally dismisses the dialog, the event is edited, but response is `canceled`. * * @default false * @platform ios * */ allowsEditing?: boolean; /** * Determines whether event can be shown in calendar day view preview. * This property applies only to invitations. * * @default false * @platform ios * */ allowsCalendarPreview?: boolean; }; export type CalendarDialogParams = { /** * ID of the event to be presented in the calendar UI. */ id: string; /** * Date object representing the start time of the desired instance, if looking for a single instance * of a recurring event. If this is not provided and **id** represents a recurring event, the first * instance of that event will be returned by default. * @platform ios */ instanceStartDate?: string | Date; }; /** * Launches the calendar UI provided by the OS to create a new event. * @param eventData A map of details for the event to be created. * @param presentationOptions Configuration that influences how the calendar UI is presented. * @return A promise which resolves with information about the dialog result. * @header systemProvidedUI */ export async function createEventInCalendarAsync( eventData: Omit<Partial<Event>, 'id'> = {}, presentationOptions?: PresentationOptions ): Promise<DialogEventResult> { if (!ExpoCalendar.createEventInCalendarAsync) { throw new UnavailabilityError('Calendar', 'createEventInCalendarAsync'); } // @ts-expect-error id could be passed if user doesn't use TypeScript or doesn't use the method with an object literal if (eventData.id) { console.warn( 'You attempted to create an event with an id. Event ids are assigned by the system.' ); } const params = stringifyDateValues(eventData); Object.assign(params, presentationOptions); return ExpoCalendar.createEventInCalendarAsync(params); } /** * Launches the calendar UI provided by the OS to preview an event. * @return A promise which resolves with information about the dialog result. * @header systemProvidedUI */ export async function openEventInCalendarAsync( params: CalendarDialogParams, presentationOptions?: OpenEventPresentationOptions ): Promise<OpenEventDialogResult> { if (!ExpoCalendar.openEventInCalendarAsync) { throw new UnavailabilityError('Calendar', 'openEventInCalendarAsync'); } if (!params.id) { throw new Error( 'openEventInCalendarAsync must be called with an id (string) of the target event' ); } const newParams = { ...params, ...presentationOptions }; return ExpoCalendar.openEventInCalendarAsync(newParams); } /** * Launches the calendar UI provided by the OS to edit or delete an event. On Android, this is the same as `openEventInCalendarAsync`. * @return A promise which resolves with information about the dialog result. * @header systemProvidedUI */ export async function editEventInCalendarAsync( params: CalendarDialogParams, presentationOptions?: PresentationOptions ): Promise<DialogEventResult> { if (!ExpoCalendar.editEventInCalendarAsync) { throw new UnavailabilityError('Calendar', 'editEventInCalendarAsync'); } if (!params.id) { throw new Error( 'editEventInCalendarAsync must be called with an id (string) of the target event' ); } const newParams = { ...params, ...presentationOptions }; return ExpoCalendar.editEventInCalendarAsync(newParams); } // @needsAudit /** * Returns whether the Calendar API is enabled on the current device. This does not check the app permissions. * * @returns Async `boolean`, indicating whether the Calendar API is available on the current device. * Currently, this resolves `true` on iOS and Android only. */ export async function isAvailableAsync(): Promise<boolean> { return !!ExpoCalendar.getCalendarsAsync; } // @needsAudit /** * Gets an array of calendar objects with details about the different calendars stored on the device. * @param entityType __iOS Only.__ Not required, but if defined, filters the returned calendars to * a specific entity type. Possible values are `Calendar.EntityTypes.EVENT` (for calendars shown in * the Calendar app) and `Calendar.EntityTypes.REMINDER` (for the Reminders app). * > **Note:** If not defined, you will need both permissions: **CALENDAR** and **REMINDERS**. * @return An array of [calendar objects](#calendar 'Calendar') matching the provided entity type (if provided). */ export async function getCalendarsAsync(entityType?: string): Promise<Calendar[]> { if (!ExpoCalendar.getCalendarsAsync) { throw new UnavailabilityError('Calendar', 'getCalendarsAsync'); } if (!entityType) { return ExpoCalendar.getCalendarsAsync(null); } return ExpoCalendar.getCalendarsAsync(entityType); } // @needsAudit /** * Creates a new calendar on the device, allowing events to be added later and displayed in the OS Calendar app. * @param details A map of details for the calendar to be created. * @return A string representing the ID of the newly created calendar. */ export async function createCalendarAsync(details: Partial<Calendar> = {}): Promise<string> { if (!ExpoCalendar.saveCalendarAsync) { throw new UnavailabilityError('Calendar', 'createCalendarAsync'); } const color = details.color ? processColor(details.color) : undefined; const newDetails = { ...details, id: undefined, color }; return ExpoCalendar.saveCalendarAsync(newDetails); } // @needsAudit /** * Updates the provided details of an existing calendar stored on the device. To remove a property, * explicitly set it to `null` in `details`. * @param id ID of the calendar to update. * @param details A map of properties to be updated. */ export async function updateCalendarAsync( id: string, details: Partial<Calendar> = {} ): Promise<string> { if (!ExpoCalendar.saveCalendarAsync) { throw new UnavailabilityError('Calendar', 'updateCalendarAsync'); } if (!id) { throw new Error( 'updateCalendarAsync must be called with an id (string) of the target calendar' ); } const color = details.color ? processColor(details.color) : undefined; if (Platform.OS === 'android') { if ( details.hasOwnProperty('source') || details.hasOwnProperty('color') || details.hasOwnProperty('allowsModifications') || details.hasOwnProperty('allowedAvailabilities') || details.hasOwnProperty('isPrimary') || details.hasOwnProperty('ownerAccount') || details.hasOwnProperty('timeZone') || details.hasOwnProperty('allowedReminders') || details.hasOwnProperty('allowedAttendeeTypes') || details.hasOwnProperty('accessLevel') ) { console.warn( 'updateCalendarAsync was called with one or more read-only properties, which will not be updated' ); } } else { if ( details.hasOwnProperty('source') || details.hasOwnProperty('type') || details.hasOwnProperty('entityType') || details.hasOwnProperty('allowsModifications') || details.hasOwnProperty('allowedAvailabilities') ) { console.warn( 'updateCalendarAsync was called with one or more read-only properties, which will not be updated' ); } } const newDetails = { ...details, id, color }; return ExpoCalendar.saveCalendarAsync(newDetails); } // @needsAudit /** * Deletes an existing calendar and all associated events/reminders/attendees from the device. __Use with caution.__ * @param id ID of the calendar to delete. */ export async function deleteCalendarAsync(id: string): Promise<void> { if (!ExpoCalendar.deleteCalendarAsync) { throw new UnavailabilityError('Calendar', 'deleteCalendarAsync'); } if (!id) { throw new Error( 'deleteCalendarAsync must be called with an id (string) of the target calendar' ); } return ExpoCalendar.deleteCalendarAsync(id); } // @needsAudit /** * Returns all events in a given set of calendars over a specified time period. The filtering has * slightly different behavior per-platform - on iOS, all events that overlap at all with the * `[startDate, endDate]` interval are returned, whereas on Android, only events that begin on or * after the `startDate` and end on or before the `endDate` will be returned. * @param calendarIds Array of IDs of calendars to search for events in. * @param startDate Beginning of time period to search for events in. * @param endDate End of time period to search for events in. * @return A promise which fulfils with an array of [`Event`](#event) objects matching the search criteria. */ export async function getEventsAsync( calendarIds: string[], startDate: Date, endDate: Date ): Promise<Event[]> { if (!ExpoCalendar.getEventsAsync) { throw new UnavailabilityError('Calendar', 'getEventsAsync'); } if (!startDate) { throw new Error('getEventsAsync must be called with a startDate (date) to search for events'); } if (!endDate) { throw new Error('getEventsAsync must be called with an endDate (date) to search for events'); } if (!calendarIds || !calendarIds.length) { throw new Error( 'getEventsAsync must be called with a non-empty array of calendarIds to search' ); } return ExpoCalendar.getEventsAsync( stringifyIfDate(startDate), stringifyIfDate(endDate), calendarIds ); } // @needsAudit /** * Returns a specific event selected by ID. If a specific instance of a recurring event is desired, * the start date of this instance must also be provided, as instances of recurring events do not * have their own unique and stable IDs on either iOS or Android. * @param id ID of the event to return. * @param recurringEventOptions A map of options for recurring events. * @return A promise which fulfils with an [`Event`](#event) object matching the provided criteria, if one exists. */ export async function getEventAsync( id: string, recurringEventOptions: RecurringEventOptions = {} ): Promise<Event> { if (!ExpoCalendar.getEventByIdAsync) { throw new UnavailabilityError('Calendar', 'getEventAsync'); } if (!id) { throw new Error('getEventAsync must be called with an id (string) of the target event'); } if (Platform.OS === 'ios') { return ExpoCalendar.getEventByIdAsync(id, recurringEventOptions.instanceStartDate); } else { return ExpoCalendar.getEventByIdAsync(id); } } // @needsAudit /** * Creates a new event on the specified calendar. * @param calendarId ID of the calendar to create this event in. * @param eventData A map of details for the event to be created. * @return A promise which fulfils with a string representing the ID of the newly created event. */ export async function createEventAsync( calendarId: string, eventData: Omit<Partial<Event>, 'id' | 'organizer'> = {} ): Promise<string> { if (!ExpoCalendar.saveEventAsync) { throw new UnavailabilityError('Calendar', 'createEventAsync'); } if (!calendarId) { throw new Error('createEventAsync must be called with an id (string) of the target calendar'); } // @ts-expect-error id could be passed if user doesn't use TypeScript or doesn't use the method with an object literal const { id, ...details } = eventData; if (id) { console.warn( 'You attempted to create an event with an id. Event ids are assigned by the system.' ); } if (Platform.OS === 'android') { if (!details.startDate) { throw new Error('createEventAsync requires a startDate (Date)'); } if (!details.endDate) { throw new Error('createEventAsync requires an endDate (Date)'); } } const newDetails = { ...details, calendarId, }; return ExpoCalendar.saveEventAsync(stringifyDateValues(newDetails), {}); } // @needsAudit /** * Updates the provided details of an existing calendar stored on the device. To remove a property, * explicitly set it to `null` in `details`. * @param id ID of the event to be updated. * @param details A map of properties to be updated. * @param recurringEventOptions A map of options for recurring events. */ export async function updateEventAsync( id: string, details: Omit<Partial<Event>, 'id'> = {}, recurringEventOptions: RecurringEventOptions = {} ): Promise<string> { if (!ExpoCalendar.saveEventAsync) { throw new UnavailabilityError('Calendar', 'updateEventAsync'); } if (!id) { throw new Error('updateEventAsync must be called with an id (string) of the target event'); } if (Platform.OS === 'ios') { if ( details.hasOwnProperty('creationDate') || details.hasOwnProperty('lastModifiedDate') || details.hasOwnProperty('originalStartDate') || details.hasOwnProperty('isDetached') || details.hasOwnProperty('status') || details.hasOwnProperty('organizer') ) { console.warn( 'updateEventAsync was called with one or more read-only properties, which will not be updated' ); } } const { futureEvents = false, instanceStartDate } = recurringEventOptions; const newDetails = { ...details, id, instanceStartDate }; return ExpoCalendar.saveEventAsync(stringifyDateValues(newDetails), { futureEvents }); } // @needsAudit /** * Deletes an existing event from the device. Use with caution. * @param id ID of the event to be deleted. * @param recurringEventOptions A map of options for recurring events. */ export async function deleteEventAsync( id: string, recurringEventOptions: RecurringEventOptions = {} ): Promise<void> { if (!ExpoCalendar.deleteEventAsync) { throw new UnavailabilityError('Calendar', 'deleteEventAsync'); } if (!id) { throw new Error('deleteEventAsync must be called with an id (string) of the target event'); } const { futureEvents = false, instanceStartDate } = recurringEventOptions; return ExpoCalendar.deleteEventAsync({ id, instanceStartDate }, { futureEvents }); } // @needsAudit /** * Gets all attendees for a given event (or instance of a recurring event). * @param id ID of the event to return attendees for. * @param recurringEventOptions A map of options for recurring events. * @return A promise which fulfils with an array of [`Attendee`](#attendee) associated with the * specified event. */ export async function getAttendeesForEventAsync( id: string, recurringEventOptions: RecurringEventOptions = {} ): Promise<Attendee[]> { if (!ExpoCalendar.getAttendeesForEventAsync) { throw new UnavailabilityError('Calendar', 'getAttendeesForEventAsync'); } if (!id) { throw new Error( 'getAttendeesForEventAsync must be called with an id (string) of the target event' ); } const { instanceStartDate } = recurringEventOptions; // Android only takes an ID, iOS takes an object const params = Platform.OS === 'ios' ? { id, instanceStartDate } : id; return ExpoCalendar.getAttendeesForEventAsync(params); } // @needsAudit /** * Creates a new attendee record and adds it to the specified event. Note that if `eventId` specifies * a recurring event, this will add the attendee to every instance of the event. * @param eventId ID of the event to add this attendee to. * @param details A map of details for the attendee to be created. * @return A string representing the ID of the newly created attendee record. * @platform android */ export async function createAttendeeAsync( eventId: string, details: Partial<Attendee> = {} ): Promise<string> { if (!ExpoCalendar.saveAttendeeForEventAsync) { throw new UnavailabilityError('Calendar', 'createAttendeeAsync'); } if (!eventId) { throw new Error('createAttendeeAsync must be called with an id (string) of the target event'); } if (!details.email) { throw new Error('createAttendeeAsync requires an email (string)'); } if (!details.role) { throw new Error('createAttendeeAsync requires a role (string)'); } if (!details.type) { throw new Error('createAttendeeAsync requires a type (string)'); } if (!details.status) { throw new Error('createAttendeeAsync requires a status (string)'); } const newDetails = { ...details, id: undefined }; return ExpoCalendar.saveAttendeeForEventAsync(newDetails, eventId); } // @needsAudit /** * Updates an existing attendee record. To remove a property, explicitly set it to `null` in `details`. * @param id ID of the attendee record to be updated. * @param details A map of properties to be updated. * @platform android */ export async function updateAttendeeAsync( id: string, details: Partial<Attendee> = {} ): Promise<string> { if (!ExpoCalendar.saveAttendeeForEventAsync) { throw new UnavailabilityError('Calendar', 'updateAttendeeAsync'); } if (!id) { throw new Error('updateAttendeeAsync must be called with an id (string) of the target event'); } const newDetails = { ...details, id }; return ExpoCalendar.saveAttendeeForEventAsync(newDetails, null); } // @needsAudit /** * Gets an instance of the default calendar object. * @return A promise resolving to the [Calendar](#calendar) object that is the user's default calendar. * @platform ios */ export async function getDefaultCalendarAsync(): Promise<Calendar> { if (!ExpoCalendar.getDefaultCalendarAsync) { throw new UnavailabilityError('Calendar', 'getDefaultCalendarAsync'); } return ExpoCalendar.getDefaultCalendarAsync(); } // @needsAudit /** * Deletes an existing attendee record from the device. __Use with caution.__ * @param id ID of the attendee to delete. * @platform android */ export async function deleteAttendeeAsync(id: string): Promise<void> { if (!ExpoCalendar.deleteAttendeeAsync) { throw new UnavailabilityError('Calendar', 'deleteAttendeeAsync'); } if (!id) { throw new Error('deleteAttendeeAsync must be called with an id (string) of the target event'); } return ExpoCalendar.deleteAttendeeAsync(id); } // @needsAudit /** * Returns a list of reminders matching the provided criteria. If `startDate` and `endDate` are defined, * returns all reminders that overlap at all with the [startDate, endDate] interval - i.e. all reminders * that end after the `startDate` or begin before the `endDate`. * @param calendarIds Array of IDs of calendars to search for reminders in. * @param status One of `Calendar.ReminderStatus.COMPLETED` or `Calendar.ReminderStatus.INCOMPLETE`. * @param startDate Beginning of time period to search for reminders in. Required if `status` is defined. * @param endDate End of time period to search for reminders in. Required if `status` is defined. * @return A promise which fulfils with an array of [`Reminder`](#reminder) objects matching the search criteria. * @platform ios */ export async function getRemindersAsync( calendarIds: (string | null)[], status: ReminderStatus | null, startDate: Date | null, endDate: Date | null ): Promise<Reminder[]> { if (!ExpoCalendar.getRemindersAsync) { throw new UnavailabilityError('Calendar', 'getRemindersAsync'); } if (status && !startDate) { throw new Error( 'getRemindersAsync must be called with a startDate (date) to search for reminders' ); } if (status && !endDate) { throw new Error( 'getRemindersAsync must be called with an endDate (date) to search for reminders' ); } if (!calendarIds || !calendarIds.length) { throw new Error( 'getRemindersAsync must be called with a non-empty array of calendarIds to search' ); } const formattedStartDate = startDate ? stringifyIfDate(startDate) : null; const formattedEndDate = endDate ? stringifyIfDate(endDate) : null; return ExpoCalendar.getRemindersAsync( formattedStartDate, formattedEndDate, calendarIds, status || null ); } // @needsAudit /** * Returns a specific reminder selected by ID. * @param id ID of the reminder to return. * @return A promise which fulfils with a [`Reminder`](#reminder) matching the provided ID, if one exists. * @platform ios */ export async function getReminderAsync(id: string): Promise<Reminder> { if (!ExpoCalendar.getReminderByIdAsync) { throw new UnavailabilityError('Calendar', 'getReminderAsync'); } if (!id) { throw new Error('getReminderAsync must be called with an id (string) of the target reminder'); } return ExpoCalendar.getReminderByIdAsync(id); } // @needsAudit /** * Creates a new reminder on the specified calendar. * @param calendarId ID of the calendar to create this reminder in (or `null` to add the calendar to * the OS-specified default calendar for reminders). * @param reminder A map of details for the reminder to be created * @return A promise which fulfils with a string representing the ID of the newly created reminder. * @platform ios */ export async function createReminderAsync( calendarId: string | null, reminder: Reminder = {} ): Promise<string> { if (!ExpoCalendar.saveReminderAsync) { throw new UnavailabilityError('Calendar', 'createReminderAsync'); } const { id, ...details } = reminder; const newDetails = { ...details, calendarId: calendarId === null ? undefined : calendarId, }; return ExpoCalendar.saveReminderAsync(stringifyDateValues(newDetails)); } // @needsAudit /** * Updates the provided details of an existing reminder stored on the device. To remove a property, * explicitly set it to `null` in `details`. * @param id ID of the reminder to be updated. * @param details A map of properties to be updated. * @platform ios */ export async function updateReminderAsync(id: string, details: Reminder = {}): Promise<string> { if (!ExpoCalendar.saveReminderAsync) { throw new UnavailabilityError('Calendar', 'updateReminderAsync'); } if (!id) { throw new Error( 'updateReminderAsync must be called with an id (string) of the target reminder' ); } if (details.hasOwnProperty('creationDate') || details.hasOwnProperty('lastModifiedDate')) { console.warn( 'updateReminderAsync was called with one or more read-only properties, which will not be updated' ); } const newDetails = { ...details, id }; return ExpoCalendar.saveReminderAsync(stringifyDateValues(newDetails)); } // @needsAudit /** * Deletes an existing reminder from the device. __Use with caution.__ * @param id ID of the reminder to be deleted. * @platform ios */ export async function deleteReminderAsync(id: string): Promise<void> { if (!ExpoCalendar.deleteReminderAsync) { throw new UnavailabilityError('Calendar', 'deleteReminderAsync'); } if (!id) { throw new Error( 'deleteReminderAsync must be called with an id (string) of the target reminder' ); } return ExpoCalendar.deleteReminderAsync(id); } // @needsAudit @docsMissing /** * @return A promise which fulfils with an array of [`Source`](#source) objects all sources for * calendars stored on the device. * @platform ios */ export async function getSourcesAsync(): Promise<Source[]> { if (!ExpoCalendar.getSourcesAsync) { throw new UnavailabilityError('Calendar', 'getSourcesAsync'); } return ExpoCalendar.getSourcesAsync(); } // @needsAudit /** * Returns a specific source selected by ID. * @param id ID of the source to return. * @return A promise which fulfils with an array of [`Source`](#source) object matching the provided * ID, if one exists. * @platform ios */ export async function getSourceAsync(id: string): Promise<Source> { if (!ExpoCalendar.getSourceByIdAsync) { throw new UnavailabilityError('Calendar', 'getSourceAsync'); } if (!id) { throw new Error('getSourceAsync must be called with an id (string) of the target source'); } return ExpoCalendar.getSourceByIdAsync(id); } // @needsAudit /** * Sends an intent to open the specified event in the OS Calendar app. * @param id ID of the event to open. * @platform android * @deprecated Use [`openEventInCalendarAsync`](#openeventincalendarasyncparams-presentationoptions) instead. * @header systemProvidedUI */ export function openEventInCalendar(id: string): void { if (!ExpoCalendar.openEventInCalendar) { console.warn(`openEventInCalendar is not available on platform: ${Platform.OS}`); return; } if (!id) { throw new Error('openEventInCalendar must be called with an id (string) of the target event'); } return ExpoCalendar.openEventInCalendar(id); } // Android // @needsAudit /** * @deprecated Use [`requestCalendarPermissionsAsync()`](#calendarrequestcalendarpermissionsasync) instead. */ export async function requestPermissionsAsync(): Promise<PermissionResponse> { console.warn( 'requestPermissionsAsync is deprecated. Use requestCalendarPermissionsAsync instead.' ); return requestCalendarPermissionsAsync(); } // @needsAudit /** * Checks user's permissions for accessing user's calendars. * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse). */ export async function getCalendarPermissionsAsync(): Promise<PermissionResponse> { if (!ExpoCalendar.getCalendarPermissionsAsync) { throw new UnavailabilityError('Calendar', 'getCalendarPermissionsAsync'); } return ExpoCalendar.getCalendarPermissionsAsync(); } // @needsAudit /** * Checks user's permissions for accessing user's reminders. * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse). * @platform ios */ export async function getRemindersPermissionsAsync(): Promise<PermissionResponse> { if (!ExpoCalendar.getRemindersPermissionsAsync) { throw new UnavailabilityError('Calendar', 'getRemindersPermissionsAsync'); } return ExpoCalendar.getRemindersPermissionsAsync(); } // @needsAudit /** * Asks the user to grant permissions for accessing user's calendars. * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse). */ export async function requestCalendarPermissionsAsync(): Promise<PermissionResponse> { if (!ExpoCalendar.requestCalendarPermissionsAsync) { throw new UnavailabilityError('Calendar', 'requestCalendarPermissionsAsync'); } return await ExpoCalendar.requestCalendarPermissionsAsync(); } // @needsAudit /** * Asks the user to grant permissions for accessing user's reminders. * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse). * @platform ios */ export async function requestRemindersPermissionsAsync(): Promise<PermissionResponse> { if (!ExpoCalendar.requestRemindersPermissionsAsync) { throw new UnavailabilityError('Calendar', 'requestRemindersPermissionsAsync'); } return await ExpoCalendar.requestRemindersPermissionsAsync(); } // @needsAudit /** * Check or request permissions to access the calendar. * This uses both `getCalendarPermissionsAsync` and `requestCalendarPermissionsAsync` to interact * with the permissions. * * @example * ```ts * const [status, requestPermission] = Calendar.useCalendarPermissions(); * ``` */ export const useCalendarPermissions = createPermissionHook({ getMethod: getCalendarPermissionsAsync, requestMethod: requestCalendarPermissionsAsync, }); // @needsAudit /** * Check or request permissions to access reminders. * This uses both `getRemindersPermissionsAsync` and `requestRemindersPermissionsAsync` to interact * with the permissions. * * @example * ```ts * const [status, requestPermission] = Calendar.useRemindersPermissions(); * ``` */ export const useRemindersPermissions = createPermissionHook({ getMethod: getRemindersPermissionsAsync, requestMethod: requestRemindersPermissionsAsync, }); // @docsMissing /** * platform ios */ export enum EntityTypes { EVENT = 'event', REMINDER = 'reminder', } // @docsMissing export enum Frequency { DAILY = 'daily', WEEKLY = 'weekly', MONTHLY = 'monthly', YEARLY = 'yearly', } // @docsMissing export enum Availability { /** * @platform ios */ NOT_SUPPORTED = 'notSupported', BUSY = 'busy', FREE = 'free', TENTATIVE = 'tentative', /** * @platform ios */ UNAVAILABLE = 'unavailable', } /** * @platform ios */ export enum CalendarType { LOCAL = 'local', CALDAV = 'caldav', EXCHANGE = 'exchange', SUBSCRIBED = 'subscribed', BIRTHDAYS = 'birthdays', UNKNOWN = 'unknown', } // @docsMissing export enum EventStatus { NONE = 'none', CONFIRMED = 'confirmed', TENTATIVE = 'tentative', CANCELED = 'canceled', } // @docsMissing /** * @platform ios */ export enum SourceType { LOCAL = 'local', EXCHANGE = 'exchange', CALDAV = 'caldav', MOBILEME = 'mobileme', SUBSCRIBED = 'subscribed', BIRTHDAYS = 'birthdays', } // @docsMissing export enum AttendeeRole { /** * @platform ios */ UNKNOWN = 'unknown', /** * @platform ios */ REQUIRED = 'required', /** * @platform ios */ OPTIONAL = 'optional', /** * @platform ios */ CHAIR = 'chair', /** * @platform ios */ NON_PARTICIPANT = 'nonParticipant', /** * @platform android */ ATTENDEE = 'attendee', /** * @platform android */ ORGANIZER = 'organizer', /** * @platform android */ PERFORMER = 'performer', /** * @platform android */ SPEAKER = 'speaker', /** * @platform android */ NONE = 'none', } // @docsMissing export enum AttendeeStatus { /** * @platform ios */ UNKNOWN = 'unknown', /** * @platform ios */ PENDING = 'pending', ACCEPTED = 'accepted', DECLINED = 'declined', TENTATIVE = 'tentative', /** * @platform ios */ DELEGATED = 'delegated', /** * @platform ios */ COMPLETED = 'completed', /** * @platform ios */ IN_PROCESS = 'inProcess', /** * @platform android */ INVITED = 'invited', /** * @platform android */ NONE = 'none', } // @docsMissing export enum AttendeeType { /** * @platform ios */ UNKNOWN = 'unknown', /** * @platform ios */ PERSON = 'person', /** * @platform ios */ ROOM = 'room', /** * @platform ios */ GROUP = 'group', RESOURCE = 'resource', /** * @platform android */ OPTIONAL = 'optional', /** * @platform android */ REQUIRED = 'required', /** * @platform android */ NONE = 'none', } // @docsMissing /** * @platform android */ export enum AlarmMethod { ALARM = 'alarm', ALERT = 'alert', EMAIL = 'email', SMS = 'sms', DEFAULT = 'default', } // @docsMissing /** * @platform android */ export enum EventAccessLevel { CONFIDENTIAL = 'confidential', PRIVATE = 'private', PUBLIC = 'public', DEFAULT = 'default', } // @docsMissing /** * @platform android */ export enum CalendarAccessLevel { CONTRIBUTOR = 'contributor', EDITOR = 'editor', FREEBUSY = 'freebusy', OVERRIDE = 'override', OWNER = 'owner', READ = 'read', RESPOND = 'respond', ROOT = 'root', NONE = 'none', } // @docsMissing /** * @platform ios */ export enum ReminderStatus { COMPLETED = 'completed', INCOMPLETE = 'incomplete', } function stringifyIfDate<T extends Date>(date: Date | T): string | T { return date instanceof Date ? date.toISOString() : date; } type StringifyDates<T extends Record<string, any>> = { [K in keyof T]: T[K] extends Date ? string : T[K]; }; function stringifyDateValues<T extends Record<string, any>>(obj: T): StringifyDates<T> { if (typeof obj !== 'object' || obj === null) return obj; return Object.keys(obj).reduce((acc, key) => { const value = obj[key]; if (value != null && typeof value === 'object' && !(value instanceof Date)) { if (Array.isArray(value)) { return { ...acc, [key]: value.map(stringifyDateValues) }; } return { ...acc, [key]: stringifyDateValues(value) }; } acc[key as keyof T] = stringifyIfDate(value); return acc; }, {} as StringifyDates<T>); }