💾 Archived View for sotiris.papatheodorou.xyz › gemlog › 20220404_generating_birthday_calendars.gmi captured on 2024-03-21 at 15:06:37. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-03-20)
-=-=-=-=-=-=-
I recently switched from using Nextcloud for managing contacts and calendars to Radicale. Radicale is much easier to configure and maintain than a Nextcloud instance but there was one feature missing: automatically generating birthday calendar events from the contants. Since both the vCard and iCalendar formats are plain-text it shouldn't be too difficult to generate them using a script.
Reading through the Wikipedia article for vCard, I saw a very useful table showing all available vCard properties. For vCard 3+ the formatted name (FN) property is required so that can be reliably used as the name of the calendar event. The other property of interest is of course the birthday date (BDAY). Looking at some of the vCard files for my contacts I noticed that the date of the BDAY property occasionally has dash separating its components. Something interesting I noticed is that for contacts whose birth year isn't known, it's set to 1604. It would be interesting to learn why.
For the iCalendar format the Wikipedia article wasn't that helpful but the RFC was. The online validator was invaluable in testing the resulting files.
After a little trial and error I came up with the following shell script:
#!/bin/sh # SPDX-FileCopyrightText: 2022-2023 Sotiris Papatheodorou # SPDX-License-Identifier: CC0-1.0 set -eu vcard_to_bday() { awk ' BEGIN { FS = ":"; OFS = "\t" } /^FN/ { name = $2 } /^BDAY/ { bday = $2 gsub("-", "", bday) # Replace missing years (1604) with 1900 to avoid using # the Julian calendar. gsub("^1604", "1900", bday) } /^END:VCARD/ { if (name && bday) print name, bday } ' "$1" } # Usage: bday_to_vcal2 NAME BDAY UID bday_to_vcal2() { cat <<- EOF BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Sotiris Papatheodorou//${progname:-}//EN BEGIN:VEVENT UID:$3 SUMMARY:$1 🎂 DTSTART;VALUE=DATE:$2 DURATION:P1D RRULE:FREQ=YEARLY DTSTAMP:$(date -u '+%Y%m%dT%H%M%SZ') BEGIN:VALARM DESCRIPTION:$1 🎂 ACTION:DISPLAY TRIGGER:PT0S END:VALARM END:VEVENT END:VCALENDAR EOF } vcard_to_calendar() { data=$(vcard_to_bday "$1") if [ -z "$data" ] then return fi name=$(printf '%s' "$data" | cut -f 1) bday=$(printf '%s' "$data" | cut -f 2) uid=$(uuidgen --md5 --namespace '@oid' --name "$progname$name$bday") bday_to_vcal2 "$name" "$bday" "$uid" > "$2/$uid.ics" } progname=${0##*/} if [ "$#" -ne 2 ] then cat <<- EOF >&2 Usage: ${progname} INDIR OUTDIR Generate birthday calendars inside OUTDIR for all contacts inside INDIR and its subdirectories. EOF exit 2 fi mkdir -p "$2" rm -f "$2"/*.ics find "$1" -type f -name '*.vcf' | while IFS= read -r file do vcard_to_calendar "$file" "$2" done
I created a new calendar from the Radicale web interface, set the script to run on a daily cron job and that was it!
Sotiris 2022/04/04