One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Calendar

Read and write native calendar events and reminders

One.Calendar reads and writes events on the system calendars and keeps reminders with due dates and completion state. Events live in the user’s real calendars on both platforms; reminders live in the system reminders on iOS but are private to your app on Android, which has no system task-list provider.

On iOS both go through EKEventStore from EventKit. On Android events go through CalendarContract, while reminders are stored by One itself and delivered through AlarmManager and a notification channel. On web both keep the unavailable contract: denied permission, empty lists, and rejections for calls that return a value.

Usage

import { One } from 'one'
const status = await One.Calendar.requestPermission()
if (status === 'fullAccess') {
const startMs = Date.now() + 86_400_000
const identifier = await One.Calendar.create({
title: 'Team review',
startMs,
endMs: startMs + 3_600_000,
allDay: false,
})
const events = await One.Calendar.list(startMs - 1, startMs + 3_600_001, 100)
console.log(events.find((event) => event.identifier === identifier)?.title)
}

Events need full access; write-only access cannot read, not even events the app created. Times are Unix milliseconds throughout.

How it works

Set native.app.calendar.usage for events and the independent native.app.calendar.remindersUsage for reminders before prebuild. On iOS they become the full-access calendars and reminders usage descriptions; on Android the event string stamps calendar read and write permissions, while the reminders string stamps notification and boot permissions plus an opt-in marker. Configure only the one your app uses. EventKit work runs on a dedicated serial queue; Android queries run on a worker thread.

list searches all visible calendars over an increasing range spanning at most 366 days, with a whole-number limit from 1 to 500 (default 100), returning events sorted by start time with identifier, title, times, all-day flag, location, and each occurrence’s recurrence rule. create saves to the default writable calendar and returns the identifier. update and delete address one occurrence by identifier plus the exact start time from list, since a series shares one identifier across occurrences; they change only that occurrence, never the series. Pass the returned start time onward for later edits. Changes carry any combination of title, times, all-day flag, and location; omitted fields keep their values, and an empty location clears it.

Recurrence rules carry a daily, weekly, monthly, or yearly frequency, an interval defaulting to 1, and at most one end: an occurrence count or an end date. Leave both ends out for an ongoing series. The interval runs 1 to 1,000, the count 1 to 10,000. Reminders accept the same rule when a due time is supplied. On iOS only the first incomplete reminder of a repeating set is visible, so completing it reveals the next one; list again after completion. On Android, completing a recurring reminder mints the next incomplete occurrence with a new identifier, respecting the interval and the count or date bound; reopening it does not remove the minted next one.

Android reminders are app-owned: tasks never appear in Google Tasks or another app’s lists, and wiping app data or uninstalling deletes them. requestRemindersPermission asks for notification permission on Android 13 and later, and fullAccess means app notifications plus the reminders channel are on; disabling the channel rejects reminder calls. Dated reminders schedule an inexact alarm that posts a notification even after the process exits, with no exact-time guarantee; undated reminders have no alarm. Completing or deleting cancels the alarm and notification. Boot and app updates reschedule undelivered reminders, but a force-stopped app stays silent until opened again.

API reference

