ICS to CalDAV synchronisation
Downloads a calendar in ICS format and uploads it to a CalDAV server, regularly. Your employee, school, or whoever shares a calendar as a link to an ICS file, and you'd like to have it on another CalDAV server? Look no further.
Standalone usage
Install the script with pip install ics_caldav_sync
(or, if installing from source, pip install .)
and run ics_caldav_sync script which should be on your PATH now.
Python 3.10 or higher is required.
There also exist Dockerfile and docker-compose.yml files so you can
run it on your Docker server.
The Docker images are published to Docker Hub,
repository przemub/ics_caldav_sync tagged by short commit hashes and versions.
Set the settings as environment variables:
| Variable | Type | Required | Default | Description |
|---|---|---|---|---|
REMOTE_URL |
string | Yes | - | ICS file URL. You can provide multiple URLs, separated by a space character. |
LOCAL_URL |
string | Yes | - | CalDAV server URL. |
LOCAL_CALENDAR_NAME |
string | Yes | - | The name of your CalDAV calendar. |
LOCAL_USERNAME |
string | Yes | - | CalDAV username. |
LOCAL_PASSWORD |
string | Yes | - | CalDAV password. |
LOCAL_AUTH |
string | No | basic |
CalDAV authentication method (either basic or digest). |
LOCAL_TLS_NO_VERIFY |
boolean | No | false |
Set to any non-empty value to skip TLS certificate verification for the CalDAV server (e.g. self-signed). |
REMOTE_USERNAME |
string | No | - | ICS host username. |
REMOTE_PASSWORD |
string | No | - | ICS host password. |
REMOTE_AUTH |
string | No | basic |
ICS host authentication method (either basic or digest). |
REMOTE_TLS_NO_VERIFY |
boolean | No | false |
Set to any non-empty value to skip TLS certificate verification for the ICS host (e.g. self-signed). |
TIMEZONE |
string | No | - | Override events timezone. Examples: Utc, Europe/Warsaw, Asia/Tokyo. |
SYNC_EVERY |
string | No | - | How often should the synchronisation occur? Examples: 30 minutes, an hour. Synchronise once if empty. |
DEBUG |
boolean | No | false |
Set to any non-empty value to print debugging messages. Please set this when reporting an error. |
SYNC_ALL |
boolean | No | false |
If set, all events in the calendar will be synced. Otherwise, only the ones occurring in the future will be. |
KEEP_LOCAL |
boolean | No | false |
Do not delete events on the CalDAV server that do not exist in the ICS file. |
IGNORED_COMPARE_FIELDS |
string | No | - | Fields that are ignored when checking if an event needs to be updated.* |
- For Google Calendar, you can ignore DTSTAMP (changes with every download of the ICS) and SEQUENCE (reported to change randomly).
Library usage
This script can be also used as a library in your Python script using ICSToCalDAVSync
class and its synchronise method.
from ics_caldav_sync import ICSToCalDAVSync
sync = ICSToCalDAVSync(
remote_url="https://example.com/calendar.ics",
local_url="https://caldav.example.com",
local_calendar_name="My Calendar",
local_username="username",
local_password="password",
# local_tls_verify=True,
# remote_username="",
# remote_password="",
# remote_auth="basic|digest",
# remote_tls_verify=True,
# sync_all=False,
# keep_local=False
# timezone=None,
# ignored_compare_fields=[]
)
sync.synchronise()
Examples
Command-line usage
ics_caldav_sync can be used from the command line when installed with pip. An example:
REMOTE_URL=https://example.com/path/to/calendar_file.ics \
LOCAL_URL=https://example.net/caldav \
LOCAL_CALENDAR_NAME="My Calendar" \
LOCAL_USERNAME=myusername \
LOCAL_PASSWORD=mypassword \
ics_caldav_sync
Docker
You can also do the same as a Docker one-liner - to avoid installing dependencies yourself.
docker run --rm \
-e REMOTE_URL=https://example.com/path/to/calendar_file.ics \
-e LOCAL_URL=https://example.net/caldav \
-e LOCAL_CALENDAR_NAME="My Calendar" \
-e LOCAL_USERNAME=myusername \
-e LOCAL_PASSWORD=mypassword \
przemub/ics_caldav_sync
Docker Compose
When setting up a long-running, periodic sync, Docker Compose can be helpful.
This example also shows settings needed by a CalDAV server Baïkal.
services:
ics_caldav_sync:
image: ics_caldav_sync
restart: unless-stopped
environment:
- REMOTE_URL=https://example.com/path/to/calendar_file.ics
- LOCAL_URL=https://baikal.myserver.com/dav.php/
- LOCAL_CALENDAR_NAME=My Calendar
- LOCAL_USERNAME=myusername
- LOCAL_PASSWORD=mypassword
- LOCAL_AUTH=digest # Required by Baikal - try removing if getting Unauthorized error
- SYNC_EVERY=30 minutes
Tested with
On the local side (CalDAV server):
On the remote site (ICS file generator):
Please feel free to open a PR against this section to add your configuration!
Rationale
In my case, my new shiny Bluetooth wristwatch, Casio Edifice ECB-10, did not support synchronisation with calendar subscriptions. And so this script was created.
Release files for ics-caldav-sync 1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ics_caldav_sync-1.3.tar.gz | 24.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ics_caldav_sync-1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.6 kB
Release files / ics_caldav_sync-1.3.tar.gz
| Download URL | ics_caldav_sync-1.3.tar.gz |
|---|---|
| Size | 24.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f43ea4742c69173160d45d04a21916773bafbf30aac6b5ca956237d505a3805d
|
|
BLAKE2b-256 checksum How to use checksums |
cd78bea9409cabd59cc761b86ed52bd821a3e9eddb0555478f7ec32f209d8313
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / ics_caldav_sync-1.3-py3-none-any.whl
| Download URL | ics_caldav_sync-1.3-py3-none-any.whl |
|---|---|
| Size | 10.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e6887d52526af4ed9007738af7fb5e83aec755427f3a1c45989af872bee626e3
|
|
BLAKE2b-256 checksum How to use checksums |
92c3418c8b61ba8a3c74a3ad9417ba3d12c153f6baf76edf4d7ada574ec5593e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|