Files
nextcloud-sync-cal-time-man…/docs/crealead-caldav.md
T

10 KiB

Export CalDAV vers CSV

Le script crealead-caldav.py récupère des événements d'agendas accessibles en CalDAV, notamment Nextcloud, et produit un fichier CSV par calendrier configuré. Ces fichiers peuvent ensuite être utilisés par csv-to-timemanager.py.

Pré-requis

  • Python 3, avec pip et le module venv. L'environnement Docker du dépôt utilise Python 3.9.2.
  • Les dépendances de requirements.txt, notamment caldav, icalendar et pytz.
  • Un serveur CalDAV accessible depuis la machine d'exécution et un compte autorisé à lire les calendriers à exporter.
  • Des droits de lecture sur config.json et d'écriture sur les fichiers CSV ainsi que sur le dossier logs/, situé à côté du script.
  • Bash pour utiliser le lanceur exec-export-caldav.sh.

Depuis la racine du dépôt, préparer l'environnement :

python3 -m venv env
source env/bin/activate
python -m pip install -r requirements.txt

L'environnement Docker fourni constitue une autre façon d'exécuter le script et les tests. Sa construction est décrite dans support/docker-dev/README.md.

Usage

Configuration minimale

Créer un fichier config.json à la racine du dépôt. Exemple pour exporter les événements d'octobre 2026 :

{
  "caldav_url": "https://cloud.example.com/remote.php/dav/",
  "username": "utilisateur",
  "password": "mot-de-passe-application",
  "year_start": 2026,
  "month_start": 10,
  "day_start": 1,
  "year_end": 2026,
  "month_end": 11,
  "day_end": 1,
  "calendars": [
    {
      "calendar_name": "Temps de travail",
      "csv_name": "csv/utilisateur.csv"
    }
  ]
}
Champ Rôle
caldav_url URL du serveur CalDAV.
username, password Identifiants du compte qui peut lire les calendriers.
year_start, month_start, day_start Date de début de la recherche, à minuit. Les valeurs sont des nombres entiers.
year_end, month_end, day_end Date de fin de la recherche, à minuit. Pour couvrir un mois, utiliser le premier jour du mois suivant.
calendars Liste des calendriers à exporter.
calendars[].calendar_name Texte contenu dans le nom du calendrier distant, avec respect de la casse. Il doit correspondre à un seul calendrier.
calendars[].csv_name Chemin du fichier CSV à écrire. Un chemin relatif est résolu depuis le répertoire de lancement.

Ajouter une entrée dans calendars pour chaque calendrier à exporter. Les champs db_*, user_name et api_password, utilisés par d'autres scripts du dépôt, ne sont pas nécessaires à cet export.

config.json est ignoré par Git. Remplacer les valeurs d'exemple par les informations du serveur et du compte utilisés.

Lancement

Lancer les commandes depuis la racine du dépôt : le script lit ./config.json, et le lanceur recherche env/bin/activate dans le répertoire courant. Créer au préalable les dossiers de destination des CSV ; le script ne les crée pas.

mkdir -p csv
bash ./exec-export-caldav.sh

Le lanceur active l'environnement env/, puis exécute le script. Il est aussi possible de lancer directement Python :

env/bin/python crealead-caldav.py

Le script ne propose pas d'arguments en ligne de commande. La période et les calendriers sont définis dans config.json.

Fichiers produits et traitement des événements

Chaque CSV existant est remplacé lors de l'export. Les lignes sont triées par date et heure de début. Le séparateur est ; et les colonnes sont :

Titre;Date debut;Heure debut;Date fin;Heure fin;Categories;Emplacement;Description

Les événements récurrents sont demandés au serveur avec expand=True. Le lecteur iCalendar prend en charge les paramètres des propriétés, comme TZID et VALUE=DATE, ainsi que les textes répartis sur plusieurs lignes. Les heures sont converties vers Europe/Paris. Une heure sans fuseau est interprétée dans ce fuseau ; une date seule est exportée à minuit. Si DTEND est absent, DURATION peut fournir la date de fin.

Les événements sans propriété SUMMARY sont ignorés. Ceux dont les dates de début ou de fin sont absentes ou invalides sont également ignorés. Chaque exclusion laisse un avertissement détaillé dans le journal, y compris en l'absence de SUMMARY. Un document iCalendar illisible ou une ressource sans composant VEVENT est signalé puis ignoré. Les autres événements restent exportés. Si un calendrier configuré est introuvable ou correspond à plusieurs calendriers distants, le script affiche un message et s'arrête avant l'écriture des CSV.

En cas d'erreur d'écriture d'un CSV, le script s'arrête avec une exception. Il laisse un avertissement pour chaque événement du calendrier en cours, dont le fichier peut être incomplet, et des calendriers restant à écrire. Les fichiers déjà écrits avec succès sont conservés.

Journalisation

