A lightweight PHP script that fetches an ICS calendar feed, applies rule-based transformations, and serves the result as a new ICS feed — no Composer, no framework, no cron job required.
Subscribe to a calendar (e.g. from Microsoft Exchange/Outlook) and transform it on the fly before it reaches your calendar app: rename events, set location or reminders, remove entries, or filter by day, time, date, title, busy status, or recurrence.
- PHP 8.1+
allow_url_fopen = Oninphp.ini- Spyc — place it as
yaml.phpin the same directory
- Copy
ics.php,yaml.php, and.htaccessto your web server. - Create a
<calendarname>.yamlfor each calendar you want to transform. - Subscribe to the transformed feed in your calendar app.
The .htaccess blocks direct HTTP access to all files except ics.php and yaml.php.
https://yourserver.com/ics.php?cal=mycalendar&pw=yourpassword
| Parameter | Description |
|---|---|
cal |
Calendar name — must match a <cal>.yaml file |
pw |
Password defined in the YAML file |
debug |
Returns a plain-text stats report with a per-event transformation log |
text |
Returns the transformed ICS as plain text for inspection |
Each calendar has its own .yaml file with the source URL, password, and rules.
password: yourpassword
source_ics: https://your-exchange-server/calendar.ics
rules:
- filter:
days:
- wed
time:
- type: exact
starts: "11:00"
apply:
name: "Weekly Meeting"
reminder: 10All keys are optional — omitting a key (or setting it to null) matches anything.
List of weekdays: mon tue wed thu fri sat sun
List of conditions (all must match). All-day events are treated as 00:00 for range/before/after comparisons.
| type | fields |
|---|---|
allday |
— matches only all-day events |
exact |
starts: "HH:MM" |
range |
from: "HH:MM" · to: "HH:MM" |
before |
at: "HH:MM" |
after |
at: "HH:MM" |
List of conditions (all must match). For recurring events only DTSTART is evaluated, not generated occurrences.
| type | fields |
|---|---|
exact |
at: "YYYY-MM-DD" |
range |
from: "YYYY-MM-DD" · to: "YYYY-MM-DD" |
before |
at: "YYYY-MM-DD" |
after |
at: "YYYY-MM-DD" |
Matches X-MICROSOFT-CDO-BUSYSTATUS: FREE · BUSY · TENTATIVE · OOF
List of conditions (all must match, case-insensitive).
| type | fields |
|---|---|
exact |
text: "Full Title" |
contains |
text: "partial" |
startswith |
text: "Prefix" |
endswith |
text: "Suffix" |
true (has RRULE or RECURRENCE-ID) · false
All keys are optional — omitting a key leaves that field unchanged.
| key | type | description |
|---|---|---|
name |
string |
Overwrite event title |
busy_status |
string |
Overwrite busy status |
location |
string |
Overwrite location |
reminder |
int |
Set VALARM to X minutes before start |
remove |
bool |
Set to true to drop the event entirely |
- First matching rule wins — subsequent rules are not evaluated
- Unmatched events pass through unchanged
- Multiple conditions within the same filter key are combined with AND
- The
calparameter is validated against[a-zA-Z0-9_-]to prevent path traversal - Password is checked before fetching or processing anything
- Keep
.yamlfiles out of version control if they contain sensitive calendar URLs
MIT