phil / calendar-sync
External calendar synchronization: busy times read from iCalendar feeds, CalDAV, Google Calendar and Microsoft Graph, and the site's events written to them, each service an adapter behind a port.
Package info
github.com/parler-haut-interagir-librement/calendar-sync
pkg:composer/phil/calendar-sync
Requires
- php: >=8.5
- ext-dom: *
- ext-intl: *
- ext-libxml: *
- ext-mbstring: *
- phil/recurrence: ^0.1@dev
- phil/safe-http: ^0.1@dev
- phil/url: ^0.1@dev
- symfony/clock: ^8.2
- symfony/http-client: ^8.2
- symfony/http-client-contracts: ^3.6
- symfony/http-foundation: ^8.2
Requires (Dev)
- phpunit/phpunit: ^13.4
- roave/security-advisories: dev-latest
Suggests
- symfony/messenger: For RefreshBusyTimes and SyncEvent handled by a worker
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-12 23:52:49 UTC
README
External calendar synchronization: busy times read from iCalendar feeds, CalDAV, Google Calendar and Microsoft Graph, and the site's events written to them, each service an adapter behind a port.
A site that books people — a game master, a room, a coach — must not offer a time its people already gave elsewhere, and its bookings should appear in the calendars they read. phil/calendar-sync does both:
- it reads when someone is taken from a calendar of theirs, never what fills the time: busy periods only, a start, an end and whether it is tentative — never a title, a place, a note or a guest;
- it writes the site's events into that calendar, replaced when written again, removed when withdrawn.
Each service is an adapter, named by the connection that uses it: ics reads a published iCalendar feed, caldav a
CalDAV collection, google a Google calendar, microsoft an Outlook calendar through Microsoft Graph. An application
adds its own by implementing the same two ports.
The application keeps the connections, their credentials and the busy periods read, behind ports: the package stores nothing, and keeps nothing between calls. Credentials are asked for at each call and cannot be serialised. The Messenger messages carry the identifier of a connection, and of an event, never a token, an address or a time read.
Every address is read by phil/url. A feed is read through phil/safe-http, and the CalDAV client is wrapped in
Symfony's NoPrivateNetworkHttpClient: neither reaches a private network, and no redirect carries credentials to
another host. Every read is bounded in time, bytes, components, pages and periods; the iCalendar reader is fuzzed.
phil/calendar-sync is a plain PHP library, on Symfony's HttpClient and Clock and on phil/recurrence for the rules of
the events it reads.
Status
| Part | State |
|---|---|
CalendarSync, the ports, BusyPeriod, BusyPeriods, SyncWindow, CalendarConnection, Credentials, OutboundEvent |
Implemented and tested |
| The iCalendar reader — events, rules, added, removed and changed occurrences, free/busy times | Implemented, tested and fuzzed |
| The iCalendar writer | Implemented and tested |
The adapters ics, caldav, google and microsoft |
Implemented and tested against HTTP test doubles |
The messages RefreshBusyTimes and SyncEvent and their handlers |
Implemented and tested |
Documentation
| Document | What it covers |
|---|---|
| Installation | Requirements, installing, and wiring the services. |
| Synchronizing | Connections, credentials, busy times, events written, messages, failures. |
| Adapters | What each adapter reads and writes, its calendar, its credentials and its bounds. |
| The iCalendar reader | What is read of a calendar, the time zones, the bounds and what is refused. |
| Extension points | The ports an application implements, its own adapters, and what stays closed. |
| Testing | The package's tests and its fuzz target. |
| Reference | Every public type, with its role. |
Quick start
composer require phil/calendar-sync
use Phil\CalendarSync\Adapter\CalDav; use Phil\CalendarSync\Adapter\GoogleCalendar; use Phil\CalendarSync\Adapter\IcsFeed; use Phil\CalendarSync\Adapter\MicrosoftGraph; use Phil\CalendarSync\CalendarSync; use Phil\CalendarSync\SyncWindow; $writers = [new CalDav(), new GoogleCalendar(), new MicrosoftGraph()]; $sync = new CalendarSync($connections, $credentials, $busyStore, sources: [new IcsFeed(), ...$writers], destinations: $writers); // The next 60 days of a member's calendar, joined into the fewest periods and given to the store. $sync->refreshBusy($connectionId, SyncWindow::days(new DateTimeImmutable('today'), 60)); // A booking written into it, then withdrawn. $sync->publish($connectionId, $booking); $sync->withdraw($connectionId, $booking->uid);
Requirements
PHP 8.5 or later with ext-dom, ext-intl, ext-libxml and ext-mbstring; Symfony 8.2's HttpClient, HTTP
foundation and Clock; phil/recurrence, phil/safe-http and phil/url. Symfony Messenger is optional.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.