With the help of ChatGPT I found a solution for using the Ruby holidays library as a kind of web service that gives back a ICS feed.
First, I login on my Zammad Debian VM as root
.
Install some libraries:
sudo apt install rubygems
gem install sinatra holidays icalendar rackup
Check, if you’re in the folder /root
with pwd
.
nano holiday.rb
Paste the following code:
# Little webservice based on Webframework Sinatra
# Call webservice with some paramters and get an iCal-File back
# Description:
# Germany 2025-2026:
# http://localhost:4567/holidays.ics?region=de&from=2025&to=2026
# Germany, Bavaria, catholic communites, current year with informal holidays (e.g. New Year's Eve and Christmas Eve):
# http://localhost:4567/holidays.ics?region=de_by_cath&from=current_year&to=current_year&type=informal
# Germany, Bavaria, City of Augsburg, previous year to following year:
# http://localhost:4567/holidays.ics?region=de_by_augsburg&from=last_year&to=next_year
# Hamburg plus company-specific holidays. To do this, save a company.yaml file in /root. Enter the company abbreviation under [regions] and pass it as a parameter
# http://localhost:4567/holidays.ics?region=de_hh+company&from=last_year&to=next_year&custom=company.yaml
# Holidays for a Bodensee based company regions Baden-Württemberg, Austria and Kanton Thurgau
# http://localhost:4567/holidays.ics?region=at+ch_tg+de_bw&from=last_year&to=next_year
require 'sinatra'
require 'holidays'
require 'icalendar'
# Method to generate iCal file from holiday data
def generate_ics(holidays)
cal = Icalendar::Calendar.new
holidays.each do |holiday|
event = Icalendar::Event.new
event.dtstart = holiday[:date]
event.summary = holiday[:name]
cal.add_event(event)
puts "#{holiday[:date]}: #{holiday[:name]}"
end
cal.to_ical
end
# Method for determining the current year
def current_year
Date.today.year
end
# Method for determining next year
def next_year
Date.today.year + 1
end
# Method for determining last year
def last_year
Date.today.year - 1
end
# Method to check and convert the year variable
def parse_year(year)
case year.upcase
when 'CURRENT_YEAR'
current_year
when 'NEXT_YEAR'
next_year
when 'LAST_YEAR'
last_year
else
if year.match?(/\A\d{4}\z/)
year.to_i
else
puts "Unrecognized year variable, defaulting to current year: #{year}"
current_year
end
end
end
get '/holidays.ics' do
content_type 'text/calendar'
from = Date.new(parse_year(params['from'] || 'CURRENT_YEAR'), 1, 1)
to = Date.new(parse_year(params['to'] || 'NEXT_YEAR'), 12, 31)
options = {}
options[:informal] = true if params['type'] == 'informal'
custom_file = params['custom']
if custom_file
custom_path = File.join(settings.root, custom_file)
Holidays.load_custom(custom_path)
end
# Split region and save in array
regions = params['region'].split(' ')
# Convert regions to symbols and save in array parameters
parameters = regions.map(&:to_sym)
# Add :informal to parameters if type is informal
parameters << :informal if params['type'] == 'informal'
puts "parameters: #{parameters}"
holidays = Holidays.between(from, to, *parameters)
ics = generate_ics(holidays)
ics
end
Now add a service (Debian, you may need to adjust the paths):
nano /etc/systemd/system/holiday.service
And paste the following:
[Unit]
Description=Holiday Web Server
After=network.target
[Service]
Type=simple
WorkingDirectory=/root
ExecStart=/usr/bin/ruby /root/holiday.rb
Restart=always
[Install]
WantedBy=multi-user.target
Save this file with Ctrl+X
sudo systemctl daemon-reload
sudo systemctl status holiday
sudo systemctl enable holiday
Now create a new calendar in Zammad and try some of the examples provided at the top of the programm code.
Here are the region codes for the DACH countries. A complete list you can get by Ruby Holidays.available_regions
or at Github Holidays:
at
ch
ch_ag
ch_ai
ch_ar
ch_be
ch_bl
ch_bs
ch_fr
ch_ge
ch_gl
ch_gr
ch_ju
ch_lu
ch_ne
ch_nw
ch_ow
ch_sg
ch_sh
ch_so
ch_sz
ch_tg
ch_ti
ch_ur
ch_vd
ch_vs
ch_zg
ch_zh
de
de_bb
de_be
de_bw
de_by
de_by_augsburg
de_by_cath
de_hb
de_he
de_hh
de_mv
de_ni
de_nw
de_rp
de_sh
de_sl
de_sn
de_sn_sorbian
de_st
de_th
de_th_cath