Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions contrib/holiday/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
calcurse-holiday
================

`calcurse-holiday` is a Python script that can create holiday entries for
calcurse for a desired list of years based on a configuration file. Please note
that this script is still in an alpha version, so please report bugs (I think
the best way for this will be github, you can mention @atticus-sullivan)

Why not simply use reocuring events?
------------------------------------
Since some holiday days are not on the same date each year (e.g.some are
connected to the date Eastersunday is placed on), it is not possible to create
calcurse native reocuring events in calcurse for these events.

Usage
-----
* Create a `yaml` file which will be the base of which holiday dates will be
inserted.
* Then run `calcurse-holiday` with the path to the yaml-file you just created
and a list of years you'd like to create the holiday-events for. (Duplicates
will be omitted)

To check if the dates are correct before inserting them, just run the script
with the `-s` option.

**Tipp:** On failiure the new events should simple by at the end of your `app` file
of calcurse and it should be easy to remove them again

Example:
--------
```yaml
holiday.yaml
----------------------
# specify the description/name of the events that will be created
holiday_msg: "%s - free day" # %s will be replaced with the name/key of the
holiday date

# current valid date specs:
# "easter [+-]x day" => Date will be x days before/after the day of Eastersunday
# "MM-DD [+-]x weekday" => Date will be the x.th mon/thu/... before/after
# YYYY-MM-DD (with given year).
# The weekday is specified as 1->Monday 2->Thuesday ...
irregular holiday:
- Eastersunday: "easter +0 day"
- Eastermonday: "easter +1 day"
- Thanksgiving: "11-01 +3 4"
```

`calcurse-holiday holiday.yaml 2021 2022` will read the holiday template from the
`holiday.yaml` file and create the holiday events for the years `2021` and `2022`

Notes
-----
* Currently the purpose of this script is ONLY for events that are weekday offset
based or on eastern. More base dates are possible, just suggest them ;)

