A replacement for the venerable `calendar` program
cc501dd5 — Richard Mavis 6 months ago
Rename the readme.
9b8e2829 — Richard Mavis 6 months ago
Start adding the readme.
e4c44954 — Richard Mavis 6 months ago
Add some example files.


browse  log 



You can also use your local clone with git send-email.


#Intro with Examples

Use calendar.rb to keep current with the birthdays, anniversaries, events, weekly reminders, and the like, that matter to you. It's easy.

# File: ~/.calendar/calendar
# Include other files like this:
#include <birthdays>
#include <anniversaries>
#include <events>
#include <tasks>
# You can define custom functions for tricky situations,
# and load those like this:
#require <custom-functions.rb>
pay_day: Payday

Add birthdays like this:

# File: ~/.calendar/birthdays
# The syntax here is:
# DD/MM: Event description
01/10: Donald Knuth (1938)
04/26: Hal Ableson (1947)
06/08: Frank Lloyd Wright (1867)

The file for one-off events looks like this:

# File: ~/.calendar/events
# The syntax here is:
# YYYY-MM-DD: Event description
2023-09-18: Your family arrives
# Times are optional.
2023-09-16 15:00: Neighborhood block party
# Descriptions can span multiple lines with leading whitepace.
2023-09-15: Grand opening party
    at The Wine Cellar

There are various ways of specifying recurring events:

# File: ~/.calendar/tasks
# The syntax for these is:
# *DD: Description
*01: First of the month
# MM*: Description
01*: Every day of January
# {weekday}: Description
Sunday: GTD Weekly Review
# YYYY-MM-DD -> [0-9][dwy]
2023-09-04 -> 2w: From 2023-09-04, repeat every two weeks

You can use custom functions to specify more complex recurrence rules. 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 (this_date.day <= 25)
    next_date = this_date
    if (date.day < 10)
      next_date = Time.new(this_date.year, this_date.month, 10)
    elsif (this_date.day < 25)
      next_date = Time.new(this_date.year, 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(Time.new((this_date.year + 1), 1, 1))
    return pay_day(Time.new(this_date.year, (this_date.month + 1), 1))

Custom functions like this all have the same signature and 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 super simple.

# Also in file: ~/.calendar/custom-functions.rb
# [
#   Time.new(2023,8,9),
#   Time.new(2023,8,10),
#   Time.new(2023,8,11),
#   Time.new(2023,8,25),
#   Time.new(2023,9,10),
#   Time.new(2023,12,30)
# ].each do |date|
#   puts "Payday for #{date} -> #{pay_day(date)}"
# end

Now, see what happened in January:

$ ./calendar.rb -s 2023-01-01 -e 2023-01-31
Jan 01: First of the month
Jan 01: Every day of January
Jan 01: GTD Weekly Review
Jan 02: Every day of January
Jan 03: Every day of January
...skipping some of those...
Jan 08: GTD Weekly Review
Jan 10: Donald Knuth (1938)
Jan 10: Every day of January
Jan 10: Payday
Jan 15: GTD Weekly Review
Jan 22: GTD Weekly Review
Jan 25: Payday
Jan 29: GTD Weekly Review
Jan 30: Every day of January
Jan 31: Every day of January

And in September:

$ ./calendar.rb -s 2023-09-01 -e 2023-09-30
Sep 01: First of the month
Sep 03: GTD Weekly Review
Sep 04: From 2023-09-04, repeat every two weeks
Sep 08: Payday
Sep 10: GTD Weekly Review
Sep 15: Grand opening party at The Wine Cellar
Sep 16 15:00: Neighborhood block party
Sep 17: GTD Weekly Review
Sep 18: Your family arrives
Sep 18: From 2023-09-04, repeat every two weeks
Sep 24: GTD Weekly Review
Sep 25: Payday