[Remind-Fans] Several questions from a newbie
Ben Love
blove+remind at kylimar.com
Sun May 16 21:43:36 EDT 2010
* Eugene wrote on [2010-05-14 22:52:35 +0400]:
> Hi.
>
> I'm trying to figure out whether Remind can satisfy my needs. There are
> basically three questions. For each of those, I'd like to know whether
> a solution exists and what it is (which keywords/clauses I must use
> etc.).
>
> 1. For each periodic and non-periodic event, I'd like to specify number
> of days N so that the software reminds me about the event N days before
> it occurs.
>
> 2. For each periodic event, I'd like to mark it as DONE (probably
> specifying the date D) so that Remind won't remind me about the
> dates when event occurs and which are <= D.
>
> 3. I'd like the software to continue reminding me about each periodic
> and non-periodic event which occured in the past but wasn't marked as
> DONE.
>
> I believe that Remind is capable to solve 1 and 2, but I doubt about
> the last one. Please help me if you can.
I believe the first case is simply solved with a +N modifier on the
date. However, 2 and 3 are a lot more complicated. I've actually
desired the same behaviour, but I couldn't figure out how to do it with
pure remind. So, here are the perl scripts that I use to make it
happen:
########################## done.pl
#!/usr/bin/perl -w
use warnings;
use strict;
use Tie::File;
# done ID
if (defined($ARGV[0]) && ($ARGV[0] =~ m/^\s*(?:-(?:h|\?|-help))\s*$/))
{
print "Marks the given reminder DONE as of today.\n\n";
print "USAGE: $0 ID\n";
print "ID - The reminder ID (\\w+)\n";
exit 0;
}
if (!defined(%ENV) || !defined($ENV{HOME}))
{
die ("Unabled to determine HOME directory from environment");
}
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(\w+)\s*$/))
{
die ("Reminder ID must be provided");
}
my $id = $1;
my ( $todayday, $todaymonth, $todayyear ) = (localtime)[3,4,5];
$todaymonth += 1;
$todayyear += 1900;
# printf ("TODAY: %04d-%02d-%02d\n", $todayyear, $todaymonth, $todayday);
my %reminders = ();
if (-r "$ENV{HOME}/.remind/.todo")
{
open (TODO, '<', "$ENV{HOME}/.remind/.todo") or die ("Unable to open .todo file: $!");
while (<TODO>)
{
next if m/^\s*$/;
next if m/^\s*#/;
chomp;
if (!(m/^\s*(\w+)\s+(SNOOZE|DONE)\s+(\d\d\d\d-\d\d-\d\d)\s*$/i))
{
print (STDERR "Invalid line in .todo at line $..\n");
next;
}
# print $_ . ": $1, $2, $3\n";
if (!defined($reminders{$1}))
{
$reminders{$1} = ();
}
push (@{$reminders{$1}}, $3 . ' ' . uc($2));
}
close (TODO);
}
# DEBUGGING:
# for my $key (keys (%reminders))
# {
# @{$reminders{$key}} = reverse(sort(@{$reminders{$key}}));
# print "$key:\n";
# foreach(@{$reminders{$key}})
# {
# print " $_\n";
# }
# }
my @lines;
tie @lines, 'Tie::File', "$ENV{HOME}/.remind/.todo" or die ("Unable to tie .todo file: $!");
my $dontadd = 0;
if (defined($reminders{$id}))
{
# it was either snoozed or done already at least once.
# we need to remove the snooze lines.
my @dates = reverse(sort(@{$reminders{$id}}));
foreach my $date (@dates)
{
$date =~ m/(\d\d\d\d)-(\d\d)-(\d\d) (SNOOZE|DONE)/;
my $lastyear = $1;
my $lastmonth = $2;
my $lastday = $3;
my $lasttype = $4;
if ($lasttype eq 'DONE' && ($lastyear > $todayyear || ($lastyear == $todayyear && $lastmonth > $todaymonth) || ($lastyear == $todayyear && $lastmonth == $todaymonth && $lastday >= $todayday)))
{
$dontadd = 1;
}
last if ($lasttype eq 'DONE');
last if ($lastyear < $todayyear || ($lastyear == $todayyear && $lastmonth < $todaymonth) || ($lastyear == $todayyear && $lastmonth == $todaymonth && $lastday < $todayday));
@lines = grep {!/^\s*$id\s+(?i:$lasttype)\s+$lastyear-$lastmonth-$lastday\s*$/} @lines;
}
}
push (@lines, sprintf('%s DONE %04d-%02d-%02d', $id, $todayyear, $todaymonth, $todayday)) unless $dontadd == 1;
exit 0;
########################## done.pl
########################## snooze.pl
#!/usr/bin/perl -w
use warnings;
use strict;
use Date::Calc qw(Add_Delta_Days);
# snooze ID
# snooze ID days
if (defined($ARGV[0]) && ($ARGV[0] =~ m/^\s*(?:-(?:h|\?|-help))\s*$/))
{
print "Marks the given reminder SNOOZEd for specified number of days.\n\n";
print "USAGE: $0 ID [days]\n";
print "ID - The reminder ID (\\w+)\n";
print "days - Positive number of days to snooze for (default: 1)\n";
exit 0;
}
if (!defined(%ENV) || !defined($ENV{HOME}))
{
die ("Unabled to determine HOME directory from environment");
}
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(\w+)\s*$/))
{
die ("Reminder ID must be provided");
}
my $id = $1;
my $days = 1;
if (defined($ARGV[0]))
{
if (!(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify a number for days to snooze");
}
$days = $1;
if ($days < 1)
{
die ("Snooze days is out of range");
}
}
my ( $todayday, $todaymonth, $todayyear ) = (localtime)[3,4,5];
$todaymonth += 1;
$todayyear += 1900;
# printf ("TODAY: %04d-%02d-%02d\n", $todayyear, $todaymonth, $todayday);
my $year;
my $month;
my $day;
($year, $month, $day) = Add_Delta_Days($todayyear, $todaymonth, $todayday, $days);
# printf ("SNOOZE: %04d-%02d-%02d\n", $year, $month, $day);
my %reminders = ();
if (-r "$ENV{HOME}/.remind/.todo")
{
open (TODO, '<', "$ENV{HOME}/.remind/.todo") or die ("Unable to open .todo file: $!");
while (<TODO>)
{
next if m/^\s*$/;
next if m/^\s*#/;
chomp;
if (!(m/^\s*(\w+)\s+(SNOOZE|DONE)\s+(\d\d\d\d-\d\d-\d\d)\s*$/i))
{
print (STDERR "Invalid line in .todo at line $..\n");
next;
}
# print $_ . ": $1, $2, $3\n";
if (!defined($reminders{$1}))
{
$reminders{$1} = ();
}
push (@{$reminders{$1}}, $3 . ' ' . uc($2));
}
close (TODO);
}
# DEBUGGING:
# for my $key (keys (%reminders))
# {
# @{$reminders{$key}} = reverse(sort(@{$reminders{$key}}));
# print "$key:\n";
# foreach(@{$reminders{$key}})
# {
# print " $_\n";
# }
# }
open (TODO, '>>', "$ENV{HOME}/.remind/.todo") or die ("Unable to open .todo file: $!");
# no record of it; so just snooze.
if (!defined($reminders{$id}))
{
printf (TODO '%s SNOOZE %04d-%02d-%02d' . "\n", $id, $year, $month, $day);
}
else
{
# it was either snoozed or done already at least once.
my @dates = reverse(sort(@{$reminders{$id}}));
$dates[0] =~ m/(\d\d\d\d)-(\d\d)-(\d\d) (SNOOZE|DONE)/;
my $lastyear = $1;
my $lastmonth = $2;
my $lastday = $3;
my $lasttype = $4;
if ($year > $lastyear || ($year == $lastyear && $month > $lastmonth) || ($year == $lastyear && $month == $lastmonth && $day > $lastday))
{
printf (TODO '%s SNOOZE %04d-%02d-%02d' . "\n", $id, $year, $month, $day);
}
else
{
printf (STDERR '%s already marked as %s on %04d-%02d-%02d.' . "\n", $id, $lasttype, $lastyear, $lastmonth, $lastday);
}
}
close (TODO);
exit 0;
########################## snooze.pl
########################## due.pl
#!/usr/bin/perl -w
use warnings;
use strict;
# due ID annual/year/annually/yearly due_m due_d orig_m orig_d
# due ID once y m d
if (defined($ARGV[0]) && ($ARGV[0] =~ m/^\s*(?:-(?:h|\?|-help))\s*$/))
{
print "Returns the earliest \"due\" date after all DONEs are accounted for.\n\n";
print "USAGE: $0 ID type [year] month day [month2] [day2]\n";
print "ID - The reminder ID (\\w+)\n";
print "type - The type of reminder it is (annual,once)\n";
print "year - Due year of reminder (depends on type)\n";
print "month - Due month of reminder\n";
print "day - Due day of reminder\n";
print "month2 - Orig month of reminder (depends on type)\n";
print "day2 - Orig day of reminder (depends on type)\n";
exit 0;
}
if (!defined(%ENV) || !defined($ENV{HOME}))
{
die ("Unabled to determine HOME directory from environment");
}
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(\w+)\s*$/))
{
die ("Reminder ID must be provided");
}
my $id = $1;
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*((?:year|annual)(?:ly)?|once)\s*$/i))
{
die ("Must specify check type in: yearly,once");
}
my $type = lc($1);
# print "DEBUG: type: $type\n";
my $year;
my $month;
my $day;
my $origmonth;
my $origday;
if ($type =~ m/once/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify due year when check type is $type.");
}
$year = $1;
die ("Year $year is out of range.") if ($year < 2000 || $year > 2500);
}
if ($type =~ m/(?:year|annual)(?:ly)?|once/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify due month when check type is $type.");
}
$month = $1;
die ("Month $month is out of range.") if ($month < 1 || $month > 12);
}
if ($type =~ m/(?:year|annual)(?:ly)?|once/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify due day when check type is $type.");
}
$day = $1;
die ("Day $day is out of range.") if ($day < 1 || $day > 31);
}
if ($type =~ m/(?:year|annual)(?:ly)?/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify original month when check type is $type.");
}
$origmonth = $1;
die ("Month $origmonth is out of range.") if ($origmonth < 1 || $origmonth > 12);
}
if ($type =~ m/(?:year|annual)(?:ly)?/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify original day when check type is $type.");
}
$origday = $1;
die ("Day $origday is out of range.") if ($origday < 1 || $origday > 31);
}
# if its a once type, the due date is always the due date!
if ($type =~ m/once/)
{
printf ('%04d/%02d/%02d', $year, $month, $day);
exit 0;
}
my %reminders = ();
if (-r "$ENV{HOME}/.remind/.todo")
{
open (TODO, '<', "$ENV{HOME}/.remind/.todo") or die ("Unable to open .todo file: $!");
while (<TODO>)
{
next if m/^\s*$/;
next if m/^\s*#/;
chomp;
if (!(m/^\s*(\w+)\s+(SNOOZE|DONE)\s+(\d\d\d\d-\d\d-\d\d)\s*$/i))
{
print (STDERR "Invalid line in .todo at line $..\n");
next;
}
# print $_ . ": $1, $2, $3\n";
if (!defined($reminders{$1}))
{
$reminders{$1} = ();
}
push (@{$reminders{$1}}, $3 . ' ' . uc($2));
}
close (TODO);
}
# DEBUGGING:
# for my $key (keys (%reminders))
# {
# @{$reminders{$key}} = reverse(sort(@{$reminders{$key}}));
# print "$key:\n";
# foreach(@{$reminders{$key}})
# {
# print " $_\n";
# }
# }
if (defined($reminders{$id}))
{
# it was either snoozed or done already at least once.
my @dates = reverse(sort(@{$reminders{$id}}));
foreach my $date (@dates)
{
$date =~ m/(\d\d\d\d)-(\d\d)-(\d\d) (SNOOZE|DONE)/;
my $lastyear = $1;
my $lastmonth = $2;
my $lastday = $3;
my $lasttype = $4;
next if $lasttype eq 'SNOOZE';
if ($type =~ m/(?:year|annual)(?:ly)?/)
{
# Are reminder and due in the same year?
if ($origmonth > $month || ($origmonth == $month && $origday > $day))
{
# Not in same year.
# find next year, based on DONE date.
# if donedate() > year(donedate())/mm/dd, year = year(donedate())+1
# else year = year(donedate())
if ($lastmonth > $month || ($lastmonth == $month && $lastday >= $day))
{
$year = $lastyear + 2;
}
else
{
#if Don between Rem and Due, still add one year.
if ($lastmonth > $origmonth || ($lastmonth == $origmonth && $lastday >= $origday))
{
$year = $lastyear + 2;
}
else
{
$year = $lastyear + 1;
}
}
}
else
{
# Yes, in same year.
# find next year, based on DONE date.
# if donedate() > year(donedate())/mm/dd, year = year(donedate())+1
# else year = year(donedate())
if ($lastmonth > $month || ($lastmonth == $month && $lastday >= $day))
{
$year = $lastyear + 1;
}
else
{
#if Don between Rem and Due, still add one year.
if ($lastmonth > $origmonth || ($lastmonth == $origmonth && $lastday >= $origday))
{
$year = $lastyear + 1;
}
else
{
$year = $lastyear;
}
}
}
printf ('%04d/%02d/%02d', $year, $month, $day);
exit 0;
}
}
}
my ( $todayday, $todaymonth, $todayyear ) = (localtime)[3,4,5];
$todaymonth += 1;
$todayyear += 1900;
if ($type =~ m/(?:year|annual)(?:ly)?/)
{
# since no record (or only SNOOZEs), assume it was most recently past-due.
if ($todaymonth > $month || ($todaymonth == $month && $todayday >= $day))
{
$year = $todayyear;
}
else
{
$year = $todayyear - 1;
}
}
printf('%04d/%02d/%02d', $year,$month,$day);
exit 0;
########################## due.pl
########################## check.pl
#!/usr/bin/perl -w
use warnings;
use strict;
# check ID annual/year/annually/yearly m d
# check ID once y m d
if (defined($ARGV[0]) && ($ARGV[0] =~ m/^\s*(?:-(?:h|\?|-help))\s*$/))
{
print "Returns the next day the given reminder should be alerted.\n";
print "Output is designed for remind's trigger() function.\n\n";
print "USAGE: $0 ID type [year] month day\n";
print "ID - The reminder ID (\\w+)\n";
print "type - The type of reminder it is (annual,once)\n";
print "year - Year of reminder (depends on type)\n";
print "month - Month of reminder\n";
print "day - Day of reminder\n";
exit 0;
}
sub maxdate
{
my ($yr1, $mo1, $dy1, $yr2, $mo2, $dy2) = @_;
if ($yr1 > $yr2 || ($yr1 == $yr2 && $mo1 > $mo2) || ($yr1 == $yr2 && $mo1 == $mo2 && $dy1 > $dy2))
{
return ($yr1, $mo1, $dy1);
}
else
{
return ($yr2, $mo2, $dy2);
}
}
if (!defined(%ENV) || !defined($ENV{HOME}))
{
die ("Unabled to determine HOME directory from environment");
}
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(\w+)\s*$/))
{
die ("Reminder ID must be provided");
}
my $id = $1;
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*((?:year|annual)(?:ly)?|once)\s*$/i))
{
die ("Must specify check type in: yearly,once");
}
my $type = lc($1);
# print "DEBUG: type: $type\n";
my ( $todayday, $todaymonth, $todayyear ) = (localtime)[3,4,5];
$todaymonth += 1;
$todayyear += 1900;
# printf ("TODAY: %04d-%02d-%02d\n", $todayyear, $todaymonth, $todayday);
my $year;
my $month;
my $day;
if ($type =~ m/once/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify year when check type is $type.");
}
$year = $1;
die ("Year $year is out of range.") if ($year < 2000 || $year > 2500);
}
if ($type =~ m/(?:year|annual)(?:ly)?|once/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify month when check type is $type.");
}
$month = $1;
die ("Month $month is out of range.") if ($month < 1 || $month > 12);
}
if ($type =~ m/(?:year|annual)(?:ly)?|once/)
{
if (!defined($ARGV[0]) || !(shift(@ARGV) =~ m/^\s*(-?\d+)\s*$/))
{
die ("Must specify day when check type is $type.");
}
$day = $1;
die ("Day $day is out of range.") if ($day < 1 || $day > 31);
}
my %reminders = ();
if (-r "$ENV{HOME}/.remind/.todo")
{
open (TODO, '<', "$ENV{HOME}/.remind/.todo") or die ("Unable to open .todo file: $!");
while (<TODO>)
{
next if m/^\s*$/;
next if m/^\s*#/;
chomp;
if (!(m/^\s*(\w+)\s+(SNOOZE|DONE)\s+(\d\d\d\d-\d\d-\d\d)\s*$/i))
{
print (STDERR "Invalid line in .todo at line $..\n");
next;
}
# print $_ . ": $1, $2, $3\n";
if (!defined($reminders{$1}))
{
$reminders{$1} = ();
}
push (@{$reminders{$1}}, $3 . ' ' . uc($2));
}
close (TODO);
}
# DEBUGGING:
# for my $key (keys (%reminders))
# {
# @{$reminders{$key}} = reverse(sort(@{$reminders{$key}}));
# print "$key:\n";
# foreach(@{$reminders{$key}})
# {
# print " $_\n";
# }
# }
# no record of it; so next occurrence is today!
if (!defined($reminders{$id}))
{
($year, $month, $day) = ($todayyear, $todaymonth, $todayday);
}
else
{
# it was either snoozed or done already at least once.
my @dates = reverse(sort(@{$reminders{$id}}));
$dates[0] =~ m/(\d\d\d\d)-(\d\d)-(\d\d) (SNOOZE|DONE)/;
my $lastyear = $1;
my $lastmonth = $2;
my $lastday = $3;
my $lasttype = $4;
# print "$lastyear.$lastmonth.$lastday.$lasttype\n";
if ($lasttype eq 'SNOOZE')
{
# if it was snoozed, the date should be max(snooze, today).
($year, $month, $day) = maxdate($lastyear, $lastmonth, $lastday, $todayyear, $todaymonth, $todayday);
}
elsif ($lasttype eq 'DONE' && $type =~ m/once/)
{
($year, $month, $day) = ($lastyear, $lastmonth, $lastday);
}
elsif ($lasttype eq 'DONE' && $type =~ m/(?:year|annual)(?:ly)?/)
{
# find next year, based on DONE date.
# if donedate() > year(donedate())/mm/dd, year = year(donedate())+1
# else year = year(donedate())
if ($lastmonth > $month || ($lastmonth == $month && $lastday > $day))
{
$year = $lastyear + 1;
}
else
{
$year = $lastyear;
}
# choose max(dategiven, today())
($year, $month, $day) = maxdate($todayyear, $todaymonth, $todayday, $year, $month, $day);
}
}
printf('%04d/%02d/%02d', $year,$month,$day);
exit 0;
########################## check.pl
This is a sample .todo file. It should be generated and updated
directly by the perl scripts. I'm providing it for reference.
########################## .todo
# ^\s*# lines are ignored.
# ^\s*$ lines are ignored.
# FORMAT: (whitespace before/after ignored; whitespace separated)
# ID TYPE DATE
# where:
# ID - case sensitive unique identifer for event (\w+)
# TYPE - (case insensitive)
# "DONE" (for last done date)
# "SNOOZE" (for snooze until date)
# DATE - a date in YYYY-MM-DD format.
Sample DONE 2008-11-01
sample2 DONE 2009-02-06
########################## .todo
And here's the last bit of magic that makes it all work. These are the
remind scripts that call out to perl and do all the checking. I usually
keep this stuff in a separate file and include it in the appropriate
places.
########################## .reminders.todo
; Create a todo list
;;;;; HELPER FUNCTIONS
; returns a INT (number of days behind or ahead of due date)
FSET _due(y,m,d) (date(y,m,d)-today())
FSET _duedt(dt) (dt-today())
; these both return DATEs (i.e. the next DATE to be reminded for)
FSET _todo_yearly(id,m,d) coerce("DATE", shell("~/bin/remind.check " + id + " yearly " + m + " " + d))
FSET _todo_once(id,y,m,d) coerce("DATE", shell("~/bin/remind.check " + id + " once " + y + " " + m + " " + d))
; these both return DATEs (i.e. the "due" DATE of the next reminder)
; due_m, due_y, orig_todo_m, orig_todo_d
FSET _due_yearly(id,m,d,m2,d2) coerce("DATE", shell("~/bin/remind.due " + id + " yearly " + m + " " + d + " " + m2 + " " + d2))
FSET _due_once(id,y,m,d) coerce("DATE", shell("~/bin/remind.due " + id + " once " + y + " " + m + " " + d))
; TODO: due date doesn't work if prev DONE occurred between REMIND and DUE dates.
; due date DOES work if prev DONE is on or after prev DUE date.
;;;;;;;;;TODOs
; Sample ToDo
; Don't forget variable names only have 12 significant characters!
if ! defined("td_sample")
SET td_sample _todo_yearly("Sample",10,01)
SET dd_sample _due_yearly("Sample",12,01,10,01)
preserve td_sample dd_sample
endif
REM [trigger(td_sample)] MSG Sample reminder due on 1 Dec. Starts reminding on 1 Oct. ([_duedt(dd_sample)] day[plural(_duedt(dd_sample))] remaining) (Sample) I always output the "name" that needs to be passed into the perl scripts.
; Sample2
if ! defined("td_sample2")
SET td_sample2 _todo_yearly("sample2",02,01)
preserve td_sample2
endif
REM [trigger(td_sample2)] MSG Simple reminder, but supports marking as done and snoozing. (sample2)
########################## .reminders.todo
Of course, the reminders themselves look very nasty. That's quite a lot
of code for every reminder to be added. I've started writing a simple
generator for them, but I haven't gotten that far yet.
These scripts are what I currently use, so they should work. But
they'll probably require a little customization for your situation (e.g.
file names and locations).
If anyone has a better/simpler way to accomplish this, I'd love to hear
about it.
Ben
--
Ben Love
http://www.kylimar.com/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 902 bytes
Desc: Digital signature
URL: <http://lists.roaringpenguin.com/pipermail/remind-fans/attachments/20100516/5318319e/attachment.pgp>
More information about the Remind-fans
mailing list