Skip to main content

ICS to CalDAV synchronisation

PyPI version Docker Pulls Licence

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)

Source distribution for ics-caldav-sync 1.3
File Size Uploaded
ics_caldav_sync-1.3.tar.gz 24.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ics-caldav-sync 1.3
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

1.3 This release

2 release files

1.2

2 release files

1.1

2 release files

1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page