website.git

commit fd0ba5eb8e8bec0378e03e56f0a3d71d1c9a92e6

Author: Adam <git@apiote.xyz>

add holiday calendar

 holidays.dirty.example | 7 +
 router.go | 182 +++++++++++++++++++++++++++++++++++++++++++
 templates/calendar.ics | 19 ++++


diff --git a/holidays.dirty.example b/holidays.dirty.example
new file mode 100644
index 0000000000000000000000000000000000000000..489152dd2a6c2351c3f1078a5f6f88305fd737eb
--- /dev/null
+++ b/holidays.dirty.example
@@ -0,0 +1,7 @@
+(
+	(
+		('name' 'New Year’s Day')
+		('month' 1)
+		('day' 1)
+	)
+)




diff --git a/router.go b/router.go
index e612c2e049791e79ebdbdaf779faafa6e5e5af7a..8598d7188df2df0ef265a7705b67572e5dc2ce73 100644
--- a/router.go
+++ b/router.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"bytes"
 	"fmt"
 	"html/template"
 	"io"
@@ -10,8 +11,9 @@ 	"math/rand"
 	"mime"
 	"net/http"
 	"os"
+	"strconv"
 	"strings"
-	"bytes"
+	"time"
 
 	"apiote.xyz/p/go-dirty"
 	"github.com/bytesparadise/libasciidoc"
@@ -38,6 +40,44 @@ 	Versions       map[string]Version
 	Tag            string
 	Module         string
 	Content        template.HTML
+}
+
+type Holiday struct {
+	Name  string
+	Month int
+	Day   int
+	Rrule string
+	Year  int
+}
+
+func (h Holiday) Format() string {
+	var result string
+	if h.Year > 0 {
+		result = strconv.FormatInt(int64(h.Year), 10)
+	} else {
+		result = "2001"
+	}
+	if h.Month < 10 {
+		result += "0"
+	}
+	result += strconv.FormatInt(int64(h.Month), 10)
+	if h.Day < 10 {
+		result += "0"
+	}
+	result += strconv.FormatInt(int64(h.Day), 10)
+
+	return result
+}
+
+func (h Holiday) UID() string {
+	uid := strings.ReplaceAll(h.Name, " ", "")
+	uid = strings.ReplaceAll(uid, "’", "")
+	uid = strings.ReplaceAll(uid, ".", "")
+	uid = strings.ToLower(uid)
+	if h.Year != 0 {
+		uid = uid + "_" + strconv.FormatInt(int64(h.Year), 10)
+	}
+	return uid + "@apiote.xyz"
 }
 
 func static(w http.ResponseWriter, r *http.Request) {
@@ -233,6 +273,145 @@ 		}
 	}
 }
 
+func easter(y int) []Holiday {
+	a := y % 19
+	b := y % 4
+	c := y % 7
+	p := y / 100
+	q := (13 + (8 * p)) / 25
+	m := (15 - q + p - (p / 4)) % 30
+	n := (4 + p - (p / 4)) % 7
+	d := (19*a + m) % 30
+	e := (n + (2 * b) + (4 * c) + (6 * d)) % 7
+
+	var easterDate time.Time
+	if d == 29 && e == 6 {
+		easterDate = time.Date(y, 4, 19, 0, 0, 0, 0, time.Local)
+	} else if d == 28 && e == 6 {
+		easterDate = time.Date(y, 4, 18, 0, 0, 0, 0, time.Local)
+	} else {
+		easterDate = time.Date(y, 3, 22, 0, 0, 0, 0, time.Local).AddDate(0, 0, d+e)
+	}
+
+	holidays := []Holiday{}
+	holidays = append(holidays, Holiday{
+		Name:  "Easter",
+		Month: int(easterDate.Month()),
+		Day:   easterDate.Day(),
+		Year:  y,
+	})
+	h := easterDate.AddDate(0, 0, -52)
+	holidays = append(holidays, Holiday{
+		Name:  "Fat Thursday",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, -47)
+	holidays = append(holidays, Holiday{
+		Name:  "Shrove Tuesday",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, -46)
+	holidays = append(holidays, Holiday{
+		Name:  "Ash Wednesday",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, -7)
+	holidays = append(holidays, Holiday{
+		Name:  "Palm Sunday",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, -3)
+	holidays = append(holidays, Holiday{
+		Name:  "Maundy Thursday",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, -2)
+	holidays = append(holidays, Holiday{
+		Name:  "Good Friday",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, -1)
+	holidays = append(holidays, Holiday{
+		Name:  "Easter Eve",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, 1)
+	holidays = append(holidays, Holiday{
+		Name:  "Easter Monday",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, 49)
+	holidays = append(holidays, Holiday{
+		Name:  "Pentecost",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+	h = easterDate.AddDate(0, 0, 60)
+	holidays = append(holidays, Holiday{
+		Name:  "Corpus Christi",
+		Month: int(h.Month()),
+		Day:   h.Day(),
+		Year:  y,
+	})
+
+	return holidays
+}
+
+func calendar(w http.ResponseWriter, r *http.Request) {
+	file, err := os.Open("holidays.dirty")
+	if err != nil {
+		log.Println(err)
+		fmt.Fprint(w, "Error 500")
+		return
+	}
+	holidays := []Holiday{}
+	err = dirty.LoadStruct(file, &holidays)
+	if err != nil {
+		log.Println(err)
+		log.Println(holidays)
+		fmt.Fprint(w, "Error 500")
+		return
+	}
+	t, err := template.ParseFiles("templates/calendar.ics")
+	if err != nil {
+		log.Println(err)
+		if os.IsNotExist(err) {
+			renderStatus(w, 501)
+			return
+		}
+		fmt.Fprint(w, "Error 500")
+		return
+	}
+	thisYear := time.Now().Year()
+	for i := 0; i < 10; i++ {
+		holidays = append(holidays, easter(thisYear+i)...)
+	}
+	w.Header().Set("Content-Type", "text/calendar")
+	err = t.Execute(w, holidays)
+	if err != nil {
+		log.Println(err)
+		fmt.Fprint(w, "Error 500")
+		return
+	}
+}
+
 func shortRedirect(w http.ResponseWriter, r *http.Request) {
 	path := strings.Split(r.URL.Path[1:], "/")
 	if len(path) == 2 && path[1] == "" {
@@ -386,6 +565,7 @@ 	http.HandleFunc("/programs/", programs)
 	http.HandleFunc("/donate", donate)
 	http.HandleFunc("/donate/", donate) // xxx historical reasons for trailing slash
 	http.HandleFunc("/pgp/", pgp)
+	http.HandleFunc("/calendar", calendar)
 
 	http.HandleFunc("/s/", shortRedirect)
 	http.HandleFunc("/short/", shortShow)




diff --git a/templates/calendar.ics b/templates/calendar.ics
new file mode 100644
index 0000000000000000000000000000000000000000..c0e172217387e2f6e56ea3d5efdf1af3a6b54239
--- /dev/null
+++ b/templates/calendar.ics
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:apiote.xyz
+COLOR:royalBlue
+{{ range . }}
+BEGIN:VEVENT
+UID:{{ .UID }}
+DTSTAMP:20221114T172530Z
+DTSTART:{{ .Format }}T000000
+DTEND:{{ .Format }}T240000
+SUMMARY:{{ .Name }}
+{{- if .Rrule }}
+RRULE:{{ .Rrule }}
+{{- else if eq .Year 0 }}
+RRULE:FREQ=YEARLY
+{{- end }}
+END:VEVENT
+{{ end }}
+END:VCALENDAR