Canadian Holiday Rules for Remind

The year 2006 approaches, with a new set of statutory holidays.

The idea of having to annually dig up the list of holidays for next year, and encode them statically into a file filled me with disgust. So I saved
myself 5 minutes of work by spending 30 minutes piecing together some remind
rules for calculating Canadian Statutory holidays.

These rules are derived (copied, mostly) from the examples/defs.rem distributed with remind.

Some common rules

SET Sunday    0
SET Monday    1
SET Tuesday   2
SET Wednesday 3
SET Thursday  4
SET Friday    5
SET Saturday  6

SET Sun 0
SET Mon 1
SET Tue 2
SET Wed 3
SET Thu 4
SET Fri 5
SET Sat 6

SET Jan 1
SET Feb 2
SET Mar 3
SET Apr 4
SET May 5
SET Jun 6
SET Jul 7
SET Aug 8
SET Sep 9
SET Oct 10
SET Nov 11
SET Dec 12

SET January   1
SET February  2
SET March     3
SET April     4
SET May       5
SET June      6
SET July      7
SET August    8
SET September 9
SET October   10
SET November  11
SET December  12

SET  Week_1              1
SET  Week_2              8
SET  Week_3             15
SET  Week_4             22

FSET _last(mo)          "1 " + MON((mo%12)+1)+" --7"
FSET _trig()            TRIGGER(TRIGDATE())
FSET _trig2(days)       TRIGGER(TRIGDATE()+days)
FSET _back(days)        TRIGGER(TODAY()-days)

The Holidays

# Federal Holidays
#      http://www.pch.gc.ca/progs/cpsc-ccsp/jfa-ha/index_e.cfm

SET easter EASTERDATE(YEAR(TODAY()))
REM  [TRIGGER(easter-46)] MSG Ash Wednesday
REM  [TRIGGER(easter-7)]  MSG Palm Sunday
OMIT [TRIGGER(easter-2)]  MSG Good Friday
OMIT [TRIGGER(easter)]    MSG Easter Sunday
OMIT [TRIGGER(easter+1)]  MSG Easter Monday
REM  [TRIGGER(easter+39)] MSG Ascension Day
REM  [TRIGGER(easter+49)] MSG Pentecost

OMIT     Jan  1         MSG New Year's Day
REM      Feb 15         MSG National Flag of Canada Day
REM      Mar 17         MSG St. Patrick's Day
REM  Mon May 18         SCANFROM [_back(7)] SATISFY 1
		        OMIT [_trig()]    MSG Victoria Day
			# Victoria Day is the Monday Preceeding May 25
REM      Jul  1		SCANFROM [_back(7)] SATISFY 1
			IF WKDAYNUM(TRIGDATE()) == Sun
			    OMIT [_trig2(+1)] MSG Canada day
			ELSE
			    OMIT [_trig()]    MSG Canada day
			ENDIF
			# Jul 2 is Canada DAY when Jul 1 is a Sunday 
REM  Mon Sep [Week_1]   SCANFROM [_back(7)] SATISFY 1
                        OMIT [_trig()] MSG Labor Day
REM      Nov 11         MSG Remembrance Day
REM  Mon Oct [Week_2]   SCANFROM [_back(7)] SATISFY 1
                        OMIT [_trig()] MSG Thanksgiving Day
OMIT     Dec 25         MSG Christmas Day
OMIT     Dec 26         MSG Boxing Day

# Provincial Holidays
#      http://www.pch.gc.ca/progs/cpsc-ccsp/jfa-ha/prov_e.cfm

REM  Mon Feb [Week_3]   MSG Family Day (Alberta)
#REM      Jun 24         MSG National Day (Quebec)
REM  Mon Aug [Week_1]   MSG BC Day
REM  Mon Aug [Week_1]   MSG New Brunswick Day
REM  Mon Aug [Week_1]   MSG Heritage Day (Alberta)
REM  Mon Aug [Week_1]   MSG Civic Holida (Manitoba,Saskatchewan,Ontario)

