diff --git a/man/remind.1 b/man/remind.1 index 9a45e248..0ab05ee5 100644 --- a/man/remind.1 +++ b/man/remind.1 @@ -1153,6 +1153,78 @@ Note that \fIduration\fR is specified either in hours and minutes as a duration of 00:00 or 0, then \fBRemind\fR behaves exactly as if no \fBDURATION\fR at all had been present. +.PP +.SH SYNTACTIC SUGAR FOR REM +.PP +The REM command has syntactic sugar to let you express common +reminders. The following pairs of reminders are equivalent: +.PP +.nf + REM First Monday April MSG Foo + REM Mon 1 April MSG Foo + + REM Second Monday May MSG Bar + REM Mon 8 May MSG Bar + + REM Third Monday MSG Third Monday of every month + REM Mon 15 MSG Third Monday of every month + + REM Fourth Sunday June 2025 MSG Fourth Sunday in June 2025 + REM Sun 22 June 2025 MSG Fourth Sunday in June 2025 + + REM Last Monday MSG Last Monday of every month + REM Mon 1 --7 MSG Last Monday of every month + + REM Last Monday April MSG Last Monday of every April + REM Mon 1 May --7 MSG Last Monday of every April + + REM Last Monday December 2025 MSG Last Monday of Dec 2025 + REM Monday 1 Jan 2026 --7 MSG Last Monday of Dec 2025 +.fi +.PP +Note that \fBLast\fR effectively adjusts the month and year, if necessary, to +make the reminder trigger on the correct date. +.PP +The keyword \fBIN\fR is completely ignored, so you can write (for example): +.PP +.nf + REM Second Monday in May MSG foo + REM Last Monday in December 2025 MSG Bar +.fi +.PP +An alternate form of \fIback\fR makes writing reminders easier. +The following pairs of reminders are equivalent: +.PP +.nf + REM ~~1 MSG Last day of every month + REM 1 --1 MSG Last day of every month + + REM May ~~1 MSG Last day of May + REM 1 June --1 MSG Last day of May + + REM Dec 2025 ~~1 MSG Last day of December 2025 + REM 1 Jan 2026 --1 MSG Last day of December 2025 + + REM Apr ~1 OMIT SAT SUN MSG Last workday of April + REM 1 May -1 OMIT SAT SUN MSG Last workday of April + + REM Apr ~~7 MSG Seventh-last day of April + REM 1 May --7 MSG Seventh-last day of April + + REM Apr ~2 OMIT SAT SUN MSG Second-last workday of April + REM 1 May -2 OMIT SAT SUN MSG Second-last workday of April +.fi +.PP +Note that the First/Second/Third/Fourth/Last keywords and the ~ and ~~ form +of \fIback\fR imply a value for the day of the month; as such, they cannot +be combined with a day. Additionally, First/Second/Third/Fourth/Last +must have at least one weekday name. The following are illegal: +.PP +.nf + REM First Monday 3 June MSG Huh? + REM April 3 ~~1 MSG What? + REM Second June MSG Where's the weekday??? +.fi .PP .SH THE SUBSTITUTION FILTER .PP diff --git a/src/dorem.c b/src/dorem.c index ae63ecdf..42ee3dbd 100644 --- a/src/dorem.c +++ b/src/dorem.c @@ -237,6 +237,9 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals) tim->delta = DefaultTDelta; tim->rep = NO_REP; tim->duration = NO_TIME; + trig->need_wkday = 0; + trig->adj_for_last = 0; + if (save_in_globals) { LastTriggerTime = NO_TIME; } @@ -250,6 +253,25 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals) /* Figure out what we've got */ FindToken(DBufValue(&buf), &tok); switch(tok.type) { + case T_In: + /* Completely ignored */ + DBufFree(&buf); + break; + + case T_Ordinal: + DBufFree(&buf); + if (trig->d != NO_DAY) return E_DAY_TWICE; + if (tok.val < 0) { + if (trig->back != NO_BACK) return E_BACK_TWICE; + trig->back = -7; + trig->d = 1; + trig->adj_for_last = 1; + } else { + trig->d = 1 + 7 * tok.val; + } + trig->need_wkday = 1; + break; + case T_Date: DBufFree(&buf); if (trig->d != NO_DAY) return E_DAY_TWICE; @@ -379,6 +401,15 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals) trig->back = tok.val; break; + case T_BackAdj: + DBufFree(&buf); + if (trig->back != NO_BACK) return E_BACK_TWICE; + if (trig->d != NO_DAY) return E_DAY_TWICE; + trig->back = tok.val; + trig->d = 1; + trig->adj_for_last = 1; + break; + case T_Once: DBufFree(&buf); if (trig->once != NO_ONCE) return E_ONCE_TWICE; @@ -480,6 +511,24 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals) } } + if (trig->need_wkday && trig->wd == NO_WD) { + Eprint("Weekday name(s) required"); + return E_PARSE_ERR; + } + + /* Adjust month and possibly year */ + if (trig->adj_for_last) { + if (trig->m != NO_MON) { + trig->m++; + if (trig->m >= 12) { + trig->m = 0; + if (trig->y != NO_YR) { + trig->y++; + } + } + } + trig->adj_for_last = 0; + } /* Check for some warning conditions */ if (!s->nonconst_expr) { diff --git a/src/token.c b/src/token.c index dc1fa9cc..f14fe94e 100644 --- a/src/token.c +++ b/src/token.c @@ -43,30 +43,34 @@ Token TokArray[] = { { "at", 2, T_At, 0 }, { "august", 3, T_Month, 7 }, { "banner", 3, T_Banner, 0 }, - { "before", 3, T_Skip, BEFORE_SKIP }, + { "before", 3, T_Skip, BEFORE_SKIP }, { "cal", 3, T_RemType, CAL_TYPE }, { "clear-omit-context", 5, T_Clr, 0 }, - { "debug", 5, T_Debug, 0 }, + { "debug", 5, T_Debug, 0 }, { "december", 3, T_Month, 11 }, { "do", 2, T_IncludeR, 0 }, - { "dumpvars", 4, T_Dumpvars, 0 }, + { "dumpvars", 4, T_Dumpvars, 0 }, { "duration", 3, T_Duration, 0 }, - { "else", 4, T_Else, 0 }, + { "else", 4, T_Else, 0 }, { "endif", 5, T_EndIf, 0 }, - { "errmsg", 6, T_ErrMsg, 0 }, + { "errmsg", 6, T_ErrMsg, 0 }, { "exit", 4, T_Exit, 0 }, { "february", 3, T_Month, 1 }, + { "first", 5, T_Ordinal, 0 }, { "flush", 5, T_Flush, 0 }, + { "fourth", 6, T_Ordinal, 3 }, { "friday", 3, T_WkDay, 4 }, { "from", 4, T_Scanfrom, FROM_TYPE }, { "fset", 4, T_Fset, 0 }, { "if", 2, T_If, 0 }, { "iftrig", 6, T_IfTrig, 0 }, + { "in", 2, T_In, 0 }, { "include", 3, T_Include, 0 }, { "includecmd", 10, T_IncludeCmd, 0 }, { "january", 3, T_Month, 0 }, { "july", 3, T_Month, 6 }, { "june", 3, T_Month, 5 }, + { "last", 4, T_Ordinal, -1 }, { "march", 3, T_Month, 2 }, { "may", 3, T_Month, 4 }, { "maybe-uncomputable", 5, T_MaybeUncomputable, 0}, @@ -90,12 +94,14 @@ Token TokArray[] = { { "saturday", 3, T_WkDay, 5 }, { "scanfrom", 4, T_Scanfrom, SCANFROM_TYPE }, { "sched", 5, T_Sched, 0 }, + { "second", 6, T_Ordinal, 1 }, { "september", 3, T_Month, 8 }, { "set", 3, T_Set, 0 }, { "skip", 3, T_Skip, SKIP_SKIP }, { "special", 7, T_RemType, PASSTHRU_TYPE }, { "sunday", 3, T_WkDay, 6 }, { "tag", 3, T_Tag, 0 }, + { "third", 5, T_Ordinal, 2 }, { "through", 7, T_Through, 0 }, { "thursday", 3, T_WkDay, 3 }, { "tuesday", 3, T_WkDay, 1 }, @@ -325,6 +331,14 @@ void FindNumericToken(char const *s, Token *t) t->type = T_Back; t->val *= mult; return; + } else if (*s == '~') { + s++; + if (*s == '~') { mult = -1; s++; } + PARSENUM(t->val, s); + if (*s) return; /* Illegal token if followed by non-numeric char */ + t->type = T_BackAdj; + t->val *= mult; + return; } return; /* Unknown token type */ } diff --git a/src/types.h b/src/types.h index 90b904c4..8c296e6b 100644 --- a/src/types.h +++ b/src/types.h @@ -69,6 +69,8 @@ typedef struct { int once; int scanfrom; int from; + int adj_for_last; /* Adjust month/year for use of LAST */ + int need_wkday; /* Set if we *need* a weekday */ int priority; int duration_days; /* Duration converted to days to search */ int eventstart; /* Original event start (datetime) */ @@ -157,7 +159,8 @@ enum TokTypes T_AddOmit, T_WkDay, T_Month, T_Time, T_Date, T_DateTime, - T_Skip, T_At, T_RemType, T_Until, T_Year, T_Day, T_Rep, T_Delta, T_Back, + T_Skip, T_At, T_RemType, T_Until, T_Year, T_Day, T_Rep, T_Delta, + T_Back, T_BackAdj, T_Once, T_Empty, T_Comment, @@ -175,7 +178,10 @@ enum TokTypes T_LongTime, T_OmitFunc, T_Through, - T_MaybeUncomputable + T_MaybeUncomputable, + T_Ordinal, + T_In, + T_LastBack }; /* The structure of a token */ diff --git a/tests/test.cmp b/tests/test.cmp index 8d189e70..a6018743 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -3836,6 +3836,63 @@ trig("Mon", "Tue", "Wed") => ../tests/test.rem(754): Trig = Monday, 18 February, 1990-01-01 ../tests/test.rem(754): Expired +# The new syntactic sugar +REM First Monday January MSG x +../tests/test.rem(757): Trig = Monday, 6 January, 1992 +REM Second Tuesday in April MSG x +../tests/test.rem(758): Trig = Tuesday, 9 April, 1991 +REM Third Wednesday in October MSG x +../tests/test.rem(759): Trig = Wednesday, 16 October, 1991 +REM Fourth Friday in July MSG x +../tests/test.rem(760): Trig = Friday, 26 July, 1991 +REM Last Tuesday in August MSG x +../tests/test.rem(761): Trig = Tuesday, 27 August, 1991 +REM Last Sunday in December MSG x +../tests/test.rem(762): Trig = Sunday, 29 December, 1991 + +REM First Monday January 2000 MSG x +../tests/test.rem(764): Trig = Monday, 3 January, 2000 +REM Second Tuesday in April 2000 MSG x +../tests/test.rem(765): Trig = Tuesday, 11 April, 2000 +REM Third Wednesday in October 2000 MSG x +../tests/test.rem(766): Trig = Wednesday, 18 October, 2000 +REM Fourth Friday in July 2000 MSG x +../tests/test.rem(767): Trig = Friday, 28 July, 2000 +REM Last Tuesday in August 2000 MSG x +../tests/test.rem(768): Trig = Tuesday, 29 August, 2000 +REM Last Sunday in December 2000 MSG x +../tests/test.rem(769): Trig = Sunday, 31 December, 2000 + +REM January ~~1 MSG y +../tests/test.rem(771): Trig = Friday, 31 January, 1992 +REM February ~~1 MSG y +../tests/test.rem(772): Trig = Thursday, 28 February, 1991 +REM February ~~2 MSG y +../tests/test.rem(773): Trig = Wednesday, 27 February, 1991 +REM February ~~3 MSG y +../tests/test.rem(774): Trig = Tuesday, 26 February, 1991 +REM February ~~8 MSG y +../tests/test.rem(775): Trig = Thursday, 21 February, 1991 +REM February ~~20 MSG y +../tests/test.rem(776): Trig = Monday, 10 February, 1992 +PUSH +OMIT 31 March +REM March ~1 MSG y +../tests/test.rem(779): Trig = Saturday, 30 March, 1991 +REM March ~~1 MSG y +../tests/test.rem(780): Trig = Sunday, 31 March, 1991 +POP +REM Dec 2000 ~~1 MSG y +../tests/test.rem(782): Trig = Sunday, 31 December, 2000 +REM Dec 2000 ~~2 MSG y +../tests/test.rem(783): Trig = Saturday, 30 December, 2000 +REM Dec 2000 ~~3 MSG y +../tests/test.rem(784): Trig = Friday, 29 December, 2000 +REM Dec 2000 ~~7 MSG y +../tests/test.rem(785): Trig = Monday, 25 December, 2000 +REM Jan 2001 ~~1 MSG y +../tests/test.rem(786): Trig = Wednesday, 31 January, 2001 + # Don't want Remind to queue reminders EXIT diff --git a/tests/test.rem b/tests/test.rem index 3aefbc67..6ada0f4a 100644 --- a/tests/test.rem +++ b/tests/test.rem @@ -753,6 +753,38 @@ ENDIF REM [trig("Mon", "Tue", "Wed", "Sat")] MSG foo REM [trig("Mon", "Tue", "Wed")] MSG bar +# The new syntactic sugar +REM First Monday January MSG x +REM Second Tuesday in April MSG x +REM Third Wednesday in October MSG x +REM Fourth Friday in July MSG x +REM Last Tuesday in August MSG x +REM Last Sunday in December MSG x + +REM First Monday January 2000 MSG x +REM Second Tuesday in April 2000 MSG x +REM Third Wednesday in October 2000 MSG x +REM Fourth Friday in July 2000 MSG x +REM Last Tuesday in August 2000 MSG x +REM Last Sunday in December 2000 MSG x + +REM January ~~1 MSG y +REM February ~~1 MSG y +REM February ~~2 MSG y +REM February ~~3 MSG y +REM February ~~8 MSG y +REM February ~~20 MSG y +PUSH +OMIT 31 March +REM March ~1 MSG y +REM March ~~1 MSG y +POP +REM Dec 2000 ~~1 MSG y +REM Dec 2000 ~~2 MSG y +REM Dec 2000 ~~3 MSG y +REM Dec 2000 ~~7 MSG y +REM Jan 2001 ~~1 MSG y + # Don't want Remind to queue reminders EXIT