Implement ruby holiday api instead of iCalender feed

Title: Holiday API instead of (or in addition to) the iCalendar feed

  1. What is your original issue/pain point you want to solve?
    • A better solution for holiday management
  2. Which are one or two concrete situations where this problem hurts the most?
    • Holidays must constantly be maintained manually
  3. Why is it not solvable with the Zammad standard?
    • iCalendar is not a useful way to manage holidays because the topic is too specific. But there are very good libraries for this.
  4. What is your expectation/what do you want to achieve?
    • Implement the following library as a replacement or in addition to iCalendar.

Your Zammad environment:

  • Average concurrent agent count: 5
  • Average tickets a day: 20
  • What roles/people are involved: admins

Anything else which you think is useful to understand your use case:
It’s just a suggestion for improvement, not a particularly urgent one.

As example, if you want all holidays for Bavaria in predominantly Catholic communities, it would look like:

from = Date.new(2024, 1, 1)
to = Date.new(2024, 12, 31)
bayern_holidays = Holidays.between(from, to, :de_by_cath)
bayern_holidays.each do |holiday|
  puts "#{holiday[:date]}: #{holiday[:name]}"
end

And the output would look this:

2024-01-01: Neujahrstag
2024-01-06: Heilige Drei Könige
2024-03-29: Karfreitag
2024-04-01: Ostermontag
2024-05-01: Tag der Arbeit
2024-05-09: Christi Himmelfahrt
2024-05-20: Pfingstmontag
2024-05-30: Fronleichnam
2024-08-08: Friedensfest
2024-08-15: Mariä Himmelfahrt
2024-10-03: Tag der Deutschen Einheit
2024-11-01: Allerheiligen
2024-12-25: 1. Weihnachtstag
2024-12-26: 2. Weihnachtstag

Or for Hawaii:

from = Date.new(2024, 1, 1)
to = Date.new(2024, 12, 31)
hawaiian_holidays = Holidays.between(from, to, :us_hi)
hawaiian_holidays.each do |holiday|
  puts "#{holiday[:date]}: #{holiday[:name]}"
end
2024-01-01: New Year's Day
2024-01-15: Martin Luther King, Jr. Day
2024-02-19: Presidents' Day
2024-03-26: Prince Jonah Kuhio Kalanianaole Day
2024-03-29: Good Friday
2024-05-27: Memorial Day
2024-06-11: King Kamehameha I Day
2024-06-19: Juneteenth National Independence Day
2024-07-04: Independence Day
2024-08-16: Statehood Day
2024-09-02: Labor Day
2024-11-05: Election Day
2024-11-11: Veterans Day
2024-11-28: Thanksgiving
2024-12-25: Christmas Day

It’s just a challenge to implement this nicely into the GUI.

2 Likes

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
1 Like

Additional comment for implemeting in the Zammad GUI:

I recommend three dropdowns: Country, Region and Additional parameter. The second and third dropdown should only appear if useful. Austria has no regional different holidays, but in Switzerland and Germany the second dropdown is for Bundesländer or Kanton. If Berlin is chosen, no third dropdown should be shown, if Bavaria is chosen, is must be shown and display “catholic” and “augsburg”.

1 Like