Methods: eventsTypeDescription
getPermissionStatus() => CalendarPermissionStatusThe events status, without prompting.
() => CalendarPermissionStatusThe events status, without prompting.
requestPermission() => Promise<CalendarPermissionStatus>Ask for full events access.
() => Promise<CalendarPermissionStatus>Ask for full events access.
list(startMs: number, endMs: number, limit: number) => Promise<CalendarEvent[]>Events in a range, by start time. Limit defaults to 100.
(startMs: number, endMs: number, limit: number) => Promise<CalendarEvent[]>Events in a range, by start time. Limit defaults to 100.
create(input: CalendarEventInput) => Promise<string>Save an event. Resolves with its identifier.
(input: CalendarEventInput) => Promise<string>Save an event. Resolves with its identifier.
update(identifier: string, originalStartMs: number, changes: CalendarEventChanges) => Promise<CalendarEvent>Edit one occurrence. Resolves with its new fields.
(identifier: string, originalStartMs: number, changes: CalendarEventChanges) => Promise<CalendarEvent>Edit one occurrence. Resolves with its new fields.
delete(identifier: string, startMs: number) => Promise<void>Remove the occurrence at that exact start time.
(identifier: string, startMs: number) => Promise<void>Remove the occurrence at that exact start time.
Methods: remindersTypeDescription
getRemindersPermissionStatus() => CalendarPermissionStatusThe reminders status, without prompting.
() => CalendarPermissionStatusThe reminders status, without prompting.
requestRemindersPermission() => Promise<CalendarPermissionStatus>Ask for full reminders access.
() => Promise<CalendarPermissionStatus>Ask for full reminders access.
listReminders(limit: number, includeCompleted: boolean) => Promise<ReminderInfo[]>Reminders by due date, undated last. Defaults to 100, open only.
(limit: number, includeCompleted: boolean) => Promise<ReminderInfo[]>Reminders by due date, undated last. Defaults to 100, open only.
createReminder(input: ReminderInput) => Promise<string>Save a reminder. Resolves with its identifier.
(input: ReminderInput) => Promise<string>Save a reminder. Resolves with its identifier.
setReminderCompleted(identifier: string, completed: boolean) => Promise<void>Complete or reopen a reminder.
(identifier: string, completed: boolean) => Promise<void>Complete or reopen a reminder.
deleteReminder(identifier: string) => Promise<void>Remove a reminder.
(identifier: string) => Promise<void>Remove a reminder.
EventTypeDescription
identifierstringShared across a series; pair with startMs.
stringShared across a series; pair with startMs.
titlestringThe event title.
stringThe event title.
startMsnumberStart in Unix milliseconds.
numberStart in Unix milliseconds.
endMsnumberEnd in Unix milliseconds.
numberEnd in Unix milliseconds.
allDaybooleanWhether the event spans the day.
booleanWhether the event spans the day.
locationstringThe location, or empty.
stringThe location, or empty.
recurrenceCalendarRecurrenceThe series rule, when repeating.
CalendarRecurrenceThe series rule, when repeating.
New eventTypeDescription
titlestringThe event title.
stringThe event title.
startMsnumberStart in Unix milliseconds.
numberStart in Unix milliseconds.
endMsnumberEnd in Unix milliseconds.
numberEnd in Unix milliseconds.
allDaybooleanWhether the event spans the day.
booleanWhether the event spans the day.
recurrenceCalendarRecurrenceA repeat rule. Optional.
CalendarRecurrenceA repeat rule. Optional.
Event changesTypeDescription
titlestringA new title, or omit to keep it.
stringA new title, or omit to keep it.
startMsnumberA new start, or omit to keep it.
numberA new start, or omit to keep it.
endMsnumberA new end, or omit to keep it.
numberA new end, or omit to keep it.
allDaybooleanA new all-day flag, or omit to keep it.
booleanA new all-day flag, or omit to keep it.
locationstringA new location. Empty clears it.
stringA new location. Empty clears it.
RecurrenceTypeDescription
frequencyCalendarRecurrenceFrequencyDaily, weekly, monthly, or yearly.
CalendarRecurrenceFrequencyDaily, weekly, monthly, or yearly.
intervalnumberEvery nth period. Default 1.
numberEvery nth period. Default 1.
endDateMsnumberStop after this time. At most one end.
numberStop after this time. At most one end.
occurrenceCountnumberStop after this many. At most one end.
numberStop after this many. At most one end.
New reminderTypeDescription
titlestringThe reminder title.
stringThe reminder title.
dueMsnumberDue in Unix milliseconds. Optional.
numberDue in Unix milliseconds. Optional.
recurrenceCalendarRecurrenceA repeat rule. Needs a due time.
CalendarRecurrenceA repeat rule. Needs a due time.
ReminderTypeDescription
identifierstringThe stable id for completion and delete.
stringThe stable id for completion and delete.
titlestringThe reminder title.
stringThe reminder title.
completedbooleanWhether it is done.
booleanWhether it is done.
dueMsnumberDue in Unix milliseconds, when dated.
numberDue in Unix milliseconds, when dated.
recurrenceCalendarRecurrenceThe repeat rule, when repeating.
CalendarRecurrenceThe repeat rule, when repeating.

Platform differences

iOSAndroidWeb
EventsSystem calendarsSystem calendarsEmpty lists
RemindersSystem remindersPrivate to the appEmpty lists
Reminder alertSystem deliveryInexact alarmNone
AccessFull or write onlyFull or deniedDenied
SetupUsage text eachPermissions eachNothing

Limits and gotchas

Examples

List this week’s events

import { useEffect, useState } from 'react'
import { Text } from 'react-native'
import { One } from 'one'
type WeekEvent = Awaited<ReturnType<typeof One.Calendar.list>>[number]
function ThisWeek() {
const [events, setEvents] = useState<WeekEvent[]>([])
useEffect(() => {
async function load() {
const status = await One.Calendar.requestPermission()
if (status !== 'fullAccess') return
const now = Date.now()
setEvents(await One.Calendar.list(now, now + 7 * 86_400_000, 100))
}
load()
}, [])
return (
<>
{events.map((event) => (
<Text key={`${event.identifier}${event.startMs}`}>
{event.title} at {new Date(event.startMs).toLocaleString()}
</Text>
))}
</>
)
}

The key pairs identifier with start time because a series reuses one identifier across occurrences. The week range stays well under the 366-day cap.

Move an event to a room

import { One } from 'one'
async function moveToRoom(identifier: string, startMs: number, room: string) {
const updated = await One.Calendar.update(identifier, startMs, {
location: room,
})
console.log('Moved', updated.title, 'to', updated.location)
return updated
}

Only the location changes; every omitted field keeps its value. The returned event carries the fields a follow-up edit or delete needs.

Remind and complete

import { One } from 'one'
async function reportReminder() {
if ((await One.Calendar.requestRemindersPermission()) !== 'fullAccess') return
const identifier = await One.Calendar.createReminder({
title: 'Send the report',
dueMs: Date.now() + 86_400_000,
})
const open = await One.Calendar.listReminders(100, false)
console.log('Open reminders:', open.length)
await One.Calendar.setReminderCompleted(identifier, true)
}

The default listing shows open reminders only, so the count confirms the new item landed. Completion flips the flag; on iOS a repeating reminder then reveals its next occurrence on the following list.

Edit this page on GitHub.