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:

root@freebsd# pkg install remind

user@openbsd$ doas pkg_add remind

user@debian$ sudo apt-get install remind

GUI

Tcl

tk

From source

On some systems, this has no dependencies; on other systems, this brings in somecomponents () because the package might also include the optional graphical tkRemind component.

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 )

user$ VER=0.3.03.00 user$ wget https://dianne.skoll.ca/projects/remind/download/remind- ${VER} .tar.gz{,.sig} user$ gpg --verify remind- ${VER} .tar.gz.sig xvfz remind- ${VER} .tar.gz user$ tar xvfz remind-${VER}.tar.gz user$ mv remind-${VER} remind

git

user$ git clone https://dianne.skoll.ca/projects/remind/git/Remind.git remind

user$ cd remind user$ ./configure user$ make user$ sudo make install

make install

remind/src/{remind,rem2ps}

~/bin/

$PATH

remind

rem

man

remind/man/*.1

$MANPATH

Getting started

Creating your first remind file

or if you want to download fromand then compile it yourself:If you prefer not toyou can move/copy the resulting binaries fromto adirectory (in your), create a link fromto, and put thepages fromin your

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:

user$ 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

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.

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.

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.

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.

will match any of them. Alternatively, you can write the date in YYYY-MM-DD format

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

user$ remind ~/.reminders 2020-01-01 user$ rem 2020-01-01

remind

user$ rem

To see (most of) these reminders, use either of these commandsIf you happen to run the command on January 1, 2020, you can omit the date becausedefaults to the current date. I most frequently invoke justto 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

Beware

with that "Mon 29" because the "Monday on-or-after the 29" 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

REM Sat 2 SATISFY [$Um % 3 == 2] \ MSG Recycling center drop-off day

Adding time information

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:

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

MSG

Getting advance notice

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

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

remind

-p a

-s a

remind

Adjusting dates

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 withor). While less useful to get the same reminder several days out,lets you adjust the reminder text based on the number of days out to make these more helpful.

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

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

However, this lets you schedule reminders relative to the last day of the month: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

Repeating 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.

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

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

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

THROUGH

*1

REM Dec 21 2019 THROUGH Jan 1 2020 MSG Vacation

*2

REM Jan 1 2019 *2 UNTIL Jan 31 2020 MSG Take medication

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

Modifying the output

Other views

Thekeyword simplifies the common "" use case, so this works exactly the same:You might also have to take a medication every other day () starting on January 1through the end of the month:Alternatively, you might have a Wednesday game night during the school year, or a meeting on the 12of each month during the school year:

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:

user$ rem \*4 user$ rem '*4'

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

user$ rem -c user$ rem -c2 user$ rem -c 2019-8-1 user$ rem -c+ user$ rem -c+2

-w $COLUMNS

user$ rem -c+2 -w $COLUMNS

remind

TTY

Other message-types

You can change the default width for either the week or month view by addingAs of version 3.3.0,automatically detects the screen width if you output to aso you no longer need to specify this explicitly unless you want something other than the current screen width.

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.

remind

PS

PSFILE

Getting rid of blank lines

provides a couple other reminder-types: RUN for running an external command, SPECIAL , for conveying out-of-band information, andfor passing PostScript information to a PostScript-processing back-end which I'll skip for now).

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 %

%

Selectively showing text

For simplicity, I've omitted the trailingfrom all of the other examples in this post, but I put it on

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

%"

Substitution filter

With this in place, it will display the full text of the reminder (without themarkers) on the agenda view but only show "Doctor" on the visual calendars.

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

remind

in 7 days' time

in 2 days' time

tomorrow

today

man

remind

SUBSTITUTION FILTER

%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"

REM Jan 1 2020 +1 AT 14:30 MSG Doctor appointment %b %2

Tweaking the banner

andwill display "Mom's birthday", …, "Mom's birthday", "Mom's birthday", and finally "Mom's birthday". Thepage forhas a whole collection of these under the "" section. I most frequently useThese combine to allow things liketo 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: %

BANNER Reminders for %w, %m %d%s , %y%o:%

and I prefer the month presented before the day:

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

subsequent_iteration

BANNER

subsequent_iteration

PRESERVE

subsequent_iteration

remind

ELSE

Sorting

On the first pass,is not defined, so we set the defaultand then set/define. Thekeyword then keeps the value ofdefined through subsequent iterations. Now defined,takes theblock, prefixing the banner with a row of dashes, a new-line, and then the normal banner text.

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 a scending or d escending order. Positionally, these indicate

the trigger date of the event the trigger time of the event the priority of the event whether to sort timed events before untimed events

rem -gaad

Sub-BANNERs for sorting

Sosorts 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.

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

BANNER %

sortbanner()

%B

%b

Next instance of reminders

I use theto suppress the regular banner, letting theassume those duties. Also, by capitalizing the substitution-filter (instead of) it capitalizes the resulting output.

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

user$ 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

user$ cd user$ user$ mv .reminders original_reminders.rem user$ mkdir -p ~/.config/remind user$ echo 'SET remind_dir getenv("HOME") + "/.config/remind/"' > \ > ~/.config/remind/reminders.rem

~/.reminders

user$ ln -s .config/remind/reminders.rem ~/.reminders user$ cd ~/.config/remind

remind

INCLUDE

rsync

$HOME

and link theto point to my main reminder file:This uses a littlemagic (see the section on expressions below) to dynamically find your reminder directory for later use in thestatements. This lets me move ormy reminders to another machine under a differentand everything will continue to work.

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

user$ ln -s .config/remind/ ~/.reminders

remind

*.rem

010-birthdays.rem

015-anniversaries.rem

020-work.rem

Initial file types

andwill process all of thefiles in the target folder in glob order. If you want a particular order, you can name the files with a prefix like, etc.

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

user$ touch ~/.config/remind/helpers.rem

INCLUDE

reminders.rem

user$ 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

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

INCLUDE

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

To save a bit of processing time, I wrap my helpers.rem in a guard so that it only gets processed once: IF !defined("HaveHelpers") SET HaveHelpers 1 # make sure this IF guard gets closed at the bottom ⋮ ⋮ ENDIF # HaveHelpers

I then create files for various event types andthem into the masterfile: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: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 ofstatements to combine other calendars:

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.

user$ 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

FSET

add_three

x

FSET add_three(x) 3 + x

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

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

Using functions in reminder text

To create a function, use. The example below creates a function namedthat takes one parameter named. When called, it adds three to the number passed to it and returns the result:To evaluate an expression put it in square brackets (all of these produce the same output)Note that you can use an expression anywhere that a number, string, or date/time can appear. This lets you do things likeWhich 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).

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" ⋮

The $Uy holds the year of the currently-rendering event whether today, the date passed on the command line, one of the subsequent days in a multi-day agenda view, or on a week/month calendar view. So by subtracting the birthday/anniversary year from the current-event's date, you get which birthday/anniversary. The ord() function turns a number into its ordinal representation ("1" → "1st", "2" → "2nd", "3" → "3rd", "4" → "4th", …)

⋮ REM Apr 1 +7 MSG %"Bob & Alice [married(1999)] %" %b ⋮ REM Jan 14 +7 MSG %"Bob [born(1980)] %" %b ⋮

Bob & Alice 20th anniversary tomorrow ⋮ Bob (40yo) in 3 days' time

Specifying the priority of a reminder

This lets me cleanly annotate things likewhich produces output like(ignore the date discrepancies)

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

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), " *****", "")

is_low_prio

is_high_prio

FSET msgprefix(p) \ iif(is_high_prio(p), Red , \ iif(is_low_prio(p), Gry , \ Nrm )) FSET msgsuffix(p) Nrm

Omitting and shifting dates

(assuming higher numbers mean higher priority; otherwise, adjust theandfunctions accordingly) or, using some of the tricks from the section on color

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

AFTER

REM 15 OMIT Sat Sun AFTER MSG Payday

BEFORE

REM 15 OMIT Sat Sun BEFORE MSG Pay water bill

Omitting/shifting for multiple events

In another case, your employer might pay you on the 15of each month, but if pay-day falls on a weekend, payment movesthe weekend to the following Monday:Or you might have a bill that you need to pay by the 15but if that falls on a weekend you must pay itthe weekend so that payment arrives on time:

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

user$ rem 2015-12-24 '*2' # got moved to 24th user$ rem 2017-12-25 '*2' # got moved to 26th user$ rem 2019-12-24 '*3' # got cancelled

OMIT

Omit notice and adjusting backwards

In 2015, Christmas fell on a Friday, in 2017, Christmas fell on a Monday, and in 2019, Christmas fell on a Wednesday.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 4since we have multipleentries.

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

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

OMIT

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

the CLEAR inside the PUSH / POP block means that my global OMIT list of holidays doesn't impact the breakfast dates if future events bump the event I only need to add them to the OMIT list at the top 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)

In reality, I also use CLEAR with the kids' school calendars too because not every federally-recognized holiday closes the schools so I find it easiest to just hard-code all the days-off for each school year.

For some calendars, I want to totally ignore the global holiday list and use customlists 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.A couple things to notice:

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 OMIT s and use the OMITFUNC instead.

FSET myomit (d) … REM OMITFUNC myomit MSG Some event

OMIT

OMIT

isomitted()

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.

moonphase()

near_a_new_moon

Using OMIT for more complex dates

If you want to take the globallist into consideration or omit certain days of the week like you might with a local, use thefunction in your(thefunction takes a date and returns a number from 0 to 359 with 0="new moon" and 180="full moon", so thefunction returns true within ~20° on either side of a new moon)

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

remind

OMIT

trigdate()

OMIT

REM Mon Feb 15 MSG President's Day OMIT [trigdate()]

Using SATISFY to conditionally test events

considers the "Mon" part of thea syntax error. To get around this, create it as a regular reminder first, then use the resultingto create theHowever, note that this might have surprising side-effects if you don't take care where you start your search

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 [$Um % 3 == 1] \ MSG The first of every 3rd month starting January REM 1 SATISFY [$Um % 3 == 2] \ MSG The first of every 3rd month starting February REM 1 SATISFY [$Um % 3 == 0] \ MSG The first of every 3rd month starting March SATISFY [$U == realtoday()] MSG Only appears today REM 13 SATISFY [$Uw == 5] MSG Friday the 13th REM Mon 29 SATISFY [monnum($U - 7) = $Um] \ 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

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.

will test the month-number ( ), 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 reminder tests if the currently-considered date ( today() , the same as $U ) matches the real-world date ( realtoday() ) and only displays the MSG portion today.

, the same as ) matches the real-world date ( ) and only displays the portion today. To find Friday the 13 th , we can't use REM Fri 13 because this finds the first Friday on or after the 13 th of each month. That might or might not coincide with the 13 th . Instead, we find all the 13 th s and then use the SATISFY clause to reject those that don't fall on a Friday (0=Sunday, …, 5=Friday, 6=Saturday)

, we can't use because this finds the first Friday on or after the 13 of each month. That might or might not coincide with the 13 . Instead, we find all the 13 s and then use the clause to reject those that don't fall on a Friday (0=Sunday, …, 5=Friday, 6=Saturday) To find the 5 th Monday we need to use the same procedure for finding the other N th 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).

Monday we need to use the same procedure for finding the other N weekdays of the month ( ) but then check that the month of the resulting date ( ) falls in the same month ( ) as the date seven days before ( ) today's date ( , 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

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)

choose()

FSET mywarnfunc(i) choose(i, 30, 10, 5, 2, 0) REM Jan 1 2020 WARN mywarnfunc \ MSG Get car inspected

remind

mywarnfunc

choose()

remind

mywarnfunc

choose()

remind

OMIT

FSET mywarnfunc(i) choose(i, 30, -10, 5, -2, 0) REM Jan 1 2020 \ WARN mywarnfunc \ MSG Get car inspected

OMIT

OMIT

remind

SCHED

AT

FSET mynotice(i) choose(i, 4*60, 2*60, 30, 0) REM January 1 2020 AT 10:00 \ SCHED mynotice \ MSG Doctor

PRECISE SCHEDULING

man

Miscellaneous tricks

Most commonly, thefunction does this, so to get reminders 30 days out, 10 days out, 5 days out, and 2 days out:When checking ifshould produce a reminder for today, it starts by calling thisfunction with a parameter of 1. Thefunction returns 30, so if January 1, 2020 is 30 days from the current date in consideration, it will remind. If not,callswith a parameter of 2. This time, thereturns 10. Again if January 1, 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,tests if the date in question is January 1, 2020. If you want to skipdays, return a negative number. The check for monotonically decreasing numbers only tests the absolute value so a function likeworks and will warn 30 days out, 10 days out (skipping overdays), 5 days out, and 2 days out (also skipping overdays).provides a similarfunctionality for fine-grained lead time for-style reminders, notifying at a series of times:to remind you at 4 hours out, 2 hours out, 30 minutes out, and at the time of your appointment. You can read more underin the-pages.

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() + ": "

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

msgprefix(x)

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

Color

This producesIf 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 thefunction:

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 -c c + or -c c . 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

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" ⋮

If you wanted to get fancy, you could even include the ANSI codes for background colors or other more exotic 24-bit color escape codes.

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

(the section on labels provides the fileprefix function)

COLOR

This checks if I've definedon 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%" ⋮

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%" ⋮

msgprefix()

msgsuffix()

remind

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

Theandfunctions have special meaning toallowing the above to

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

calprefix()

calsuffix()

After the addition of $DefaultColor in version 3.3.0

These work wonderfully for the agenda view, but unfortunately don't work for the week/month view (attempting to hack it with the specialandfunctions doesn't work either)

I submitted a patch that has (with modifications) made it into the mainline codebase in version 3.3.0, released January 31st, 2020 . 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%" ⋮

remind

-@

Raw output

As an added bonus in 3.3.0, if you invokewith theparameter, it will colorize agenda items for you in addition to the week/month calendar views.

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

user$ rem -s 2020-01-29 2020/01/29 COLOR work,home 195 510 255 128 0 8:30-11:45am my message user$ 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

-s

The date in YYYY/MM/DD format The SPECIAL type (in this case COLOR ) a comma-separated list of TAG s associated with the reminder the duration in minutes the start-time in minutes-from-midnight (510 = 60 minutes * 8 hours) the $RED , $GREEN , and $BLUE values from the COLOR (255, 128, and 0 here) the body of the message, prepended with the time (if present)

-p

a literal # rem2ps begin 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 ) the days of the week in the local language the next month and its number of days the previous month and its number of days the list of reminders like -s produces a literal # rem2ps end

# rem2ps end

*

SPECIAL reminders

Remind will output as follows:Theoffers a simplified format of only the calendar data:while theoption wraps it in the following meta-data:If producing multiple months of data theindicates 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.

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,

echo Hello in 2 days ' time

REM 1 ONCE RUN backup.sh [$U]

backup.sh 2020-01-01

ONCE

remind

atime

/etc/fstab

noatime

ONCE

the shell will complain on certain days because it lacks a closing single-quote trying to runHowever, you could do something likewhich would runThekeyword will only run the reminder once, even if you invokemore than once in a given day. However, it relies on the(most recent access-time) of the reminder file. Since mysetson all of my mount-points, any reminders marked withrun 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

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

RUN

FSET external_annotation(marker) \ "sed -n '/^BEGIN: *" + marker + "$/,/^END: *" + marker + "$/p' " + \ filedir() + "/annotations.txt" ⋮ REM Feb 18 2020 RUN [external_annotation("FakeCON")]

RUN

and then usingto emit the contentHowever, I found these of limited use, so I don't really usein my day-to-day.

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

remind

REM Jan 18 2020 TAG letter \ MSG Date to see the new Jumanji movie

-s

user$ rem -s 2020-01-01 | > awk '$3 ~ /letter/'

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}'

-r

remind

RUN

[$U - 1]

remind

Whiledoesn'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.I can then use the raw output of theand filter for only those items:so I have a list of what we did during the month. This works well in my reminders too, using a RUN directive (thepreventsfrom executingentries further, and the expressionfinds the last day of the previous month to pass to) 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 ⋮

3

Crontab

These let me typeas a command and get 3 days of colorized agenda output.

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' 45 6 * * 0 rem '*7'

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.

user$ cd ~/.config/remind user$ git init . user$ git add *.rem user$ git commit -m "Initial checkin" ⋮ modify reminder files ⋮ user$ git commit -am "Added John's birthday"

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

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

[filedir()]

spoken.rem

(theexpands to the path of the containingfile)

And to read them:

user$ 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

Mar 1 --1

++[26 + isleap($Uy)]

Maturing bonds

Theresults in the last day of February while thegives 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.

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 [$Um % 6 == 2] \ MSG %"Expect bond check for $31.41%"

$Um % 6 == 2

Notice until action

This uses the SATISFY keyword, repeatedly looking for the next 25of the month until— the month in question is either February or August.

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

+

I use a single "" for Grandma's notification because sometimes holidays interfere with the mail so I appreciate having the extended notice.

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%"

REM Sat MSF Scrub %"[ \ choose((weekno() % 3)+1, \ "Kitchen", \ "Master", \ "Kids'") \ ] grout%"

Selectively getting advanced notice

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:

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%"

Annoying credit-reporting company rules

On Sunday, January 12, 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.

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

Post-holiday candy sales

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.

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%"%

remind

easterdate()

Did any of these reminders trigger?

The post-Easter one requires actual calculations anddoes this without much thought at all thanks to the built-infunction. The rest wouldn't pose any trouble for an ordinary calendar program.

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

$NumTrig

IF

SPECIAL SHADE

FizzBuzz

If any of the events occurred after we snapshot the, then thewill allow for theto happen for the day in question.

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() \ ) \ ) \ )]

doy()

$U

More complex calendar recipes

Shifting garbage day

Thefunction returns the Julian day of the year (as found by taking the current date,, and subtracting December 31of the previous year) and then uses that to calculate the fizzbuzz-ness of the day. Dumb? Yep. Useless? You bet. Fun demonstration? Why not.

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

garbday

OMIT

holiday.rem

PUSH

CLEAR

POP

remind

Change thevalue to your garbage day (Monday = 1, Friday = 5) and set up your holidayentries in your(or hard-code them inside theblock) andwill automatically shift the garbage day reminder based on the holiday schedule.

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) \ )

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%"% ⋮

user$ cat remify_manpages.awk BEGIN { print "SET ManStart date(2020,1,5)" } $7 ~ /^[1678]$/ { printf("REM [ManStart + %i] MSG man %s %%\"%s%%\"%%

", NR - 1, $7, $6) } user$ find /usr/share/man/man[1678]/ -maxdepth 2 -type f \ > | grep -vi perl \ > | sort -R \ > | awk -F'[./]' -f remify_manpages.awk > manpages.rem

man

/usr/share/man/man[1678]/

perl

awk

remind

remind

Creating this manually would have taken a long time. A little shell-script easily generated the file:This finds all of thefiles inremoving the many-related pages I don't care about, shuffles them so I get a nice variety, then uses thescript to transform them into afile. 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 thatcan use.

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%"

OMIT

BEFORE

rem -p3 2019-08-01 | rem2ps | ps2pdf - calendar.pdf

Medicine schedule

Warning: This one gets a bit complex but hopefully walking through it step-by-step helps make it easy to digest. But I've never encountered any other calendar program that came remotely close to letting me do things like this.

(I have someandin there too to jostle things around a bit) I can then useto generate the calendar PDF that I can email out to the rest of the leadership team.

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

⋮ 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) ⋮

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

SATISFY 1

trigdate()

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:Now we need to find the date of the next time we run out of pills.Theproduces no output, but sets the most recent trigger date. Using thefunction, 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 ⋮

-1

OMIT

SATISFY

+1

We evaluate the day when the pills run out, and then back it off by one day (""). Because this uses the single minus, it will skip overdays, including holidays that close the pharmacy & doctor's office, as well as Sundays when the pharmacy is closed. Theensures 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 (). 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 ⋮

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

SATISFY

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):Again, we use theto 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

MostRecentlyGotPills

MostRecentlyGotPills

MedStartDate

MedFreq

RXLeadTime

Then each time I pick up the pills, I update thewith the new start-date. You can use the same template adjusting the, and, 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

September 7th, 2020

September 6th, 2021

SCANFROM

REM Mon 1 Sep SCANFROM [$U-7] SATISFY 1 OMIT [$T] MSG Labor Day

# 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

School N-day cycles ignoring holidays & weekends

If you run this onit will fail to find the event on today but will instead find theinstance on. By starting the search a week back using, we encounter the right date:Similarly, if you want to find both the 4of July and the day on which it gets observed, you might need to look backwards a couple days to find the closest 4(taken from the defs.rem

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

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.

user$ remind -z 5 -k' espeak %s & ' ~/.reminders

remind

~/.reminders

AT

remind

espeak

%s

xmessage

zenity

user$ remind -z5 -k' xmessage %s & ' ~/.reminders

xmessage

notify-send

user$ remind -z5 -k' notify-send %s & ' ~/.reminders

MSG

This will launchwhich will check for changes in yourevery 5 minutes. If anpasses,will spawnpassing the reminder-text (shell-escaped) in place of theso that the reminder text gets spoken. Alternatively you could useorto pop up a dialog(beware that this can end up creating lot ofdialog boxes) or useto display the reminder-text as a notification: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 yourcontents 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

Other utilities

Generating iCalendar/ .ics files

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

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.

user$ rem -s 12 | > TZ= CST8CDT rem2ics -do > reminders.ics

reminders.ics

Other external utilities

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 resultingfile verbatim, and having the wrong timezone can cause lots of problems.

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:

user$ echo 'REM 1 --1 MSG Last' | > remind - 2020-1-31

echo

user$ ( echo 'OMIT Dec 31' > echo 'REM 1 --1 MSG Last' > ) | remind - 2020-12-31

user$ echo REM 1 --1 MSG Last \ > remind - 2020-12-31 '*33'

user$ for m in {1..12} > do > echo "REM 1 --1 MSG Last" | > remind - 2020- ${m} -30 > done

Using a custom .reminders file

Occasionally you need a couple lines to thoroughly test, in which case wrap thestatements in parentheses:Alternatively, sometimes you need to test multiple daysor months

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

user$ cp ~/.reminders{,_orig}

user$ $EDITOR .reminders # create test case

user$ mv ~/{.,working_}reminders

user$ mv ~/.reminders{_orig,} # restore the original

$EDITOR

~/working_reminders

create a new one afresh or modify the existing onetest against that until I get it right. I then snapshot the working reminders,and restore my originalFinally, I use myto copy the working bits frominto 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).

user$ rem -c12 -w$COLUMNS 2020-1-1 | less

user$ rem -p12 2020-1-1 | less

Printing values in MSG output

Alternatively, I will use the -p / -s flags to dump all the reminders in a easy-to-filter format.

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

SET SomeVar REM … MSG SomeVar= [SomeVar] on [$T]/[$U]

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

user$ rem -dextvlf 2>&1 | less

man

x

t

v

Wrap up

turns on all the debugging flags. The-page details each of those options, but it has occasionally helped me spot something I would have otherwise missed. I find the(tracing expression evaluation),(tracing trigger-date computation), and(dumping all variables) help the most.

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.