The deceptively complex world of RRULEs
nylas.comThis is actually what prompted me to start work on luatz: https://github.com/daurnimator/luatz
Trying to implement RRULEs you need all sorts of fun date math + converting back and forth between time zones.
I never released my ical library (didn't get it open sourced from work); but luatz lets you write your own :)
that's pretty awesome :) too bad about the ics library! the world needs more tools like this
i got tired of all calendars being based on this system, which i find too restricted. You cannot encode many types of holiday, Easter or many religious festivals. You can't track the moon either.
So, i wrote my own calendar and the events work as a start datetime, end datetime and optional recurrence. the recurrence is encoded as a small script "generator" statement (LUA actually). The script uses a rich set of built in "function kit" such as "fullmoon", "newmoon" etc. Things like Easter need the moon, and so do many religious festivals.
The generator returns the NEXT datetime, so that the calendar can chain these events so that scrolling works nicely and you only calculate what you need.
# Example Generators
## Annual date (eg birthdays) return nextMonthDay(d, september, 23)
## monthly date, `v' of month return nextMonth(d)+v-1
## first monday of next month return nthkDay(nextMonth(d),1,monday)
## last sunday in a month next year eg last sunday next march. finds next april 1 and works back to last sunday return nthkDay(nextMonthDay(d+7,april,1),-1,sunday)
next monday on or after next May 1st eg may day return nthkDay(nextMonthDay(d,may,1),1,monday)
eg last monday in may, spring bank holiday return nthkDay(nextMonthDay(d+7,june,1),-1,monday)
## third tuesday of the month return nthkDay(nextMonth(d),3,tuesday)
## fourth thursday in November (Thanksgiving USA) return nthkDay(nextMonthDay(d, november, 1), 4, thursday)
## second monday of october (Thanksgiving Canada) return nthkDay(nextMonthDay(d, october, 1), 2, monday)
## mothers day UK 4th sunday of Lent = 3 weeks before easter sunday return cal.nextEasterSunday(d+22)-21
## first and third thursday of the month function cvrs(da) local d1 = nthkDay(thisMonth(da),3,thursday) local d2 = nthkDay(nextMonth(da),1,thursday) if d1 > da then return d1 end return d2 end return cvrs(d)
## first weekday of month function weekday(d) local da = cal.dayOfWeek(d) if da == sunday then d = d + 1 elseif da == saturday then d = d + 2 end return d end return weekday(nextMonth(d))
## first weekday on or before given date of of month function weekdayb4(d) local da = cal.dayOfWeek(d) if da == sunday then d = d - 2 elseif da == saturday then d = d - 1 end return d end return weekdayb4(nextMonth(d)+25) -- example
## easter return cal.nextEasterSunday(d)
eg good friday return cal.nextEasterSunday(d+3)-2
eg. Easter monday return cal.nextEasterSunday(d)+1
eg shrove tuesday return cal.nextEasterSunday(d+48)-47
##* moon phases return cal.nextNewMoon(d+1) return cal.nextFullMoon(d+1)
## ramadan
this generates the logical 1/9 islamic. Howeever the fast of Ramadan is often started the day earlier if based on the observational moon note that it always begins the sunset beforehand.
y,m = cal.islamicFromFixed(d) if m >= 9 then y=y+1 end return cal.fixedFromIslamic(y,9,1)
-- end of ramadan is the end of month 9 -- Eid-al-Fitr (Ramadan ends) y,m = cal.islamicFromFixed(d+1) if m >= 10 then y=y+1 end return cal.fixedFromIslamic(y,9,30)
-- Al-Hijra (Islamic New Year) return cal.fixedFromIslamic(cal.islamicFromFixed(d)+1,1,1)
## Hebrew
-- Rosh Hashanah (Jewish New Year) -- 1st of Tishri return cal.fixedFromHebrew(cal.hebrewFromFixed(d)+1,7,1)
-- Yom Yippur -- tishri 10 return cal.fixedFromHebrew(cal.hebrewFromFixed(d)+1,7,10)
Did you release the code to this? It's a really interesting idea! How did you ensure they are deterministic or run in constant time? (Or are they not guaranteed to?)
One of our goals with the Nylas calendar APIs was to make them backwards compatible, so that developers could still interact with Gcal/Exchange. This means we're really restricted in what we can expose at this point. Having more sophisticated tools for defining repeating events or moments in time is certainly where we want to go, though!
I developed this as an app for Windows and Android. it could work on linux too. Although i didn't release it. The main reason is that i didn't consider it finished. I did a month view and a day view, but i wanted also the week view. Although i do use it as my daily calendar.
Complete calendar data and rules are stored as a single text file! that's what i wanted too (about 1MB for 20 years' data). I can make manual backups!
Regarding the exiting formats, i wrote an importer that converts iCal to LUA. Basically, just a set of templates. Indeed the original idea was to offer a UI to people whereby clicking on things would automatically generate bits of LUA generators - sort of non-programmatic interface for non-power users.
For efficiency (deterministic run-time), each generator takes a date and returns a date. So that it always returns a later date than it is given.
The calendar is generated on demand, so today I'm viewing August 2015. If i scroll down to September/October etc. The generators are called until they return dates later than my current view (or stop generating). I can scroll down years and years and everything works fine.
Primitives such as "nextNewMoon" etc can either be coded in LUA as part of initialisation, or added to the internal LUA run-time. The calendar algorithms are all from "Calendrical Calculations" by Nachum-Dershowitz. Although i converted them to C++ (from lisp!).
At some point i need to finished the Chinese calendar :-)
Software is never finished, you just decide it's ready to ship. :) If you'd like a smaller audience, I'd love to try it out and maybe write some of my own patterns/generators. I'm going to check out that book too.