Grammar And Calendars

This January I moved into a house. After 22 years in apartments, the change was exhilarating. There were yards to fill with flowers and strawberry plants, a library to fill with books, and so many walls to fill with art.

There were also pipes to replace, gates to replace, design decisions made by the former owners to undo. For example: the kitchen floor’s tile is natural slate. It’s impossible to know if it’s clean, and the tiles can vary wildly in height. My son trips in there at least once a week.

For a time, there was also a homeless camp lining the block across the street. Directly across from our house, a couple living in their broken-down Subaru openly did and sold drugs, chopped bikes, screamed at each other all night, shat in the bushes, etc. I’d lived in downtowns for nearly 20 years and have never felt less secure than in this house.

But that’s a story for another time. Right now I want to show you what I’ve done with my calendar. (Here’s the backstory, if you’re curious.)

So here’s the main calendar file.

# File: ~/.calendar/calendar
# These are various specialized files.
#include <birthdays>
#include <anniversaries>
#include <events>
#include <tasks>
# You can define custom functions for tricky situations.
#require <custom-functions.rb>
pay_day: Payday

As you can see, it mostly just loads other files. The only new thing here is the ability to #require files that define custom functions.

The file for one-off events looks like this.

# File: ~/.calendar/events
2023-09-18: Your brother leaves
2023-09-16 15:00: Neighborhood block party
2023-09-15: Your brother arrives
2023-09-15: Grand opening at The Wine Cellar

There are various ways of specifying recurring events.

# File: ~/.calendar/tasks
# First of the month.
*01: Pay mortgage, credit cards, etc.
# The 15th of every month.
*15: Pay power bill, etc.
# Diapers every Wednesday.
Wednesday: Tidee Didee
# Skip the Weekly Review as your peril.
Sunday: GTD: Weekly Review
# Trash day is every other Monday.
2023-09-04 -> 2w: Trash day

Some events recur in ways that might most clearly be described procedurally. For example, payday at my company is on the 10th and 25th of every month, unless that day falls on a weekend, in which case it’s the prior Friday.

# File: ~/.calendar/custom-events.rb
# pay_day :: Time -> Time
def pay_day(this_date)
  if ( <= 25)
    next_date = this_date
    if ( < 10)
      next_date =, this_date.month, 10)
    elsif ( < 25)
      next_date =, this_date.month, 25)
    if (next_date.sunday?)
      next_date = next_date - (86400 * 2)
    elsif (next_date.saturday?)
      next_date = next_date - 86400
    if (next_date <= this_date)
      return pay_day(this_date + 86400)
      return next_date
  if (this_date.month == 12)
    return pay_day( + 1), 1, 1))
    return pay_day(, (this_date.month + 1), 1))

Custom functions like this all serve the same purpose: receive a Time object representing a reference date, and return a Time object indicating the next occurrence of the event following that date. So writing them and testing them is simple.

# Also in file: ~/.calendar/custom-functions.rb
# [
# ].each do |date|
#   puts "Payday for #{date} -> #{pay_day(date)}"
# end

So there’s also some new syntax for different types of recurring events (weekdays, date base + intervals). But the really fun parts of this upgrade were internal.

The previous version of the script relied on regular expressions for parsing the calendar files. Which was mostly fine, but presented some pain. Specifically, using the regular expressions both for reading the file and making the objects processed by the script. There are a lot of valid patterns, so of course I wanted to name and reuse them. Which of course led to the idea of a grammar. So of course I had to write a parsing expression grammar library. It’s very much inspired by Janet’s, and it’s buggy and lacks a lot of very important features. But it’s working, at least roughly, and was a ton of fun to write. If you’d like to help improve it, get it touch.

And now enjoy some Joan Of Arc.