# Misc
REM      Feb  2         MSG Ground Hog Day
REM      Feb 14         MSG Valentine's Day
REM      Apr  1         MSG April Fool's Day
REM  Sun May [Week_2]   MSG Mother's Day
REM  Sun Jun [Week_3]   MSG Father's Day
REM      Oct 31         MSG Halloween

# Daylight Savings time.
REM  Sun Apr  1         MSG Daylight Savings Time starts (loose an hour)
REM  Sun [_last(Oct)]   MSG Daylight Savings Time over (gain an hour)

jack | posted Mon Dec 12 18:41:41 2005 | #
category: projects/calendar

Calendar Reminders for my z600

The remind program doesn't actually put reminders into the calendars it renders (for good reason), o had to use part of the MSG. I could have used a
SPECIAL ALARM, but then I only get the date.

For example, I use this:

REM Monday 2005 AT 09:00 DURATION 1:00 MSG +90 Weekly meeting

To provide a 90 minute reminder of the event. This meant that my parsing of the output of remind had to get smarter. So I rewrote it in perl:

<font color="#8080ff"><b>#!/usr/bin/env perl</b></font>
<font color="#ffff00"><b>use warnings</b></font>;
<font color="#ffff00"><b>use strict</b></font>;
<font color="#ffff00"><b>use </b></font>Date::Parse;

<font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$CALENDAR</b></font>=<font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>./calendar</b></font><font color="#ff40ff"><b>&quot;</b></font>;
<font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$WEEKS</b></font>=<font color="#ff40ff"><b>2</b></font>;

<font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>BEGIN:VCALENDAR</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
<font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>VERSION:1.0</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;

<font color="#ffff00"><b>open</b></font>(<font color="#00ffff"><b>CAL</b></font>, <font color="#ff40ff"><b>&#39;</b></font><font color="#ff40ff"><b>-|</b></font><font color="#ff40ff"><b>&#39;</b></font>, <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>remind -s+${WEEKS} -b1 ${CALENDAR}</b></font><font color="#ff40ff"><b>&quot;</b></font>)
    <font color="#ffff00"><b>or</b></font> <font color="#ffff00"><b>die</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>can&#39;t start remind: </b></font><font color="#00ffff"><b>$!</b></font><font color="#ff40ff"><b>&quot;</b></font>;
<font color="#ffff00"><b>while</b></font> (<font color="#00ffff"><b>&lt;CAL&gt;</b></font>) {

    <font color="#ffff00"><b>chomp</b></font>;
    <font color="#ffff00"><b>my</b></font> (<font color="#00ffff"><b>$date</b></font>, <font color="#00ffff"><b>$x</b></font>, <font color="#00ffff"><b>$y</b></font>, <font color="#00ffff"><b>$duration</b></font>, <font color="#00ffff"><b>$z</b></font>, <font color="#00ffff"><b>$msg</b></font>)
        = <font color="#ffff00"><b>split</b></font>( <font color="#ffff00"><b>/</b></font><font color="#ff6060"><b>\s</b></font><font color="#ffff00"><b>/</b></font>, <font color="#00ffff"><b>$_</b></font>, <font color="#ff40ff"><b>6</b></font>);
    <font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>@msg</b></font>=<font color="#ffff00"><b>split</b></font>(<font color="#ffff00"><b>/</b></font><font color="#ff6060"><b>\s</b></font><font color="#ffff00"><b>/</b></font>, <font color="#00ffff"><b>$msg</b></font>);
    <font color="#ffff00"><b>my</b></font> (<font color="#00ffff"><b>$start</b></font>,<font color="#00ffff"><b>$alarm</b></font>);

    <font color="#ffff00"><b>next</b></font> <font color="#ffff00"><b>if</b></font> ( <font color="#00ffff"><b>$date</b></font> =~ <font color="#ffff00"><b>m/</b></font><font color="#ff40ff"><b>^#</b></font><font color="#ffff00"><b>/</b></font> ) ;

    <font color="#ffff00"><b>if</b></font> ( <font color="#00ffff"><b>$duration</b></font> <font color="#ffff00"><b>eq</b></font> <font color="#ff40ff"><b>&#39;</b></font><font color="#ff40ff"><b>*</b></font><font color="#ff40ff"><b>&#39;</b></font> )  { <font color="#00ffff"><b>$duration</b></font>=<font color="#ff40ff"><b>60</b></font> ; }
    <font color="#ffff00"><b>if</b></font> ( <font color="#00ffff"><b>$z</b></font> <font color="#ffff00"><b>eq</b></font> <font color="#ff40ff"><b>&#39;</b></font><font color="#ff40ff"><b>*</b></font><font color="#ff40ff"><b>&#39;</b></font> ) {
        <font color="#00ffff"><b>$start</b></font>=<font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>00:00</b></font><font color="#ff40ff"><b>&quot;</b></font>;
        <font color="#00ffff"><b>$duration</b></font>=<font color="#ff40ff"><b>1439</b></font>;
        <font color="#00ffff"><b>$alarm</b></font>=<font color="#ff40ff"><b>0</b></font>;
    } <font color="#ffff00"><b>else</b></font> {
        <font color="#00ffff"><b>$start</b></font>=<font color="#ffff00"><b>shift</b></font> <font color="#00ffff"><b>@msg</b></font>;
        <font color="#ffff00"><b>if</b></font> ( <font color="#00ffff"><b>$msg</b></font>[<font color="#ff40ff"><b>0</b></font>] =~<font color="#ffff00"><b> /</b></font><font color="#ff6060"><b>\+</b></font><font color="#ff6060"><b>.*</b></font><font color="#ffff00"><b>/</b></font> ) {
            <font color="#00ffff"><b>$alarm</b></font> = <font color="#ffff00"><b>shift</b></font> <font color="#00ffff"><b>@msg</b></font>;
            <font color="#00ffff"><b>$alarm</b></font> =~ <font color="#ffff00"><b>s/</b></font><font color="#ff6060"><b>\+</b></font><font color="#ffff00"><b>//</b></font>
        } <font color="#ffff00"><b>else</b></font> {
            <font color="#00ffff"><b>$alarm</b></font> = <font color="#ff40ff"><b>0</b></font>
        }
        <font color="#00ffff"><b>$msg</b></font>=<font color="#ffff00"><b>join</b></font>(<font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b> </b></font><font color="#ff40ff"><b>&quot;</b></font>,<font color="#00ffff"><b>@msg</b></font>);
    }

    <font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$t</b></font>=str2time(<font color="#ff40ff"><b>&quot;</b></font><font color="#00ffff"><b>$date</b></font><font color="#ff40ff"><b> </b></font><font color="#00ffff"><b>$start</b></font><font color="#ff40ff"><b>&quot;</b></font>);

    <font color="#ffff00"><b>die</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>unable to parse </b></font><font color="#00ffff"><b>$date</b></font><font color="#ff40ff"><b> </b></font><font color="#00ffff"><b>$start</b></font><font color="#ff40ff"><b>&quot;</b></font> <font color="#ffff00"><b>if</b></font> <font color="#ffff00"><b>not</b></font> <font color="#ffff00"><b>defined</b></font> <font color="#00ffff"><b>$t</b></font>;

    <font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$d</b></font>=<font color="#00ffff"><b>$duration</b></font>*<font color="#ff40ff"><b>60</b></font>;
    <font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$a</b></font>=<font color="#00ffff"><b>$alarm</b></font>*<font color="#ff40ff"><b>60</b></font>;

    <font color="#ffff00"><b>my</b></font> (<font color="#00ffff"><b>$sec</b></font>,<font color="#00ffff"><b>$min</b></font>,<font color="#00ffff"><b>$hour</b></font>,<font color="#00ffff"><b>$mday</b></font>,<font color="#00ffff"><b>$mon</b></font>,<font color="#00ffff"><b>$year</b></font>) = <font color="#ffff00"><b>localtime</b></font>(<font color="#00ffff"><b>$t</b></font>);
    <font color="#00ffff"><b>$year</b></font> += <font color="#ff40ff"><b>1900</b></font>; <font color="#00ffff"><b>$mon</b></font>++;
    <font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$s_string</b></font>=<font color="#ffff00"><b>sprintf</b></font>(<font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>%04d%02d%02dT%02d%02d%02d</b></font><font color="#ff40ff"><b>&quot;</b></font>,
            <font color="#00ffff"><b>$year</b></font>, <font color="#00ffff"><b>$mon</b></font>, <font color="#00ffff"><b>$mday</b></font>, <font color="#00ffff"><b>$hour</b></font>, <font color="#00ffff"><b>$min</b></font>, <font color="#00ffff"><b>$sec</b></font>);

    (<font color="#00ffff"><b>$sec</b></font>,<font color="#00ffff"><b>$min</b></font>,<font color="#00ffff"><b>$hour</b></font>,<font color="#00ffff"><b>$mday</b></font>,<font color="#00ffff"><b>$mon</b></font>,<font color="#00ffff"><b>$year</b></font>) = <font color="#ffff00"><b>localtime</b></font>(<font color="#00ffff"><b>$t</b></font>+<font color="#00ffff"><b>$d</b></font>);
    <font color="#00ffff"><b>$year</b></font> += <font color="#ff40ff"><b>1900</b></font>; <font color="#00ffff"><b>$mon</b></font>++;
    <font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$e_string</b></font>=<font color="#ffff00"><b>sprintf</b></font>(<font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>%04d%02d%02dT%02d%02d%02d</b></font><font color="#ff40ff"><b>&quot;</b></font>,
            <font color="#00ffff"><b>$year</b></font>, <font color="#00ffff"><b>$mon</b></font>, <font color="#00ffff"><b>$mday</b></font>, <font color="#00ffff"><b>$hour</b></font>, <font color="#00ffff"><b>$min</b></font>, <font color="#00ffff"><b>$sec</b></font>);

    (<font color="#00ffff"><b>$sec</b></font>,<font color="#00ffff"><b>$min</b></font>,<font color="#00ffff"><b>$hour</b></font>,<font color="#00ffff"><b>$mday</b></font>,<font color="#00ffff"><b>$mon</b></font>,<font color="#00ffff"><b>$year</b></font>) = <font color="#ffff00"><b>localtime</b></font>(<font color="#00ffff"><b>$t</b></font>-<font color="#00ffff"><b>$a</b></font>);
    <font color="#00ffff"><b>$year</b></font> += <font color="#ff40ff"><b>1900</b></font>; <font color="#00ffff"><b>$mon</b></font>++;
    <font color="#ffff00"><b>my</b></font> <font color="#00ffff"><b>$a_string</b></font>=<font color="#ffff00"><b>sprintf</b></font>(<font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>%04d%02d%02dT%02d%02d%02d</b></font><font color="#ff40ff"><b>&quot;</b></font>,
            <font color="#00ffff"><b>$year</b></font>, <font color="#00ffff"><b>$mon</b></font>, <font color="#00ffff"><b>$mday</b></font>, <font color="#00ffff"><b>$hour</b></font>, <font color="#00ffff"><b>$min</b></font>, <font color="#00ffff"><b>$sec</b></font>);

    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>BEGIN:VEVENT</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>CATEGORIES:MEETING</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>SUMMARY:</b></font><font color="#00ffff"><b>$msg</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>DTSTART:</b></font><font color="#00ffff"><b>$s_string</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>DTEND:</b></font><font color="#00ffff"><b>$e_string</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>AALARM:</b></font><font color="#00ffff"><b>$a_string</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font> <font color="#ffff00"><b>if</b></font> ( <font color="#00ffff"><b>$alarm</b></font> != <font color="#ff40ff"><b>0</b></font> ) ;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>END:VEVENT</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
    <font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
}
<font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;
<font color="#ffff00"><b>print</b></font> <font color="#ff40ff"><b>&quot;</b></font><font color="#ff40ff"><b>END:VCALENDAR</b></font><font color="#ff6060"><b>\n</b></font><font color="#ff40ff"><b>&quot;</b></font>;

jack | posted Thu Sep 22 23:09:16 2005 | #
category: projects/calendar

Finding calendar software that I like. AKA, writing it.

Tam and I crossed the line sometime this spring where we could no longer keep all of our commitments in our heads. Equally likely for this is the increasing scope and complexity of our commitments, or our decreasing mental facilities.

First off, we decided to use remind. It seems to have the most descriptive frontend for making entries, and many flexible backends for displaying calendars as postscript, html, or even vcard. We created a different calender file for different categories, and a top level calendar that includes them all.

Since remind uses flat text files, we decided to use monotone. This allows us to do all of the usual good version control stuff (ancestry, logs, branching and merging), as well has having a distributed repository (useful for moving files around).

A simple script, run from cron, maintains an online, HTML version of the calendar:

#!/bin/sh

PATH=/bin:/usr/bin:/opt/bin
IN=....
OUT=....

cd $IN && monotone update

year=$(date +%Y)
for i in $(seq 12)
do
	month=$(date +%B --date=$i/01/$year)
	remind -p calendar $month 1 $year | rem2html  > $OUT/${i}.html
done

I have a cell phone that has a calendar application, and a bluetooth interface, so I thought I'd give having the calendars on my phone a swing.

This turns out to be easy possible using gammu. Gammu can interpret and load vcs (I'm sure there's actually a standard for that) onto my Sony Ericsson z600 over bluetooth. This requires getting bluetooth running under linux, and getting gammu working over bluetooth.

Bluetooth has been around a while now, so there is a fair amount of documentation on how to get it working. It basically boils down to:

Once you've established that the phone and the linux box can talk (using l2ping), then setting up gammu is pretty easy. Here is my gammurc:

[gammu]
port = BT ADDRESS
connection = blueat
logformat = textall
startinfo = yes

You can test out if gammu is working by simply doing a gammu --identify. After doing a bit of reverse engineering, and some digging around on the web about how to load calendars onto phones using gammu, I patched together a script to create the vcs files from remind.

#!/bin/bash
# XXX: any event without a duration gets assigned a duration of 60 minutes
# XXX: any all-day event gets assigned a start time of midnight and a duration
# of 23:59
# XXX: this most certainly does not deal with events that wrap over day
# boundaries correctly. 

CAL=./calendar
WEEKS=2

remind -s+${WEEKS} -b2 ${CAL} | \
    (	echo BEGIN:VCALENDAR
	echo VERSION:1.0
 
	while read date x y duration start msg 
	do 
	    [ $start = '*' ] && { start=0000 ; duration=1439 ; } 
	    [ $duration = '*' ] && duration=60
	    date=${date//\//}

	    sh=$(printf %.2d $((start/60)))
	    sm=$(printf %.2d $((start%60)))
	    eh=$(printf %.2d $(( (start+duration) / 60 )))
	    em=$(printf %.2d $(( (start+duration) % 60 )))
			# the printfs are needed for zero padding

	    echo
	    echo BEGIN:VEVENT
	    echo CATEGORIES:MEETING
	    echo SUMMARY:$msg
	    echo DTSTART:${date}T${sh}${sm}00
	    echo DTEND:${date}T${eh}${em}00
	    echo END:VEVENT
	    echo 

	done 
	echo END:VCALENDAR
    )

Since bluetooth has suprising range, I also wrote a script to be run from cron that will backup the phone, and then load the calendars onto the phone once a day. I usually have my phone somewhere around me, and I'm usually sleeping or at least at home at 0400h, so that's when the cron jobs run.

#!/bin/sh

PATH=/opt/bin
IN=...
date=$(date +%y%m%d)
BACKUP=/usr/home/jack/var/phone/z600.backup.${date}

tmp=/usr/home/jack/tmp/sync.${$}.vcs
	# gammu uses filename extensions to recognize files. 

gammu --backup $BACKUP -yes

cd $IN && monotone sync && monotone update

./scripts/make_vcs.sh > $tmp
echo ALL | gammu --restore  $tmp
rm $tmp

Only 2 weeks of calendar are loaded onto the phone, mainly to make for reasonable transfer times.

There are still a few things left to do. Having the phone issue reminders would be cool. As would having email reminders.


jack | posted Wed Jul 20 11:56:50 2005 | #
category: projects/calendar

A weblog by Jack Cummings