* Maybe one day this will support "normal" reoccuring Events too. But since this
can much better be done via calcurses native reoccuring events (which is much
better, since one doesn't need to remember to recreate the events), this is not
the main purpose of this script.

* The functionallity of avoiding duplicate entries might be added into native
calcurse via an commandline option. If this is complete one can remove the
calls to calcurse to check if an event is already added to calcurse
125 changes: 125 additions & 0 deletions contrib/holiday/calcurse-holiday.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3

import sys
import os
import subprocess
from datetime import datetime, timedelta
import argparse

import yaml
import re

# to calculate the date of the Eastersunday
def spencerFormula(y:int) -> datetime:
a = y % 19
b = y // 100
c = y % 100
d = b // 4
e = b % 4
f = (b+8) // 25
g = (b-f+1) // 3
h = (19*a+b-d-g+15) % 30
i = c // 4
k = c % 4
l = (32+2*e+2*i-h-k) % 7
m = (a+11*h+22*l) // 451
n = (h+l-7*m+114) // 31
p = (h+l-7*m+114) % 31

return datetime(year=y, month=n, day=p+1)


def fullDayAppointmentIcal(date:datetime, offset:int, desc:str, noDup:bool = True, simu:bool = False) -> str:
ret = []
ret.append("BEGIN:VEVENT")
ret.append("DTSTART;VALUE=DATE:" + (date + timedelta(days=offset)).strftime("%Y%m%d"))
ret.append("SUMMARY:" + desc)
if simu:
print("Event", desc, "on ", (date + timedelta(days=offset)).strftime("%Y-%m-%d"), "would be created")
ret.append("END:VEVENT")
if noDup:
command = [
"/usr/bin/calcurse",
"-Q",
"-d", "%s" % (date + timedelta(days=offset)).strftime("%d/%m/%Y"),
"--filter-pattern",'"^%s$"' % re.escape(desc).replace("\\\\", "\\")]
p = subprocess.Popen(" ".join(command), stderr=None, stdin=None, stdout=subprocess.PIPE, encoding="utf-8", shell=True)
stdout, stderr = p.communicate()
if stdout != "":
print(ret, "is already present -> skip")
return ""

return "\n".join(ret)


def toCalcurse(data:list[str]):
if "".join(data) != "":
data = \
["BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//calcurse-holiday//NONSGML calcurse-holiday//EN"] \
+ data \
+ ["END:VCALENDAR\n"]

command = ["calcurse"] + [
'-i', '-', #read from stdin
'--dump-imported',
'-q' # quiet
]
p = subprocess.Popen(command, stderr=None, stdin=subprocess.PIPE, stdout=None, encoding="utf-8")
p.communicate("\n".join(data))


def createForYears(file:str, years:list[int], simu:bool):
with open(file, 'r') as f:
c = yaml.load(f, Loader=yaml.FullLoader)

msg = c["holiday_msg"] if "holiday_msg" in c else "%s"

if "irregular holiday" in c:
ical = []
for d in c["irregular holiday"]:
d = list(d.items())
if len(d) > 1:
print("Error while parsing the config", file=sys.stderr)
continue

name,dateDesc = d[0]

r1 = re.match(r'^easter ([+-]\d+) day$', dateDesc)
r2 = re.match(r'^(\d{2}\-\d{2}) ([+-]\d+) ([1-7])$', dateDesc)

for year in years:
if r1:
offset = r1.groups()[0]
dtStart = spencerFormula(year)
ical.append(fullDayAppointmentIcal(dtStart, int(offset), msg % name, simu=simu))
elif r2:
start,scale,weekday = r2.groups()
dtStart = datetime.strptime(str(year)+"-"+start, "%Y-%m-%d")
# print(int(weekday), "-", dtStart.weekday(),"- 1", "% 7 + ", int(scale), "* 7")
offset = (int(weekday) - dtStart.weekday()-1) % 7 + int(scale)*7
ical.append(fullDayAppointmentIcal(dtStart, offset, msg % name, simu=simu))
else:
print("Failed to read entry", dateDesc, file=sys.stderr)
if simu:
print("\nThis ical (+header) would have been imported")
print("\n".join(ical))
else:
toCalcurse(ical)


parser = argparse.ArgumentParser("calcurse-holiday",
description="Add holiday entries based on given holiday.yaml file to calcurse")

parser.add_argument("file", nargs='?',
default="./holiday.yaml",
help="The yaml file to read the holidays from [defaults to './holiday.yaml']")
parser.add_argument("year", nargs='+',
type=int,
help="Specify a list of years to generate the holidays for")
parser.add_argument("-s", "--simulate",
action='store_true',
help="Only simulate the insertion of the events")

args = parser.parse_args()

createForYears(args.file, args.year, args.simulate)
11 changes: 11 additions & 0 deletions contrib/holiday/holiday.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# holiday_msg: "Frei %s"

regular holiday:
- Tag der Arbeit: 1.5.

# valid specifications are "easter +- x day" or MM.DD. +- weekday (given as 1-7 starting at monday)
irregular holiday:
- Ostersonntag: "easter +0 day"
- Ostermontag: "easter +1 day"
- Buß- und Bettag: "11-23 -1 3"
- Thanksgiving: "11-01 +3 4"
33 changes: 33 additions & 0 deletions contrib/holiday/testing-functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/python
import math

# According to wikipedia these are critical years for Eastersunday date calculations
l = [(1590,"22.4") ,(1666,"25.4") ,(1685,"22.4") ,(1924,"20.4") ,(1943,"25.4") ,(1962,"22.4") ,(2019,"21.4") ,(2038,"25.4") ,(2057,"22.4") ,(2076,"19.4") ,(2095,"24.4") ,(2133,"19.4") ,(2152,"23.4") ,(2171,"21.4") ,(2190,"25.4") ,(1876,"16.4") ,(1974,"14.4") ,(2045,"9.4") ,(2069,"14.4") ,(2089,"3.4") ,(2096,"15.4") ,(1802,"18.4") ,(1805,"14.4") ,(1818,"22.3") ,(1825,"3.4") ,(1829,"19.4") ,(1845,"23.3") ,(1900,"15.4") ,(1903,"12.4") ,(1923,"1.4") ,(1927,"17.4") ,(1954,"18.4") ,(1967,"26.3") ,(1981,"19.4") ,(2049,"18.4") ,(2076,"19.4") ,(2106,"18.4") ,(2119,"26.3") ,(2133,"19.4") ,(2147,"16.4") ,(2150,"12.4") ,(2170,"1.4") ,(2174,"17.4")]

def spencerFormula(y:int):
a = y % 19
b = y // 100
c = y % 100
d = b // 4
e = b % 4
f = (b+8) // 25
g = (b-f+1) // 3
h = (19*a+b-d-g+15) % 30
i = c // 4
k = c % 4
l = (32+2*e+2*i-h-k) % 7
m = (a+11*h+22*l) // 451
n = (h+l-7*m+114) // 31
p = (h+l-7*m+114) % 31

return str(p+1) + "." + str(n)

def checkEastern(foo, verbose:bool=False):
for y,e in l:
x = foo(y)
if x != e:
print(y, "failed, expected", e, "but was", x)
elif verbose:
print("success")

checkEastern(spencerFormula)