Skip to content

Instantly share code, notes, and snippets.

@mtesch-um
Last active January 19, 2026 06:18
Show Gist options
  • Select an option

  • Save mtesch-um/125f77da7b53a542480f2cce971b5f82 to your computer and use it in GitHub Desktop.

Select an option

Save mtesch-um/125f77da7b53a542480f2cce971b5f82 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -euo pipefail
usage() {
cat <<EOF
Usage: $(basename "$0") <calendar.ics> <year> <month> [--rewrite-domain OLD NEW]
Removes all events before the specified year/month and saves the result
to a new file with .trimmed.ics suffix.
Arguments:
calendar.ics Path to the iCalendar file (absolute or relative)
year 4-digit year (e.g., 2024)
month 2-digit month (01-12)
Options:
--rewrite-domain OLD NEW
Replace email domain OLD with NEW in all events.
Handles iCalendar line folding automatically.
Examples:
$(basename "$0") ~/calendars/work.ics 2024 06
# Creates ~/calendars/work.trimmed.ics with events from June 2024 onwards
$(basename "$0") ~/calendars/work.ics 2024 06 --rewrite-domain utilimarc.com smith-system.com
# Also rewrites all @utilimarc.com emails to @smith-system.com
Requirements (macOS):
brew install coreutils # provides gcsplit
EOF
exit 1
}
ics_unfold() {
awk '{
if (substr($0, 1, 1) == " " || substr($0, 1, 1) == "\t") {
printf "%s", substr($0, 2)
} else {
if (NR > 1) print ""
printf "%s", $0
}
}
END { print "" }'
}
ics_fold() {
awk '{
line = $0
if (length(line) <= 75) {
print line
} else {
print substr(line, 1, 75)
line = substr(line, 76)
while (length(line) > 74) {
print " " substr(line, 1, 74)
line = substr(line, 75)
}
if (length(line) > 0) {
print " " line
}
}
}'
}
cleanup() {
if [[ -n "${temp_dir:-}" && -d "$temp_dir" ]]; then
rm -rf "$temp_dir"
fi
}
trap cleanup EXIT
if [[ $# -lt 3 ]]; then
usage
fi
calendar_input="$1"
year="$2"
month="$3"
shift 3
old_domain=""
new_domain=""
while [[ $# -gt 0 ]]; do
case "$1" in
--rewrite-domain)
if [[ $# -lt 3 ]]; then
echo "Error: --rewrite-domain requires OLD and NEW arguments" >&2
exit 1
fi
old_domain="$2"
new_domain="$3"
shift 3
;;
*)
echo "Error: Unknown option: $1" >&2
usage
;;
esac
done
if [[ ! "$year" =~ ^[0-9]{4}$ ]]; then
echo "Error: Year must be 4 digits (e.g., 2024)" >&2
exit 1
fi
if [[ ! "$month" =~ ^(0[1-9]|1[0-2])$ ]]; then
echo "Error: Month must be 01-12" >&2
exit 1
fi
if [[ "$calendar_input" = /* ]]; then
calendar_file="$calendar_input"
else
calendar_file="$(pwd)/$calendar_input"
fi
if [[ ! -f "$calendar_file" ]]; then
echo "Error: Calendar file not found: $calendar_file" >&2
exit 1
fi
calendar_dir="$(dirname "$calendar_file")"
calendar_basename="$(basename "$calendar_file" .ics)"
output_file="${calendar_dir}/${calendar_basename}.trimmed.ics"
if command -v gcsplit &>/dev/null; then
CSPLIT=gcsplit
elif command -v csplit &>/dev/null; then
if csplit --version 2>&1 | grep -q GNU; then
CSPLIT="csplit"
else
echo "Error: GNU csplit required. On macOS, install with: brew install coreutils" >&2
exit 1
fi
else
echo "Error: csplit not found. On macOS, install with: brew install coreutils" >&2
exit 1
fi
temp_dir=$(mktemp -d)
temp_files_digits=5
cd "$temp_dir"
total_files=$($CSPLIT -n "$temp_files_digits" "$calendar_file" '/BEGIN:VEVENT/' '{*}' 2>/dev/null | wc -l)
total_files=$((total_files))
if [[ $total_files -lt 2 ]]; then
echo "Error: No events found in calendar file" >&2
exit 1
fi
echo "Found $((total_files - 1)) events in calendar"
grep -l DTSTART xx* 2>/dev/null | while read -r file; do
dtstart=$(grep -m1 'DTSTART' "$file" | head -1)
if [[ "$dtstart" =~ ([0-9]{4})([0-9]{2}) ]]; then
event_year="${BASH_REMATCH[1]}"
event_month="${BASH_REMATCH[2]}"
if (( 10#$event_year > 10#$year || (10#$event_year == 10#$year && 10#$event_month >= 10#$month) )); then
cat "$file"
fi
fi
done | grep -v '^END:VCALENDAR$' > events
header_file="xx$(printf '%0*d' "$temp_files_digits" 0)"
{
cat "$header_file"
cat events
echo "END:VCALENDAR"
} > "$output_file"
if [[ -n "$old_domain" ]]; then
echo "Rewriting @${old_domain} to @${new_domain}..."
escaped_old=$(printf '%s' "$old_domain" | sed 's/[.[\*^$/]/\\&/g')
escaped_new=$(printf '%s' "$new_domain" | sed 's/[&/\]/\\&/g')
tr -d '\r' < "$output_file" \
| ics_unfold \
| sed "s/@${escaped_old}/@${escaped_new}/g" \
| ics_fold > "${output_file}.tmp"
mv "${output_file}.tmp" "$output_file"
fi
kept_events=$(grep -c 'BEGIN:VEVENT' "$output_file" 2>/dev/null || echo 0)
echo "Kept $kept_events events from ${year}-${month} onwards"
echo "Output saved to: $output_file"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment