Skip to main content

How I use remind(1)

Overview

The remind calendar utility offers an amazing degree of power for doing calendar-related scheduling unmatched by any other calendar program I've used. If you haven't encountered it before, this presentation (PDF) gives a good overview of some of the features, as do Linux Journal and 43 Folders. In this post, I'll walk through how I use both the basic and advanced features.

This post makes a couple assumptions about you as the reader:

General working knowledge of the CLI
While most of the shell commands should work in the majority of Unix-like shells (/bin/sh, bash, ksh, zsh, or csh/tcsh) some have slight syntax differences. Such differences might include the for loop syntax (csh/tcsh has a different syntax than the sh style loops I use), and what variables the shell provides (such as $COLUMNS that bash & ksh provide, which you might have to define or hard-code before using in other shells). If you build from source into a local ~/bin/ directory, you will want to know about modifying your $PATH as well. You don't need a Ph.D. in using the command-line to do any of these, but I do assume basic directory creation & navigation skills using mkdir & cd, as well as moving/renaming and copying with mv/cp and output redirection. I also have a section on using git or rcs to version your reminders but that section aims to help you integrate whatever version-control software you already use rather than try to teach either as a requirement.
General competency with a text editor
Reminder files are pure plain-text. So you can edit them with your favorite $EDITOR, whether nano, emacs/mg, vi/vim/neovim, ed, cat, a magnetized needle and a steady hand, or butterflies. But this guide assumes that you can edit text files and save them.
You have remind already installed or can install it
If you administer your personal system, you should have root/sudo/doas access to install remind from a binary package (available in most repositories) or possibly install a C compiler like gcc/clang to install from source. If you do not administer your system, you can either talk to your system administrator and ask them to install remind (try offering baked goods); or you can install from source, specifying a user-local directory such as $HOME/bin/ (as long as you include that in your $PATH, you have a C compiler on the system, and your administrator didn't mark your $HOME/ directory's mount-point as noexec).

Installing

As a package

Initially released in 1990 via USENET on comp.sources.misc, nearly every package repository should provide remind, making it as easy to install as any other package:

pkg install remind
FreeBSD
doas pkg_add remind
OpenBSD
sudo apt-get install remind
Debian/Ubuntu based
On some systems, this has no dependencies; on other systems, this brings in some GUI components (Tcl/tk) because the package might also include the optional graphical tkRemind component.

From source

Alternatively, download the source directly from the latest tarball (and hopefully verify it using gpg with the PGP key which should have the fingerprint 738E:4D95:4052:902C:147D:07B2:685A:5A5E:511D:30E2)

VER=0.3.03.00
wget https://dianne.skoll.ca/projects/remind/download/remind-${VER}.tar.gz{,.sig}
gpg --verify remind-${VER}.tar.gz.sig  xvfz remind-${VER}.tar.gz
tar xvfz remind-${VER}.tar.gz
mv remind-${VER} remind
or if you want to download from git:
git clone https://dianne.skoll.ca/projects/remind/git/Remind.git remind
and then compile it yourself:
cd remind
./configure
make
sudo make install
If you prefer not to make install you can move/copy the resulting binaries from remind/src/{remind,rem2ps} to a ~/bin/ directory (in your $PATH), create a link from remind to rem, and put the man pages from remind/man/*.1 in your $MANPATH.

Getting started

Creating your first remind file

By default, invoking rem expects data in ~/.reminders and you can just dump all your reminders in there (though remind also lets you split reminders up into individual files). When invoked as remind rather than rem you must specify the reminder file directly:

remind path/to/reminders.rem

The most basic reminder consists of REM followed by a date of some sort, followed by MSG and the text of the reminder. A few things to keep in mind

  • remind ignores blank lines. and treats lines beginning with a # as comments
  • to continue a reminder on the next line, end the previous line with a backslash ("\")
  • If you only include part of the date remind assumes the missing parts repeat.
  • You can specify either the numeric day of the month or the day of the week (in which case it will repeat on that day of the week).
  • For the month and week-day, remind does not distinguish between upper/lower-case and thus treats "Jan", "jan", "JaN", "jAn", and "JAN" all as the same.
  • Additionally, while remind requires the first three characters of months ("Jan") and weekday names ("Wed"), you may also fully spell out the month ("January") or weekday name ("Wednesday") if that makes it easier for you to read.
  • If you provide more than one weekday, remind will match any of them.
  • Alternatively, you can write the date in YYYY-MM-DD format
  • If you omit all of the values, omit the REM too
  • For example if you use your $EDITOR to create this ~/.reminders file
    # this is a comment
    # these and the next (blank) line are ignored
    
    REM Jan 1 2020 MSG The first day of the 2020
    REM 2020-1-1 MSG Also the first day of the 2020
    REM Jan 1 MSG The first day of the year
    REM 1 MSG The first day of every month
    REM Jan MSG Every day in each January
    REM January MSG Also every day in each January
    REM Jan 1 MSG Every January 1st
    REM Jan 2020 MSG Every day in January 2020
    REM Wed MSG Every Wednesday
    REM Wednesday MSG Also every Wednesday
    REM Wed Jan MSG Every Wednesday in every January
    REM Wed Jan 2020 MSG Every Wednesday in January 2020
    REM Mon Wed Fri MSG Every Monday, Wednesday, and Friday
    REM Sat Sun Jan 2020 MSG Every weekend in January of 2020
    MSG With no REM, this happens every single day
    REM Jan 1 2020 \
      MSG This line has a continuation
    
    ~/.reminders
    To see (most of) these reminders, use either of these commands
    remind ~/.reminders 2020-01-01
    rem 2020-01-01
    
    If you happen to run the command on January 1st, 2020, you can omit the date because remind defaults to the current date. I most frequently invoke just
    rem
    
    to get today's events.

    By specifying a day of the week and a numeric date, remind will find the next date-match and will then scan forward until it finds the matching day of the week.

    REM Mon 1 MSG First Monday of the month
    REM Mon 1 Jan MSG First Monday in January
    REM Mon 1 Jan 2020 MSG First Monday in January, 2020
    REM Mon 8 MSG Second Monday of the month
    REM Mon 15 MSG Third Monday of the month
    REM Mon 22 MSG Fourth Monday of the month
    REM Mon 29 MSG Infrequent Fifth Monday of the month
    REM Sat Sun 15 MSG Third Saturday or Sunday of the month
    
    ~/.reminders
    Beware with that "Mon 29" because the "Monday on-or-after the 29th" might fall in the following month. When we discuss the SATISFY clause later, we can limit this so it only notifies on the fifth day in the same month.

    Also note that this can have interesting side-effects such as trying to find Labor Day.

    Similarly, our city offers a program for recycling items that we shouldn't put in our recycle bin (hazardous household waste, electronics, etc). These events occur quarterly on the Saturday after the first Friday of the month. We can express the date as

    REM Sat 2 MSG Recycling center drop-off day
    
    ~/.reminders
    to ensure that it falls on the Saturday after the first Friday in the month. We can then add the SATISFY (discussed later) to ensure it only falls on one of the quarterly dates:
    REM Sat 2 SATISFY [$Tm % 3 == 2] \
      MSG Recycling center drop-off day
    
    ~/.reminders

    Adding time information

    To indicate the time and length of an event use AT (in military/24-hr time format; I wish the parser allowed for 12-hr AM/PM notation, but alas) and optional DURATION (in hours and minutes). Here I have a doctor's appointment at 2:30pm but don't know how long it will run. And I go to the gym on Tuesday/Thursday at 5:00am for an hour and fifteen minutes:

    REM Jan 30 2020 AT 14:30 MSG Doctor's appointment
    REM Tue Thu AT 5:00 DURATION 1:15 MSG Gym
    
    ~/.reminders
    At the moment, adding time information doesn't net you any big gains in the agenda view, but they will appear in the week/month view and when we discuss substitution filters they let you include time information in the MSG.

    Getting advance notice

    Sometimes you want a bit of advanced warning like knowing about your doctor's appointment tomorrow. Perhaps you want a week of notice before your mother's birthday so you can get something in the mail. Or you want a 60 days of notice before you have to pay taxes so you can get all your paperwork in order. remind uses +n and ++n to give you n days of advanced warning. The single + skips over OMIT dates, while ++ ignores OMIT dates. For now, use the ++n form for simplicity.

    REM Jan 1 2020 ++1 AT 14:00 MSG Doctor's appointment
    REM May 12 ++7 MSG Mom's birthday
    REM Apr 15 ++60 MSG Taxes
    
    ~/.reminders
    remind recognizes that you likely want this advanced notice in the agenda view but smartly prevents the advanced noticed from cluttering up in the ASCII week or month calendar views (unless you explicitly request them with -pa or -sa). While less useful to get the same reminder several days out, remind lets you adjust the reminder text based on the number of days out to make these more helpful.

    Adjusting dates

    To adjust a date backwards, use -n or --n to specify the number of days. At first glance, this provides little benefit over just doing the math and hard-coding it for normal reminders:

    REM 15 --3 MSG Really the 12th
    REM 12 MSG Just use the 12th instead
    
    ~/.reminders
    However, this lets you schedule reminders relative to the last day of the month:
    REM 1 --1 MSG Last day of the month
    REM 1 --7 MSG One week left in the month
    REM 1 Wed --7 MSG Last Wednesday of the month
    REM Feb 1 Wed --7 MSG Last Wednesday in January
    
    ~/.reminders
    The first one finds the first day of the month and then goes back 1 day. The second entry goes back 7 days from the first of each month. The third entry finds the first Wednesday in the month and then goes back 7 days (to the last Wednesday of the previous month). The last entry does similarly, finding the first Wednesday in February, then going back 7 days to find the last Wednesday in January.

    As with +n vs. ++n, the single "-" jumps over OMIT dates, (so they can end up going back more than n days if they encounter an intervening holiday); while the double "--" ignores OMIT dates. These adjustments can combine

    REM 1 --1 ++3 MSG Last day of the month
    
    ~/.reminders
    This will put the event on the last day of the month but also remind you 3 days in advance that the end of the month will arrive soon.

    Repeating reminders

    As shown above, if you omit portions of a REM statement, remind treats the missing date part as a repeat. This lets you create reminders such as birthdays and anniversaries, holidays, weekly events like church, or invoicing customers on a given day of each month:

    REM Jan 13 MSG Steve's birthday
    REM Feb 18 ++7 MSG Mom & Dad's anniversary
    REM Apr 1 ++1 MSG April Fool's Day
    REM Sun AT 9:00 MSG Church
    REM 15 MSG Invoice customers
    
    ~/.reminders
    Note the "++7" style notation to get a week of notice before my parents' anniversary so I get something in the mail for them. I also get a warning one day before April Fool's Day so I don't get surprised by pranks.

    On occasion I need a reminder every N days like picking up a 90-day prescription that started on January 27th of 2019. To do this, use an asterisk followed by the number of days to repeat.

    REM Jan 27 2019 *90 MSG Pick up medication
    
    ~/.reminders
    I'll cover a more complex example of a medication schedule towards the end.

    For repeating events, remind lets you specify date ranges using FROM, UNTIL, and THROUGH keywords. For example, if your vacation starts on December 21st and repeats daily (*1) until January 1st you can use:

    REM Dec 21 2019 *1 UNTIL Jan 1 2020 MSG Vacation
    
    ~/.reminders
    The THROUGH keyword simplifies the common "*1" use case, so this works exactly the same:
    REM Dec 21 2019 THROUGH Jan 1 2020 MSG Vacation
    
    ~/.reminders
    You might also have to take a medication every other day (*2) starting on January 1st through the end of the month:
    REM Jan 1 2019 *2 UNTIL Jan 31 2020 MSG Take medication
    
    ~/.reminders
    Alternatively, you might have a Wednesday game night during the school year, or a meeting on the 12th of each month during the school year:
    REM Wed FROM Aug 21 2019 UNTIL Jun 1 2020 MSG Game Night
    REM 12 FROM Aug 21 2019 UNTIL Jun 1 2020 MSG School year meeting
    
    ~/.reminders

    Modifying the output

    Other views

    If you have no reminders for a given day, remind prints "No reminders." Otherwise, remind prints a banner of the form "Reminders for Wednesday, 1st January, 2020 (today)" followed by an agenda-view of the reminders for that day. If you want more than one day worth, specify the number of days with "*n". Note that you should quote the "*" either by escaping it with a backslash or by wrapping it in quotes. Otherwise the shell might expand it to a filename in your current directory. To show four days of reminders:

    rem \*4
    rem '*4'
    

    remind also offers an ASCII calendar view for either the month or week:

    rem -c # a month view
    rem -c2 # two month view
    rem -c 2019-8-1 # one-month-view of 2019-08-01
    rem -c+ # a week view
    rem -c+2 # two week view
    
    You can change the default width for either the week or month view by adding -w$COLUMNS
    rem -c+2 -w$COLUMNS
    As of version 3.3.0, remind automatically detects the screen width if you output to a TTY so you no longer need to specify this explicitly unless you want something other than the current screen width.

    Other message-types

    Up to this point, we've only discussed the MSG message-type. remind also provides CAL for events that should only show up in the week/month view but not the agenda view, and MSF which behaves the same as the MSG except it wraps long text in the agenda view.

    REM Jan 1 2020 CAL This only appears on the calendar view
    REM Jan 1 2020 MSF This is a very long reminder \
      and includes several lines \
      of text all with the escaping backslash \
      but when you view it \
      in the agenda view \
      it will be wrapped \
      so that it formats nicely.
    
    ~/.reminders
    (remind provides a couple other reminder-types: RUN for running an external command, SPECIAL, for conveying out-of-band information, and PS/PSFILE for passing PostScript information to a PostScript-processing back-end which I'll skip for now).

    Getting rid of blank lines

    I find it annoying to get a blank line after each reminder. You can prevent this by ending reminder text with a %

    REM Jan 30 2020 +3 MSG Doctor's appointment%
    ~/.reminders
    For simplicity, I've omitted the trailing % from all of the other examples in this post, but I put it on every single reminder.

    Selectively showing text

    You might want certain text to appear on the daily agenda calendar but you don't need that level of detail on the week/month calendar view. You can wrap the text you want on the week/month calendar view with %"%"

    REM Jan 30 2020 +3 MSF %"Doctor%"'s appointment \
      123 Main St., Anytown, NY\
      800-555-1212
    
    ~/.reminders
    With this in place, it will display the full text of the reminder (without the %" markers) on the agenda view but only show "Doctor" on the visual calendars.

    Substitution filter

    Your reminder-message can include escape sequences (beginning with a %) that adjust based on the context. So instead of a reminder that just reads "Mom's birthday" you can use

    REM May 12 ++7 MSG Mom's birthday %b
    ~/.reminders
    and remind will display "Mom's birthday in 7 days' time", …, "Mom's birthday in 2 days' time", "Mom's birthday tomorrow", and finally "Mom's birthday today". The man page for remind has a whole collection of these under the "SUBSTITUTION FILTER" section. I most frequently use

    %b
    described above, producing "in N days' time", "tomorrow", and "today"
    %l
    replaced with "on YYYY-MM-DD", "tomorrow", and "today"
    %c
    replaced with the day-of-the-week of the event such as "on Wednesday", then "tomorrow", and "today"
    %2
    replaced with the AT time in 12-hour am/pm time, e.g. "at 2:30pm"
    These combine to allow things like
    REM Jan 1 2020 +1 AT 14:30 MSG Doctor appointment %b %2
    ~/.reminders
    to produce a reminder like "Doctor appointment tomorrow at 2:30pm".

    remind prints a "banner" before each day's agenda. The default BANNER contains "Reminders for %w, %d%s %m, %y%o:" but you can use any of the substitution filter formatting sequences that fit your wants. The extra blank line after the BANNER bothers me so let's get rid of that by putting a % at the end:

    BANNER Reminders for %w, %d%s %m, %y%o:%
    ~/.reminders
    and I prefer the month presented before the day:
    BANNER Reminders for %w, %m %d%s, %y%o:%
    
    ~/.reminders

    When I use rem '*3' to get 3 days worth of reminders I dislike how it formats the dividers because they all flow together visually, making it hard for me to read. Fortunately, remind lets you use an IF/ELSE/ENDIF block to conditionally tweak this as well:

    IF defined("subsequent_iteration")
      BANNER ---------------------%_%w, %m %d%s, %y%o:%
    ELSE
      BANNER %w, %m %d%s, %y%o:%
      SET subsequent_iteration 1
      PRESERVE subsequent_iteration
    ENDIF
    ~/.reminders
    On the first pass, subsequent_iteration is not defined, so we set the default BANNER and then set/define subsequent_iteration. The PRESERVE keyword then keeps the value of subsequent_iteration defined through subsequent iterations. Now defined, remind takes the ELSE block, prefixing the banner with a row of dashes, a new-line, and then the normal banner text.

    Sorting

    By default, remind prints agenda items in the order in which they appear in the reminder files. Using the -g flag tells remind to sort the output. Following the -g comes up to four "a" or "d" characters to indicate ascending or descending order. Positionally, these indicate

  1. the trigger date of the event
  2. the trigger time of the event
  3. the priority of the event
  4. whether to sort timed events before untimed events
So rem -gaad sorts first by trigger date (ascending), then by trigger time (ascending), and finally by priority (descending), while not specifying whether timed or untimed events should come first.

Sub-BANNERs for sorting

When sorting reminders, I like to have a visual indication between where today's reminders stop and subsequent days' advanced notices so I override the special sortbanner() function. Much like the BANNER, this lets you define a separator that comes between these events. I use the $SortByDate, $SortByPrio, and $SortByTime variables to determine if any sort-order creates these logical breaks, and then use a substitution filter in my function-result:

SET banner_str "…"
IF $SortByDate + $SortByPrio + $SortByTime > 0
  BANNER %
  FSET sortbanner(x) iif(x == $U, \
    banner_str, \
    "----%B----%" \
    )
ELSE
  BANNER [banner_str]
ENDIF
~/.reminders
I use the BANNER % to suppress the regular banner, letting the sortbanner() assume those duties. Also, by capitalizing the substitution-filter (%B instead of %b) it capitalizes the resulting output.

Next instance of reminders

If you want to know the next time a reminder will occur, remind offers the "next" (-n) flag. Beware as this produces ~900 reminders for me (one for each of my 1600+ reminders yet to happen in the future), but you can grep this output or pipe it to $PAGER

rem -n |
grep Alice |
sort |
less

File management

Organizing reminders

While remind will happily let you dump all your reminders in ~/.reminders, it also provides functionality to split and combine files using the INCLUDE directive. I like to organize reminders into files based on reminder-type, so I created a directory to house them all

cd
# don't tromp over existing reminders if we have them
mv .reminders original_reminders.rem
mkdir -p ~/.config/remind
echo 'SET remind_dir getenv("HOME") + "/.config/remind/"' > \
~/.config/remind/reminders.rem
and link the ~/.reminders to point to my main reminder file:
ln -s .config/remind/reminders.rem ~/.reminders
cd ~/.config/remind
This uses a little remind magic (see the section on expressions below) to dynamically find your reminder directory for later use in the INCLUDE statements. This lets me move or rsync my reminders to another machine under a different $HOME and everything will continue to work.

Alternatively, you can create a link to the containing folder instead of a reminder file full of INCLUDE lines:

ln -s .config/remind/ ~/.reminders
and remind will process all of the *.rem files in the target folder in glob order. If you want a particular order, you can name the files with a prefix like 010-birthdays.rem, 015-anniversaries.rem, 020-work.rem, etc.

Initial file types

I start by creating a helpers.rem file to store a variety of helper functions and constants.

touch ~/.config/remind/helpers.rem
I then create files for various event types and INCLUDE them into the master reminders.rem file:
for f in ushol birthdays anniversaries work bills 
do
echo "INCLUDE [filedir()]/helpers.rem" > ~/.config/remind/${f}.rem
echo "INCLUDE [remind_dir]/${f}.rem" >> ~/.config/remind/reminders.rem
done
While I've simplified my list of calendars above for the sake of brevity, I have nearly 30 different calendar files to keep things organized:

  • an individual file for myself
  • one for my wife
  • one for each kid
  • one for each kid's school calendar
  • birthdays
  • anniversaries
  • family events
  • household chores
  • finances
  • church events
  • one for each place I volunteer
  • US holidays
  • events at our local library
  • work
as well as a couple odd-balls like a man-page-per-day and a schedule of when the Cowboys play (I don't care much about football, but Dunkin offers a free medium coffee on the day following a Cowboys win so my calendar consists of reminders for the day following a game to check the score). I also have a couple calendars consisting only of INCLUDE statements to combine other calendars:
  • one for the kids (combining both kids' personal calendars and their school calendars)
  • one for the whole family (combining my calendar and my wife's calendar with the combined kids' calendar)
  • and of course the master reminders.rem that contains everything

At first glance, splitting out each file doesn't seem to offer any advantages. However, this lets you do things like show a calendar of birthdays only without all the other reminders, or show just a school calendar.

remind -c ~/.config/remind/birthdays.rem

The majority of my ushol.rem file comes from the examples on the remind website. It creates a bunch of OMIT entries useful for any reminders that get bumped because of US holidays.

Advanced features

While all that is useful, other calendar programs like Google Calendar, Microsoft Outlook, or even the venerable calendar(1) CLI utility can do many of those things. However remind really shines when you want to do things that these others simply can't.

Expression evaluation

For even more power & flexibility, remind lets you define variables & functions as well as evaluate expressions (documented in the man-pages as "EXPRESSION PASTING").

To set a variable, use SET and remind will evaluate the right-hand side (the "4 - 3" in the example), assigning the result to the variable on the left-hand side (the myvar in the example):

SET myvar 4 - 3
~/.reminders
To create a function, use FSET. The example below creates a function named add_three that takes one parameter named x. When called, it adds three to the number passed to it and returns the result:
FSET add_three(x) 3 + x
~/.reminders
To evaluate an expression put it in square brackets (all of these produce the same output)
REM Jan 1 2020 +1 MSG 4 - 3 = [4 - 3]
REM Jan 1 2020 +1 MSG 4 - 3 = [myvar]
REM Jan 1 2020 +1 MSG 4 - 3 = [add_three(-2)]
REM Jan [4 - 3] 2020 +1 MSG 4 - 3 = 1
REM Jan 1 [add_three(2017)] +1 MSG 4 - 3 = 1
REM Jan 1 2020 +[myvar] MSG 4 - 3 = 1
~/.reminders
Note that you can use an expression anywhere that a number, string, or date/time can appear. This lets you do things like
SET VacationStart date(2020, 7, 15)
SET VacationEnd VacationStart + 10
REM [VacationStart - 60] OMIT Sat Sun BEFORE \
  MSG Submit time-off paperwork
REM [VacationStart - 50] MSG Buy tickets
REM [VacationStart - 3] OMIT Sun BEFORE MSG Stop mail delivery
REM [VacationStart - 1] MSG Pack bags
REM [VacationStart] THROUGH [VacationEnd] MSG Vacation
REM [VacationEnd + 1] OMIT Sat Sun AFTER MSG Back to work
~/.reminders
Which lets you easily change the vacation start-date and the length of the vacation, automatically adjusting the items relative to those dates (even moving relevant messages off of weekends thanks to OMIT functionality).

Using functions in reminder text

If I know the birthday or anniversary of a friend, I like to include that in the reminder text. So in my helpers.rem I have


FSET born(y) "(" + ($Uy-y) + "yo)"
FSET married(y) (ord($Uy-y)) + " anniversary"

helpers.rem
This lets me cleanly annotate things like

REM Apr 1 +7 MSG %"Bob & Alice [married(1999)]%" %b

REM Jan 14 +7 MSG %"Bob [born(1980)]%" %b

~/.reminders
which produces output like
Bob & Alice 20th anniversary tomorrow

Bob (40yo) in 3 days' time
(ignore the date discrepancies)

Days until an annual event

As an example of function use, say you want to determine the number of days until an annual event. If it has already passed this year, you want to refer to the event next year; if it hasn't already passed this year, you want to refer to the event this year. A function wraps up this functionality nicely:

FSET DaysUntil(dt, m, d) iif(\
    date(year(dt), m, d) > dt,\
    date(year(dt)+1, m, d),\
    date(year(dt), m, d)\
    ) - dt
FSET DaysTillStr(dt, m, d) \
    DaysUntil(dt, m, d) + \
    plural(DaysUntil(dt, m, d), " day")
FSET ChristmasDays(dt) DaysTillStr(dt, 12, 25)
MSG %"[ChristmasDays($T)] until Christmas%"%
~/.reminders

When run, this will produce output

rem 2020-12-1
Reminders for Tuesday, 1st December, 2020:

24 days until Christmas
rem 2020-12-28
Reminders for Monday, 28th December, 2020:

362 days until Christmas

Specifying the priority of a reminder

While I don't currently use task priority much, remind allows you to provide a PRIORITY modifier (an integer between 0 and 9999) for an event. If unspecified, remind sets the priority to $DefaultPrio (5000 by default). As far as I can tell, there's no inherent meaning to the priority, so if it makes more sense to you that 9999 means "high priority" and 0 means "low priority", then use them as such; if it makes more sense to you that 0 means "high priority" and 9999 means "low priority", use that. I tend to declare a couple constants using SET and then use those, making it easier to reverse the meaning should I need to.

SET PRIORITY_HIGH 9999
SET PRIORITY_LOW 0
REM Jan 1 2020 \
    AT 10:00 DURATION 1:00 \
    PRIORITY [PRIORITY_HIGH] \
    MSG Very important meeting
REM Jan 1 2020 \
    AT 14:00 DURATION 1:00 \
    PRIORITY [PRIORITY_LOW] \
    MSG Unimportant thing
~/.reminders

Adding a prefix/suffix to agenda items

remind provides a pair of special functions, msgprefix(p) and msgsuffix(p), that emit a prefix & suffix around each agenda item. These functions take an explicit priority parameter (although you can reference other variables and functions) allowing you to do things like

# Annotate high-vs-low priority
FSET is_low_prio(p) p < $DefaultPrio
FSET is_high_prio(p) p > $DefaultPrio

FSET msgprefix(p) \
    iif(is_high_prio(p), "+ ", \
    iif(is_low_prio(p), "- ", \
    "  "))

# add some asterisks after high-priority tasks
FSET msgsuffix(p) \
    iif(is_high_prio(p), " *****", "")
~/.reminders
(assuming higher numbers mean higher priority; otherwise, adjust the is_low_prio and is_high_prio functions accordingly) or, using some of the tricks from the section on color
FSET msgprefix(p) \
    iif(is_high_prio(p), Red, \
    iif(is_low_prio(p), Gry, \
    Nrm))
FSET msgsuffix(p) Nrm
~/.reminders

Omitting and shifting dates

Some events shift around or get cancelled based on whether they fall on particular days like holidays, weekends, or even just for arbitrary reasons ("somebody else booked the conference room so we need to move it to next week"). The OMIT keyword tells remind how to identify these cases.

Omitting/shifting for a single event

You might have a project at work that runs for a couple weeks but you don't want your agenda to display it on weekends because you have work/life boundaries. Use the OMIT keyword to tell which days, and SKIP to tell remind to ignore events that fall on these days. Put them in your REM command:

REM Jan 2 2020 THROUGH Jan 15 2020 \
    OMIT Sat Sun SKIP \
    MSG Work project
~/.reminders
In another case, your employer might pay you on the 15th of each month, but if pay-day falls on a weekend, payment moves AFTER the weekend to the following Monday:
REM 15 OMIT Sat Sun AFTER MSG Payday
~/.reminders
Or you might have a bill that you need to pay by the 15th but if that falls on a weekend you must pay it BEFORE the weekend so that payment arrives on time:
REM 15 OMIT Sat Sun BEFORE MSG Pay water bill
~/.reminders

Omitting/shifting for multiple events

Certain events such as federal holidays can displace multiple reminders. Rather than try and OMIT every single holiday in every single reminder, remind provides bare OMIT entries similar to REM but they get added to a contextual omit-list. If you have important weekly meetings on Monday and Friday and an unimportant meeting on Wednesday, the following will bump the important Monday meeting back to Tuesday if Christmas fell on Monday, will bump the important Friday meeting back to Thursday if Christmas fell on a Friday, and cancel the unimportant Wednesday meeting if Christmas fell on a Wednesday,

OMIT Jul 4 MSG 4th of July
OMIT Dec 25 MSG Christmas
REM Mon AFTER MSG Important Monday Meeting
REM Wed SKIP MSG Unimportant Wednesday meeting
REM Fri BEFORE MSG Important Friday Meeting
~/.reminders
In 2015, Christmas fell on a Friday, in 2017, Christmas fell on a Monday, and in 2019, Christmas fell on a Wednesday.
rem 2015-12-24 '*2' # got moved to 24th
rem 2017-12-25 '*2' # got moved to 26th
rem 2019-12-24 '*3' # got cancelled
The first command shows that the Friday meeting got pushed back to Thursday, while the second command shows that the important Monday meeting got pushed forward to Tuesday and the unimportant Wednesday meeting simply vanished. The same thing happens around July 4th since we have multiple OMIT entries.

Omit notice and adjusting backwards

As discussed in the advanced-notice and adjusting-dates sections, remind can give you advanced notice of reminders and adjust dates backwards a certain number of days. In that section, I recommended using the double ++/-- because they ignore OMIT dates, making them easier to understand. However sometimes you want extra days' notice if intervening OMIT holidays happens (such as closing the bank or post-office) in which case you should use the single + form. Likewise, use -n to find the last day of the month, but you want it to skip over OMIT dates.

OMIT Dec 25 MSG Christmas
REM Dec 26 +1 MSG shows on the 24th, 25th, and 26th
REM Dec 26 ++1 MSG shows on the 25th and 26th but not 24th
REM Jan 1 -7 MSG shows on the 24th
REM Jan 1 --7 MSG shows on the 25th
~/.reminders

Omit contexts

Because not all places or calendars use the same list of holidays to determine OMIT days, remind lets you create custom OMIT contexts with PUSH (ending those temporary contexts with POP) and optionally use CLEAR to temporarily remove previous OMIT dates. As an example, our kids' schools close for (most) federal holidays but each has its own list of other days off such as in-service days and bad-weather make-up days.

# our son's school has certain days off
PUSH
OMIT Apr 27 2020 MSG %"In service day%" \
  No school for son
OMIT May 22 2020 MSG %"Bad-weather make-up day%" \
  No school for son
REM Mon Tue Wed Thu Fri \
  FROM Aug 14 2019 \
  UNTIL Jun 1 2020 \
  SKIP \
  MSG %"Son's school%"
POP
# our daughter's school has other days off
PUSH
OMIT Apr 13 2020 MSG %"In service day%" \
  No school for daughter
OMIT May 8 2020 MSG %"Bad-weather make-up day%" \
  No school for daughter
REM Mon Tue Wed Thu Fri \
  FROM Aug 14 2019 \
  UNTIL Jun 1 2020 \
  SKIP \
  MSG %"Daughter's school%"
POP
~/.reminders
For some calendars, I want to totally ignore the global holiday list and use custom OMIT lists instead. For example, a group of guys get together for breakfast on the first Saturday of each month. But a couple times someone else has booked our venue, pushing it back a week.
PUSH
CLEAR
OMIT 5 Oct 2019 MSG %"No breakfast get-together%" \
  (venue unavailable)
REM Sat 1 +1 AT 07:45 DURATION 1:30 \
  OMIT Sun Mon Tue Wed Thur Fri AFTER \
  FROM Aug 31 2019 \
  MSG %"Breakfast get-together%" %b %2
POP
~/.reminders
A couple things to notice:

  1. the CLEAR inside the PUSH/POP block means that my global OMIT list of holidays doesn't impact the breakfast dates
  2. if future events bump the event I only need to add them to the OMIT list at the top
  3. by having a local OMIT list as well (consisting of the non-Saturdays) the AFTER postpones to the following Saturday rather than pushing it to the next day (Sunday)

Frankly, as a guideline, I lean towards putting every OMIT inside its own PUSH/CLEAR/POP block.

Omit function

On occasion you might find it easier to describe the days you want to OMIT with a function. remind lets you create a such a function that takes a date as the only argument, and returns 0 if it should consider the date, or returns non-0 if it should treat the date as omitted. Use the OMITFUNC modifier to tell remind to ignore the local and global OMITs and use the OMITFUNC instead.

FSET myomit(d) …
REM OMITFUNC myomit MSG Some event
~/.reminders
If you want to take the global OMIT list into consideration or omit certain days of the week like you might with a local OMIT, use the isomitted() function in your OMITFUNC
FSET near_a_new_moon(d) \
  moonphase(d) < 20 || \
  moonphase(d) > 340
FSET allmyomits(d) \
  wkdaynum(d) == 0 || \
  wkdaynum(d) == 6 || \
  isomitted(d) || \
  near_a_new_moon(d)

REM OMITFUNC allmyomits AFTER \
  MSF This doesn't happen \
  on weekends, \
  OMIT dates, \
  or around a new moon.
~/.reminders
(the moonphase() function takes a date and returns a number from 0 to 359 with 0="new moon" and 180="full moon", so the near_a_new_moon function returns true within ~20° on either side of a new moon)

Using OMIT for more complex dates

In certain cases you might want to create an OMIT that falls on certain days that otherwise work with regular reminders. However if you try to do something like

OMIT Mon Feb 15 MSG President's Day
~/.reminders
remind considers the "Mon" part of the OMIT a syntax error. To get around this, create it as a regular reminder first, then use the resulting trigdate() to create the OMIT:
REM Mon Feb 15 MSG President's Day
OMIT [trigdate()]
~/.reminders
However, note that this might have surprising side-effects if you don't take care where you start your search.

Using SATISFY to conditionally test events

Sometimes you want remind to perform additional tests on a date to determine whether an event should occur, and if not, continue seeking the next matching date and test that. The SATISFY clause lets you evaluate an expression to do just that. You might only want something to happen every Nth month or test that something only occurs on certain days. As previously suggested when discussing the Nth weekday of a month you might need to check that the date falls in the same month.

REM 1 SATISFY [$Tm % 3 == 1] \
  MSG The first of every 3rd month starting January
REM 1 SATISFY [$Tm % 3 == 2] \
  MSG The first of every 3rd month starting February
REM 1 SATISFY [$Tm % 3 == 0] \
  MSG The first of every 3rd month starting March

REM MAYBE-UNCOMPUTABLE \
  SATISFY [$U == realtoday()] \
  MSG Only appears today
REM [realtoday()] \
  MSG Also only appears today

REM 13 SATISFY [$Uw == 5] MSG Friday the 13th

REM Mon 29 SATISFY [monnum($T - 7) = $Tm] \
  MSG Infrequent Fifth Monday of the month

PUSH
CLEAR

OMIT Dec 23 2019 THROUGH Jan 6 2020 \
  SATISFY [$Uw == 1] \
  MSG %"No club%"

REM Mon \
  FROM Aug 1 2019 \
  UNTIL May 31 2020 \
  OMIT SKIP \
  MSG Club
POP
~/.reminders

  • The first three reminders trigger on the first of each month. However remind will test the month-number ($Um), dividing it by 3 and getting the remainder ("%" does division and results in the modulus/remainder). If the remainder is 1, we have January/April/July/October; if the remainder is 2, we have February/May/August/November; if the remainder is 0, we have March/June/September/December.
  • The second two reminders test if the currently-considered date (today(), the same as $U) matches the real-world date (realtoday()) and only displays the MSG portion today.
  • To find Friday the 13th, we can't use REM Fri 13 because this finds the first Friday on or after the 13th of each month. That might or might not coincide with the 13th. Instead, we find all the 13ths and then use the SATISFY clause to reject those that don't fall on a Friday (0=Sunday, …, 5=Friday, 6=Saturday)
  • To find the 5th Monday we need to use the same procedure for finding the other Nth weekdays of the month (Mon 29) but then check that the month of the resulting date ($Um) falls in the same month (monnum()) as the date seven days before (-7) today's date ($U, the date currently under consideration).
  • In the final pair of entries, I have a club meeting every Monday night from August through May. However, club takes off during Christmas break (and some other days I've not included here), so the OMIT takes care of this. However, I don't want notifications on every day during the break telling me not to attend club. I only want notifications on the Mondays during the break. Using the SATISFY clause lets me limit these so they only fall on Mondays ($Uw == 1).

Fine-grained scheduling

Sometimes you want advanced warning of an event at certain intervals like ++n provides, but you don't want those warnings on every day during that interval. The WARN modifier lets you provide a function that tells remind which days you want reminders. The function gets called repeatedly first with 1, then with 2, etc. The search stops if one of the following conditions occur:

  • remind encounters the current number of days out for the next instance of this reminder
  • it encounters a zero (today)
  • the function results cease decreasing monotonically (they must continue decreasing, so "10, 5, 7, 0" will bail at 7 because it went up from 5)
Most commonly, the choose() function does this, so to get reminders 30 days out, 10 days out, 5 days out, and 2 days out:
FSET mywarnfunc(i) choose(i, 30, 10, 5, 2, 0)
REM Jan 1 2020 WARN mywarnfunc \
  MSG Get car inspected
~/.reminders
When checking if remind should produce a reminder for today, it starts by calling this mywarnfunc function with a parameter of 1. The choose() function returns 30, so if January 1st, 2020 is 30 days from the current date in consideration, it will remind. If not, remind calls mywarnfunc with a parameter of 2. This time, the choose() returns 10. Again if January 1st, 2020 is 10 days from the current date, you will get the reminder. Same with 5 and 2 days out. Finally, if none of the results match, remind tests if the date in question is January 1st, 2020. If you want to skip OMIT days, return a negative number. The check for monotonically decreasing numbers only tests the absolute value so a function like
FSET mywarnfunc(i) choose(i, 30, -10, 5, -2, 0)
REM Jan 1 2020 \
  WARN mywarnfunc \
  MSG Get car inspected
~/.reminders
works and will warn 30 days out, 10 days out (skipping over OMIT days), 5 days out, and 2 days out (also skipping over OMIT days). remind provides a similar SCHED functionality for fine-grained lead time for AT-style reminders, notifying at a series of times:
FSET mynotice(i) choose(i, 4*60, 2*60, 30, 0)
REM January 1 2020 AT 10:00 \
  SCHED mynotice \
  MSG Doctor
~/.reminders
to remind you at 4 hours out, 2 hours out, 30 minutes out, and at the time of your appointment. You can read more under PRECISE SCHEDULING in the man-pages.

Miscellaneous tricks

As I've built up my remind files, I've developed a collection of tricks and tips that I use for various scenarios.

Labels

Because I have a file for each type of reminder, I find it handy to prefix each item with its filename using the special msgprefix(x) function (which I also use for colorizing agenda reminders):

# fileprefix() reduces the current
# path+filename+extension
# to just the filename
# /path/to/file.rem -> "FILE"
FSET fileprefix() upper( \
  substr( \
    filename(), \
    strlen(remind_dir)+1, \
      strlen(filename())-4 \
      ) \
  )
FSET msgprefix(x) fileprefix() + ": "
reminders.rem
This produces
Friday, 27th December, 2019 (today):
USHOL: Chanukah 5
BIRTHDAYS: David (10yo) in 3 days' time
BIRTHDAYS: Annabelle (12yo)
BIRTHDAYS: Nate today
MANPAGES: man 1 ed
SCHOOL: Christmas: No School
FINANCES: Pay gas bill (today)
HOUSEHOLD: Garbage day (Delayed) today
If you don't put your reminders in separate files, you could achieve the same effect in a single file with a variable and a function that references it as part of the msgprefix(x) function:
SET topic ""
FSET msgprefix(x) iif(strlen(topic) > 1, topic + ": ", "")
FSET born(y) "(" + ($Uy-y) + "yo)"
FSET married(y) (ord($Uy-y)) + " anniversary"
SET topic "Birthdays"
REM Jan 1 MSG %"Mom%" [born(1950)]
REM Jan 6 MSG %"Dianne%" [born(1970)]
SET topic "Anniv"
REM Apr 1 +7 MSG %"Bob & Alice [married(1999)]%" %b
SET topic "Work"
REM Fri AT 7:30 MSG %"Meeting%" %2
~/.reminders

Color

As a rule, I generally want plain-text reminders, whether emailing them to myself or displaying a calendar with rem -c. But when at the command-line, I like a bit of color.

General per-reminder foreground color

As one of the SPECIAL codes, remind offers COLOR (or it will happily accept "COLOUR") for reminders that alters the color used when generating a week/month calendar with -cc+ or -cc. The entry takes three numbers as arguments, the Red, Green, and Blue values, each ranging from 0 to 255, followed directly by the MSG-style text (without the MSG keyword):

REM Jan 1 SPECIAL COLOR 255 0 255 \
  Happy New Year in bright magenta
~/.reminders
When displayed on a traditional ANSI terminal those get quantized into sixteen colors (eight basic colors plus eight bold/bright versions of each).

This makes it easy to colorize a single reminder, but it only works in week/month view (or external apps that support it via the -p option), not the agenda view (unless you have version 3.3.0 or later where the -@ option enables color in the agenda view). However sometimes I want to colorize whole swaths of reminders and do it on the agenda view. I want all my birthday and anniversary reminders in green, my personal reminders in blue, my wife's reminders in bright magenta, the kids' reminders in cyan, church/volunteering reminders in magenta, work in yellow, and some low-priority entries in gray. I don't want to have to specify the color for each and every one of my 1,600+ reminders.

Prior to the addition of $DefaultColor in version 3.3.0

To do this, I first add a number of ANSI escape codes as constants to my helpers.rem that I can then use elsewhere.


# ANSI colors
SET Esc CHAR(27)
SET Nrm Esc + "[0m"
SET Blk Esc + "[0;30m"
SET Red Esc + "[0;31m"
SET Grn Esc + "[0;32m"
SET Ylw Esc + "[0;33m"
SET Blu Esc + "[0;34m"
SET Mag Esc + "[0;35m"
SET Cyn Esc + "[0;36m"
SET Wht Esc + "[0;37m"
SET Gry Esc + "[30;1m"
SET BrRed Esc + "[31;1m"
SET BrGrn Esc + "[32;1m"
SET BrYlw Esc + "[33;1m"
SET BrBlu Esc + "[34;1m"
SET BrMag Esc + "[35;1m"
SET BrCyn Esc + "[36;1m"
SET BrWht Esc + "[37;1m"

helpers.rem

Then, in my base reminders.rem file, I have the following:

IF defined("COLOR")
  SET bannercolor BrWht
  SET calcolor Nrm
  SET labelcolor Nrm
  # if desired, use the priority "x"
  # to change the prefix color
  FSET prefixcolor(x) Nrm
  FSET color() calcolor
  FSET msgsuffix(x) Nrm
ELSE
  SET bannercolor ""
  SET calcolor ""
  SET labelcolor ""
  FSET prefixcolor(x) ""
  FSET color() ""
ENDIF

FSET msgprefix(x) \
  prefixcolor(x) + \
  fileprefix(x) + \
  ": " + \
  color()

IF defined("subsequent_iteration")
  BANNER [labelcolor]=====================[bannercolor]%_%w, %d%s %m, %y%o:%
ELSE
  BANNER [bannercolor]%w, %d%s %m, %y%o:%
  SET subsequent_iteration 1
  PRESERVE subsequent_iteration
ENDIF
reminders.rem
This checks if I've defined COLOR on the command-line and sets up functions & constants for default colors.

I then set a color at the top of each of my files after including the helpers.rem and it changes the color of everything following, allowing me to have multiple color blocks in the same file

INCLUDE [filedir()]/helpers.rem
SET calcolor Ylw
REM 1 MSG %"Send invoice%"

work.rem
INCLUDE [filedir()]/helpers.rem
SET calcolor BrBlu
REM Jan 10 MSG %"Alice%"
REM Jan 14 +7 MSG %"Bob [born(1980)]%" %b
SET calcolor Blu
REM Jan 6 MSG %"Dianne's birthday%"

birthdays.rem
The msgprefix() and msgsuffix() functions have special meaning to remind allowing the above to

  1. set the prefix color for the labels
  2. determine and display the file-name-based prefix
  3. switch to whatever color calcolor currently contains
  4. display the reminder text
  5. reset back to a normal color

I can then use rem -iCOLOR=1 to get my results in color. (ignore the fact that the events actually fall on different dates)

BIRTHDAYS: Alice
BIRTHDAYS: Bob (39yo)
BIRTHDAYS: Dianne
WORK: Send invoice
These work wonderfully for the agenda view, but unfortunately don't work for the week/month view (attempting to hack it with the special calprefix() and calsuffix() functions doesn't work either)

After the addition of $DefaultColor in version 3.3.0

I submitted a patch that has (with modifications) made it into the mainline codebase in version 3.3.0, released . This patch allows you to set a $DefaultColor variable combining the benefits of the COLOR keyword (appearing on the week/month calendars and getting passed to external utilities) with the benefits of my calcolor/msgprefix()/msgsuffix() trick above (colorizing the agenda view, without needing to specify the color for every single event).


SET $DefaultColor "0 255 0"
# birthdays for which I take some sort of action
REM Jan 10 MSG %"Alice%"
REM Jan 14 +7 MSG %"Bob [born(1980)]%" %b
SET $DefaultColor "0 128 0"
# birthdays I want to know about
REM Jan 1 MSG %"Eric's Mom's birthday%"
SET $DefaultColor "-1 -1 -1"
# Other misc. birthdays in the original default color
REM Jan 6 MSG %"Dianne's birthday%"

birthdays.rem
As an added bonus in 3.3.0, if you invoke remind with the -@ parameter, it will colorize agenda items for you in addition to the week/month calendar views.

Raw output

While pretty output works well for humans, sometimes I desire to pipe output to another program. I have no interest in trying to parse the week or month calendar views. The agenda view can serve as a primitive & fragile output format for post-processing. However, remind provides the -p and -s options to produce machine-readable output. This output format works much better with tools such as grep, sed, awk, and python/perl/ruby, as well as the tkremind program. remind also allows your reminders to convey additional metadata to these back-ends. Given this reminder:

REM 2020-01-29 \
  AT 8:30 DURATION 3:15 \
  TAG work \
  TAG home \
  SPECIAL COLOR 255 128 0 \
  my message
~/.reminders
Remind will output as follows:
rem -s 2020-01-29
2020/01/29 COLOR work,home 195 510 255 128 0 8:30-11:45am  my message
rem -p 2020-01-29
# rem2ps begin
January 2020 31 3 0
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
December 31
February 29
2020/01/29 COLOR work,home 195 510 255 128 0 8:30-11:45am  my message
# rem2ps end
The -s offers a simplified format of only the calendar data:

  1. The date in YYYY/MM/DD format
  2. The SPECIAL type (in this case COLOR)
  3. a comma-separated list of TAGs associated with the reminder
  4. the duration in minutes
  5. the start-time in minutes-from-midnight (510 = 60 minutes * 8 hours)
  6. the $RED, $GREEN, and $BLUE values from the COLOR (255, 128, and 0 here)
  7. the body of the message, prepended with the time (if present)
while the -p option wraps it in the following meta-data:
  1. a literal # rem2ps begin
  2. a line showing the month currently rendering, the current year, the current month, the number of days in the month, the week-day (0=Sunday, 1=Monday, … 6=Saturday) of the first day of the month, and a boolean 0/1 based on whether the calendar should display Monday first (based on the "-m" option passed to remind)
  3. the days of the week in the local language
  4. the next month and its number of days
  5. the previous month and its number of days
  6. the list of reminders like -s produces
  7. a literal # rem2ps end
If producing multiple months of data the # rem2ps end indicates the end of one month but the receiving-program should handle the case that more months' data could follow. The date (first column) should always appear, but the other fields might display an asterisk ("*") if not present in the reminder.

SPECIAL reminders

While I don't give much space to them, remind's SPECIAL token lets you pass out-of-band information to external back-ends. This can include things like changing the color of an entry (with SPECIAL COLOR $RED $GREEN $BLUE), shading the background of a calendar square (with SPECIAL SHADE $GRAYLEVEL or SPECIAL SHADE $RED $GREEN $BLUE), drawing the phase-of-the-moon (with SPECIAL MOON $PHASE), or adding per-week annotations (with SPECIAL WEEK $TEXT). I use rem2ps in the example regarding relative dates but don't employ any of the SPECIAL commands therein.

RUN reminders

The RUN message-type acts much like a MSG message-type except that after passing the body through the substitution filter it executes the resulting command instead of displaying the result. Beware of quoting and escaping issues. While you can do things like

REM Jan 31 +3 RUN echo Hello %b,
~/.reminders
the shell will complain on certain days because it lacks a closing single-quote trying to run
echo Hello in 2 days' time
However, you could do something like
REM 1 ONCE RUN backup.sh [$U]
~/.reminders
which would run
backup.sh 2020-01-01
The ONCE keyword will only run the reminder once, even if you invoke remind more than once in a given day. However, it relies on the atime (most recent access-time) of the reminder file. Since my /etc/fstab sets noatime on all of my mount-points, any reminders marked with ONCE run every time.

I keep a list of "to-do" items so I tried including those in my reminders:

REM SATISFY [$U == realtoday()] RUN head -5 ~/todo.txt
~/.reminders
to output the top 5 things on my to-do list.

I also experimented with keeping larger stretches of prose in an external file like

BEGIN: gifts

END: gifts
BEGIN: FakeCON
Flight:
 Southwest Flight 3141 DAL → BWI
 Departs: 1:41pm
 Arrives: 3:14pm
 Confirmation number: 1414
Car:
 Thrifty reservation# 15278-3955
Hotel:
 Marriott
 753 Old Oak
 Reservation: 20200115732
 Member#: 1783613807
END: FakeCON
BEGIN: other

END: other
annotations.txt
and then using RUN to emit the content
FSET external_annotation(marker) \
    "sed -n '/^BEGIN: *" + marker + "$/,/^END: *" + marker + "$/p' " + \
    filedir() + "/annotations.txt"

REM Feb 18 2020 RUN [external_annotation("FakeCON")]
~/.reminders
However, I found these of limited use, so I don't really use RUN in my day-to-day.

Tagging

Similar to the SPECIAL keyword, the TAG keyword lets you assign one or more tags to your reminder.

REM 15 TAG work MSG Run commission report
REM 1 TAG home TAG chores MSG Test smoke alarms
REM 1 TAG car TAG chores MSG Check oil levels
~/.reminders
While remind doesn't do anything with them directly, it passes them through to other back-ends when using the raw-output modes. Because we write monthly letters to our grandparents I use this functionality to tag newsworthy events in our calendar.
REM Jan 18 2020 TAG letter \
  MSG Date to see the new Jumanji movie
~/.reminders
I can then use the raw output of the -s and filter for only those items:
rem -s 2020-01-01 |
awk '$3 ~ /letter/'
so I have a list of what we did during the month. This works well in my reminders too, using a RUN directive:
REM 1 MSF [iif($Tm % 2, "Honey", "Hubby")] \
  writes %"Grandparent letter%"
REM 1 RUN rem -sr [$U - 1] | \
  awk '$3 ~ /letter/{for (i=2; i<6; i++) $i=""; print}'
~/.reminders
(the -r prevents remind from executing RUN entries further, and the expression [$U - 1] finds the last day of the previous month to pass to remind) Thus, not only do I get a reminder to let me know who writes the letter but I get a list of the letter-worthy things we did in the previous month.

I wish remind had more native functionality to support tags such as a trigtags() function (to return the tags for the current reminder), or a hastag() function (to ask if the current reminder has a given tag). This would help me filter output or expand tags in message-bodies. Alas, not yet. I look forward to some day (hopefully) when these are even more useful. Perhaps for my next patch.

Shell tips & tricks

Aliases

I've set up a couple bash aliases in my shell to minimize typing:


alias 1='rem -gaad -iCOLOR=1'
for d in {2..6} ; do
    alias ${i}='rem -gaad -iCOLOR=1 "*"'${i}
done

~/.bash_aliases
These let me type 3 as a command and get 3 days of colorized agenda output.

Crontab

I have crontab entries that email me my today/tomorrow reminders each day and a full week of reminders on Sunday morning:

@daily rem -g '*2'
 * * 0 rem '*7'
crontab

Version control

I find it useful to keep my reminder directory, ~/.config/remind/, in version control. I use git for multiple files, but you could just as easily use mercurial, Subversion, or even CVS or rcs. This gives me a lot of freedom to experiment with complex reminder syntax. If I mess something up, I can always revert to the known-good state. Also, I can delete reminders that have passed so they don't clutter my calendar or increase processing time, but still have historical record of their existence. I can also git push & git pull changes between different machines to keep my reminders in sync.

cd ~/.config/remind
git init .
git add *.rem
git commit -m "Initial checkin"

modify reminder files

git commit -am "Added John's birthday"
git
cd
mkdir RCS
echo "Initial checkin" | ci -l .reminders
ed -p"* " .reminders
3141
a
REM Apr 1 MSG Tom's birthday%
.
wq
3170
echo "Added Tom's birthday" | ci -l .reminders
rcs

Speech

The readable nature of the output makes it easy to pipe output to a program like espeak (or another text-to-speech engine which you would also have to install) to read your reminders aloud. You might create a custom spoken.rem file that you don't INCLUDE in your regular reminders.rem in which you remove the BANNER, emit section dividers (if you don't choose to use display labels), and INCLUDE a select subset of calendars:

BANNER %
MSG Birthdays%
INCLUDE [filedir()]/birthdays.rem
MSG Personal items%
INCLUDE [filedir()]/tim.rem
MSG Work items%
INCLUDE [filedir()]/work.rem
spoken.rem
(the [filedir()] expands to the path of the containing spoken.rem file)

And to read them:

remind ~/.config/remind/spoken.rem | espeak

Easier or more boring recipes

Car registration

We need to get our car inspected and get our (re)registration submitted by the end of February so I want notifications starting at the beginning of February:

REM Mar 1 --1 ++[26 + isleap($Uy)] \
  SATISFY [$Uy > 2019] \
  MSG %"Car reg./inspection due%" %b
household.rem
The Mar 1 --1 results in the last day of February while the ++[26 + isleap($Uy)] gives 26 or 27 days of advanced warning depending on whether the current year is a leap year. Once I've done the registration/inspection, I update the year in the SATISFY so it stops reminding me about it for that year.

Maturing bonds

I purchased a couple simple-interest bonds and want to know when to expect the interest payment checks. They arrive around the 25th of the month (or AFTER if it falls on a Sunday when the USPS doesn't deliver mail), every 6 months until the end of the bond-term. But remind makes this pretty easy:

REM 25 UNTIL Feb 26, 2025 \
  OMIT Sun AFTER \
  SATISFY [$Tm % 6 == 2] \
  MSG %"Expect bond check for $31.41%"
finances.rem
This uses the SATISFY keyword, repeatedly looking for the next 25th of the month until $Tm % 6 == 2 — the month in question is either February or August.

Notice until action

I send birthday cards to certain friends & family members so I want about a week of notice to get a card, write a note, and get it in the mail. However, once I've sent the card, I don't need further advanced warning reminders. By updating that year in the expression once I've completed the task I no longer get advanced warning for the given year. Similar to the car registration example, in this case I still want to receive notification on the day of the birthday too, not drop the reminder entirely.

FSET last_sent(y) iif($Uy > y, 7, 0)

# once I've sent a card to Grandma in 2020
# I'll change the "2019" to "2020":
REM Jul 6 +[last_sent(2019)] MSG %"Grandma%" %b
birthdays.rem
I use a single "+" for Grandma's notification because sometimes holidays interfere with the mail so I appreciate having the extended notice.

Messages tweaked based on date

As a Christmas gift to our grandparents, we take turns hand-writing a letter to them each month. So on the first of each month, I want a reminder, but also want to know whose turn:

REM 1 MSF [iif($Tm % 2, "Honey", "Hubby")] \
  writes %"Grandparent letter%"
household.rem
Similarly, if I scrub the tile grout in the kitchen one week, our master bathroom the next, and the kids' bathroom the third week I can achieve this with:
REM Sat MSF Scrub %"[ \
  choose((weekno() % 3)+1, \
  "Kitchen", \
  "Master", \
  "Kids'") \
  ] grout%"
household.rem

Selectively getting advanced notice

For friends I see at church on Sunday I would like advance notice of their birthday on Sunday but don't want additional reminders during the intervening days of the week. For this, I use an expression evaluation to dynamically determine the number of days worth of notice:

FSET church_notice() iif($Uw == 0, 6, 0)

REM Jan 16 ++[church_notice()] MSG %"Tony %c%"
~/.reminders
On Sunday, January 12th, I get notice about Tony's birthday coming up on Thursday so I can wish him a happy birthday when I see him. And I get notice on Thursday, his actual birthday. But I don't have my agenda cluttered with extra advance warnings on Monday through Wednesday.

Annoying credit-reporting company rules

I learned the hard way that the credit-reporting companies will not let you order your credit report on the same day each year because a full year hasn't actually passed. Yes, I'd love to pull my free credit report on the same day of each year. However, I need to pull them more than 365 days (366 on a leap-year) apart. So I use the repeat modifier to get reminded every 367 days. I also intersperse them throughout the year rather than all at once so that I get a more frequent sampling in case something has gone wrong:

REM Jan 1 2019 *367 MSF Order credit report: %"Equifax%" \
  (1-877-322-8228)%_\
  https://www.annualcreditreport.com/index.action
REM May 1 2019 *367 MSF Order credit report: %"Experian%" \
  (1-877-322-8228)%_\
  https://www.annualcreditreport.com/index.action
REM Sep 13 2019 *367 MSF Order credit report: %"TransUnion%" \
  (1-877-322-8228)%_\
  https://www.annualcreditreport.com/index.action
~/.reminders
The subsequent dates then drift forward automatically but I don't have to worry about trying again inside the one-year window of time. Also, I include the phone-number and the URL so I don't have to look it up every time.

Post-holiday candy sales

I tend to stock up on chocolate when it goes on sale after some of the big candy holidays. (I keep stashes of chocolate hidden around the house so that when my beloved has a hankering for a certain type, I can often play the miracle-worker and bring it out of hiding). So I use remind to make sure I don't forget:

REM Feb 15 MSG %"Post-Valentine candy sales%"%
REM [easterdate($Uy) + 1] MSG %"Post-Easter candy sales%"%
REM Nov 1 MSG %"Post-Halloween candy sales%"%
REM Dec 26 MSG %"Post-Christmas candy sales%"%
~/.reminders
The post-Easter one requires actual calculations and remind does this without much thought at all thanks to the built-in easterdate() function. The rest wouldn't pose any trouble for an ordinary calendar program.

Did any of these reminders trigger?

Sometimes you have several events and you want to provide some SPECIAL modifier for all of them. You can use remind's special $NumTrig variable to check this:

SET n $NumTrig
REM Sat Sun SATISFY 1
REM Feb 14 MSG Valentine's Day
REM [easterdate()] MSG Easter
IF $NumTrig > n
  # At least one triggered
  REM SPECIAL SHADE 75
ENDIF
~/.reminders
If any of the events occurred after we snapshot the $NumTrig, then the IF will allow for the SPECIAL SHADE to happen for the day in question.

FizzBuzz

Often in interviews the interviewer will instruct the candidate to whiteboard the classic fizzbuzz solution where the program emits n numbers but if the number is divisible by 3 print "fizz", if the number is divisible by 5 print "buzz", and if the number is divisible by 3 & 5 print "fizzbuzz" (otherwise just printing the number). So in case you ever need to do this using remind:

FSET doy() $U - date($Uy-1, 12, 31)
MSG [iif(doy() % 15 == 0, \
  "fizzbuzz", \
  iif(doy() % 5 == 0, \
    "buzz", \
    iif(doy() % 3 == 0, \
      "fizz", \
      doy() \
      ) \
    ) \
  )]
~/.reminders
The doy() function returns the Julian day of the year (as found by taking the current date, $U, and subtracting December 31st of the previous year) and then uses that to calculate the fizzbuzz-ness of the day. Dumb? Yep. Useless? You bet. Fun demonstration? Why not.

More complex calendar recipes

Shifting garbage day

The go-to for demonstrating the power of remind involves shifting your garbage collection day back if a holiday occurs earlier in the week:

PUSH
CLEAR

INCLUDE [filedir()]/holidays.rem

SET garbday 4
SET garbdayname wkday(garbday)
FSET _garbhol(x) wkdaynum(x) == garbday \
  && slide(x - garbday, garbday) != x
FSET _garbdel() iif($Tw != garbday, " (Delayed)", "")
REM [garbdayname] +1 AFTER OMITFUNC _garbhol \
  MSG %"Garbage day[_garbdel()]%" %b%
POP
~/.reminders
Change the garbday value to your garbage day (Monday = 1, Friday = 5) and set up your holiday OMIT entries in your holiday.rem (or hard-code them inside the PUSH/CLEAR/POP block) and remind will automatically shift the garbage day reminder based on the holiday schedule.

Pretty date-diff

While remind lets you subtract two dates to get the number of days between them, sometimes you want that difference expresses as a number of years, months, and days. To do that, I have this ymd_diff function which returns values like "3y 2m 11d" or "3y 1m 14d ago" which you might find more useful:

FSET ymd_diff_gt(d1, d2) \
  iif(year(d2) > year(d1), \
    (year(d2) - year(d1)) - ( \
      (monnum(d2) < monnum(d1)) \
      || \
      (monnum(d2) == monnum(d1) && day(d2) < day(d1)) \
      ), \
    0 \
    ) + "y " + \
  iif(monnum(d2) > monnum(d1), \
    (monnum(d2) - monnum(d1)) - (day(d2) < day(d1)), \
    iif(monnum(d2) < monnum(d1), \
      (12 + monnum(d2) - monnum(d1)) - (day(d2) < day(d1)), \
      iif(day(d2) < day(d1), 11, 0) \
      )) + "m " + \
  iif(day(d2) >= day(d1), \
    day(d2) - day(d1), \
    (daysinmon(monnum(d1), year(d2)) + day(d2)) - day(d1) \
    ) + "d"
FSET ymd_diff(d1, d2) \
  iif(d1 > d2, ymd_diff_gt(d2, d1) + " ago",\
  ymd_diff_gt(d1, d2) \
  )
~/.reminders

Generating reminder files programmatically

One of my calendars gives me a daily man-page to read based on my list of system man-pages looking something like

SET ManStart date(2020,1,1)
REM [ManStart + 0] MSG man 5 %"fstab%"%
REM [ManStart + 1] MSG man 1 %"rs%"%
REM [ManStart + 2] MSG man 1 %"awk%"%
REM [ManStart + 3] MSG man 6 %"cribbage%"%

manpages.rem
Creating this manually would have taken a long time. A little shell-script easily generated the file:
cat remify_manpages.awk
BEGIN {
 print "SET ManStart date(2020,1,5)"
}
$7 ~ /^[1678]$/ {
 printf("REM [ManStart + %i] MSG man %s %%\"%s%%\"%%\n", NR - 1, $7, $6)
}

find /usr/share/man/man[1678]/ -maxdepth 2 -type f \
| grep -vi perl \
| sort -R \
| awk -F'[./]' -f remify_manpages.awk > manpages.rem
This finds all of the man files in /usr/share/man/man[1678]/ removing the many perl-related pages I don't care about, shuffles them so I get a nice variety, then uses the awk script to transform them into a remind file. Now I have several years worth of daily man-pages to read. If you have a pre-existing list of dates, you can use the standard suite of Unix tools to transform the list into something that remind can use.

Relative date calculations for event-planning

I serve on the leadership team for a local non-profit that works to provide clothing-vouchers to kids on the school free/reduced-lunch program. However the calendar changes each year based on certain fixed dates including when the school counselors have their training before school starts, when school starts, and which weekends the shopping event(s) take(s) place. I use SET to define the fixed-dates, and calculate the other dates based off them.

SET CounselorTraining date(2019,7,31)
SET SchoolStart date(2019,8,15)
SET Event date(2019,10,19)

SET ApplicationsGoHome (Event - 47)

REM [CounselorTraining] MSG %"Attend counselor training%"
REM [SchoolStart] MSG %"First day of school%"
REM [ApplicationsGoHome - 3] \
  MSG %"Remind counselors applications will be going home%"
REM [ApplicationsGoHome] \
  MSG %"Applications go home with kids%"
REM [ApplicationsGoHome] \
  MSG %"School calls families to let them know to watch for applications%"
REM [Event - 37] \
  MSG %"Remind counselors that applications must be back [Event - 33]%"
REM [Event - 33] MSG %"Check funding levels%"
REM [Event - 32] MSG %"Pick up applications from school%"
REM [Event - 32] THROUGH [Event - 26] MSG %"Data entry%"
REM [Event - 10] MSG %"Print acceptance letters%"
REM [Event - 8] MSG %"Email counselors to expect letters%"
REM [Event - 8] MSG %"Acceptance letters go home%"
REM [Event - 5] MSG %"Email volunteers a reminder%"
REM [Event] MSG %"Event%"
REM [Event + 2] MSG %"Email volunteers to thank%"
~/.reminders
(I have some OMIT and BEFORE in there too to jostle things around a bit) I can then use rem -p3 2019-08-01 | rem2ps | ps2pdf - calendar.pdf to generate the calendar PDF that I can email out to the rest of the leadership team.

Medicine schedule

One of our kids takes a medication that we receive in a 30-day supply. This means we need to pick it up at the pharmacy at least the day before to ensure uninterrupted dosing. The pharmacy occasionally needs a business day or two to get the medication in, so I like to drop off the prescription a couple days early. Which means I need to pick up the prescription at the doctor's office before that. Which means I need to call the doctor's office 72hr before that. And all of those dates can get shifted by weekends and holidays. On a less-powerful calendar, I'd schedule a reminder every 30 days and then hope I remember to take weekends and holidays into consideration.

On the other hand, remind makes this fairly straightforward to express. While doing this, I'd also like to get nagging reminders and advanced notice if I haven't yet picked up the pills. But once I have, I don't want continued reminders. First we'll begin by creating an OMIT context to hold the holidays that the pharmacy & doctor's office close.

PUSH
CLEAR
# the Dr. and pharmacy close on these dates

OMIT Dec 24
OMIT Dec 25

# reminders will go here

POP
~/.reminders
With that in place, we'll set up some variables to know when we most recently picked up the medication and when we started taking the medication as well as how many days worth of medication we receive and the number of days of lead-time the doctor's office has requested:

SET MostRecentlyGotPills date(2020, 1, 24)
# Started taking the medication on
SET MedStartDate date(2019, 3, 18)
# we get 30 pills
SET MedFreq 30
# Dr. office requests 72hr of notice
# but remind needs it in days
SET RXLeadTime (72/24)

~/.reminders
Now we need to find the date of the next time we run out of pills.

REM [MedStartDate] *[MedFreq] SATISFY 1
SET PillsRunOut trigdate()

~/.reminders
The SATISFY 1 produces no output, but sets the most recent trigger date. Using the trigdate() function, you can capture the date of the most recent event for use in further calculations and later reuse. So those lines determine the next day we'll run out of pills.

From here, we know that we need to pick up the pills the day before then (possibly more than one day before if there's an intervening holiday or weekend):


# need to pick up meds at least one day before we run out
REM [PillsRunOut] -1 +1 \
    OMIT Sun BEFORE \
    SATISFY [$U > MostRecentlyGotPills] \
    MSG %"Pick up pills%" at pharmacy %b

~/.reminders
We evaluate the day when the pills run out, and then back it off by one day ("-1"). Because this uses the single minus, it will skip over OMIT days, including holidays that close the pharmacy & doctor's office, as well as Sundays when the pharmacy is closed. The SATISFY ensures that, if we have picked up the pills on or after the date currently under consideration, we don't bother reminding about this. I also like a little advanced warning (+1). In case life gets busy, I have a little more room to fit in a trip to the pharmacy.

Once we know when we need to pick up the pills, we can capture that date (again, with trigdate()) and use it to determine when we need to pick up the prescription and take it to the pharmacy (the day before, but the doctor's office doesn't open for prescriptions on weekends or over holidays so it might get bumped back further).


SET RealPillPickUpDate trigdate()
REM [RealPillPickUpDate - 1] +1 \
    OMIT Sat Sun BEFORE \
    SATISFY [$U > MostRecentlyGotPills] \
    MSF %"Pick up Rx%" at Dr. %b \
    and take to pharmacy

~/.reminders
Then we need to create a reminder based on the date we need to pick up the pills and back it off by the number of days that the doctor's office says they need for lead-time (72 hours):

SET RealRxPickUpDate trigdate()
REM [RealRxPickUpDate] -[RXLeadTime] \
    OMIT Sat Sun BEFORE \
    SATISFY [$U > MostRecentlyGotPills] \
    MSF %"Call Dr. office to request Rx refill%" %b

~/.reminders
Again, we use the SATISFY to ensure we only get this if we haven't already picked up the most recent batch of pills.

This produces a final series of variables and reminders that notify me at appropriate times and do all the holiday/weekend math so we don't accidentally end up without medication:

################
# Update here: #
################
SET MostRecentlyGotPills date(2020, 1, 24)

###################
# Configure here: #
###################
SET MedStartDate date(2019, 11, 27)
# we get 30 pills
SET MedFreq 30
# Dr. office requests 72hr of notice
# but remind needs it in days
SET RXLeadTime (72/24)

PUSH
CLEAR
# the Dr. and pharmacy close on these dates
OMIT Jan 1

OMIT Dec 24
OMIT Dec 25

#########################

# when's the next occurrence?
REM [MedStartDate] *[MedFreq] SATISFY 1
SET PillsRunOut trigdate()

# need to pick up meds at least one day before we run out
REM [PillsRunOut] -1 +1 \
    OMIT Sat Sun BEFORE \
    SATISFY [$U > MostRecentlyGotPills] \
    MSG %"Pick up pills%" at pharmacy %b
SET RealPillPickUpDate trigdate()
REM [RealPillPickUpDate - 1] +1 \
    OMIT Sat Sun BEFORE \
    SATISFY [$U > MostRecentlyGotPills] \
    MSF %"Pick up Rx%" at Dr. %b \
    and take to pharmacy
SET RealRxPickUpDate trigdate()
REM [RealRxPickUpDate] -[RXLeadTime] \
    OMIT Sat Sun BEFORE \
    SATISFY [$U > MostRecentlyGotPills] \
    MSF %"Call Dr. office to request Rx refill%" %b
POP
~/.reminders
Then each time I pick up the pills, I update the MostRecentlyGotPills with the new start-date. You can use the same template adjusting the MostRecentlyGotPills, MedStartDate, MedFreq, and RXLeadTime, for your use case.

Scanning forward from an earlier date

Sometimes you have an OMIT that has happened recently and would thus impact how advanced notice with + and adjusting dates with BEFORE/AFTER/SKIP calculations occur, but because remind first determines the OMIT dates based on the "today" in question, it might not adjust properly. For example, consider a floating holiday like Labor Day (the first Monday in September):

REM Mon 1 Sep SATISFY 1
OMIT [$T] MSG Labor Day
~/.reminders
If you run this on it will fail to find the event on today but will instead find the next instance on . By starting the search a week back using SCANFROM, we encounter the right date:
REM Mon 1 Sep SCANFROM [$U-7] SATISFY 1
OMIT [$T] MSG Labor Day
~/.reminders
Similarly, if you want to find both the 4th of July and the day on which it gets observed, you might need to look backwards a couple days to find the closest 4th:
# Start looking for July 4th
# beginning 7 days ago:
REM 4 July SCANFROM [$U - 7] SATISFY 1

# if it fell on a Saturday
# observed on the preceding Friday
IF wkdaynum(trigdate()) == 6
  REM [trigdate()] MSG Independence day (actual)
  OMIT [trigdate()-1] MSG Independence day (observed)
ELSE
  # if it fell on a Sunday
  # observed on the following Monday
  IF wkdaynum(trigdate()) == 0
    REM [trigdate()] MSG Independence day (actual)
    OMIT [trigdate()+1] MSG Independence day (observed)
  ELSE
    # otherwise, observed=actual
    OMIT [trigdate()] MSG Independence day
  ENDIF
ENDIF
~/.reminders
(taken from the defs.rem)

School N-day cycles ignoring holidays & weekends

Sometimes you want to know the number of days between two dates, without counting holidays or other OMIT dates. This often shows up in school schedules where a 4- or 6-day cycle often rotates across the standard 5-day week. The nonomitted() function lets you do this with minimal fuss. However, the start-date (the first parameter) must be less-than or equal to the end-date or remind will produce an error.

SET FirstDayOfSchool date(2019, 8, 14)
# a 4-day cycle
SET SchoolPhase 4
PUSH
CLEAR
# list of school holidays
# and in-service days:
OMIT Jan 1

OMIT Dec 23 2019 THROUGH Jan 6 2020 \
  SATISFY [$Uw > 0 && $Uw < 6] \
  MSG %"Christmas: No school%"%

OMIT Mar 13 2020 MSG %"In-service: No school%"%

IF $U >= FirstDayOfSchool
  SET DayNumber nonomitted(  \
    FirstDayOfSchool, \
    $U, \
    "SAT", "SUN" \
    )
  SET schedule DayNumber % SchoolPhase + 1
  REM Aug 13 2021 AT 7:15 THROUGH May 21 2020 \
    SKIP \
    OMIT Sat Sun \
    MSG %"School%" %2 ([choose( \
      schedule, \
      "music", \
      "art", \
      "language", \
      "health" \
      )])%
ENDIF
~/.reminders

Daemon mode

In addition to invoking remind manually or from scripts, remind also offers a "daemon mode" that will run in the background and fire off commands as AT events come around.

remind -z5 -k'espeak %s &' ~/.reminders
~/.reminders
This will launch remind which will check for changes in your ~/.reminders every 5 minutes. If an AT passes, remind will spawn espeak passing the reminder-text (shell-escaped) in place of the %s so that the reminder text gets spoken. Alternatively you could use xmessage or zenity to pop up a dialog
remind -z5 -k'xmessage %s &' ~/.reminders
~/.reminders
(beware that this can end up creating lot of xmessage dialog boxes) or use notify-send to display the reminder-text as a notification:
remind -z5 -k'notify-send %s &' ~/.reminders
~/.reminders
Because of the composability of commands, you could create your own shell-script to email reminders, text them to your phone, hit a web service, send them to a printer, or print your MSG contents on a 3d-printer. Endless possibilities.

Your reminder file can use the $Daemon variable to test to see if remind is running in daemon mode:

REM Jan 5 2020 AT 10:30 MSG [ \
  iif($Daemon, "Hey, you! ", "") \
  ] Doctor's appointment
~/.reminders
When run in daemon mode, this will prefix your message with "Hey, you!" to get your attention, especially if you use espeak to speak your reminders

Other utilities

Generating iCalendar/.ics files

Great. Now you have all your reminders generating just the way you want. How do you share them with other people? Most other cough*inferior*cough calendar programs still support .ics format files to describe events. You can use the rem2ics utility to convert the output of remind into a .ics file that you can import into Google Calendar, Outlook, or iCalendar. Note: While it will export all of your reminders including repeating reminders, it will not include enough meta-information to preserve things like repeats, so the events show up in the external calendar as individual events.

rem -s12 |
TZ=CST8CDT rem2ics -do >reminders.ics
If you have your local timezone exported as an environment variable, you don't need to set it explicitly, but it gets copied into the resulting reminders.ics file verbatim, and having the wrong timezone can cause lots of problems.

Other external utilities

Outside the scope of this article you'll find things like tkremind (a GUI front-end to remind) and wyrd (a TUI front-end to remind). I don't currently use either of these so I'll leave those for the reader to explore.

Troubleshooting & debugging

While the vast majority of my reminders pose no issues, occasionally one ends up complex enough that I want to make sure it really works like I expect it to. Because these really are code, I appreciate having some tools to debug them.

Echoing into remind -

As a first line of debugging, I usually try to isolate the single reminder and then run remind with the date I want to test. If you specify a filename of "-", remind will read reminders from stdin which lets you echo a single reminder to test it:

echo 'REM 1 --1 MSG Last' |
remind - 2020-1-31
Occasionally you need a couple lines to thoroughly test, in which case wrap the echo statements in parentheses:
(echo 'OMIT Dec 31'
echo 'REM 1 --1 MSG Last'
) | remind - 2020-12-31
Alternatively, sometimes you need to test multiple days
echo REM 1 --1 MSG Last \
remind - 2020-12-31 '*33'
or months
for m in {1..12}
do
echo "REM 1 --1 MSG Last" |
remind - 2020-${m}-30
done

Using a custom .reminders file

Sometimes you really do need a lengthier test case. For these, I back-up my ~/.reminders file,

cp ~/.reminders{,_orig}
create a new one afresh or modify the existing one
$EDITOR .reminders # create test case
test against that until I get it right. I then snapshot the working reminders,
mv ~/{.,working_}reminders
and restore my original
mv ~/.reminders{_orig,} # restore the original
Finally, I use my $EDITOR to copy the working bits from ~/working_reminders into my reminder file.

Alternatively, I also have my machines configured with a "guest" user, so sometimes it ends up faster to log in as the "guest" (who has no ~/.reminders file of consequence), blow away any previous test ~/.reminders file, and test there on an isolated case without risk to my real reminders.

Showing LOTS of dates

Sometimes I find it easiest to look at a spew of calendars and eyeball the results. I like to do this in month view, producing a whole year and paging through the results with my $PAGER (less in my case).

rem -c12 -w$COLUMNS 2020-1-1 | less
Alternatively, I will use the -p/-s flags to dump all the reminders in a easy-to-filter format.
rem -p12 2020-1-1 | less

Printing values in MSG output

In the same classic tradition of printf() debugging, I also find it useful to see what remind thinks a variable holds:

SET SomeVar some complex expression
REM … MSG SomeVar=[SomeVar] on [$T]/[$U]
~/.reminders

Debugging flags

For really getting under the hood remind has a -d option that turns on additional debugging output. It gets verbose quickly, but invoking

rem -dextvlf 2>&1 | less
turns on all the debugging flags. The man-page details each of those options, but it has occasionally helped me spot something I would have otherwise missed. I find the x (tracing expression evaluation), t (tracing trigger-date computation), and v (dumping all variables) help the most.

Wrap up

When I first heard about remind it sounded interesting, but I tried multiple times before it finally clicked for me (much like git which took me about three serious attempts before I felt like I understood it). But once I figured out that I could express things that no other calendar program let me do, I have trouble keeping my primary calendar in anything else. I use rsync and git to keep my calendar in sync across multiple machines without exposing my calendar info to third-parties like Google or Microsoft. Hopefully this post gives you the motivation to evaluate it for your hardest calendar test-cases.

And if you come up with strange questions, use-cases, or other recipes for using remind feel free to contact me via email, on the remind mailing list, or over on Twitter @gumnos and I'll do my best to help out.