Les sorties standard et d'erreur sont affichées dans le terminal et copiées dans logs/crealead-caldav.log, à côté du script. Le dossier est créé automatiquement. Le journal utilise UTF-8, conserve les exécutions précédentes et enregistre les traces d'exception en cas de plantage pendant le traitement. Il n'y a pas de rotation automatique du journal. logs/ est ignoré par Git.

Chaque nouvelle ligne du journal est préfixée par l'heure d'écriture en UTC, au format ISO 8601 YYYY-MM-DDTHH:MM:SSZ, puis par [info] :

[2026-10-15T12:34:56Z]: [info] - csv/utilisateur.csv ...

Cela s'applique aussi aux messages écrits en plusieurs fragments et à chaque ligne d'une trace d'exception. L'horodatage est ajouté dans le fichier de journal, sans modifier les messages ordinaires du terminal.

Tout événement non exporté est signalé avec [warning][event], à la place de [info]. Le message indique le calendrier distant (ou son nom configuré), le fichier CSV, le titre, le début, la fin, l'UID et la raison de l'exclusion :

[2026-10-15T12:34:56Z]: [warning][event] - Calendrier='Temps de travail' ; CSV='csv/utilisateur.csv' ; titre='Réunion' ; début='2026-10-15T08:00:00Z' ; fin='indisponible' ; UID='reunion-1' ; événement non exporté : dates de début/fin absentes ou invalides (date absente ou invalide)

L'heure entre crochets est celle du message ; les champs début et fin décrivent le moment de l'événement. Les dates et heures décodées sont affichées en UTC, les dates de journée entière restent des dates seules. En cas de document illisible ou de date invalide, le script récupère les champs bruts disponibles. Une métadonnée absente ou irrécupérable vaut indisponible. Les retours à la ligne dans les métadonnées et le motif sont échappés pour garder un avertissement sur une seule ligne. Ces avertissements sont aussi visibles dans le terminal.

Tests

Les tests se trouvent dans tests/test_crealead_caldav.py et utilisent unittest, fourni avec Python. Aucune dépendance de test supplémentaire n'est nécessaire.

Cas couverts

Les 13 tests de ExportEventsTests vérifient :

  • La lecture de DTSTART et DTEND avec TZID, leur conversion vers Paris et le tri chronologique des lignes.
  • La conversion des dates UTC en été et en hiver, ainsi que celle d'un autre fuseau horaire.
  • Les événements sur une journée entière (VALUE=DATE), les heures sans fuseau et le calcul de la fin à partir de DURATION.
  • L'exclusion des événements sans propriété SUMMARY, des dates manquantes ou invalides et des documents iCalendar illisibles, sans bloquer les événements valides, ainsi que la présence d'un avertissement contextualisé dans le journal pour chaque événement écarté.
  • La récupération des métadonnées de chaque événement dans un document illisible, la conservation d'une date brute invalide et le signalement d'une ressource sans composant VEVENT.
  • La conservation des deux-points dans les textes, des paramètres de texte, des catégories et des lignes repliées, ainsi que le nettoyage de la description HTML encodée et le contenu du CSV écrit.

Les 9 tests de LogOutputTests vérifient :

  • La création de logs/ à côté du script, la copie des deux sorties et l'écriture immédiate des messages, y compris les accents.
  • L'ajout au journal existant et le choix du nom du journal d'après celui du script, y compris lorsqu'une exécution se termine sans retour à la ligne.
  • L'horodatage en UTC, le préfixe [info] de chaque ligne, les écritures en plusieurs fragments, les messages sur plusieurs lignes et les lignes vides.
  • Le préfixe [warning][event] sans [info], les métadonnées de l'événement, l'échappement des retours à la ligne et la séparation d'un avertissement après un message ordinaire inachevé.
  • La sauvegarde des traces d'exception et la restauration des sorties après une erreur.
  • La conservation du code de sortie d'un SystemExit, sans ajouter de trace d'exception, et la sauvegarde de son éventuel message.

Le test de ExportFailureTests provoque une erreur d'écriture CSV. Il vérifie qu'un avertissement est enregistré pour les événements du calendrier en échec et des calendriers restant à écrire, et que le premier CSV déjà écrit est conservé.

Ces tests utilisent le vrai lecteur icalendar, des événements synthétiques et des dossiers temporaires pour les CSV et les journaux. Le module CalDAV et les réponses du serveur sont simulés : aucun accès réseau, compte, config.json ou calendrier réel n'est nécessaire. Ils vérifient que la recherche demande expand=True, mais ne valident pas l'expansion des récurrences par un serveur réel ni l'import dans TimeManager.

Exécution

Depuis la racine du dépôt, avec les dépendances installées dans env/ :

env/bin/python -B -m unittest discover -s tests -v

L'option -B évite de créer des fichiers de cache Python. La suite contient actuellement 23 tests ; une exécution réussie se termine par OK.

Avec l'image Docker du dépôt déjà construite :

docker run --rm -v "$(pwd)":/app -w /app cal-to-time-manager:dev -B -m unittest discover -s tests -v