commit 22f049ebb4c14247f40e4134fc5754042a63824a Author: Paolo Donadeo Date: Sat Jun 20 00:10:09 2026 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b9a367 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# ---> OCaml +*.annot +*.cmo +*.cma +*.cmi +*.a +*.o +*.cmx +*.cmxs +*.cmxa + +# ocamlbuild working directory +_build/ + +# ocamlbuild targets +*.byte +*.native + +# oasis generated files +setup.data +setup.log + +# Merlin configuring file for Vim and Emacs +.merlin + +# Dune generated files +*.install + +# Local OPAM switch +_opam/ + diff --git a/.ocamlformat b/.ocamlformat new file mode 100644 index 0000000..7f0213f --- /dev/null +++ b/.ocamlformat @@ -0,0 +1,7 @@ +profile = default +version = 0.29.0 + +margin = 120 +break-cases = fit-or-vertical +break-infix = fit-or-vertical +exp-grouping = preserve diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..af659fc --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2025 pdonadeo + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..848df76 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# remind-sync + +Simple program to convert iCalendar files into remind format \ No newline at end of file diff --git a/bin/commandLine.ml b/bin/commandLine.ml new file mode 100644 index 0000000..a68b368 --- /dev/null +++ b/bin/commandLine.ml @@ -0,0 +1,17 @@ +open Cmdliner +open Cmdliner.Term.Syntax + +let ical_file = + let doc = "TODO" in + let docv = "ICAL" in + Arg.(required & pos ~rev:true 0 (some string) None & info [] ~docv ~doc) + +let main_command f = + let doc = "Convert iCalendar files to remind format" in + let man = [] in + Cmd.make (Cmd.info "ical2rem" ~version:"%%VERSION%%" ~doc ~man) + @@ + let+ ical_file = ical_file in + f ical_file + +let main f = Cmd.eval @@ main_command f diff --git a/bin/dune b/bin/dune new file mode 100644 index 0000000..1d64537 --- /dev/null +++ b/bin/dune @@ -0,0 +1,13 @@ +(executable + (public_name remind_sync) + (name main) + (modules main commandLine remind eventPredicates utils) + (preprocess + (pps ppx_deriving.show)) + (libraries + remind_sync + cmdliner + icalendar + timedesc-tzdb.full + timedesc-tzlocal.unix + timedesc)) diff --git a/bin/eventPredicates.ml b/bin/eventPredicates.ml new file mode 100644 index 0000000..3ea0624 --- /dev/null +++ b/bin/eventPredicates.ml @@ -0,0 +1,349 @@ +open Remind_sync +open Icalendar +open Utils + +(* CASE ANALYSIS PREDICATES + - id: P00 + pattern: Ha un SUMMARY? + ics: "SUMMARY:…" + remind_support: nativo + strategia: "REM MSG " + snippet: 'REM 2025-12-25 MSG Natale' + priorita: Subito + + - id: P01 + pattern: All-day singolo + ics: "DTSTART;VALUE=DATE, opzionale DTEND=giorno+1" + remind_support: nativo + strategia: "REM MSG " + snippet: 'REM 2025-12-25 MSG Natale' + priorita: Subito + + - id: P02 + pattern: All-day multi-giorno + ics: "DTSTART;VALUE=DATE + DTEND;VALUE=DATE esclusivo" + remind_support: nativo+accorgimenti + strategia: "espandi in eventi giornalieri; stesso SUMMARY" + snippet: 'REM 2025-08-10 THROUGH 2025-08-15 MSG Ferie # oppure espansione per-giorno' + priorita: Subito + + - id: P03 + pattern: Evento a orario locale + ics: "DTSTART;TZID=… + DTEND oppure DURATION" + remind_support: nativo + strategia: "REM AT [DURATION] MSG …" + snippet: 'REM 2025-10-05 AT 09:00 DURATION 1:00 MSG Riunione' + priorita: Subito + + - id: P04 + pattern: Evento a orario in UTC + ics: "DTSTART/DTEND con suffisso Z" + remind_support: nativo+accorgimenti + strategia: "converti a fuso locale prima di emettere AT" + snippet: 'REM 2025-09-03 AT 06:45 MSG Treno' + priorita: Subito + + - id: P05 + pattern: Ricorrenza settimanale semplice + ics: "RRULE:FREQ=WEEKLY;BYDAY=…;[UNTIL|COUNT]" + remind_support: nativo + strategia: "REM FROM [UNTIL ] AT MSG …" + snippet: 'REM Mon Wed FROM 2025-09-01 UNTIL 2025-10-31 AT 09:00 MSG Standup' + priorita: Subito + + - id: P06 + pattern: Ricorrenza giornaliera semplice + ics: "RRULE:FREQ=DAILY;[UNTIL|COUNT]" + remind_support: nativo + strategia: "REM FROM UNTIL AT EVERY 1 MSG …" + snippet: 'REM FROM 2025-10-01 UNTIL 2025-10-10 AT 08:30 MSG Daily' + priorita: Subito + + - id: P07 + pattern: Ricorrenza mensile per giorno fisso + ics: "RRULE:FREQ=MONTHLY;BYMONTHDAY=…" + remind_support: nativo + strategia: "REM AT FROM/UNTIL" + snippet: 'REM 15 AT 10:00 FROM 2025-01-01 MSG Fatture' + priorita: Dopo + + - id: P08 + pattern: Ricorrenza “n-esimo weekday” del mese + ics: "RRULE:FREQ=MONTHLY;BYDAY=MO;BYSETPOS=3" + remind_support: espansione + strategia: "materializza occorrenze in singoli REM o calcola in codice" + snippet: '# genera REM per ciascuna data calcolata' + priorita: Dopo + + - id: P09 + pattern: Ricorrenza annuale semplice + ics: "RRULE:FREQ=YEARLY;[BYMONTH][BYMONTHDAY]" + remind_support: nativo + strategia: "REM MSG …" + snippet: 'REM Jul 29 MSG Compleanno' + priorita: Dopo + + - id: P10 + pattern: Eccezioni + ics: "EXDATE (una o più), RDATE aggiuntive" + remind_support: nativo+accorgimenti + strategia: "usa OMIT per rimuovere date; aggiungi REM singoli per RDATE" + snippet: 'REM Mon AT 09:00 FROM 2025-09-01 UNTIL 2025-10-31 MSG Standup\nOMIT 2025-10-13' + priorita: Subito + + - id: P11 + pattern: Override/cancellazioni per istanza + ics: "RECURRENCE-ID con contenuto modificato o STATUS:CANCELLED" + remind_support: espansione + strategia: "OMIT la data dalla serie; aggiungi REM singolo con i campi override" + snippet: '# serie + REM specifico per l’istanza' + priorita: Subito + + - id: P12 + pattern: DURATION al posto di DTEND + ics: "DURATION:PT…" + remind_support: nativo + strategia: "mappa su DURATION in REM" + snippet: 'REM 2025-10-05 AT 14:00 DURATION 2:30 MSG Workshop' + priorita: Subito + + - id: P13 + pattern: Allarmi + ics: "VALARM DISPLAY/AUDIO/EMAIL; TRIGGER relativo" + remind_support: parziale + strategia: "mappa 1 allarme principale su WARN; multipli opzionali come REM duplicati HIDE" + snippet: 'REM 2025-10-05 AT 09:00 WARN 15 MSG Riunione' + priorita: Dopo + + - id: P14 + pattern: Fusi orari dichiarati (VTIMEZONE, TZID diversi) + ics: "VTIMEZONE + DTSTART;TZID=…" + remind_support: nativo+accorgimenti + strategia: "normalizza tutto al fuso locale del sistema prima dell’output" + snippet: '# conversione in pre-processing' + priorita: Subito + + - id: P15 + pattern: Partecipanti/organizzatore + ics: "ORGANIZER, ATTENDEE*, PARTSTAT…" + remind_support: non previsto + strategia: "appendi a DESCRIPTION/MSG come testo" + snippet: '# nessuna semantica in Remind' + priorita: Quando serve + + - id: P16 + pattern: Allegati/URL esterni + ics: "ATTACH, URL" + remind_support: non previsto + strategia: "conserva URL in coda al MSG" + snippet: '# link nel testo' + priorita: Quando serve + + - id: P17 + pattern: Meeting online (Google/Teams metadati) + ics: "X-GOOGLE-CONFERENCE, X-MICROSOFT-*" + remind_support: non previsto + strategia: "estrai solo URL di join nel MSG" + snippet: '# riduci al link' + priorita: Quando serve + + - id: P18 + pattern: Visibilità/trasparenza + ics: "CLASS, TRANSP" + remind_support: non previsto + strategia: "ignora o aggiungi prefisso [FREE]/[BUSY] nel MSG" + snippet: '# opzionale' + priorita: Ignora + + - id: P19 + pattern: Stato/versioning + ics: "STATUS, SEQUENCE, CREATED, LAST-MODIFIED" + remind_support: non previsto + strategia: "ignora; usa solo STATUS:CANCELLED per soppressioni" + snippet: '# già coperto in P11' + priorita: Ignora + + - id: P20 + pattern: Categorie/etichette + ics: "CATEGORIES:…" + remind_support: parziale + strategia: "prefisso nel MSG o uso TAG se ti serve filtrare" + snippet: 'REM 2025-10-05 AT 09:00 TAG Work MSG [Work] Riunione' + priorita: Dopo +*) + +type event_description = + [ `Collect_uuid + | `Has_summary + | `All_day_event + | `Expand_recurrence + | `Yearly_simple_date + | `Simple_weekly_recurrence ] +[@@deriving show] + +type error = Invalid_date of string | Skip [@@deriving show] + +let invalid_date s e = + Error (Invalid_date (spf "Invalid date: %s, error: %s" s (Remind_sync.Timedesc.Date.Ymd.show_error e))) + +let skip = Error Skip + +type collector = Remind.rem -> event -> (Remind.rem, error) result + +let collect_uuid rem ev : (Remind.rem, error) result = + let uid = Utils.get_uid ev in + Ok { rem with Remind.original_uuid = uid } + +let collect_summary rem ev : (Remind.rem, error) result = + let summary_opt = + List.find_map + (function + | `Summary (_, s) -> Some s + | _ -> None) + ev.props + in + match summary_opt with + | Some s -> Ok { rem with Remind.summary = s } + | None -> Ok { rem with Remind.summary = "" } + +let collect_start_end_duration rem ev : (Remind.rem, error) result = + let _, dtstart = ev.dtstart in + match dtstart with + | `Date (year, month, day) -> ( + match Timedesc.Date.Ymd.make ~year ~month ~day with + | Error e -> invalid_date "DTSTART" e + | Ok day_start -> + begin match ev.dtend_or_duration with + | None -> { rem with Remind.date = day_start } |> Result.ok + | Some (`Dtend (_, `Datetime _)) -> skip + | Some (`Dtend (_, `Date (year, month, day))) -> + begin match Timedesc.Date.Ymd.make ~year ~month ~day with + | Error e -> invalid_date "DTEND" e + | Ok day_end -> + let day_end = Timedesc.Date.add ~days:(-1) day_end in + if Timedesc.Date.diff_days day_end day_start = 0 then + Ok { rem with Remind.date = day_start; Remind.end_date = None } + else Ok { rem with Remind.date = day_start; Remind.end_date = Some day_end } + end + | Some (`Duration (_, _duration)) -> skip + end) + | `Datetime datetime -> begin + let start_td = Utils.timedesc_of_timestamp datetime in + let rem = { rem with Remind.date = Timedesc.date start_td; Remind.time = Some (Timedesc.time start_td) } in + + match ev.dtend_or_duration with + | None -> Ok rem + | Some (`Dtend (_, date_or_datetime)) -> + begin match date_or_datetime with + | `Datetime datetime -> begin + let end_td = Utils.timedesc_of_timestamp datetime in + let duration = + Timedesc.Span.sub (Timedesc.to_timestamp_single end_td) (Timedesc.to_timestamp_single start_td) + in + let rem = { rem with Remind.duration = Some duration } in + Ok rem + end + | `Date (_year, _month, _day) -> skip + end + | Some (`Duration (_, duration)) -> + let span = Timedesc.Utils.span_of_ptime_span duration in + let rem = { rem with Remind.duration = Some span } in + Ok rem + end + +let expand_recurrence rem ev : (Remind.rem, error) result = + if List.length rem.Remind.recurring > 0 then skip else Ok rem + +let yearly_simple_date rem ev : (Remind.rem, error) result = + match ev.rrule with + | Some (_, (`Yearly, None, None, [])) -> + let month, day = (Timedesc.Date.month rem.Remind.date, Timedesc.Date.day rem.Remind.date) in + Ok { rem with Remind.simple_yearly = Some (month, day) } + | Some _ -> Ok rem + | None -> Ok rem + +let simple_weekly_recurrence rem ev : (Remind.rem, error) result = + (* +type recur = + [ `Byminute of int list + | `Byday of (int * weekday) list + | `Byhour of int list + | `Bymonth of int list + | `Bymonthday of int list + | `Bysecond of int list + | `Bysetposday of int list + | `Byweek of int list + | `Byyearday of int list + | `Weekday of weekday ] +[@@deriving show] + +type freq = [ `Daily | `Hourly | `Minutely | `Monthly | `Secondly | `Weekly | `Yearly ] [@@deriving show] +type count_or_until = [ `Count of int | `Until of utc_or_timestamp_local (* TODO date or datetime *) ] [@@deriving show] +type interval = int [@@deriving show] +type recurrence = freq * count_or_until option * interval option * recur list [@@deriving show] + +QUESTE SONO **TUTTE** LE RRULE NEL MIO DATASET + +RRULE: (`Daily, (Some `Count (11)), None, []) UID: 0b48208b22php2mv6r157rk23v@google.com +RRULE: (`Daily, (Some `Count (11)), None, []) UID: 0cb95edhq1d00bd3gcpomb9mcg@google.com +RRULE: (`Daily, (Some `Count (11)), None, []) UID: 0kbt3i5d6dpq6uncmhlcr335vq@google.com +RRULE: (`Daily, (Some `Until (`Utc (2008-05-11 11:15:00 +00:00))), None, [`Weekday (`Monday)]) UID: dmkfr0h3p1fq6p6v8i62vm1n4k@google.com +RRULE: (`Daily, (Some `Until (`Utc (2008-05-11 15:00:00 +00:00))), None, [`Weekday (`Monday)]) UID: bh5mhev3uq6p5casisrqufksd8@google.com +RRULE: (`Daily, (Some `Until (`Utc (2014-12-24 22:00:00 +00:00))), None, [`Weekday (`Monday)]) UID: tsdjd2jlcsgi0c0ei1celg41v4@google.com +RRULE: (`Daily, (Some `Until (`Utc (2014-12-25 10:00:00 +00:00))), None, [`Weekday (`Monday)]) UID: ha0rjkp62uqh3boc9n6k4f6cuo@google.com +RRULE: (`Daily, (Some `Until (`Utc (2025-05-22 07:00:00 +00:00))), (Some 1), []) UID: 040000008200E00074C5B7101A82E008000000008FD3AF9B24B9DB01000000000000000010000000A152B8147DB736439366702297C68F98 +RRULE: (`Daily, (Some `Until (`Utc (2026-02-04 13:30:00 +00:00))), (Some 1), []) UID: 040000008200E00074C5B7101A82E00800000000CEF108493A94DC010000000000000000100000005D7F32754B6575419990179984830EFC +RRULE: (`Weekly, (Some `Count (3)), None, [`Byday ([(0, `Wednesday)])]) UID: 605de987-4600-419f-a40a-eb585b7e1ba2 +RRULE: (`Weekly, (Some `Count (7)), (Some 2), [`Byday ([(0, `Tuesday)])]) UID: 13C-6A06C880-D-48221A00 +RRULE: (`Weekly, (Some `Until (`Utc (2009-07-31 18:00:00 +00:00))), None, [`Byday ([(0, `Tuesday); (0, `Friday)]); `Weekday (`Monday)]) UID: hrpg4ovdou2ae57pqb9niobb3c@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2013-04-18 17:30:00 +00:00))), None, [`Byday ([(0, `Monday); (0, `Thursday)])]) UID: ool8g85jgfd5db57mdqbkt52nk@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2014-12-20 10:30:00 +00:00))), None, [`Byday ([(0, `Saturday)])]) UID: rr96e7fr98g8j9vner8mmdtfls@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2020-09-16 21:59:59 +00:00))), None, [`Byday ([(0, `Thursday)])]) UID: 5n174r33j7ep7t5eete9307949@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2021-08-25 21:59:59 +00:00))), None, [`Byday ([(0, `Wednesday)]); `Weekday (`Monday)]) UID: 20kb6se0oog5e9hi5l7uu6jiq6@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2021-09-18 21:59:59 +00:00))), (Some 1), [`Byday ([(0, `Sunday)]); `Weekday (`Monday)]) UID: 6sp30e9oc4sjebb264o3gb9kcos3ab9pccoj2b9j6kom2chjcco6ad9nck@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2024-06-12 08:00:00 +00:00))), (Some 4), [`Weekday (`Sunday); `Byday ([(0, `Wednesday)])]) UID: 040000008200E00074C5B7101A82E00800000000DD1EB23CE8ACDA01000000000000000010000000FED71D085A97144F8C716EC999301E3A +RRULE: (`Weekly, (Some `Until (`Utc (2025-02-04 22:59:59 +00:00))), (Some 1), [`Weekday (`Sunday); `Byday ([(0, `Wednesday)])]) UID: _60q30c1g60o30e1i60o4ac1g60rj8gpl88rj2c1h84s34h9g60s30c1g60o30c1g85248hhg6kq30hhn6ork8ghg64o30c1g60o30c1g60o30c1g60o32c1g60o30c1g6kqj4g9g89234chl852kadpk890j2h9m6op44dpn6t238h1k8ks0_R20250129T080000@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2025-06-22 21:59:59 +00:00))), (Some 1), [`Weekday (`Monday); `Byday ([(0, `Monday)])]) UID: 040000008200E00074C5B7101A82E00800000000AE5AF0ED6ADCDB0100000000000000001000000040E4ACABB0843749950BEB4B273F862E +RRULE: (`Weekly, (Some `Until (`Utc (2026-02-24 22:59:59 +00:00))), (Some 2), [`Weekday (`Monday); `Byday ([(0, `Tuesday)])]) UID: fjlqvi1ekuefpa8rb65meoklct@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2026-03-25 00:00:00 +00:00))), None, [`Weekday (`Monday); `Byday ([(0, `Wednesday)])]) UID: 1iue0uq2l3imtfdsff785o9u35@google.com +RRULE: (`Weekly, (Some `Until (`Utc (2026-07-01 09:00:00 +00:00))), None, []) UID: 4479f7fd-7be9-470f-bc10-5ed61636547b +*) + match ev.rrule with + | Some (_, (`Yearly, None, None, [])) -> Ok rem + | Some (_, (freq, count_or_until, interval, recurs)) -> + let _recur = (freq, count_or_until, interval, recurs) in + let uid = Utils.get_uid ev in + Printf.eprintf "RRULE: %s\t\t\tUID: %s\n" (Icalendar.show_recurrence _recur) uid; + skip + (* TODO: implementare *) + | None -> Ok rem + +let all_collectors : (collector * event_description) list = + [ + (collect_uuid, `Collect_uuid); + (collect_summary, `Has_summary); + (collect_start_end_duration, `All_day_event); + (expand_recurrence, `Expand_recurrence); + (yearly_simple_date, `Yearly_simple_date); + (simple_weekly_recurrence, `Simple_weekly_recurrence); + ] + +let remind_of_event (ev : Icalendar.event list) : (Remind.rem, error) result = + let () = if List.length ev = 0 then failwith "No events provided" in + + let master, recurrence = + if List.length ev > 1 then begin + separate_master_and_recurrence ev + end + else begin + let ev = List.hd ev in + (ev, []) + end + in + + let rem = { Remind.empty with Remind.recurring = recurrence } in + + ListLabels.fold_left ~init:(Ok rem) all_collectors ~f:(fun rem_or_error (pred, _desc) -> + match rem_or_error with + | Error e -> Error e + | Ok rem -> pred rem master) diff --git a/bin/main.ml b/bin/main.ml new file mode 100644 index 0000000..75cedfd --- /dev/null +++ b/bin/main.ml @@ -0,0 +1,44 @@ +open Remind_sync +module Map = MoreLabels.Map.Make (String) + +(* + We use a list of events here because there can be multiple events with the same UID, and we want to preserve all of + them. This is important for handling cases where there are multiple events with the same UID but different properties + (e.g., due to updates or recurring events or cancellations). +*) + +let ical2rem ical_file = + let ic = open_in ical_file in + let n = in_channel_length ic in + let s = Bytes.create n in + really_input ic s 0 n; + close_in ic; + let cal_or_error = Icalendar.parse (Bytes.unsafe_to_string s) in + match cal_or_error with + | Error e -> + if e = ": not enough input" then + exit 0 (* This is a common error when the file is empty, so we treat it as a non-error case *) + else prerr_endline ("Error parsing iCalendar file: " ^ e) + | Ok (_, components) -> begin + let events_map : Icalendar.event list Map.t = + ListLabels.fold_left ~init:Map.empty components ~f:(fun acc comp -> + match comp with + | `Event ev -> + let uid = Utils.get_uid ev in + let event_list = Map.find_opt uid acc |> Option.value ~default:[] in + Map.add ~key:uid ~data:(ev :: event_list) acc + | _ -> acc (* Ignore non-event components *)) + in + (* Now revert all the lists *) + let events_map = Map.map ~f:List.rev events_map in + (* Printf.printf "Events: %d\n\n" (Map.cardinal events_map); *) + + Map.iter events_map ~f:(fun ~key:uid ~data:events -> + let rem_or_error = EventPredicates.remind_of_event events in + match rem_or_error with + | Ok rem -> begin Printf.printf "%s\n" (Remind.string_of_rem rem) end + | Error (EventPredicates.Invalid_date s) -> Printf.eprintf "UID: %s Invalid date: %s\n" uid s + | Error Skip -> Printf.eprintf "UID: %s Skipped\n" uid) + end + +let () = if !Sys.interactive then () else exit (CommandLine.main ical2rem) diff --git a/bin/remind.ml b/bin/remind.ml new file mode 100644 index 0000000..e725b5e --- /dev/null +++ b/bin/remind.ml @@ -0,0 +1,62 @@ +open Remind_sync +open Utils + +type rem = { + original_uuid : string; (** Original UID from the iCalendar event *) + summary : string; (** Summary or title of the reminder *) + date : Timedesc.Date.t; (** Date specification (day, month, year) *) + end_date : Timedesc.Date.t option; (** Optional end date for a date range *) + time : Timedesc.Time.t option; (** Optional time specification (hour, minute) *) + duration : Timedesc.Span.t option; (** Optional duration for timed events *) + simple_yearly : (int * int) option; (** Optional simple yearly recurrence (month, day) *) + recurring : Icalendar.event list; + (** List of events that are part of the same recurring series: these are only the overrides, not the master event + *) +} +[@@deriving show] +(** A complete REM command *) + +let empty = + { + original_uuid = ""; + summary = ""; + date = Timedesc.Date.Ymd.make_exn ~year:1970 ~month:1 ~day:1; + end_date = None; + time = None; + duration = None; + simple_yearly = None; + recurring = []; + } + +let render_simple_yearly month day summary = + let month_str = month_of_int month |> string_of_month in + spf "REM %s %d MSG %s" month_str day summary + +let string_of_rem rem = + match rem.simple_yearly with + | Some (month, day) -> render_simple_yearly month day rem.summary + | None -> begin + let b = Buffer.create 256 in + Buffer.add_string b "REM "; + Buffer.add_string b (spf "INFO \"UID: %s\" " rem.original_uuid); + Buffer.add_string b (Timedesc.Date.to_rfc3339 rem.date); + (match rem.time with + | Some time -> + Buffer.add_string b " AT "; + Buffer.add_string b (string_of_time time) + | None -> ()); + (match rem.duration with + | Some duration -> + Buffer.add_string b " DURATION "; + Buffer.add_string b (string_of_span duration); + Buffer.add_string b "" + | None -> ()); + (match rem.end_date with + | Some end_date -> + Buffer.add_string b " THROUGH "; + Buffer.add_string b (Timedesc.Date.to_rfc3339 end_date) + | None -> ()); + Buffer.add_string b " MSG "; + Buffer.add_string b rem.summary; + Buffer.contents b + end diff --git a/bin/utils.ml b/bin/utils.ml new file mode 100644 index 0000000..fe0322a --- /dev/null +++ b/bin/utils.ml @@ -0,0 +1,167 @@ +open Remind_sync +open Icalendar + +type months = Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec + +let month_of_int = function + | 1 -> Jan + | 2 -> Feb + | 3 -> Mar + | 4 -> Apr + | 5 -> May + | 6 -> Jun + | 7 -> Jul + | 8 -> Aug + | 9 -> Sep + | 10 -> Oct + | 11 -> Nov + | 12 -> Dec + | _ -> failwith "Invalid month number" + +let string_of_month = function + | Jan -> "Jan" + | Feb -> "Feb" + | Mar -> "Mar" + | Apr -> "Apr" + | May -> "May" + | Jun -> "Jun" + | Jul -> "Jul" + | Aug -> "Aug" + | Sep -> "Sep" + | Oct -> "Oct" + | Nov -> "Nov" + | Dec -> "Dec" + +let spf = Printf.sprintf + +let get_uid ev = + let _, uid = ev.uid in + uid + +(* Questa funzione serve solo da esempio per copia e incolla *) +let unpack_date_or_datetime (d_or_dt : Icalendar.date_or_datetime) = + match d_or_dt with + | `Datetime (`Local _ptime_ts) -> () + | `Datetime (`Utc _ts) -> () + | `Datetime (`With_tzid (_ts, (_b, _tz_name))) -> () + | `Date (_year, _month, _day) -> () + +(* Questa funzione serve solo da esempio per copia e incolla *) +let unpack_dtend_or_duration dtend_or_dur = + match dtend_or_dur with + | None -> () + | Some (`Dtend (_, date_or_datetime)) -> unpack_date_or_datetime date_or_datetime + | Some (`Duration (_, _duration)) -> () + +let string_of_time (t : Timedesc.Time.t) : string = + let view = Timedesc.Time.view t in + let hour, minute = (view.Timedesc.Time.hour, view.Timedesc.Time.minute) in + spf "%02d:%02d" hour minute + +let string_of_span (sp : Timedesc.Span.t) : string = + let view = Timedesc.Span.For_human.view sp in + let hours, minutes = (view.Timedesc.Span.For_human.hours, view.Timedesc.Span.For_human.minutes) in + spf "%02d:%02d" hours minutes + +let timedesc_of_timestamp (ts : timestamp) : Timedesc.t = + let local_tz = Timedesc.Time_zone.local_exn () in + match ts with + | `Local t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz + (* this case is not present in my current dataset… *) + | `Utc t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz + | `With_tzid (ts, (_b, tz_name)) -> + (* Qui il timestamp è SCRITTO come se fosse UTC (+00:00) ma in realtà va interpretato con + il fuso orario indicato da tz_name. *) + let tz = Timedesc.Time_zone.make_exn tz_name in + let wrong_ts = Timedesc.Utils.timestamp_of_ptime ts in + let date = Timedesc.date (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in + let year, month, day = (Timedesc.Date.year date, Timedesc.Date.month date, Timedesc.Date.day date) in + let time = Timedesc.time_view (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in + let hour, minute, second = (time.Timedesc.Time.hour, time.Timedesc.Time.minute, time.Timedesc.Time.second) in + Timedesc.make_exn ~year ~month ~day ~hour ~minute ~second ~tz () + +let timedesc_of_date_or_datetime (t : date_or_datetime) : Timedesc.t = + match t with + | `Datetime (`Local _ptime_ts) -> + (* this case is not present in my current dataset… *) + failwith "Unhandled case: `Local datetime" + | `Datetime (`Utc ts) -> + Timedesc.Utils.timestamp_of_ptime ts + |> Timedesc.of_timestamp_exn ~tz_of_date_time:(Timedesc.Time_zone.local_exn ()) + | `Datetime (`With_tzid (ts, (_b, tz_name))) -> + (* Qui il timestamp è SCRITTO come se fosse UTC (+00:00) ma in realtà va interpretato con + il fuso orario indicato da tz_name. *) + let tz = Timedesc.Time_zone.make_exn tz_name in + let wrong_ts = Timedesc.Utils.timestamp_of_ptime ts in + let date = Timedesc.date (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in + let year, month, day = (Timedesc.Date.year date, Timedesc.Date.month date, Timedesc.Date.day date) in + let time = Timedesc.time_view (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in + let hour, minute, second = (time.Timedesc.Time.hour, time.Timedesc.Time.minute, time.Timedesc.Time.second) in + Timedesc.make_exn ~year ~month ~day ~hour ~minute ~second ~tz () + | `Date (year, month, day) -> + Timedesc.make_exn ~year ~month ~day ~hour:0 ~minute:0 ~second:0 ~tz:(Timedesc.Time_zone.local_exn ()) () + +let get_exdates ev = + let event_props = ev.props in + let dates_or_datetimes = + List.filter_map + (fun prop -> + match prop with + | `Exdate (_, dates) -> Some dates + | _ -> None) + event_props + in + ListLabels.fold_left ~init:[] dates_or_datetimes ~f:(fun acc dates -> + let added = + match dates with + | `Datetimes ts_list -> List.map (fun ts -> `Datetime ts) ts_list + | `Dates date_list -> List.map (fun date -> `Date date) date_list + in + added @ acc) + |> List.map timedesc_of_date_or_datetime + +let get_rdates ev = + let event_props = ev.props in + let dates_or_datetimes = + List.filter_map + (fun prop -> + match prop with + | `Rdate (_, dates) -> Some dates + | _ -> None) + event_props + in + ListLabels.fold_left ~init:[] dates_or_datetimes ~f:(fun acc dates -> + let added = + match dates with + | `Datetimes ts_list -> List.map (fun ts -> `Datetime ts) ts_list + | `Dates date_list -> List.map (fun date -> `Date date) date_list + | `Periods _ -> + (* Ignored for now, does not appear in my current dataset *) + failwith "Unhandled case: `Periods in RDATE" + in + added @ acc) + |> List.map timedesc_of_date_or_datetime + +let get_recurrence_id ev = + List.find_map + (fun prop -> + match prop with + | `Recur_id (_, date_or_datetime) -> Some date_or_datetime + | _ -> None) + ev.props + +let separate_master_and_recurrence (events : Icalendar.event list) : Icalendar.event * Icalendar.event list = + (* List.iteri (fun i e -> Printf.eprintf "%02d: %s\n" (i + 1) (Icalendar.show_component (`Event e))) events; *) + let recur_ids = List.map (fun ev -> (ev, get_recurrence_id ev)) events in + + let master_and_recurrences = + List.partition_map + (fun (ev, recur_id_opt) -> + match recur_id_opt with + | None -> Right ev + | Some _ -> Left ev) + recur_ids + in + match master_and_recurrences with + | [], _ -> failwith "No master event found" + | master :: _, recurrences -> (master, recurrences) diff --git a/contrib/remind/remind_manual.txt b/contrib/remind/remind_manual.txt new file mode 100644 index 0000000..1373da9 --- /dev/null +++ b/contrib/remind/remind_manual.txt @@ -0,0 +1,5302 @@ +REMIND(1) VERSION 06.02.05 REMIND(1) + +NAME + remind - a sophisticated reminder service + +THE BOOK OF REMIND + This man page is a good reference for Remind. However, if you are a novice wishing to learn Remind, I suggest + downloading "The Book of Remind" from the Remind home page at https://dianne.skoll.ca/projects/remind/ + +SYNOPSIS + remind [options] filename [date] [*rep] [time] + +DESCRIPTION + Remind reads the supplied filename and executes the commands found in it. The commands are used to issue re‐ + minders and alarms. Each reminder or alarm can consist of a message sent to standard output, or a program to be + executed. + + If filename is specified as a single dash '-', then Remind takes its input from standard input. In this case, + if Remind has been compiled against the GNU Readline library, it will use Readline to give you an interactive + line-editing interface. + + If filename happens to be a directory rather than a plain file, then Remind reads all of the files (but not any + subdirectories!) in that directory that match the pattern "*.rem". The files are read in sorted order; the + sort order may depend on your locale, but should match the sort order used by the shell to expand "*.rem". + + Remind reads its files starting from the beginning to the end, or until it encounters a line whose sole content + is "__EOF__" (without the quotes.) Anything after the __EOF__ marker is completely ignored. + +MODES OF OPERATION + Remind has four major modes of operation: + + Agenda Mode + Agenda mode is the default mode. In this mode, Remind prints today's reminders on standard output and + exits. It may fork a background process to pop up queued reminders for later in the day. + + Calendar Mode + In this mode, Remind generates a calendar either by drawing it in the terminal or by sending data in com‐ + puter-readable format to a back-end program that actually draws the calendar. + + Daemon Mode + This is a special mode of operation in which Remind is invoked by a front-end and runs as a daemon, ac‐ + cepting requests from the front-end and sending messages back to the front-end. TkRemind uses Remind in + daemon mode. + + Purge Mode + In this mode, Remind produces no output, but creates new versions of its input files with all expired re‐ + minders commented out. + +OPTIONS + Remind has a slew of options. If you're new to the program, ignore them for now and skip to the section "RE‐ + MINDER FILES". + + -n The -n option causes Remind to print the next occurrence of each reminder in a simple calendar format. + You can sort this by date by piping the output through sort(1). Note that the -n option causes any -g + option to be ignored and also implicitly enables the -o option. + + -j[n] Runs Remind in "purge" mode to get rid of expired reminders. See the section PURGE MODE for details. + + -r The -r option disables RUN directives and the shell() function. + + -c[flags]n + The -c option causes Remind to produce a calendar that is sent to standard output. If you supply a num‐ + ber n, then a calendar will be generated for n months, starting with the current month. By default, a + calendar for only the current month is produced. This option implicitly enables the -o option. + + You can precede n (if any) with a set of flags. The flags are as follows: + + '+' causes a calendar for n weeks to be produced. + + 'a' causes Remind to display reminders on the calendar on the day they actually occur as well as on + any preceding days specified by the reminder's delta. This also causes Remind to include text + outside %"...%" sequences that would otherwise be removed (though the actual %" markers themselves + are removed.) + + 'l' causes Remind to use VT100 line-drawing characters to draw the calendar. The characters are hard- + coded and will only work on terminals that emulate the VT00 line-drawing character set. + + 'u' is similar to 'l', but causes Remind to use UNICODE line-drawing characters to draw the calendar. + The characters are hard-coded and will only work on terminals that are set to UTF-8 character en‐ + coding. This flag also enables the use of the UNICODE "left-to-right" mark that can fix up for‐ + matting problems with right-to-left languages in the calendar display. + + 'z' has the effect of setting the system variable $TerminalHyperlinks to 1. See the documentation of + this variable in the section "SYSTEM VARIABLES" + + 'c' causes Remind to use VT100 escape sequences to approximate SPECIAL COLOR reminders. Note that + this flag is kept for backwards-compatibility; you should use the -@[n][,m][,b] command-line op‐ + tion instead. + + In a UTF-8 locale, Remind will use "left-to-right marks" when creating a calendar with the -c option. + Some terminals don't handle this correctly and garble the rendering of the calendar; see the documenta‐ + tion of $SuppressLRM in the section "SYSTEM VARIABLES" for a workaround. + + -@[n][,m][,b] + Tells Remind to approximate SPECIAL COLOR and SHADE reminders using VT100 escape sequences. The approxi‐ + mation is (of necessity) very coarse, because the VT100 only has eight different color sequences, each + with one of two brightnesses. A color component greater than 64 is considered "on", and if any of the + three color components is greater than 128, the color is considered "bright". + + If you supply the optional numeric parameters, the have the following meanings: n=0 tells Remind to use + the standard 16 VT100 colors. n=1 tells it to use an extended 256-color palette supported by many termi‐ + nal emulators such as xterm. And n=2 tells it to use escape sequences that support true 24-bit colors, + again supported by many terminal emulators such as xterm. + + If the optional m parameter is supplied following a comma, then m=0 tells Remind that the terminal back‐ + ground is dark, and Remind will brighten up dark colors to make them visible. If m=1, then Remind as‐ + sumes the terminal background is light and it will darken bright colors to make them visible. If m is + specified as 2, then Remind does not perform any adjustments, and some reminders may be hard or impossi‐ + ble to see if the color is too close to the terminal background color. If you supply the letter t rather + than a number, then Remind attempts to guess the background color of the terminal, even if stdout is not + a terminal. + + On startup, if the standard output is a terminal, Remind attempts to determine if the terminal background + is dark or light by sending a special escape sequence to determine the background color. The m parameter + can override this check (or force it if m is given as t.) + + If the optional b parameter is supplied following a comma, then b=0 tells Remind to ignore SPECIAL SHADE + reminders (the default) and b=1 tells Remind to respect SPECIAL SHADE reminders by emitting VT100 escape + codes to color the background of the calendar cell. Note that SHADE does not work well unless you are + using the extended 256-color palette (n=1) or the true 24-bit colors (n=2). Note that for calendar cells + that are shaded, the clamping mechanism described earlier for m=0 or m=1 is skipped; it is assumed that + if you set both the foreground color of a reminder and the background color of a cell, then you know what + you are doing. + + -wcol[,pad[,spc[,spc2]]]] + The -w option specifies the output width, padding and spacing of the formatted calendar output. Col + specifies the number of columns in the output device. If col is not specified, or is specified as 0, it + defaults to the larger of 71 or the actual width of your terminal, or to 80 if standard output is not a + terminal. If col is specified as the letter t, then Remind attempts to get the width of the /dev/tty + terminal device. This is useful, for example, if you pipe calendar output into less; even though stan‐ + dard output is a pipe, you want the calendar to be sized correctly for your terminal window: + + remind -c -wt .reminders | less + + Note that the value of col is also used to set the system variable $FormWidth, which is initialized to + col - 8. See "SYSTEM VARIABLES" for details. + + Pad specifies how many lines to use to "pad" empty calendar boxes. This defaults to 5. If you have many + reminders on certain days that make your calendar too large to fit on a page, you can try reducing pad to + make the empty boxes smaller. Spc specifies how many blank lines to leave between the day number and the + first reminder entry. It defaults to 1. spc2 may be 0 or 1 and it specifies whether or not blank lines + should be printed in between reminders on the same day. The default is 1, which causes the blank lines + to be printed. + + Any of col, pad or spc can be omitted, providing you provide the correct number of commas. Don't use any + spaces in the option. + + -s[a]n The -s option is very similar to the -c option, except that the output calendar is not formatted. It is + listed in a "simple format" that can be used as input for more sophisticated calendar-drawing programs. + If n starts with "+", then it is interpreted as a number of weeks. This option also implicitly enables + the -o option. + + If you immediately follow the s with the letter a, then Remind displays reminders on the calendar on the + day they actually occur as well as on any preceding days specified by the reminder's delta. + + -p[a][p][p][q][+]n + The -p option is very similar to the -s option, except that the output contains additional information + for use by a back-end such as the Rem2PS program, which creates a PostScript calendar, and various other + back-end programs. If n starts with "+", then it specifies a number of weeks rather than a number of + months, and back-ends are expected to produce weekly calendars. Note that not all back-ends support + weekly calendars; currently, only rem2pdf and rem2html do. Specifying a weekly calendar implicitly en‐ + ables the pure JSON interchange format, similar to -ppp. + + The format of the -p output is described in the rem2ps(1) man page. If you immediately follow the p with + the letter a, then Remind displays reminders on the calendar on the day they actually occur as well as on + any preceding days specified by the reminder's delta. If you follow the p with another p, then Remind + uses a more comprehensive JSON-based format rather than the "simple calendar" format. This format is + also documented in the rem2ps(1) man page. Finally, if you use three p's, as in -ppp, then Remind uses a + pure JSON format, again documented in rem2ps(1). If you include a q letter with this option, then the + usual calendar-mode substitution filter is disabled and the %"...%" sequences are preserved in the out‐ + put. + + Note that to pass INFO strings to a back-end, you must use -pp or -ppp. The simpler -p format is not ca‐ + pable of transmitting the INFO strings to the back-end. + + The -p, -pp and -ppp options implicitly enable the -o option. + + Note that the -pp or -ppp options also enable the -l option. + + -l If you use the -l option in conjunction with the -p option, then Remind outputs additional information + for back-end programs such as rem2ps. This additional information lets the back-end programs correlate a + reminder with the source file and line number that produced it. + + -m The -m option causes the -c or -p options to produce a calendar whose first column is Monday rather than + Sunday. (This conforms to the international standard.) + + -v The -v option makes the output of Remind slightly more verbose. Currently, this causes Remind to echo a + bad line in case of an error, and to print a security message if a script tests the $RunOff system vari‐ + able. + + -o The -o option causes Remind to ignore all ONCE directives. Note that ONCE is also ignored if any of the + -c, -n, -p, or -s options is used, if a repetition factor *n is used, or if a date other than today's + date is specified on the command-line. + + -t The -t option causes Remind to trigger all non-expired reminders, regardless of the delta supplied for + each reminder. + + -tn If you supply a number n after the -t option, then Remind pretends that every REM command has a delta of + ++n, regardless of any existing delta. + + -tz If you supply the letter z after the -t option, then Remind sets all REM statements' deltas to zero, re‐ + gardless of the value supplied in the REM statement itself. In effect, this disables all deltas of the + form +n and ++n. + + -tt[n] The -tt option causes Remind to assume a default delta of n minutes for all timed reminders. If -tt is + given with no n, a default delta of 5 minutes is used. + + -h The -h option ("hush...") suppresses certain warning and information messages. In particular, if no re‐ + minders are triggered, this mode produces no output. + + -a The -a option causes Remind not to immediately trigger timed reminders that trigger on the current day. + It also causes Remind not to place timed reminders in a calendar. If you supply two or more -a options, + then Remind will trigger timed reminders that are in the future, but will not trigger timed reminders + whose time has passed. (Regardless of how many -a options you supply, Remind will not include timed re‐ + minders in the calendar if at least one -a option is used.) + + -q The -q option causes Remind not to queue timed reminders for later execution. + + -f The -f option causes Remind to remain in the foreground when processing queued reminders, rather than + forking off a background process to handle them. + + -e The -e option diverts error messages (normally sent to the standard error stream) to the standard output + stream. + + -dchars + The -d option enables certain debugging modes. The chars specify which modes to enable: + + e Echo all input lines + + x Trace all expression evaluation + + t Display all trigger date computation + + v Dump the variable table after execution of the reminder script + + l Echo lines when displaying error messages + + f Trace the reading of reminder files + + p Issue warnings if a POP-OMIT-CONTEXT, POP-VARS or POP-FUNCS matches a corresponding PUSH that is + in a different file. + + s Trace expression parsing and display the internal expression node tree. This is unlikely to be + useful unless you are working on Remind's expression evaluation engine. + + h Dump hash-table statistics on exit. + + u When Remind exits, print a list of variables that were SET, but not subsequently used. + + Note that the u debugging flag may produce spurious warnings. For example, this sequence of com‐ + mands: + + DEBUG +u + SET a 1 + IFTRIG Wed + MSG a = [a] + ENDIF + + will issue a warning about a being unused unless it is run on a Wednesday. + + n Print debugging information about why Remind considers an expression to be "non-constant" + + q Output a TRANSLATE command each time the _() built-in function is called or the %(...) substitu‐ + tion sequence is encountered. + + -g[a|d[a|d[a|d[a|d]]]] + Normally, reminders are issued in the order in which they are encountered in the reminder script. The -g + option cause Remind to sort reminders by date and time prior to issuing them. The optional a and d char‐ + acters specify the sort order (ascending or descending) for the date, time and priority fields. See the + section "SORTING REMINDERS" for more information. + + Note that -g is ignored if you use the -n option. + + -b[n] Set the time format for the calendar and simple-calendar outputs. N can range from 0 to 2, with the de‐ + fault 0. A value of 0 causes times to be inserted in 12-hour (am/pm) format. 1 causes times to be in‐ + serted in 24-hour format, and 2 inhibits the automatic insertion of times in the calendar output. + + -x[n] Sets the iteration limit for the SATISFY clause of a REM command. Defaults to 1000. + + -kcmd Instead of simply printing MSG-type reminders, this causes them to be passed to the specific cmd. You + must use '%s' where you want the body to appear, and may need to enclose this option in quotes. Note + that all shell characters in the body of the reminder are escaped with a backslash, and the entire body + of the reminder is passed as a single argument. Note that this option overrides the -r option and the + RUN OFF command. + + As an example, suppose you have an X Window program called xmessage that pops up a window and displays + its invocation arguments. You could use: + + remind '-kxmessage %s &' ... + + to have all of your MSG-type reminders processed using xmessage. + + A word of warning: It is very easy to spawn dozens of xmessage processes with the above technique. So be + very careful. Because all shell and whitespace characters are escaped, the program you execute with the + -k option must be prepared to handle the entire message as a single argument. + + If you follow the -k option with a colon, then the command is applied only to queued timed reminders. + Normal reminders are handled as usual. In the above example, if you want normal reminders to simply be + displayed as usual, but queued reminders to be sent to notify-send, you could use: + + remind '-k:notify-send %s &' ... + + You use both -kcmd1 and -k:cmd2 to use different commands for queued versus non-queued reminders. + + -z[n] Runs Remind in "daemon mode". If n + is supplied, it specifies how often (in minutes) Remind should wake up to check if the reminder script + has been changed. N defaults to 1, and can range from 1 to 60. Note that the use of the -z option also + enables the -f option. + + If Remind is compiled on a system that supports inotify(7), then if the reminder script supplied on the + command-line is actually a directory, Remind additionally checks if all files within that directory have + been modified since startup. + + If you supply the option -zj, Remind runs in a special mode called server mode. This is documented in + the tkremind man page; see tkremind(1). The older server mode option -z0 still works, but is deprecated; + it uses an ad-hoc method to communicate with the client rather than using JSON to communicate with the + client. + + -uname Runs Remind with the uid and gid of the user specified by name. The option changes the uid and gid as + described, and sets the environment variables HOME, SHELL and USER to the home directory, shell, and user + name, respectively, of the specified user. LOGNAME is also set to the specified user name. This option + is meant for use in shell scripts that mail reminders to all users. Note that as of Remind 3.00.17, us‐ + ing -u implies -r -- the RUN directive and shell() functions are disabled. However, if you prefix name + with a +-sign, then RUN and shell() are not disabled. That is, -uwhatever switches the user to whatever + and disables RUN, whereas -u+whatever switches the user to whatever but leaves RUN enabled. + + Non-root users can also use the -u option. However, in this case, it only changes the environment vari‐ + ables as described above. It does not change the effective uid or gid. + + -+username + Causes Remind to trust files owned by the user username. Normally, if Remind reads a file that you do + not own, it disables RUN and the shell() function. This option causes it to also trust files owned by + username. You can supply multiple -+ options to trust multiple users, up to a limit of 20 trusted users. + + -y Causes Remind to synthesize a tag for any reminder that lacks a TAG clause. + + -ivar=expr + Sets the value of the specified var to expr, and preserves var. Expr can be any valid Remind expression. + See the section "INITIALIZING VARIABLES ON THE COMMAND LINE" for more details. If you omit the =expr + part, then var is initialized to 0. In other words, -ivar is exactly the same as -ivar=0. + + -ifunc(args)=definition + Allows you to define a function on the command line. + + If you supply a date on the command line, it must consist of day month year, where day is the day of the month, + month is at least the first three letters of the English name of the month, and year is a year (all 4 digits) + from 1990 to about 2075. You can leave out the day, which then defaults to 1. + + If you do supply a date on the command line, then Remind uses it, rather than the actual system date, as its no‐ + tion of "today." This lets you create calendars for future months, or test to see how your reminders will be + triggered in the future. Similarly, you can supply a time to set Remind's notion of "now" to a particular time. + Supplying a time on the command line also implicitly enables the -q option and disables the -z option. The time + may be specified in 24-hour format (e.g., 13:20) or common "AM/PM" format (1:20pm). + + If you would rather specify the date more succinctly, you can supply it as YYYY-MM-DD or YYYY/MM/DD. You can + even supply a date and time on the command line as one argument: YYYY-MM-DD@HH:MM. + + In addition, you can supply a repeat parameter, which has the form *rep. This causes Remind to be run rep + times, with the date incrementing on each iteration. You may have to enclose the parameter in quotes to avoid + shell expansion. See the subsection "Repeated Execution" in the section "CALENDAR MODE" for more information. + +LONG OPTIONS + Remind supports the following long options, which are case-sensitive: + + --version + The --version option causes Remind to print its version number to standard output and then exit. + + --hide-completed-todos + In Calendar Mode, Remind normally shows all TODOs. If you supply this option, the it will not show TODOs + that have been marked as completed. + + --only-todos + Only issue TODO-type reminders. + + --only-events + Do not issue TODO-type reminders. + + --json In Agenda Mode, output JSON instead of the normal text-mode output. --json also disables sorting (the -g + option) and disables queueing (the -q option). See the section "AGENDA MODE JSON OUTPUT" for more de‐ + tails. The --json option is ignored in Calendar Mode. Note that in JSON mode, the output from any RUN- + type reminder that would normally appear on standard output is redirected to standard error instead; this + is so that RUN-type reminders don't mess up the output and cause invalid JSON to be produced on standard + output. + + --print-errs + The --print-errs option causes Remind to print all possible error messages to standard output and then + exit. The messages are printed in a format suitable for the first argument of a TRANSLATE command. If + you TRANSLATE the error messages, then Remind will use the translated versions when outputting error and + warning messages. See also TRANSLATE GENERATE in the section "THE TRANSLATION TABLE". + + Note that if an untranslated message contains printf-style formatting sequences like "%s" or "%d", then + the translated message must contain the same sequences in the same order, or Remind will ignore it and + use the original untranslated message. + + --print-config-cmd + This option causes Remind to print the exact ./configure command that was used when Remind was built. + You can use this to build a new version of Remind using the same configuration as an existing one by run‐ + ning: + + eval `remind --print-config-cmd` + + from the top-level Remind source directory. (However, it's safer to simply run remind --print-config-cmd + and then type in the command once you've verified that it looks OK.) + + --print-tokens + The --print-tokens option causes Remind to print the tokens used by the parser, built-in function names, + and system variable names to standard output and then exit. This output is designed to make it easy to + create a syntax-highlighting file for various text editors. The output can be modified by hand or by a + script into a syntax-highlighting file with relative ease. + + --max-execution-time=n + Limit the total execution time (as measured by the wall clock) to n seconds. This is useful if Remind is + invoked on potentially-untrustworthy files that could attempt to use a lot of resources. Note that the + limit n is approximate and Remind might execute for one or two more seconds before it is killed. If n is + specified as zero, then no limit is applied, just as if the option had not been used at all. + + If a limit is applied, it applies only to the foreground run of Remind. If Remind finishes processing + the script and then starts handling queued reminders, the time limit is reset to no limit. + + --max-expr-complexity=n + Limit the total complexity of expression valuation for a given line in a script to n nodes. Roughly + speaking, each function call, operator, constant, variable reference, etc corresponds to one expression + node. By default, the limit is set to 10000000 (ten million). You can explicitly set it to zero if you + don't want any limit to apply. The default limit of ten million should never be triggered by any sensi‐ + ble Remind script, however, and we don't recommend changing the limit. + + --test The --test long option is only for use by the acceptance tests run by "make test". Do not use this op‐ + tion in production. + + --flush + The --flush long option makes Remind's standard output and standard error streams unbuffered. It is + mostly used for testing and should probably not be used in production. + +REMINDER FILES + Remind uses scripts to control its operation. You can use any text editor capable of creating plain-text files + to create a Remind script. The commands inside a script can range from the very simple and almost immediately + understandable: + + REM Mar 31 MSG International Transgender Day of Visibility + + to the baroque and obscure: + + REM [date(thisyear, 1, 1) + 180] ++5 OMIT \ + sat sun BEFORE MSG [ord(thisyear-1980)] payment due %b! + + A reminder file consists of commands, with one command per line. Several lines can be continued using the back‐ + slash character, as in the above example. In this case, all of the concatenated lines are treated as a single + line by Remind. Note that if an error occurs, Remind reports the line number of the last line of a continued + line. + + Remind ignores blank lines, and lines beginning with the '#' or ';' characters. You can use the semicolon as a + comment character if you wish to pass a Remind script through the C pre-processor, which interprets the '#' + character as the start of a pre-processing directive. + + Note that Remind processes line continuations before anything else. For example: + + # This is a comment \ + This line is part of the comment because of line continuation \ + and so on. + REM MSG This line is not ignored (no \ above) + + Remind is not case sensitive; you can generally use any mixture of upper- or lower-case for commands, parame‐ + ters, invocation options, etc. + +THE REM COMMAND + The most powerful command in a Remind script is the REM command. This command is responsible for issuing re‐ + minders. Its syntax is: + + REM [ONCE] [date_spec] [back] [delta] [repeat] [TODO] [MAX-OVERDUE n] [COMPLETE-THROUGH complete_date] + [PRIORITY prio] [SKIP | BEFORE | AFTER] [OMIT omit_list] [ADDOMIT] [NOQUEUE] [OMITFUNC omit_function] [AT + time [tdelta] [trepeat]] [SCHED sched_function] [WARN warn_function] [UNTIL expiry_date | THROUGH + last_date] [SCANFROM scan_date | FROM start_date] [DURATION duration] [TAG tag] [TZ timezone] [INFO + "info_string"] MSG | MSF | RUN | CAL | SATISFY | SPECIAL special | PS | PSFILE body + + The parts of the REM command can be specified in any order, except that the body must come immediately after the + MSG, RUN, CAL, PS, PSFILE or SATISFY keyword. The portion of the REM command before the MSG, MSF RUN, CAL or + SATISFY clause is called a trigger. + + In earlier versions of Remind, the REM token was optional providing that the remainder of the command cannot be + mistaken for another Remind command. However, this use is deprecated and will now cause a warning to be issued. + All of your reminder lines should be written to start with the REM command. + + MSG, MSF, RUN, CAL, SPECIAL, PS and PSFILE + + These keywords denote the type of the reminder. (SATISFY is more complicated and will be explained later.) A + MSG-type reminder normally prints a message to the standard output, after passing the body through a special + substitution filter, described in the section "THE SUBSTITUTION FILTER." However, if you have used the -k com‐ + mand-line option, then MSG-type reminders are passed to the appropriate program. Note that the options -c, -s, + -p and -n disable the -k option. + + Earlier versions of Remind let you omit the reminder type, in which case it defaulted to MSG. However, this us‐ + age is deprecated and will cause a warning. Something like: + + REM 6 January Dianne's Birthday + + will issue the warning "Missing REM type; assuming MSG" + + The MSF keyword is almost the same as the MSG keyword, except that the reminder is formatted to fit into a para‐ + graph-like format. Three system variables control the formatting of MSF-type reminders - they are $FirstIndent, + $SubsIndent and $FormWidth. They are discussed in the section "SYSTEM VARIABLES." The MSF keyword causes the + spacing of your reminder to be altered - extra spaces are discarded, and two spaces are placed after periods and + other characters, as specified by the system variables $EndSent and $EndSentIg. Note that if the body of the + reminder includes newline characters (placed there with the %_ sequence), then the newlines are treated as the + beginnings of new paragraphs, and the $FirstIndent indentation is used for the next line. You can use two con‐ + secutive newlines to have spaced paragraphs emitted from a single reminder body. + + A RUN-type reminder also passes the body through the substitution filter, but then executes the result as a sys‐ + tem command. A CAL-type reminder is used only to place entries in the calendar produced when Remind is run with + the -c, -s or -p options. When Remind runs a command, it sets the command's standard input to come from + /dev/null. + + A PS or PSFILE-type reminder is used to pass PostScript code directly to the printer when producing PostScript + calendars. This can be used to shade certain calendar entries (see the psshade() function), include graphics in + the calendar, or almost any other purpose you can think of. You should not use these types of reminders unless + you are an expert PostScript programmer. The PS and PSFILE reminders are ignored unless Remind is run with the + -p option. See the section "More about PostScript" for more details. + + A SPECIAL-type reminder is used to pass "out-of-band" information from Remind to a calendar-producing back-end. + It should be followed by a word indicating the type of special data being passed. The type of a special re‐ + minder depends on the back-end. For the Rem2PS back-end, SPECIAL PostScript is equivalent to a PS-type re‐ + minder, and SPECIAL PSFile is equivalent to a PSFILE-type reminder. The body of a SPECIAL reminder is obviously + dependent upon the back-end. A back-end must ignore a SPECIAL that it does not recognize. + + DATE SPECIFICATIONS + + A date_spec consists of zero to four parts. These parts are day (day of month), month (month name), year and + weekday. Month and weekday are the English names of months and weekdays. At least the first three characters + must be used. The following are examples of the various parts of a date_spec: + + day: 1, 22, 31, 14, 3 + + month: JANUARY, feb, March, ApR, may, Aug + + year: 1990, 1993, 2030. The year can range from 1990 to 2075. + + weekday: + Monday, tue, Wed, THU, Friday, saturday, sundAy + + Note that there can be several weekday components separated by spaces in a date_spec. + + INTERPRETATION OF DATE SPECIFICATIONS + + The following examples show how date specifications are interpreted. + + 1. Null date specification - the reminder is triggered every day. The trigger date for a specific run is simply + the current system date. For example: + + REM MSG This is triggered every time Remind runs + + 2. Only day present. The reminder is triggered on the specified day of each month. The trigger date for a par‐ + ticular run is the closest such day to the current system date. For example: + + REM 1 MSG First of every month. + REM 31 MSG 31st of every month that has 31 days. + + 3. Only month present. The reminder is triggered every day of the specified month. Example: + + REM Feb MSG Every day in February + + 4. day and month present. Examples: + + REM 6 Jan MSG Every 6th of January + REM Feb 29 MSG Every 29th of February + + 5. Only year present. Example: + + REM 1991 MSG Every day in 1991 + + 6. year and day present. Examples: + + REM 1 1990 MSG 1st of every month in 1990 + REM 1992 23 MSG 23rd of every month in 1992 + + 7. year and month present. Examples: + + REM Feb 1991 MSG Every day in Feb 1991 + REM 1992 September MSG Every day in Sept 1992 + + 8. year, month and day present. Examples: + + REM 8 Jan 1991 MSG 8th January 1991. + REM 1992 March 9 MSG 9th March 1992. + + 9. weekday only. Examples: + + REM Sat MSG Every Saturday + REM Mon Tue Wed Thu Fri MSG Every working day + REM Monday Wednesday MSG Every Monday and Wednesday + + 10. weekday and day present. Examples: + + REM Sat 1 MSG First Saturday of every month + REM Mon Tue Wed Thu Fri 15 \ + MSG 1st working day on or after 15th of every month + + 11. weekday and month present. Examples: + + REM Mon March MSG Every Monday in March + REM Mon Tue Wed Thu Fri Feb MSG Every working day in February + + 12. weekday, month and day present. Examples: + + REM Mon 1 March MSG First Monday in March + REM Sat Sun 15 July MSG First Sat or Sun on or after 15 July + + 13. weekday and year present. Example: + + REM Sat Sun 1991 MSG Every Saturday and Sunday in 1991 + + 14. weekday, day and year present. Examples: + + REM Mon 15 1990 MSG 1st Mon after 15th of every month in 1990 + REM Mon Tue Wed Thu Fri 1 1990 \ + MSG 1st working day of every month in 1990 + + 15. weekday, month and year present. Example: + + REM Mon Wed 1991 Feb MSG Every Mon and Wed in Feb 1991. + + 16. weekday, day, month and year present. Example: + + REM Mon Tue Wed Thu Fri 28 Oct 1990 \ + MSG 1st working day on or after 28 October 1990. + + Note that when both weekday and day are specified, Remind chooses the first date on or after the specified day + that also satisfies the weekday constraint. It does this by picking the first date on or after the specified + day that is listed in the list of weekdays. Thus, a reminder like: + + REM Mon Tue 28 Oct 1990 MSG Hi + + would be issued only on Monday, 29 October, 1990. It would not be issued on Tuesday, 30 October, 1990, since + the 29th is the first date to satisfy the weekday constraints. + + SHORT-HAND DATE SPECIFICATIONS + + In addition to spelling out the day, month and year separately, you can specify YYYY-MM-DD or YYYY/MM/DD. For + example, the following statements are equivalent: + + REM 5 June 2010 MSG Cool! + REM 2010-06-05 MSG Cool! + + You can also specify a date and time as YYYY-MM-DD@HH:MM. These statements are equivalent: + + REM 19 Dec 2010 AT 16:45 MSG Hi + REM 2010-12-19@16:45 MSG Hi + + There's one subtlety with short-hand date specifications: The following statements are not equivalent: + + REM 19 Dec 2010 AT 16:45 +60 MSG Hi + REM 2010-12-19@16:45 +60 MSG Hi + + In the second statement, the "+60" is a delta that applies to the date rather than a tdelta that applies to the + time. We recommend explicitly using the AT keyword with timed reminders. + + THE REMIND ALGORITHM + + Remind uses the following algorithm to compute a trigger date: Starting from the current date, it examines each + day, one at a time, until it finds a date that satisfies the date specification, or proves to itself that no + such date exists. (Actually, Remind merely behaves as if it used this algorithm; it would be much too slow in + practice. Internally, Remind uses much faster techniques to calculate a trigger date.) See DETAILS ABOUT TRIG‐ + GER COMPUTATION for more information. + + BACKWARD SCANNING + + Sometimes, it is necessary to specify a date as being a set amount of time before another date. For example, + the last Monday in a given month is computed as the first Monday in the next month, minus 7 days. The back + specification in the reminder is used in this case: + + REM Mon 1 -7 MSG Last Monday of every month. + + A back is specified with one or two dashes followed by an integer. This causes Remind to move "backwards" from + what would normally be the trigger date. The difference between --7 and -7 will be explained when the OMIT key‐ + word is described. + + ADVANCE WARNING + + For some reminders, it is appropriate to receive advance warning of the event. For example, you may wish to be + reminded of someone's birthday several days in advance. The delta portion of the REM command achieves this. It + is specified as one or two "+" signs followed by a number n. Again, the difference between the "+" and "++" + forms will be explained under the OMIT keyword. Remind will trigger the reminder on computed trigger date, as + well as on each of the n days before the event. Here are some examples: + + REM 6 Jan +5 MSG Remind me of birthday 5 days in advance. + + The above example would be triggered every 6th of January, as well as the 1st through 5th of January. + + PERIODIC REMINDERS + + We have already seen some built-in mechanisms for certain types of periodic reminders. For example, an event + occurring every Wednesday could be specified as: + + REM Wed MSG Event! + + However, events that do not repeat daily, weekly, monthly or yearly require another approach. The repeat compo‐ + nent of the REM command fills this need. To use it, you must completely specify a date (year, month and day, + and optionally weekday); this is the start date of the repetition period. The repeat component is an asterisk + followed by a number specifying the repetition period in days. + + For example, suppose you get paid every second Wednesday, and your last payday was Wednesday, 28 October, 1992. + You can use: + + REM 28 Oct 1992 *14 MSG Payday + + This issues the reminder every 14 days, starting from 28 Oct 1992. You can use delta and back with repeat. + Note, however, that the back is used only to compute the starting date; thereafter, the reminder repeats with + the specified period. Similarly, if you specify a weekday, it is used only to calculate the starting date, and + does not affect the repetition period. + + SCANFROM and FROM + + The SCANFROM and FROM keywords are for advanced Remind programmers only, and will be explained in the section + "DETAILS ABOUT TRIGGER COMPUTATION" near the end of this manual. Note that SCANFROM is available only in ver‐ + sions of Remind from 03.00.04 up. FROM is available only from 03.01.00 and later. + + PRIORITY + + The PRIORITY keyword must be followed by a number from 0 to 9999. It is used in calendar mode and when sorting + reminders. If two reminders have the same trigger date and time, then they are sorted by priority. If the PRI‐ + ORITY keyword is not supplied, a default priority of 5000 is used. (This default can be changed by adjusting + the system variable $DefaultPrio. See the section "SYSTEM VARIABLES" for more information.) + + EXPIRY DATES + + Some reminders should be issued periodically for a certain time, but then expire. For example, suppose you have + a class every Friday, and that your last class is on 11 December 1992. You can use: + + REM Fri UNTIL 11 Dec 1992 MSG Class today. + + Another example: Suppose you have jury duty from 30 November 1992 until 4 December 1992. The following re‐ + minder will issue the message every day of your jury duty, as well as 2 days ahead of time: + + REM 1992-11-30 *1 +2 UNTIL 1992-12-04 MSG Jury duty + + Note that the repeat of *1 is necessary; without it, the reminder would be issued only on 30 November (and the + two days preceding.) + + As a special case, you can use the THROUGH keyword instead of *1 and UNTIL. The following two REM commands are + equivalent: + + REM 1992-11-30 *1 +2 UNTIL 1992-12-04 MSG Jury duty + + REM 1992-11-30 +2 THROUGH 1992-12-04 MSG Jury duty + + If you have an expiry date via the use of THROUGH or UNTIL, then Remind will never trigger the reminder after + the expiry date. For example, if you have this: + + OMIT 2021-01-08 + REM 2021-01-01 THROUGH 2021-01-08 AFTER MSG Test + + the reminder will not be triggered on 2021-01-08, and nor will it be triggered on 2021-01-09; even though the + AFTER keyword would normally move the 8th's reminder to the 9th, the expiry date of 2021-01-08 overrides that. + + THE ONCE KEYWORD + + Sometimes, it is necessary to ensure that reminders are run only once on a given day. For example, compare the + following two reminders: + + REM Fri RUN do_backup + REM Fri ONCE RUN do_backup + + The first will be run every time you invoke Remind on a Friday, whereas the second will be run only the first + time you invoke Remind on a given Friday. + + If you run Remind from your .login script, for example, and log in several times per day, the do_backup program + in the first reminder will be run each time you log in. If, however, you use the ONCE keyword in the reminder, + the Remind checks the last access date of the reminder script. If it is the same as the current date, Remind + assumes that it has already been run, and will not issue reminders containing the ONCE keyword. + + Note that if you view or edit your reminder script, the last access date will be updated, and the ONCE keyword + will not operate properly. You can fix this by setting a timestamp file for Remind to track the last-run date; + see the documentation of $OnceFile in the SYSTEM VARIABLES section. If you use standard input as your Remind + input file, then you must use $OnceFile for the ONCE keyword to work properly. + + If you start Remind with the -o option, then the ONCE keyword will be ignored and any $OnceFile will be ignored. + + LOCALLY OMITTING WEEKDAYS + + The OMIT portion of the REM command is used to "omit" certain days when counting the delta or back. It is spec‐ + ified using the keyword OMIT followed by a list of weekdays. Its action is best illustrated with examples: + + REM 1 +1 OMIT Sat Sun MSG Important Event + + This reminder is normally triggered on the first of every month, as well as the day preceding it. However, if + the first of the month falls on a Sunday or Monday, then the reminder is triggered starting from the previous + Friday. This is because the delta of +1 does not count Saturday or Sunday when it counts backwards from the + trigger date to determine how much advance warning to give. + + Contrast this with the use of "++1" in the above command. In this case, the reminder is triggered on the first + of each month, as well as the day preceding it. The omitted days are counted. + + REM 1 -1 OMIT Sat Sun MSG Last working day of month + + Again, in the above example, the back of -1 normally causes the trigger date to be the last day of the month. + However, because of the OMIT clause, if the first of the month falls on a Sunday or Monday, the trigger date is + moved backwards past the weekend to Friday. (If you have globally omitted holidays, the reminder will be moved + back past them, also. See "The OMIT command" for more details.) + + By comparison, if we had used "--1", the reminder would be triggered on the last day of the month, regardless of + the OMIT. + + If you locally omit weekdays but also have globally-omitted weekdays, then the list of omitted weekdays is the + union of the two. Consider this example: + + OMIT Sat Sun + REM 15 OMIT Fri Sat MSG Whatever + + In the REM command, the effective list of omitted weekdays will be Friday, Saturday and Sunday. + + COMPUTED LOCAL OMITS + + The OMITFUNC phrase of the REM command allows you to supply a function that determines whether or not a date is + omitted. Note that OMITFUNC must be given just the name of a user-defined function; it can't take an arbitrary + expression or the name of a built-in function. + + The function is passed a single parameter of type DATE, and must return a non-zero integer if the date is con‐ + sidered "omitted" and 0 otherwise. Here's an example: + + FSET _third(x) (day(x) % 3) || \ + (wkdaynum(x) == 0) || \ + (wkdaynum(x) == 6) + REM OMITFUNC _third AFTER MSG Working day divisible by 3 + + In the example above, the reminder is triggered every Monday to Friday whose day-of-month number is divisible by + three. Here's how it works: + + o The OMITFUNC _third portion causes all days for which _third(x) returns non-zero to be considered "omit‐ + ted". This causes all days whose day-of-month number is not a multiple of three to be omitted. Note + that _third also returns non-zero if the weekday is Sunday or Saturday. + + o The AFTER keyword causes the reminder to be moved after a block of omitted days. + + The combination of OMITFUNC and AFTER keyword causes the reminder to be issued on all days whose day-of-month + number is divisible by three, but not on Saturday or Sunday. + + Note that if you use OMITFUNC, then a local OMIT is ignored as are all global OMITs. If you want to omit spe‐ + cific weekdays, your omit function will need to test for them specifically. If you want to take into account + the global OMIT context, then your omit function will need to test for that explicitly (using the isomitted() + function.) + + Note that an incorrect OMITFUNC might cause all days to be considered omitted. For that reason, when Remind + searches through omitted days, it terminates the search after the SATISFY iteration limit (command-line option + -x.) + + ADDING TRIGGER DATES TO THE OMIT CONTEXT + + If the ADDOMIT keyword appears in a REM command, then the trigger date (if one could be calculated) is automati‐ + cally added to the list of global OMITs. + + The command: + + REM ... whatever ... ADDOMIT MSG Foo + + is identical in behavior to the sequence: + + REM ... whatever ... SATISFY 1 + IF trigvalid() + OMIT [trigdate()] MSG Foo + ENDIF + + TIMED REMINDERS + + Timed reminders are those that have an AT keyword followed by a time and optional tdelta and trepeat. The time + may be specified in 24-hour format, with 0:00 representing midnight, 12:00 representing noon, and 23:59 repre‐ + senting one minute to midnight. Alternatively, it may be specified in common "AM/PM" format; in this case, the + hour must range from 1 to 12. 12:00am represents midnight, 12:00pm represents noon, and 11:59pm represents one + minute to midnight. The "am" and "pm" portions are case-insensitive and the "m" is optional. + + You can use either a colon or a period to separate the hours from the minutes. That is, 13:39 and 13.39 are + equivalent. + + Remind treats timed reminders specially. If the trigger date for a timed reminder is the same as the current + system date, the reminder is queued for later activation. When Remind has finished processing the reminder + file, it puts itself in the background, and activates timed reminders when the system time reaches the specified + time. Note that if you use the NOQUEUE modifier in the REM command, then this queuing and background activation + is not performed. NOQUEUE is useful if you want a time to be associated with a reminder (e.g., in the calendar) + but are not interested in a popup reminder happening at the specified time. + + If the trigger date is not the same as the system date, the reminder is not queued. + + For example, the following reminder, triggered every working day, will emit a message telling you to leave at + 5:00pm: + + REM Mon Tue Wed Thu Fri AT 17:00 MSG Time to leave! + + The following reminder will be triggered on Thursdays and Fridays, but will only be queued on Fridays: + + REM Fri ++1 AT 1:00PM MSG Lunch at 1pm Friday. + + The tdelta and trepeat have the same form as a repeat and delta, but are specified in minutes. For example, + this reminder will be triggered at 12:00pm as well as 45 minutes before: + + REM AT 12:00 +45 MSG Example + + The following will be issued starting at 10:45, every half hour until 11:45, and again at noon. + + REM AT 12:00 +75 *30 MSG Example2 + + The "+75" means that the reminder is issued starting 75 minutes before noon; in other words, at 10:45. The *30 + specifies that the reminder is subsequently to be issued every 30 minutes. Note that the reminder is always is‐ + sued at the specified time, even if the tdelta is not a multiple of the trepeat. So the above example is issued + at 10:45am, 11:15am, 11:45am, and 12:00pm. Note that in the time specification, there is no distinction between + the "+" and "++" forms of tdelta. + + Normally, Remind will issue timed reminders as it processes the reminder script, as well as queuing them for + later. If you do not want Remind to issue the reminders when processing the script, but only to queue them for + later, use the -a command-line option. If you do not want reminders to be queued for later, use the -q command- + line option. + + Normally, Remind forks a background process to handle queued reminders. If you want Remind to remain in the + foreground, use the -f command-line option. This is useful, for example, in .xinitrc scripts, where you can use + the command: + + remind -fa myreminders & + + This ensures that when you exit X-Windows, the Remind process is killed. + + WARNING ABOUT TIMED REMINDERS + + Note: If you use user-defined functions or variables (described later) in the bodies of timed reminders, then + when the timed reminders are activated, the variables and functions have the definitions that were in effect at + the end of the reminder script. These definitions may not necessarily be those that were in effect at the time + the reminder was queued. In addition, the OMIT context is whatever was in effect at the end of the reminder + script, which may not necessarily be the same as when the REM command was first processed. + + THE SCHED AND WARN KEYWORDS + + The SCHED keyword allows more precise control over the triggering of timed reminders, and the WARN keyword al‐ + lows precise control over the advance triggering of all types of reminders. However, discussion must be de‐ + ferred until after expressions and user-defined functions are explained. See the subsection "PRECISE SCHEDUL‐ + ING" further on. + + TAG, INFO AND DURATION + + The TAG keyword lets you "tag" certain reminders. This facility is used by certain back-ends or systems built + around Remind, such as TkRemind. These back-ends have specific rules about tags; see their documentation for + details. + + The TAG keyword is followed by a tag consisting of up to 48 characters. You can have as many TAG clauses as you + like in a given REM statement. A tag can contain any character except for whitespace and a comma. + + If you supply the -y option to Remind, then any reminder that lacks a TAG will have one synthesized. The syn‐ + thesized tag consists of the characters "__syn__" followed by the hexadecimal representation of the MD5 sum of + the REM command line. This lets you give a more-or-less unique identifier to each distinct REM command. + + The INFO keyword is similar to TAG but is intended to convey metadata about an event, such as its location. + Back-ends will have their own rules about which info_strings they recognize, and must ignore info_strings they + don't recognize. Note that INFO must be followed by a quoted string; you can include newlines in the string by + supplying them as "\n". + + An INFO string must be of the form "Header: Value". The header can consist of any printable character, but can‐ + not contain whitespace. The value can consist of any characters you like. Space may not appear before the + colon, but can appear afterwards; such space is not considered to be part of the value. If there is more than + one INFO string for a given reminder, there cannot be any duplicate headers. Case is ignored when determining + if a header is a duplicate of an existing one. + + For example, a hypothetical back-end might let you set the location and description of a reminder like this: + + REM 26 Feb 2025 INFO "Location: Boardroom #2" \ + INFO "Description: Go over latest pull requests" \ + MSG Engineering meeting + + While back-ends can choose which INFO strings to support, all back-ends should endeavor to support three stan‐ + dard ones: Location:, Description: and Url:. TkRemind supports all three of these, turning reminders with a + Url: INFO string into hyper-links, and popping up information windows for the Location: and Description: INFO + strings. Location: and Url: are self-explanatory; Description: is meant for a longer, more in-depth description + of the reminder than the summary that is normally displayed. + + The DURATION keyword makes sense only for timed reminders; it specifies the duration of an event. For example, + if you have a 90-minute meeting starting at 1:00pm, you could use any of the following: + + REM 5 March 2021 AT 13:00 DURATION 1:30 MSG Meeting + REM 5 March 2021 AT 13:00 DURATION 90 MSG Meeting + REM 5 March 2021 AT 1:00pm DURATION 1:30 MSG Meeting + REM 5 March 2021 AT 1:00pm DURATION 90 MSG Meeting + + For long-duration reminders, it is convenient to use expressions to simplify writing the DURATION. For example, + if you are away from 20 Feb 2023 through 23 Feb 2023 (a total of 4 days) you could write: + + REM 20 Feb AT 00:00 DURATION [4*24]:00 MSG away + REM 20 Feb AT 00:00 DURATION [4*24*60] MSG away + + Note that duration is specified either as hours:minutes or just as minutes specified as an integer. + + If you specify a duration of 00:00 or 0, then Remind behaves exactly as if no DURATION at all had been present. + Although durations specified as hours:minutes look superficially like a time-of-day, they are not; the hours + component is not limited to the range 00-23. + +SYNTACTIC SUGAR FOR REM + The REM command has syntactic sugar to let you express common reminders. The following pairs of reminders are + equivalent: + + 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 + + Note that Last effectively adjusts the month and year, if necessary, to make the reminder trigger on the correct + date. + + The keyword IN is completely ignored, so you can write (for example): + + REM Second Monday in May MSG foo + REM Last Monday in December 2025 MSG Bar + + An alternate form of back makes writing reminders easier. The following groups of reminders are equivalent: + + REM ~~1 MSG Last day of every month + REM Lastday MSG Last day of every month + REM 1 --1 MSG Last day of every month + + REM May ~~1 MSG Last day of May + REM Lastday May 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 Lastday Dec 2025 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 Lastworkday April 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 + + As we see, "Lastday" is equivalent to ~~1 and "Lastworkday" to ~1. + + Note that the First/Second/Third/Fourth/Last keywords and the ~ and ~~ form of back 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: + + REM First Monday 3 June MSG Huh? + REM April 3 ~~1 MSG What? + REM Second June MSG Where's the weekday??? + +THE SUBSTITUTION FILTER + Before being processed, the body of a REM command is passed through a substitution filter. The filter scans for + sequences "%x" (where "x" is any letter and certain other characters) and performs substitutions as shown below. + (All dates refer to the trigger date of the reminder.) + + %a is replaced with "on weekday, day month, year" + For example, consider the reminder: + + REM 18 Oct 1990 +4 MSG Meeting with Bob %a. + + On 16 October 1990, it would print "Meeting with Bob on Thursday, 18 October, 1990." + + On 17 October 1990, it would print "Meeting with Bob tomorrow." + + On 18 October 1990, it would print "Meeting with Bob today." + + %b is replaced with "in diff day's time" where diff is the actual number of days between the current date + and the trigger date. (OMITs have no effect.) + For example, consider: + + REM 18 Oct 1990 +4 MSG Meeting with Bob %b. + + On 16 October 1990, it would print "Meeting with Bob in 2 days' time." + + On 17 October 1990, it would print "Meeting with Bob tomorrow." + + On 18 October 1990, it would print "Meeting with Bob today." + + %c is replaced with "on weekday" + Example: REM 18 Oct 1990 +4 MSG Meeting with Bob %c. + + On 16 October 1990, it would print "Meeting with Bob on Thursday." + + On 17 October 1990, it would print "Meeting with Bob tomorrow." + + On 18 October 1990, it would print "Meeting with Bob today." + + %d is replaced with "day", the day of the month. + + %e is replaced with "on dd-mm-yyyy" + + %f is replaced with "on mm-dd-yyyy" + + %g is replaced with "on weekday, day month" + + %h is replaced with "on dd-mm" + + %i is replaced with "on mm-dd" + + %j is replaced with "on weekday, month day-th, year" This form appends the characters "st", "nd", "rd" or + "th" to the day of the month, as appropriate. + + %k is replaced with "on weekday, month day-th" + + %l is replaced with "on yyyy-mm-dd" + + %m is replaced with "month", the name of the month. + + %n is replaced with the number (1 to 12) of the month. + + %o is replaced with " (today)" if and only if the current system date is the same as the date being used by + Remind as the current date. Recall that you can specify a date for Remind to use on the command line. + This substitution is not generally useful in a REM command, but is useful in a BANNER command. (See "The + BANNER Command.") + + %p is replaced with "s" if the diff between the current date and the trigger date is not 1. You can use + this to construct reminders like: + REM 1 Jan +4 MSG %x day%p to go before New Year! + + %q is replaced with "'s" if the diff between the trigger date and the current date is 1. Otherwise, it is + replaced with "s'" This can be used as follows: + REM 1 Jan +4 MSG New Year in %x day%q time! + + %r is replaced with the day of the month (01 to 31) padded with a leading zero if needed to pad to two dig‐ + its. + + %s is replaced with "st", "nd", "rd" or "th" depending on the day of the month. + + %t is replaced with the number of the month (01 to 12) padded to two digits with a leading zero. + + %u is replaced with "on weekday, day-th month, year" This is similar to %a except that "st", "nd", "rd" or + "th" is added to the day as appropriate. + + %v is replaced with "on weekday, day-th month" + + %w is replaced with "weekday", the name of the day of the week. + + %x is replaced with the diff between the current date and the trigger date. The diff is defined as the ac‐ + tual number of days between these two dates; OMITs are not counted. (Strict date subtraction is per‐ + formed.) + + %y is replaced with "year", the year of the trigger date. + + %z is replaced with "yy", the last two digits of the year. + + %(any_text) + is replaced with the lookup of any_text in the translation table. It is the equivalent of + [_("any_text")] but is more convenient to type. + + % + is replaced with the INFO value associated with the header any_text or the empty string if no such INFO + value exists. It is the equivalent of [triginfo("any_text")] but is more convenient to type. + + %_ (percent-underscore) is replaced with a newline. You can use this to achieve multi-line reminders. Note + that calendar back-ends vary in how they handle multi-line reminders: + + o Running remind -c preserves newlines in the terminal calendar output. + + o rem2pdf preserves newlines if remind is invoked with the -pp or -ppp option. + + o rem2html preserves newlines if remind is invoked with the -pp option. + + o tkremind preserves newlines. + + o rem2ps converts newlines to spaces. But rem2ps is deprecated; use rem2pdf instead. + + o The "simple calendar" formats (i.e., remind's -s, -n and -p options) convert newlines to spaces. + + All calendar back-ends collapse multiple spaces to a single space and multiple newlines to a single new‐ + line. + + %1 is replaced with "now", "m minutes from now", "m minutes ago", "h hours from now", "h hours ago", "h + hours and m minutes from now" or "h hours and m minutes ago", as appropriate for a timed reminder. Note + that unless you specify the -a option, timed reminders will be triggered like normal reminders, and thus + a timed reminder that occurred earlier in the day may be triggered. This causes the need for the + "...ago" forms. + + %2 is replaced with "at hh:mmam" or "..pm" depending on the AT time of the reminder. + + %3 is replaced with "at hh:mm" in 24-hour format. + + %4 is replaced with "mm" where mm is the number of minutes between "now" and the time specified by AT. If + the AT time is earlier than the current time, then the result is negative. + + %5 is replaced with "ma" where ma is the absolute value of the number produced by %4. + + %6 is replaced with "ago" or "from now", depending on the relationship between the AT time and the current + time. + + %7 is replaced with the number of hours between the AT time and the current time. It is always non-nega‐ + tive. + + %8 is replaced with the number of minutes between the AT time and the current time, after the hours (%7) + have been subtracted out. This is a number ranging from 0 to 59. + + %9 is replaced with "s" if the value produced by %8 is not 1. + + %0 is replaced with "s" if the value produced by %7 is not 1. + + %! is replaced with "is" if the current date and time is before the trigger date and the AT time, or "was" + if it is after. The %! sequence may be used in a non-timed reminder, in which case only dates are com‐ + pared. + + %? is replaced with "are" if the current date and time is before the trigger date and the AT time, or "were" + if it is after. The %? sequence may be used in a non-timed reminder, in which case only dates are com‐ + pared. + + %@ is similar to %2 but displays the current time. + + %# is similar to %3 but displays the current time. + + %: is replaced with " (done)" for a TODO reminder whose trigger date is on or after its COMPLETE-THROUGH + date. It is replaced with the empty string in any other situation. Note that because Remind does not + display completed TODO reminders in Agenda Mode, this escape sequence is really only useful in Calendar + Mode. + + %" (percent-doublequote) is removed. This sequence is not used by the substitution filter, but is used to + tell Remind which text to include in a calendar entry when the -c, -s or -p option is chosen. See "CAL‐ + ENDAR MODE" + + Notes: + + o Remind normally prints a blank line after each reminder; if the last character of the body is "%", the + blank line will not be printed. You can globally suppress the extra blank lines by setting $Ad‐ + dBlankLines to 0. + + o Substitutions a, b, c, e, f, g, h, i, j, k, l, u and v all are replaced with "today" if the current date + equals the trigger date, or "tomorrow" if the trigger date is one day after the current date. Thus, they + are not the same as substitutions built up from the simpler %w, %y, etc. sequences. + + o The a, c, e, f, g, h, i, j, k, l, u, v, 2, and 3 substitutions may be preceded by an asterisk (for exam‐ + ple, %*c) which causes the word "at" or "on" that would normally be included in the output to be omitted. + + o The ! and ? substitutions may be preceded by an asterisk (%*! or %*?), in which case the comparison is + made between the trigger date and realtoday() instead of today(). + + o Any of the substitutions dealing with time (0 through 9) produce undefined results if used in a reminder + that does not have an AT keyword. Also, if a reminder has a delta and may be triggered on several days, + the time substitutions ignore the date. Thus, the %1 substitution may report that a meeting is in 15 + minutes, for example, even though it may only be in 2 days time, because a delta has triggered the re‐ + minder. It is recommended that you use the time substitutions only in timed reminders with no delta that + are designed to be queued for timed activation. + + o Capital letters can be used in the substitution sequence, in which case the first character of the sub‐ + stituted string is capitalized (if it is normally a lower-case letter.) + + o All other characters following a "%" sign are simply copied. In particular, to get a "%" sign out, use + "%%" in the body. To start the body of a reminder with a space, use "% ", since Remind normally scans + for the first non-space character after a MSG, CAL or RUN token. + +EVENTS AND TODOS + The REM command is normally used to create an EVENT. This is something that happens at a certain time, possibly + recurring, and happens no matter what action you take. Events include things like birthdays, holidays, meet‐ + ings, etc... pretty much everything that occurs on a particular schedule. Once the date of an event has passed, + Remind will no longer remind you about the event. + + A TODO is different; it is a task that you have to complete by a specific date or date and time. If you don't + explicitly mark a TODO as done, Remind will keep reminding you about it even past the due date.. + + To mark a REM as a TODO, simply include the TODO keyword. For example: + + REM TODO 15 August 2025 ++5 MSG Buy cat food %b. + + In Agenda Mode, Remind will start warning you on 10 Aug 2025 that you have to but cat food in 5 days' time, 4 + days' time, etc... + + However, on 16 Aug 2025, Remind will say "Buy cat food yesterday." and it will keep reminding you of your need + to buy cat food until the end of time, or until you mark the TODO as done. + +MARKING TODOS AS DONE + There are two ways to mark a TODO as done. If the TODO is not recurring, the simplest way is simply to remove + the TODO designator from the REM command (or indeed, to completely delete it.) The former keeps the reminder on + the calendar while the latter completely removes it. + + If a TODO is recurring, use the COMPLETE-THROUGH clause to mark which recurrences have been completed. For ex‐ + ample: + + REM TODO 30 April ++15 COMPLETE-THROUGH 2025-04-30 MSG File taxes + + Canadian income taxes must be filed every 30 April. The above command will remind you to pay taxes in 2026. If + you don't update the COMPLETE-THROUGH date, then after 30 April 2026, Remind will keep reminding you until the + end of time that your taxes were due on 30 April 2026. To indicate that you've paid them, simply update the + COMPLETE-THROUGH date to 2026-04-30 and then Remind will start reminding you of your 2027 taxes (starting 15 + days before the due date.) + + It is an error to specify COMPLETE-THROUGH without also specifying TODO. The keyword COMPLETED-THROUGH may be + used as a synonym for COMPLETE-THROUGH. + +LIMITING REMINDERS ABOUT OVERDUE TODOS + Although Remind is happy to keep reminding you about overdue TODOs for hundreds of years, for some things this + may be pointless. If you want Remind to stop nagging you about an overdue TODO after a certain number of days, + use the MAX-OVERDUE n clause. In this case, Remind stops reminding you of a TODO that is overdue by more than n + days. Here is an example. + + REM TODO 2025-08-13 ++5 MAX-OVERDUE 5 MSG Task: %b. + + Remind starts reminding you of the task on 2025-08-08, because of the ++5 back value. It keeps reminding you of + the task after the due date. However, the last time it will remind you will be on 2025-08-18, because of the + MAX-OVERDUE clause. Starting on 2025-08-19, Remind will no longer remind you of the task since it's probably + pointless---it has passed the MAX-OVERDUE period. + +MORE DETAILS ABOUT TODOS + TODOs are treated specially only in Agenda Mode. In Calendar Mode, they appear in the calendar exactly as a + normal event would. + + TODOs are implemented internally by using the COMPLETE-THROUGH date plus one day as the starting point for Re‐ + mind's date-scanning algorithm. If you have a recurring TODO without a COMPLETE-THROUGH clause, then Remind + starts scanning from the beginning of time, which we all know is 1 January 1990. Consider this command: + + REM TODO Wed +7 MSG Take out the trash %a (%b) + + Running that command in Agenda Mode on 2025-08-13 yields the following output: + + Take out the trash on Wednesday, 3 January, 1990 (13006 days ago) + + Remind is very persistent about reminding you of tasks! If you take out the trash and mark it done: + + REM TODO Wed +7 COMPLETE-THROUGH 2025-08-13 MSG Take out the trash %a (%b) + + then you get this: + + Take out the trash on Wednesday, 20 August, 2025 (in 7 days' time) + + You can use FROM and UNTIL to limit the recurrence interval of tasks just as you can with events. For example, + if you are house-sitting for the month of August and need to water plants every Wednesday: + + REM TODO Wed +7 FROM 2025-08-06 UNTIL 2025-08-27 MSG Plants %b. + + Running that command on 13 Aug yields: "Plants 7 days ago." because you have not told Remind that you completed + the first watering. If you finish your duties and add a COMPLETE-THROUGH date of 2025-08-27, then Remind never + reminds you of that task in the future. + + In Purge Mode, Remind will not purge TODOs unless they have been marked as complete. In the case of a recurring + TODO, Remind will not purge it until the last occurrence is marked as complete. + +THE OMIT COMMAND + In addition to being a keyword in the REM command, OMIT is a command in its own right. Its syntax is: + + OMIT weekday [weekday...] + + or: + + OMIT [day] month [year] + + or: + + OMIT [day1] month1 [year1] THROUGH [day2] month2 [year2] + + The OMIT command is used to "globally" omit certain days (usually holidays). These globally-omitted days are + skipped by the "-" and "+" forms of back and delta, but not by the "--" and "++" forms. Some examples: + + OMIT Saturday Sunday + OMIT 1 Jan + OMIT 7 Sep 1992 + OMIT 15 Jan THROUGH 14 Feb + OMIT May # Equivalent to OMIT May 1 THROUGH May 31 + OMIT 25 Dec THROUGH 4 Jan + OMIT 2023-05-03 THROUGH 2023-05-12 + OMIT Jun THROUGH July # Equivalent to OMIT Jun 1 THROUGH July 31 + + The first example omits every Saturday and Sunday. This is useful for reminders that shouldn't trigger on week‐ + ends. + + The second example specifies a holiday that occurs on the same date each year - New Year's Day. + + The third example specifies a holiday that changes each year - Labour Day. For these types of holidays, you + must create an OMIT command for each year. (Later, in the description of expressions and some of the more ad‐ + vanced features of Remind, you will see how to automate this for some cases.) + + As with the REM command, you can use shorthand specifiers for dates; the following are equivalent: + + OMIT 7 Sep 1992 + OMIT 1992-09-07 + + For convenience, you can use a delta and MSG or RUN keyword in the OMIT command. The following sequences are + equivalent: + + OMIT 1 Jan + REM 1 Jan +4 MSG New year's day is %b! + + and + + OMIT 1 Jan +4 MSG New year's day is %b! + + The THROUGH keyword lets you conveniently OMIT a range of days. For example, the following sequences are equiv‐ + alent: + + OMIT 3 Jan 2011 + OMIT 4 Jan 2011 + OMIT 5 Jan 2011 + + and + + OMIT 3 Jan 2011 THROUGH 5 Jan 2011 + + Note that Remind has a compiled-in limit to the number of full OMITs. If you omit a range of N fully-specified + (i.e., year included) days, then N full OMITs are used up. Trying to omit a very large range may result in the + error "Too many full OMITs" + + You can make a THROUGH OMIT do double-duty as a REM command as long as both dates are fully specified + + OMIT 6 Sep 2010 THROUGH 10 Sep 2010 MSG Vacation + + If you use a THROUGH clause, then either the year must be supplied before and after the THROUGH, or it must be + missing before and after the THROUGH. The following are legal: + + OMIT 25 Dec THROUGH 6 Jan + OMIT 25 Dec 2024 THROUGH 6 Jan 2025 + + But the following are not: + + OMIT 25 Dec THROUGH 6 Jan 2025 + OMIT 25 Dec 2024 THROUGH 6 Jan + + You can debug your global OMITs with the following command: + + OMIT DUMP + + The OMIT DUMP command prints the current global omits to standard output. + + THE BEFORE, AFTER AND SKIP KEYWORDS + + Normally, days that are omitted, whether by a global OMIT command or the local OMIT or OMITFUNC keywords in a + REM statement, only affect the counting of the -back or the +delta. For example, suppose you have a meeting + every Wednesday. Suppose, too, that you have indicated 11 Nov as a holiday: + + OMIT 11 Nov +4 MSG Remembrance Day + REM Wed +1 MSG Code meeting %b. + + The above sequence will issue a reminder about a meeting for 11 November 1992, which is a Wednesday. This is + probably incorrect. There are three options: + + BEFORE This keyword moves the reminder to before any omitted days. Thus, in the above example, use of BEFORE + would cause the meeting reminder to be triggered on Tuesday, 10 November 1992. + + AFTER This keyword moves the reminder to after any omitted days. In the above example, the meeting reminder + would be triggered on Thursday, 12 November 1992. + + SKIP This keyword causes the reminder to be skipped completely on any omitted days. Thus, in the above exam‐ + ple, the reminder would not be triggered on 11 November 1992. However, it would be triggered as usual on + the following Wednesday, 18 November 1992. + + The BEFORE and AFTER keywords move the trigger date of a reminder to before or after a block of omitted days, + respectively. Suppose you normally run a backup on the first day of the month. However, if the first day of + the month is a weekend or holiday, you run the backup on the first working day following the weekend or holiday. + You could use: + + REM 1 OMIT Sat Sun AFTER RUN do_backup + + Let's examine how the trigger date is computed. The 1 specifies the first day of the month. The local OMIT + keyword causes the AFTER keyword to move the reminder forward past weekends. Finally, the AFTER keyword will + keep moving the reminder forward until it has passed any holidays specified with global OMIT commands. + +TIMEZONE SUPPORT + The REM command supports an optional TZ keyword, which should be followed by the case-sensitive time zone name + in which the command is to be interpreted. Note that if you use the TZ keyword, then you must also use an AT + clause. Here are some examples: + + REM Wednesday AT 14:00 TZ America/Toronto MSG 2PM Eastern (%2). + REM Wednesday AT 23:59 TZ America/Los_Angeles SATISFY [$Td == 13] MSG Foo %b %2. + + Within a SATISFY clause and an OMITFUNC function, all trigger functions and the trigger date are interpreted in + the time zone specified in the REM command. Outside the REM command, however, trigger functions are adjusted to + the local time zone. If the local time zone is UTC and we feed Remind the following file on 2025-09-04 UTC: + + SET $AddBlankLines 0 + BANNER % + REM Wednesday AT 14:00 TZ America/Toronto MSG 2PM Eastern (%2). + set a $T + set b $Tt + REM MSG a = [a], b = [b] + REM Wednesday AT 23:59 TZ America/Los_Angeles SATISFY [$Td == 13] MSG Foo %b %2. + set c $T + set d $Tt + REM MSG c = [c], d = [d] + + Then the output is as follows: + + a = 2025-09-10, b = 18:00 + c = 2026-05-14, d = 06:59 + + That is because the trigger date of the first (Wednesday, 2025-09-10 at 14:00 Eastern time) is 2025-09-10 at + 18:00 UTC. In the second case, Wednesday, 13 May 2026 is the SATISFied trigger date, which is adjusted to + Thursday, 14 May 2026 at 06:59 UTC because of the time zone adjustment. + + If you use an invalid time zone name after the TZ keyword, results are undefined. As mentioned previously, time + zone names are case-sensitive; America/Toronto is valid, but america/toronto is not. + + If you are on a system that stores time zone data in /usr/share/zoneinfo, then Remind will attempt to validate + the time zone name and will warn you if it appears to be invalid. If you want to specify a time zone that lacks + a file under /usr/share/zoneinfo anyway, and want to suppress the warning, prefix the time zone name with "!". + For example: + + REM Thursday AT 14:45 TZ !EST+5EDT,M3.2.0/2,M11.1.0/2 MSG Old TZ format + + In a reminder with the TZ keyword, OMIT dates are evaluated in the specified time zone. Here's an example: Sup‐ + pose the local time zone is America/Toronto and you have this script: + + # A Friday + OMIT 2025-09-05 + + # A Saturday + OMIT 2025-09-13 + + REM Saturday AT 01:00 TZ Europe/Amsterdam SKIP MSG Early Sat AM + + On 2025-09-05 in the America/Toronto time zone, the reminder will trigger. Even though 2025-09-05 has been + OMITted, the SKIP keyword evaluates the date in the Europe/Amsterdam time zone, and 2025-09-06 (the trigger + date) is not omitted. + + Conversely, on 2025-09-12 in the America/Toronto time zone, the reminder will not trigger. Even though + 2025-09-12 is not OMITted, 2025-09-13 is, and that is the trigger date in the Europe/Amsterdam time zone. + +THE DO, INCLUDE AND SYSINCLUDE COMMANDS + Remind allows you to include other files in your reminder script, similar to the C preprocessor #include direc‐ + tive. For example, you might organize different reminders into different files like this: + + INCLUDE holidays.rem + INCLUDE birthdays.rem + INCLUDE "quote files with spaces.rem" + + INCLUDE files can be nested up to a depth of 8. As shown above, if a filename has spaces in it (not recom‐ + mended!) you can use double-quotes around the filename. + + If you specify a filename of "-" in the INCLUDE command, Remind will begin reading from standard input. + + If you specify a directory as the argument to INCLUDE, then Remind will process all files (but not subdirecto‐ + ries!) in that directory that match the shell pattern "*.rem". The files are processed in sorted order; the + sort order matches that used by the shell when it expands "*.rem". + + Note that the file specified by an INCLUDE command is interpreted relative to the current working directory of + the Remind process. If you want to include a file relative to the directory containing the currently-processing + file, use DO instead. For example, if the current file is /home/user/.reminders/foo.rem and Remind's working + directory is /home/user, then: + + # Read /home/user/.reminders/bar.rem + DO bar.rem + + # Read /usr/share/bar.rem - absolute path + DO /usr/share/bar.rem + + # Read /home/user/bar.rem + INCLUDE bar.rem + + # Read /usr/share/bar.rem - absolute path + INCLUDE /usr/share/bar.rem + + Arguably, the INCLUDE command should have worked the way DO does right from the start, but changing it would + have broken backward-compatibility, hence the introduction of DO. + + Note that if the currently-processing reminders file was specified as a symbolic link to a file that is not in + the same directory as the symbolic link itself, DO will fail. Remind does not resolve the real path of symbolic + links, so you should avoid using symbolic links to files. + + The SYSINCLUDE command is similar to DO, but it looks for relative pathnames under the system directory contain‐ + ing standard reminder scripts. For this installation of Remind, the system directory is "/usr/local/share/re‐ + mind". + +THE RETURN COMMAND + The RETURN command causes Remind to ignore the remaining contents of the file currently being processed. It can + be used as a quick way to "exit" from an included file (though it also works at the top-level.) + + Here is an example of how RETURN might be used: + + IF already_done + RETURN + ENDIF + set already_done 1 + preserve already_done + # Do expensive processing here + +THE RUN COMMAND + If you include other files in your reminder script, you may not always entirely trust the contents of the other + files. For example, they may contain RUN-type reminders that could be used to access your files or perform un‐ + desired actions. The RUN command can restrict this: If you include the command RUN OFF in your top-level re‐ + minder script, any reminder or expression that would normally execute a system command is disabled. RUN ON will + re-enable the execution of system commands. Note that the RUN ON command can only be used in your top-level re‐ + minder script; it will not work in any files accessed by the INCLUDE command. This is to protect you from some‐ + one placing a RUN ON command in an included file. However, the RUN OFF command can be used at top level or in + an included file. + + If you run Remind with the -r command-line option, RUN-type reminders and the shell() function will be disabled, + regardless of any RUN commands in the reminder script. However, any command supplied with the -k option will + still be executed. + + In addition, Remind contains a few other security features. It will not read a file that is group- or world- + writable. It will not run set-uid. If it reads a file you don't own, it will disable RUN and the shell() func‐ + tion. And if it is run as root, it will only read files owned by root. + + Note that if Remind reads standard input, it does not attempt to check the ownership of standard input, even if + it is coming from a file, and hence does not disable RUN and shell() in this situation. + +THE EXPR COMMAND + Remind lets you completely disable expression evaluation. This could be useful if you are running Remind on a + somewhat-untrustworthy file that is not expected to contain expressions. To disable expression evaluation, use: + + EXPR OFF + + If Remind encounters an expression while EXPR OFF is in effect, it returns an error + + To re-enable expression evaluation, use: + + EXPR ON + + As with RUN ON, EXPR ON can be used only in the top-level script, not in an included file. + +THE INCLUDECMD COMMAND + Remind allows you to execute a shell command and evaluate the output of that command as if it were an included + file. For example, you could have scripts that extract reminders out of a database and print them on stdout as + REM commands. Here is an example: + + INCLUDECMD extract_reminders_for dfs + + We assume that the command "extract_reminders_for" extracts reminders out of a central database for the named + user. Another use-case of INCLUDECMD is if you have your reminders stored in a file in some non-Remind format; + you can write a command that transforms them to Remind format and then Remind can "include" the file with an ap‐ + propriate INCLUDECMD command. + + Note that if RUN is disabled, then INCLUDECMD will fail with the error message "RUN disabled" + + Remind arranges so that when the command specified by INCLUDECMD is executed, its standard input is opened to + /dev/null. + + INCLUDECMD passes the rest of the line to popen(3), meaning that the command is executed by the shell. As such, + shell meta-characters may need escaping or arguments quoting, depending on what you're trying to do. Remind it‐ + self does not perform any modification of the command line (apart from the normal [expr] expression-pasting + mechanism). + + If the command passed to INCLUDECMD begins with an exclamation mark "!", then Remind disables RUN for the output + of the command. If you are running a command whose output you don't quite trust, you should prefix it with "!" + so that any RUN commands it emits fail. + + An INCLUDECMD command counts towards the INCLUDE nesting depth. For any given Remind run, a given INCLUDECMD + command is only executed once and the results are cached. For example, if you generate a calendar, each unique + INCLUDECMD command is run just once, not once for each day of the produced calendar. "Uniqueness" is determined + by looking at the command that will be passed to the shell, so if (for example) your INCLUDECMD uses expression- + pasting that results in differences depending on the value of today(), then each unique version of the command + will be executed once. + + If a given reminder file contains more than one identical INCLUDECMD, only the first one will actually be exe‐ + cuted. All subsequent identical ones will use the cached output from the first one. + +THE BANNER COMMAND + When Remind first issues a reminder, it prints a message like this: + + Reminders for Friday, 30th October, 1992 (today): + + (The banner is not printed if any of the calendar-producing options is used, or if the -k option is used.) + + The BANNER command lets you change the format. It should appear before any REM commands. The format is: + + BANNER format + + The format is similar to the body of a REM command. It is passed through the substitution filter, with an im‐ + plicit trigger of the current system date. Thus, the default banner is equivalent to: + + BANNER Reminders for %w, %d%s %m, %y%o: + + You can disable the banner completely with BANNER %. Or you can create a custom banner: + + BANNER Hi - here are your reminders for %y-%t-%r: + +CONTROLLING THE OMIT CONTEXT + Sometimes, it is necessary to temporarily change the global OMITs that are in force for a few reminders. Three + commands allow you to do this: + + PUSH-OMIT-CONTEXT + This command saves the current global OMITs on an internal stack. + + CLEAR-OMIT-CONTEXT + This command clears all of the global OMITs, starting you off with a "clean slate." + + POP-OMIT-CONTEXT + This command restores the global OMITs that were saved by the most recent PUSH-OMIT-CONTEXT. + + For example, suppose you have a block of reminders that require a clear OMIT context, and that they also intro‐ + duce unwanted global OMITs that could interfere with later reminders. You could use the following fragment: + + PUSH-OMIT-CONTEXT # Save the current context + CLEAR-OMIT-CONTEXT # Clean the slate + # Block of reminders goes here + POP-OMIT-CONTEXT # Restore the saved omit context + +EXPRESSIONS + In certain contexts, to be described later, Remind will accept expressions for evaluation. Remind expressions + resemble C expressions, but operate on different types of objects. + + DATA TYPES + + Remind expressions operate on five types of objects: + + INT The INT data type consists of the integers representable in one machine word. The INT data type corre‐ + sponds to the C "int" type. + + STRING The STRING data type consists of strings of characters. It is somewhat comparable to a C character ar‐ + ray, but more closely resembles the string type in BASIC. + + Remind normally expects to be running in a UTF-8 environment. In this environment, there is a difference + between bytes and characters since in UTF-8, a character may be represented by a sequence of more than + one byte. For example, in a UTF-8 environment, the string "🙂" contains one character but four bytes. + And the string "één" contains three characters but five bytes. + + Remind has a set of functions that work on bytes, namely index, strlen and substr, and several more. + These are not safe to use on multi-byte strings; instead use mbindex, mbstrlen and mbsubstr, and the mb + variants of the others. If you know for sure that a string contains only single-byte characters, then + the byte-oriented versions may be used and are faster than the multi-byte versions. + + TIME The TIME data type is used for two different purposes: To represent a time of day with one-minute preci‐ + sion or to represent a duration with one-minute precision. The context of where a TIME is used deter‐ + mines whether it is interpreted as a time of day or a duration. + + In contexts where a TIME represents a time of day, it may range from 00:00 to 23:59 and is stored inter‐ + nally as an integer from 0 to 1439 representing the number of minutes since midnight. + + In contexts where a TIME represents a duration, there is no upper limit on the hour component (beyond + that imposed by the restriction that a duration expressed in minutes must fit into the signed integer + type of your CPU architecture.) Internally, a duration is stored as an integer number of minutes. + + DATE The DATE data type consists of dates (later than 1 January 1990.) Internally, DATE objects are stored as + the number of days since 1 January 1990. + + DATETIME + The DATETIME data type consists of a date and time together. Internally, DATETIME objects are stored as + the number of minutes since midnight, 1 January 1990. You can think of a DATETIME object as being the + combination of DATE and TIME parts. + + CONSTANTS + + The following examples illustrate constants in Remind expressions: + + INT constants + 12, 36, -10, 0, 1209, 0x1F, 0xfe00 (the last two demonstrate the use of hexadecimal constants) + + STRING constants + "Hello there", "This is a test", "\nHello\tThere", "", "π is Cool! 🙂" + + Note that the empty string is represented by "". Remind supports the escape sequences "\a", "\b", "\f", + "\n", "\r", "\t" and "\v" which have the same meanings as their counterparts in C. It also supports + "\xAB" where A and B are hexadecimal digits; this operates just as in C. The "\x" must be followed by + one or two hex digits; the escape sequence "\x00" is not permitted. + + To include a quote in a string, use "\"". Any other character preceded by a backslash is inserted into + the string as-is, but the backslash itself is removed. To include a backslash in a string, use "\\". + + TIME constants + 12:33, 0:01, 14:15, 16:42, 12.16, 13.00, 1.11, 4:30PM, 12:20am + + Note that TIME constants may be written in 24-hour format or in common "AM/PM" format. If you use + "AM/PM" format, then the hour can range from 1 to 12. Either a period or colon can be used to separate + the minutes from the hours. However, Remind will consistently output times in 24-hour format using only + one separator character. (The output separator character is chosen at compile-time.) + + If the TIME is used where Remind expects a time-of-day (for example, in an AT clause), then it can be + written in 24-hour format (ranging from 00:00 to 23:59) or 12-hour format (ranging from 12:00am to + 11:59pm). If the TIME is used where Remind expects a duration, it must not have an am or pm suffix and + the hour can be as large as you want, so long as the total number of minutes in the duration fits in a + signed integer variable. + + For convenience, a TIME constant may be surrounded by single quotes to match DATE and DATETIME constants, + but these quotes are optional. That is, 12:56 and '12:56' represent the same TIME constant. + + DATE constants + DATE constants are expressed as 'yyyy/mm/dd' or 'yyyy-mm-dd', and the single quotes must be supplied. + This distinguishes date constants from division or subtraction of integers. Examples: + + '1993/02/22', '1992-12-25', '1999/01/01' + + Note that DATE values are printed without the quotes. Although either '-' or '/' is accepted as a date + separator on input, when dates are printed, only one will be used. The choice of whether to use '-' or + '/' is made at compile-time. Note also that versions of Remind prior to 03.00.01 did not support date + constants. In those versions, you must create dates using the date() function. Also, versions prior to + 03.00.02 did not support the '-' date separator. + + DATETIME constants + DATETIME constants are expressed similarly to DATE constants with the addition of an "@HH:MM" part, op‐ + tionally followed by "am" or "pm". For example: + + '2008-04-05@23:11', '1999/02/03@14:06', '2001-04-07@08:30', '2020-01-01@3:20pm' + + DATETIME values are printed without the quotes. Notes about date and time separator characters for DATE + and TIME constants apply also to DATETIME constants. + + ZERO VALUES AND TRUE/FALSE + + All types have an associated zero value, which is treated as false by the IF command, the IIF function, and the + logical operators. The zero values are: + + INT - 0 + + DATE - '1990-01-01' + + TIME - 00:00 + + DATETIME - '1990-01-01@00:00' + + STRING - "" (the empty string) + + Any value other than the zero value is treated as true. + + OPERATORS + + Remind has the following operators. Operators on the same line have equal precedence, while operators on lower + lines have lower precedence than those on higher lines. The operators approximately correspond to C operators. + + ! - (unary logical negation and arithmetic negation) + * / % (multiplication, division, modulus) + + - (addition/concatenation, subtraction) + < <= > >= (comparisons) + == != (equality and inequality tests) + && (logical AND) + || (logical OR) + + DESCRIPTION OF OPERATORS + + ! Logical negation. Can be applied to any type. If the operand is non-zero, returns 0. Otherwise, re‐ + turns 1. + + - Unary minus. Can be applied to an INT. Returns the negative of the operand. + + * Multiplication. Returns the product of two INTs. Alternatively, if one argument is a STRING and the + other an INT, returns a STRING consisting of the INT number of repeats of the original STRING. In this + case, the INT argument cannot be negative. + + / Integer division. Returns the quotient of two INTs, discarding the remainder. + + % Modulus. Returns the remainder upon dividing one INT by another. + + + Has several uses. These are: + + INT + INT - returns the sum of two INTs. + + INT + TIME or TIME + INT - returns a TIME obtained by adding INT minutes to the original TIME. The re‐ + sult will always range from 00:00 through 23:59. + + TIME + TIME treats the second TIME parameter as a duration, converting it to an integer number of minutes + past midnight, and then performs addition as with TIME + INT. + + INT + DATE or DATE + INT - returns a DATE obtained by adding INT days to the original DATE. + + INT + DATETIME or DATETIME + INT - returns a DATETIME obtained by adding INT minutes to the original + DATETIME. + + DATETIME + TIME or TIME + DATETIME treats the TIME parameter as a duration, converting it to an integer + number of minutes past midnight, and then performs addition as with DATETIME + INT. + + STRING + STRING - returns a STRING that is the concatenation of the two original STRINGs. + + STRING + anything or anything + STRING - converts the non-STRING argument to a STRING, and then performs + concatenation. See the coerce() function. + + - Has several uses. These are: + + INT - INT - returns the difference of two INTs. + + DATE - DATE - returns (as an INT) the difference in days between two DATEs. + + TIME - TIME - returns (as an INT) the difference in minutes between two TIMEs. + + DATETIME - DATETIME - returns (as an INT) the difference in minutes between two DATETIMEs. + + DATE - INT - returns a DATE that is INT days earlier than the original DATE. + + TIME - INT - returns a TIME that is INT minutes earlier than the original TIME. + + DATETIME - INT - returns a DATETIME that is INT minutes earlier than the original DATETIME. + + DATETIME - TIME - coerces the TIME to an INT and then performs subtraction as above. + + <, <=, >, and >= + These are the comparison operators. They can take operands of any type, but both operands must be of the + same type. The comparison operators return 1 if the comparison is true, or 0 if it is false. Note that + string comparison is done following the lexical ordering of characters on your system, and that upper and + lower case are distinct for these operators. + + ==, != == tests for equality, returning 1 if its operands are equal, and 0 if they are not. != tests for in‐ + equality. + + If the operands are not of the same type, == returns 0 and != returns 1. Again, string comparisons are + case-sensitive. + + && This is the logical AND operator. Returns the second operand if both operands are non-zero. Otherwise, + returns whichever operand is zero. Operands can be any type and "zero" is interpreted as appropriate for + each operand's type. + + || This is the logical OR operator. It returns the first operand that is non-zero; if both operands are + zero, then returns the second operand. Operands can be any type and "zero" is interpreted as appropriate + for each operand's type. + + NOTES + + If the result of an addition, subtraction or multiplication operation would not fit in a C "int" type, Remind + issues a "Number too high" error. Unlike C, integer operations will not simply give the wrong answer in case of + overflow. + + Operators of equal precedence are always evaluated from left to right, except where parentheses dictate other‐ + wise. This is important, because the enhanced "+" operator is not necessarily associative. For example: + + 1 + 2 + "string" + 3 + 4 yields "3string34" + 1 + (2 + "string") + (3 + 4) yields "12string7" + 12:59 + 1 + "test" yields "13:00test" + 12:59 + (1 + "test") yields "12:591test" + + The logical operators are so-called short-circuit operators, as they are in C. This means that if the first + operand of || is true, then the second operand is not evaluated. Similarly, if the first operand of && is + false, then the second operand is not evaluated. + + VARIABLES + + Remind allows you to assign values to variables. The SET command is used as follows: + + SET var expr + + Var is the name of a variable. It must start with a letter or underscore, and consist only of letters, digits + and underscores. Only the first 64 characters of a variable name are significant. Variable names are not case + sensitive; thus, "Afoo" and "afOo" are the same variable. Examples: + + SET a 10 + (9*8) + SET b "This is a test" + SET mydir getenv("HOME") + SET time 12:15 + SET date today() + + Note that variables themselves have no type. They take on the type of whatever you store in them. + + Variables set with SET or on the command-line with -ivar=expr have global scope. + + To delete a variable, use the UNSET command: + + UNSET var [var...] + + For example, to delete all the variables declared above, use: + + UNSET a b mydir time date + +SYSTEM VARIABLES + In addition to the regular user variables, Remind has several "system variables" that are used to query or con‐ + trol the operating state of Remind. System variables are available starting from version 03.00.07 of Remind. + + All system variables begin with a dollar sign '$'. They can be used in SET commands and expressions just as + regular variables can. All system variables always hold values of a specified type. In addition, some system + variables cannot be modified, and you cannot create new system variables. System variables can be initialized + on the command line with the -i option, but you may need to quote them to avoid having the shell interpret the + dollar sign. System variable names are not case-sensitive. + + The following system variables are defined. Those marked "read-only" cannot be changed with the SET command. + All system variables hold values of type INT, unless otherwise specified. + + $AddBlankLines + If set to 1 (the default), then Remind normally prints a blank line after the banner and each reminder. + (This can be suppressed by ending the reminder or banner with a single percent sign.) If $AddBlankLines + is set to 0, then Remind does not print the blank line. In this case, ending a reminder with % has no + effect. If you do want a blank line after a reminder, end it with %_ to insert a newline. + + $CalcUTC + If 1 (the default), then Remind uses C library functions to calculate the number of minutes between local + and Universal Time Coordinated. This affects astronomical calculations (sunrise() for example.) If 0, + then you must supply the number of minutes between local and Universal Time Coordinated in the $MinsFro‐ + mUTC system variable. + + $CalMode (read-only) + If non-zero, then the -c option was supplied on the command line. + + $CalType (read-only, STRING type) + If the -c, -s or -p command-line options were used, then this variable has the value "monthly". If -c+, + -s+ or -p+ were used, then "weekly". Otherwise, "none". + + $Daemon (read-only) + If "daemon mode" -z was invoked, contains the number of minutes between wakeups. If not running in dae‐ + mon mode, contains 0. In server mode (either -z0 or -zj), contains -1. + + $DateSep (STRING type) + This variable can be set only to "/" or "-". It holds the character used to separate portions of a date + when Remind prints a DATE or DATETIME value. + + $DedupeReminders + If this variable is set to 1, then Remind will suppress duplicate reminders. A given reminder is consid‐ + ered to be a duplicate of a previous one if it has the exact same trigger date, trigger time, and body. + By default, this variable is set to 0 and Remind does not suppress duplicate reminders. + + As an example, consider the following reminder file: + + SET $DedupeReminders 1 + REM Wednesday MSG Phooey + REM 20 MSG Phooey + + On Wednesday, 20 November 2024, only one "Phooey" will be issued. In December of 2024, "Phooey" will be + issued every Wednesday as well as on Friday, 20 December 2024 + + If you set $DedupeReminders to 0, then Remind does not even track reminders to detect duplicates. Con‐ + sider the following example: + + SET $DedupeReminders 0 + REM Wednesday MSG Hello + SET $DedupeReminders 1 + REM Wednesday MSG Hello + + Every Wednesday, Remind will issue two "Hello" reminders. Because $DedupeReminders was 0 when the first + "Hello" was issued, it won't be tracked for potential duplicates. + + Duplicates are detected after all variable expansion and substitutions have been done. Consider the fol‐ + lowing: + + SET $DedupeReminders 1 + set a "foo" + REM MSG [a] + set a "bar" + REM MSG [a] + set a "foo" + REM MSG [a] + + The first REM will trigger and print "foo". The second will trigger and print "bar". The third will not + trigger because it's a duplicate of the first "foo". + + $DefaultColor (STRING type) + This variable can be set to a string that has the form of three space-separated numbers. Each number + must be an integer from 0 to 255, or all three numbers must be -1. The default value of $DefaultColor is + "-1 -1 -1", which suppresses default coloring of MSG-type reminders. If you set $DefaultColor to any + other value, then all MSG-, MSF- and CAL-type reminders are effectively converted into SPECIAL COLOR re‐ + minders whose color value is specified by $DefaultColor. + + Unlike other system variables, the value of $DefaultColor is not preserved between calendar iterations; + rather, it is reset to "-1 -1 -1" at the start of each iteration. + + $DefaultPrio + The default priority assigned to reminders without a PRIORITY clause. You can set this as required to + adjust the priorities of blocks of reminders without having to type priorities for individual reminders. + At startup, $DefaultPrio is set to 5000; it can range from 0 to 9999. + + $DefaultDelta + You can set this variable to a number from 0 through 10000. If set to a non-zero number, then Remind + triggers any REM statement that lacks a delta as if it had a delta of ++$DefaultDelta. By default, $De‐ + faultDelta is zero. + + $DefaultTDelta + The default time delta used if no +N is given in an AT clause. This is normally 0, but can be set with + the -tt option or explicitly set in your script. If $DefaultDelta is non-zero, you can use an explicit + delta of +0 in an AT clause to countermand the default delta. + + $DeltaOverride (read-only) + If non-zero, corresponds to the n argument given to a -tn command-line option. + + $DontFork (read-only) + If non-zero, then the -c option was supplied on the command line. + + $DontTrigAts (read-only) + The number of times that the -a option was supplied on the command line. + + $DontQueue (read-only) + If non-zero, then the -q option was supplied on the command line. + + $EndSent (STRING type) + Contains a list of characters that end a sentence. The MSF keyword inserts two spaces after these char‐ + acters. Initially, $EndSent is set to ".!?" (period, exclamation mark, and question mark.) + + $EndSentIg (STRING type) + Contains a list of characters that should be ignored when MSF decides whether or not to place two spaces + after a sentence. Initially, is set to "'>)]}"+CHAR(34) (single-quote, greater-than, right parenthesis, + right bracket, right brace, and double-quote.) + + For example, the default values work as follows: + + MSF He said, "Huh! (Two spaces will follow this.)" Yup. + + because the final parenthesis and quote are ignored (for the purposes of spacing) when they follow a pe‐ + riod. + + $ExpressionTimeLimit + If set to a non-zero value n, than any expression that takes longer than n seconds to evaluate will be + aborted and an error returned. This is to prevent maliciously-crafted expressions for creating a denial- + of-service. In an included file, $ExpressionTimeLimit can only be lowered from its current value. In + the top-level file, it can be set to any value, including zero to disable the time limit. + + $FirstIndent + The number of spaces by which to indent the first line of a MSF-type reminder. The default is 0. + + $FoldYear + The standard Unix library functions may have difficulty dealing with dates later than 2037. If this + variable is set to 1, then the UTC calculations "fold back" years later than 2037 before using the Unix + library functions. For example, to find out whether or not daylight saving time is in effect in June, + 2077, the year is "folded back" to 2027, because both years begin on a Friday, and both are non- + leapyears. The rules for daylight saving time are thus presumed to be identical for both years, and the + Unix library functions can handle 2027. By default, this variable is 0. Set it to 1 if the sun or UTC + functions misbehave for years greater than 2037. See also the section "MACHINES WITH A 32-BIT TIME_T + TYPE" + + $FormWidth + The maximum width of each line of text for formatting MSF-type reminders. The default is the width of + the terminal in columns, minus 8, but clamped at a minimum of 20 and a maximum of 500. If standard out‐ + put is not a terminal, then the default is 72.If an MSF-type reminder contains a word too long to fit in + this width, it will not be truncated - the width limit will be ignored. + + $HideCompletedTodos (read-only) + If non-zero, then the --hide-completed-todos option was supplied on the command line. + + $HushMode (read-only) + If non-zero, then the -h option was supplied on the command line. + + $IgnoreOnce (read-only) + If non-zero, then the -o option was supplied on the command line, or implicitly enabled for some other + reason. In this case, ONCE directives will be ignored. + + $InfDelta (read-only) + If non-zero, then the -t option was supplied on the command line, with no n argument. + + $IntMax (read-only) + The largest representable INT. On a machine with 32-bit signed integers using twos-complement represen‐ + tation, this will be 2147483647. + + $IntMin (read-only) + The smallest representable INT. On a machine with 32-bit signed integers using twos-complement represen‐ + tation, this will be -2147483648. + + $Latitude (STRING type) + The latitude of your location, expressed as a string that is a floating-point number. Because Remind + does not have a native floating-point type, we need to express it as a string. $Latitude can range from + "-90.0" to "90.0", with positive numbers representing points north of the equator and negative numbers + representing south. Note that regardless of your locale, $Latitude is always interpreted in the "C" lo‐ + cale and as such, the decimal point must be a period ("."). + + $Longitude (STRING type) + The longitude of your location, expressed as a string that is a floating-point number. Because Remind + does not have a native floating-point type, we need to express it as a string. $Longitude can range from + "-180.0" to "180.0", with positive numbers representing points east of the Greenwich Meridian and nega‐ + tive numbers representing west. Note that regardless of your locale, $Longitude is always interpreted in + the "C" locale and as such, the decimal point must be a period ("."). + + For example, the coordinates of the Statue of Liberty in New York City are approximately set by: + + SET $Latitude "40.68933" + SET $Longitude "-74.04454" + + $LatDeg, $LatMin, $LatSec (DEPRECATED) + These specify the latitude of your location. $LatDeg can range from -90 to 90, and the others from -59 + to 59. Northern latitudes are positive; southern ones are negative. For southern latitudes, all three + components should be negative. These three variables are deprecated; you should use $Latitude instead. + + $Location (STRING type) + This is a string specifying the name of your location. It is usually the name of your town or city. It + can be set to whatever you like, but good style indicates that it should be kept consistent with the lat‐ + itude and longitude system variables. + + $LongDeg, $LongMin, $LongSec (DEPRECATED) + These specify the longitude of your location. $LongDeg can range from -180 to 180. Western longitudes + are positive; eastern ones are negative. Note that all three components should have the same sign: All + positive for western longitudes and all negative for eastern longitudes. Note that for historical rea‐ + sons, the sign for longitude is different from the usual convention! If you find the longitude of your + location from a search engine, you will most likely need to invert the sign to have it work correctly + with Remind. These three variables are deprecated; you should use $Longitude instead. Note also that + $Longitude uses the standard convention of negative for western longitudes and positive for eastern ones. + + The latitude and longitude information is required for the functions sunrise() and sunset(). Default + values can be compiled into Remind, or you can SET the correct values at the start of your reminder + scripts. + + Note that setting any of $LongDec, $LongMin and $LongSec updates $Longitude correspondingly, and setting + $Longitude updates $LongDeg, $LongMin and $LongSec. Similar rules apply to $Latitude, $LatDeg, $LatMin + and $LatSec. + + $JSONMode (read-only) + If non-zero, then the --json command-line option was supplied. + + $MaxLateMinutes + This variable controls how Remind reacts to a computer being suspended and then woken. Normally, if a + timed reminder is queued and then the computer suspended, and then the computer is woken after the timed + reminder's trigger time, Remind will trigger the timer anyway, despite the fact that the trigger time has + already passed. + + If you set $MaxLateMinutes to a non-zero integer between 1 and 1440, then Remind will not trigger a timed + reminder whose trigger time is more than $MaxLateMinutes minutes in the past. + + Note that Remind uses the value of $MaxLateMinutes that is in effect when it has finished reading the re‐ + minder file and puts itself in the background. Generally, you should set $MaxLateMinutes once near the + beginning of the file and not change it after that. + + $MaxSatIter + The maximum number of iterations for the SATISFY clause (described later.) Must be at least 10. The de‐ + fault value is 10,000. + + $MaxStringLen + A limit on the longest string that Remind will allow you to create. The default is 65535. If you set + $MaxStringLen to 0 or to -1, then remind will allow you to create arbitrarily-long strings, at least un‐ + til it runs out of memory. We do not recommend setting $MaxStringLen to 0 or -1 because it is very easy + to write code that DOSes Remind in that case. + + $MinsFromUTC + The number of minutes between Universal Time Coordinated and local time. If $CalcUTC is non-zero, this + is calculated upon startup of Remind. Otherwise, you must set it explicitly. If $CalcUTC is zero, then + $MinsFromUTC is used in the astronomical calculations. You must adjust it for daylight saving time your‐ + self. Also, if you want to initialize $MinsFromUTC using the -i command-line option, you must also set + $CalcUTC to 0 with the -i option. + + $NextMode (read-only) + If non-zero, then the -n option was supplied on the command line. + + $MaxFullOmits (read-only) + The maximum number of full OMITs allowed (a compiled-in constant.) + + $MaxPartialOmits (read-only) + The maximum number of partial OMITs allowed (a compiled-in constant.) + + $NumFullOmits (read-only) + The number of full OMITs in the current OMIT context. + + $NumPartialOmits (read-only) + The number of partial OMITs in the current OMIT context. + + $NumQueued (read-only) + Contains the number of reminders queued so far for background timed triggering. + + $NumTrig (read-only) + Contains the number of reminders triggered for the current date. One use for this variable is as fol‐ + lows: Suppose you wish to shade in the box of a PostScript calendar whenever a holiday is triggered. + You could save the value of $NumTrig in a regular variable prior to executing a block of holiday re‐ + minders. If the value of $NumTrig after the holiday block is greater than the saved value, then at least + one holiday was triggered, and you can execute the command to shade in the calendar box. (See the sec‐ + tion "Calendar Mode".) + + Note that $NumTrig is affected only by REM commands; triggers in IFTRIG commands do not affect it. + + $OnceFile (STRING type) + If you set this variable to a non-empty string, then rather than using the file access date to determine + whether or not to run a ONCE-type reminder, Remind will maintain a timestamp in the file $OnceFile. This + is more reliable than using the access date of the reminder file. + + If $OnceFile does not exist, then it will be created the first time a ONCE keyword is processed. The + file must be writable by the current user. If you try to set $OnceFile after a ONCE reminder has already + been processed, Remind will issue a warning and ignore the attempt to set $OnceFile. + + $ParseUntriggered + A flag indicating whether or not Remind should fully parse REM statements that are not triggered. 0 (the + default) means to skip parsing them and 1 means to parse them. + + For example, if we have the following REM statement: + + REM 2020-01-01 MSG ["bad_expression" / 2] + + Then if $ParseUntriggered is set to 1, Remind will fully parse the line and issue a "Type mismatch" error + even if the reminder is not triggered. However, if $ParseUntriggered is set to 0, the default, then Re‐ + mind will not issue the error except on 2020-01-01, when the reminder is triggered. + + Keeping $ParseUntriggered at 0 may slightly improve performance, at the risk of not catching errors until + a reminder is triggered. We recommend leaving it set to 0. + + $PrefixLineNo (read-only) + If non-zero, then the -l option was supplied on the command line. + + $PSCal (read-only) + If non-zero, then the -p option was supplied on the command line. + + $RunOff (read-only) + If non-zero, the RUN directives are disabled. + + $Shaded (read-only) + Returns the number of times a SHADE special reminder has triggered. This variable is set only in calen‐ + dar mode, not agenda mode. You can use this variable to avoid shading a calendar day that has already + been shaded. + + For example, suppose you want to shade all calendar boxes yellow if any reminders have triggered on that + day. But if a box has been explicitly shaded, you don't want to overwrite that shading. You could use + something like this: + + SET n $NumTrig + # Do all your reminders here... + + # If anything has triggered and the box is + # not already shaded, then shade it yellow + IF $NumTrig > n && !$Shaded + REM SPECIAL SHADE 255 255 128 + ENDIF + + $SimpleCal (read-only) + Set to a non-zero value if either of the -p or -s command-line options was supplied. + + $SortByDate (read-only) + Set to 0 if no -g option is used, 1 if sorting by date in ascending order, or 2 if sorting by date in de‐ + scending order. + + $SortByPrio (read-only) + Set to 0 if no -g option is used, 1 if sorting by priority in ascending order, or 2 if sorting by prior‐ + ity in descending order. + + $SortByTime (read-only) + Set to 0 if no -g option is used, 1 if sorting by time in ascending order, or 2 if sorting by time in de‐ + scending order. + + $SubsIndent + The number of spaces by which all lines (except the first) of an MSF-type reminder should be indented. + The default is 0. + + $SuppressImplicitWarnings + Normally, Remind issues a warning if a line begins with an unknown token and is treated as a REM command, + or if a REM command is missing a type and is treated as a MSG-type reminder. Setting $SuppressImplicit‐ + Warnings to 1 suppresses these warnings. The default is 0 and we do not recommend disabling the warn‐ + ings. + + $SuppressLRM + Normally, when Remind is run with the -c option in a UTF-8 locale, it emits a left-to-right mark sequence + after printing day names or reminders. Some terminals render this incorrectly, so you can use: + + SET $SuppressLRM 1 + + at the top of your reminder file to suppress the LRM sequences, or you can invoke Remind with the option + '-i$SuppressLRM=1'. + + $SysInclude (read-only, STRING type) + A directory path containing standard reminder scripts. Currently, Remind ships with some standard holi‐ + day files and language packs. The value of $SysInclude is "/usr/local/share/remind" on this installa‐ + tion. + + $T (read-only, DATE or INT type) + Equivalent to trigdate(). (See BUILT-IN FUNCTIONS.) + + $Tb (read-only, DATE or INT type) + Equivalent to trigbase(). + + $Td (read-only) + Equivalent to day(trigdate()). + + $Tm (read-only) + Equivalent to monnum(trigdate()). + + $Tu (read-only, DATE or INT type) + Equivalent to triguntil(). + + $Tw (read-only) + Equivalent to wkdaynum(trigdate()). + + $Ty (read-only) + Equivalent to year(trigdate()). + + $Tt (read-only, TIME or INT type) + Equivalent to trigtime(). + + $TimeSep (STRING type) + This variable can be set only to ":" or ".". It holds the character used to separate portions of a time + when Remind prints a TIME or DATETIME value. + + $TimetIs64bit (read-only) + This variable returns 1 if the internal C time_t type is at least 64 bits long. If it returns 0, then + the internal C library is unable to represent dates after about 2038, and Remind will use a workaround to + avoid problems. See also the section "MACHINES WITH A 32-BIT TIME_T TYPE" + + $TodoFilter (read-only) + If 0, then both events and TODOs are being output. If 1, then the --only-todos command-line option was + supplied. If 2, then the --only-events command-line option was supplied. + + $UntimedFirst (read-only) + Set to 1 if the -g option is used with a fourth sort character of "d"; set to 0 otherwise. + + $U (read-only, DATE type) + Exactly equivalent to today(). (See BUILT-IN FUNCTIONS.) + + $Ud (read-only) + Equivalent to day(today()). + + $Um (read-only) + Equivalent to monnum(today()). + + $Uw (read-only) + Equivalent to wkdaynum(today()). + + $Uy (read-only) + Equivalent to year(today()). + + $UseVTColors (read-only) + Set to 1 if the -@ or -cc options were used; 0 otherwise. + + $UseBGVTColors (read-only) + Set to 1 if the -@,,1 option was used; 0 otherwise. + + $Use256Colors (read-only) + Set to 1 if the -@1 option was used; 0 otherwise. + + $UseTrueColors (read-only) + Set to 1 if the -@2 option was used; 0 otherwise. + + $TerminalBackground (read-only) + Returns -1 if the terminal background color could not be determined, 0 if it was found to be dark (or was + specified as dark with the -@,0 option) or 1 if it was found to be light (or specified as light with the + -@,1 option.) The terminal background is considered to be "dark" if the average of the red, green and + blue components is at most 85 out of 255, and if the maximum of any component is at most 128 out of 255. + + $TerminalHyperlinks (INT type) + If your terminal supports escape sequences to allow HTML-like anchors around text (see + https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda), then you can set this variable to 1. + Remind will then make any reminder with a "Url:" info string into a hyperlink in your terminal. By de‐ + fault, $TerminalHyperlinks is set to 1 if Remind's standard output is a terminal, or to 0 if it is not. + + $WarningLevel (STRING type) + As new versions of Remind are released, new warnings may be added. If your formerly-fine scripts sud‐ + denly start issuing warnings when you upgrade Remind, then as a stopgap measure, you may set $Warn‐ + ingLevel to a string of the form AB.CD.EF where AB, CD and EF are pairs of decimal digits. This will + suppress any warnings that were introduced after Remind version AB.CD.EF. If you do not set $Warn‐ + ingLevel, then it defaults to the current version of Remind, meaning all warnings will be issued. + + For example, if you want the warnings you receive limited to what Remind 05.00.00 would have produced, + use: + + SET $WarningLevel "05.00.00" + + We do not recommend setting $WarningLevel as a matter of course; you should use it to suppress warnings + only for as long as it takes for you to fix your remind scripts so they no longer cause warnings to be + emitted. + + Note: If any of the calendar modes are in effect, then the values of $Daemon, $DontFork, $DontTrigAts, $Don‐ + tQueue, $HushMode, $IgnoreOnce, $InfDelta, and $NextMode are not meaningful. + +SAVING AND RESTORING VARIABLES + Just as with the OMIT context, you can save and restore the values of global variables and all (modifiable) sys‐ + tem variables. For example: + + SET a 1 + UNSET b + + # Save variables + PUSH-VARS $DefaultColor $AddBlankLines a b + + SET $DefaultColor "255 0 0" + SET $AddBlankLines 0 + SET a 3 + SET b 4 + REM MSG Hello + + # Restore all the variables we pushed earlier + POP-VARS + + # Now the changes to $DefaultColor and $AddBlankLines + # have been undone. Additionally, a is restored to 1 + # and b is unset + + As you see from the example, PUSH-VARS takes a list of global variable names and/or system variable names. You + can save the values of any modifiable system variables and any global variable. You can even push global vari‐ + ables that are not set. + + The POP-VARS command restores the values of all system variables and global variables that were pushed by the + most recent PUSH-VARS command. If you push an undefined global variable, then that variable is unset by the + POP-VARS command. + +BUILT-IN FUNCTIONS + Remind has a plethora of built-in functions. The syntax for a function call is the same as in C - the function + name, followed a comma-separated list of arguments in parentheses. Function names are not case-sensitive. If a + function takes no arguments, it must be followed by "()" in the function call. Otherwise, Remind will interpret + it as a variable name, and probably not work correctly. + + In the descriptions below, short forms are used to denote acceptable types for the arguments. The characters + "i", "s", "d", "t" and "q" denote INT, STRING, DATE, TIME and DATETIME arguments, respectively. If an argument + can be one of several types, the characters are concatenated. For example, "di_arg" denotes an argument that + can be a DATE or an INT. "x_arg" denotes an argument that can be of any type. The type of the argument is fol‐ + lowed by an underscore and an identifier naming the argument. + + The built-in functions are: + + _(s_message) + Returns the translation table entry for message. If there is no such translation table entry, then re‐ + turns message unmodified. For example, consider this sequence: + + TRANSLATE "Goodbye" "Tot ziens" + SET a _("Goodbye") + + After those two lines have been executed, the variable a will be set to "Tot ziens". See the section THE + TRANSLATION TABLE for more information. + + In the body of a reminder, the substitution sequence %(text) is (almost) the equivalent of [_("text")]. + Therefore, the following reminders are almost equivalent: + + REM MSG %(Goodbye) + REM MSG [_("Goodbye")] + + The only difference is that if _("Goodbye") contains a % sign, then that result will be run through the + substitution filter, whereas in the first reminder, it will not. That is because the second REM command + performs expression pasting followed by a substitution filter pass, while the first one performs the + translation as part of the substitution filter (and does not make a second substitution filter pass.) + + abs(i_num) + Returns the absolute value of num. + + access(s_file, si_mode) + Tests the access permissions for the file file. Mode can be a string, containing a mix of the characters + "rwx" for read, write and execute permission testing. Alternatively, mode can be a number as described + in the UNIX access(2) system call. The function returns 0 if the file can be accessed with the specified + mode, and -1 otherwise. + + adawn([dq_date]) + Returns the time of "astronomical dawn" on the specified date. If date is omitted, defaults to today(). + If a datetime object is supplied, only the date component is used. + + adusk([dq_date]) + Returns the time of "astronomical twilight" on the specified date. If date is omitted, defaults to to‐ + day(). + + ampm(tq_time [,s_am [,s_pm [,i_lz]]]) + Returns a STRING that is the result of converting time (which is either a TIME or a DATETIME object) to + "AM/PM" format. The optional arguments am and pm are the strings to append in the AM and PM case, re‐ + spectively; they default to "AM" and "PM". The optional argument lz specifies whether or not the hour + should be padded to two digits with a leading zero. If lz is zero, then a leading 0 is not added; other‐ + wise, the hour is padded out to two digits with a leading zero. If not supplied, lz defaults to zero. + + The function obeys the system variables $DateSep, $TimeSep and $DateTimeSep when formatting its output. + Here are some examples of its output: + + ampm(0:22) returns "12:22AM" + ampm(17:45, "am", "pm") returns "5:45pm" + ampm(17:45, "am", "pm", 1) returns "05:45pm" + ampm('2020-03-14@21:34') returns "2020-03-14@9:34PM" + + ansicolor(i_red, i_green, i_blue [,i_bg [,i_clamp]]) + Returns a STRING that contains an ANSI escape sequence for changing the terminal text color. The parame‐ + ters red, green and blue are integers from 0 to 255 specifying the value of the respective color compo‐ + nent. As a special case, all three values can be -1, in which case the ANSI sequence "ESC[0m" is re‐ + turned, which resets all text attributes to normal. + + The string returned by ansicolor depends on the color mode that Remind is running in, as specified by the + -@ option. If color mode is not enabled, then ansicolor always returns the empty string. Otherwise, it + returns the escape sequence that best approximates the color according to the -@ color mode. + + The optional bg argument is either 0 or 1. If 0 (the default), then the foreground color is set. If 1, + then the background color is set. Note that setting the background color only works in 256-color or + true-color mode. + + The optional clamp argument is either 0 or 1. If 0 (the default), then colors are not adjusted based on + the terminal background color. If 1, then Remind attempts to adjust dark or bright colors so they have + enough contrast to be visible in the terminal. + + The first three arguments may alternatively be specified as a string consisting of three space-separated + numbers, as in this example: "128 128 0" + + As a special case, ansicolor("") is equivalent to ansicolor(-1,-1,-1) and returns the ANSI sequence to + reset all text attributes to normal. + + Note that inserting ANSI color sequences in calendar mode will produce garbled results. Therefore, we + recommend defining functions such as the ones below that return the empty string in calendar mode: + + IF $CalMode + FSET fg(r,g,b) "" + FSET bg(r,g,b) "" + ELSE + FSET fg(r,g,b) ansicolor(r,g,b) + FSET bg(r,g,b) ansicolor(r,g,b,1) + ENDIF + REM [fg(255,0,0)][bg(64,64,64)]Red on Gray[fg(-1,-1,-1)] in agenda mode + REM SPECIAL COLOR 0 255 0 Green in agenda and calendar mode + + If you use the ansicolor function, don't forget to reset the color back to normal with ansi‐ + color(-1,-1,-1) or subsequent reminders will continue to be colored. + + args(s_fname) + Returns the number of arguments expected by the user-defined function fname, or -1 if no such user-de‐ + fined function exists. Note that this function examines only user-defined functions, not built-in func‐ + tions. Its main use is to determine whether or not a particular user-defined function has been defined + previously. The args() function is available only in versions of Remind from 03.00.04 and up. + + asc(s_string) + Returns an INT that is the ASCII code of the first byte in string. As a special case, asc("") returns 0. + For UTF-8 strings, this will return the UTF-8 byte with which the string begins, which is not likely to + be very useful. + + codepoint(s_string) + Returns an INT that is the code point of the first character in string, treating multi-byte characters + correctly. As a special case, codepoint("") returns 0. + + baseyr() + Returns the "base year" that was compiled into Remind (normally 1990.) All dates are stored internally + as the number of days since 1 January of baseyr(). + + catch(x_arg1, x_arg2) + Evaluates arg1 and if no error occurs, returns the resulting value. If an error occurs during the evalu‐ + ation of arg1, evaluates and returns arg2. Note that catch() can only catch run-time evaluation errors. + It cannot catch syntax errors. + + Here are some examples: + + catch(4/2, 33) => 2, because 4/2 evaluates without error + catch(4/0, 33) => 33, because 4/0 gives a divide-by-zero error + catch(2, 1/0) => 2, and the second argument is not evaluated + catch(4/0, 1/0) results in a divide-by-zero error + + Note in the last example that catch does not protect you from errors in the evaluation of arg2. + + catcherr() + Returns a string representing the error message from the most recently-evaluated catch() function whose + first argument yielded an error. Note that the error message is always in English even if Remind has + been localized; this lets you reliably test the return value. Here are some examples: + + # No "catch" invocations yet: sets a to "Ok" + set a catcherr() + + # Sets b to "oops" and a to "Division by zero" + set b catch(1/0, "oops") + set a catcherr() + + # Sets b to 1 and a to "Division by zero". The catch error + # is never cleared by a non-error catch() + set b catch(4-3, "not-evaluated") + set a catcherr() + + char(i_i1 [,i_i2...]) + This function can take any number of INT arguments. It returns a STRING consisting of the bytes speci‐ + fied by the arguments. It is easy to create invalid UTF-8 sequences; char does not check for this. Note + that none of the arguments can be 0, unless there is only one argument. As a special case, char(0) re‐ + turns "". + + mbchar(i_i1 [,i_i2...]) + This function can take any number of INT arguments. It returns a STRING consisting of the characters + specified by the arguments. Any codepoint may be supplied and a correct multi-byte character string will + be returned. Note that none of the arguments can be 0, unless there is only one argument. As a special + case, mbchar(0) returns "". Additionally, no argument may be a negative number. + + choose(i_index, x_arg1 [,x_arg2...]) + Choose must take at least two arguments, the first of which is an INT. If index is n, then the nth sub‐ + sequent argument is returned. If index is less than 1, then arg1 is returned. If index is greater than + the number of subsequent arguments, then the last argument is returned. Examples: + + choose(0, "foo", 1:13, 1000) returns "foo" + choose(1, "foo", 1:13, 1000) returns "foo" + choose(2, "foo", 1:13, 1000) returns 1:13 + choose(3, "foo", 1:13, 1000) returns 1000 + choose(4, "foo", 1:13, 1000) returns 1000 + + Note that only the first argument and the chosen result are evaluated. Any non-chosen arguments will not + be evaluated. + + coerce(s_type, x_arg) + This function converts arg to the specified type, if such conversion is possible. Type must be one of + "INT", "STRING", "DATE", "TIME" or "DATETIME" (case-insensitive). The conversion rules are as follows: + + If arg is already of the type specified, it is returned unchanged. + + If type is "STRING", then arg is converted to a string consisting of its printed representation. + + If type is "DATE", then an INT arg is converted by interpreting it as the number of days since 1 January + baseyr(). A STRING arg is converted by attempting to read it as if it were a printed date. A DATETIME + is converted to a date by dropping the time component. A TIME arg cannot be converted to a date. + + If type is "TIME", then an INT arg is converted by interpreting it as the number of minutes since mid‐ + night. A STRING arg is converted by attempting to read it as if it were a printed time. A DATETIME is + converted to a time by dropping the date component. A DATE arg cannot be converted to a time. + + If type is "DATETIME", then an INT arg is converted by interpreting it as the number of minutes since + midnight, 1 January baseyr(). A STRING is converted by attempting to read it as if it were a printed + datetime. Other types cannot be converted to a datetime. + + If type is "INT", then DATE, TIME and DATETIME arguments are converted using the reverse of procedures + described above. A STRING arg is converted by parsing it as an integer. + + columns([s_arg]) + If called with no arguments, columns() behaves as follows: If either standard output or standard error is + a TTY, returns the width of the terminal in columns. If neither standard output nor standard error is a + TTY, attempts to open "/dev/tty" to obtain the terminal size. If this fails, returns -1. + + If called with a single string argument, columns(str) returns the number of columns str will occupy if + printed to a terminal. ANSI color-changing sequences occupy zero columns whereas some Unicode characters + occupy two columns. columns(str) takes all of that into account. Note that if Remind was compiled with‐ + out Unicode support, columns(str) returns a type mismatch error. + + The result of columns(str) may be less than, equal to, or greater than the result of mbstrlen(str). This + is because some Unicode characters are so-called combining characters that add one to the character + length, but don't occupy any columns on their own. And other Unicode characters are double-width charac‐ + ters that add one to the character length, but two to the number of display columns. + + const(x_arg) + Evaluates arg and returns it, but also ensures that the result is marked "constant". Use with caution; + incorrect use could result in unwanted reminders being purged in Purge Mode. See also the section "NON- + CONSTANT EXPRESSIONS" + + current() + Returns the current date and time as a DATETIME object. This may be the actual date and time, or may be + the date and time supplied on the command line. + + date(i_y, si_m, i_d) + The date() function returns a DATE object with the year, month and day components specified by y, m and + d. The month can be specified as an integer from 1 to 12, or a string that is the name of a month. + + datepart(dq_datetime) + Returns a DATE object representing the date portion of datetime. + + datetime(args) + The datetime() function can take anywhere from two to five arguments. It always returns a DATETIME gen‐ + erated from its arguments. + + If you supply two arguments, the first must be a DATE and the second a TIME. + + If you supply three arguments, the first must be a DATE and the second and third must be INTs. The sec‐ + ond and third arguments are interpreted as hours and minutes and converted to a TIME. + + If you supply four arguments, they are interpreted as year, month, day and time. Year and day must be + INTs. Month may be an INT from 0 to 12 or a string naming a month. Time must be a TIME. + + Finally, if you supply five arguments, they must all be INTs and are interpreted as year, month, day, + hour and minute. (Actually, month can also be a string that is the name of a month.) + + dawn([dq_date]) + Returns the time of "civil dawn" on the specified date. If date is omitted, defaults to today(). If a + datetime object is supplied, only the date component is used. + + day(dq_date) + This function takes a DATE or DATETIME as an argument, and returns an INT that is the day-of-month compo‐ + nent of date. + + daysinmon(si_m, i_y) or daysinmon(dq_date) + Returns the number of days in month m (1-12) of the year y. The first argument can either be an integer + from 1 to 12, or a string that is the name of a month. If given a single DATE or DATETIME argument, re‐ + turns the number of days in the month containing the argument. + + defined(s_var) + Returns 1 if the variable named by var is defined, or 0 if it is not. + Note that defined() takes a STRING argument; thus, to check if variable X is defined, use: + + defined("X") + + and not: + + defined(X) + + The second example will attempt to evaluate X, and will return an error if it is undefined or not of type + STRING. + + dosubst(s_str [,d_date [,t_time]]) or dosubst(s_str [,q_datetime]) + Returns a STRING that is the result of passing str through the substitution filter described earlier. + The parameters date and time (or datetime) establish the effective trigger date and time used by the sub‐ + stitution filter. If date and time are omitted, they default to today() and now(). + + Note that if str does not end with "%", a newline character will be added to the end of the result. + Also, calling dosubst() with a date that is in the past (i.e., if date < today()) will produce undefined + results. + + Dosubst() is only available starting from version 03.00.04 of Remind. + + dusk([dq_date]) + Returns the time of "civil twilight" on the specified date. If date is omitted, defaults to today(). + + easterdate([dqi_arg]) + If arg is an INT, then returns the date of Easter Sunday for the specified year. If arg is a DATE or + DATETIME, then returns the date of the next Easter Sunday on or after arg. (The time component of a + datetime is ignored.) If arg is omitted, then it defaults to today(). + + Note that easterdate computes the Western Easter. For the Orthodox Easter date, see orthodoxeaster. + + escape(s_string [,i_add_quotes]) + Returns a STRING that is the same as the input string, but with all special characters backslashed-es‐ + caped. For example, the following command: + + set a escape("foo" + char(10) + "bar") + + will set a to "foo\nbar" where "\n" is the literal sequence "\" followed by "n". This is useful if you + want to compute a string in a pasted-in expression that Remind will then parse as a quoted string again, + such as in a TRANSLATE command or an INFO clause. + + If the optional add_quotes argument is supplied and is non-zero, then the return value from escape will + include surrounding double-quotes. + + eval(s_arg) + Parses the string arg as an expression and evaluates it, returning the result. Note that any variable + names in the parsed expression refer to global variables, not any local variables. For example, consider + this: + + SET x 2 + FSET f(x) x + eval("1 + x") + REM MSG F is [f(9)] + + The result will be "F is 12" because the reference to x inside the eval() argument refers to the global + variable x and not the function argument. + + Note that for safety, RUN is disabled during the evaluation of eval(), which means you can't use the + shell() function from within an eval(). + + evaltrig(s_trigger [,dq_start]) + Evaluates trigger as if it were a REM or IFTRIG trigger specification and returns the trigger date as a + DATE (or as a DATETIME if there is an AT clause.) Returns a negative INT if no trigger could be com‐ + puted. + + Normally, evaltrig finds a trigger date on or after today. If you supply the start argument, then it + scans starting from there. + + For example, the expression: + + evaltrig("Mon 1", '2008-10-07') + + returns '2008-11-03', since that is the first date on or after 7 October 2008 that satisfies "Mon 1". + + If you want to see how many days it is from the first Monday in October, 2008 to the first Monday in No‐ + vember, 2008, use: + + evaltrig("Mon 1", '2008-11-01') - evaltrig("Mon 1", '2008-10-01') + + and the answer is 28. The trigger argument to evaltrig can have all the usual trigger clauses (OMIT, AT, + SKIP, etc.) but cannot have a SATISFY, MSG, etc. reminder-type clause. + + filedate(s_filename) + Returns the modification date of filename. If filename does not exist, or its modification date is be‐ + fore the year baseyr(), then 1 January of baseyr() is returned. + + filedatetime(s_filename) + Returns the modification date and time of filename. If filename does not exist, or its modification date + is before the year baseyr(), then midnight, 1 January of baseyr() is returned. + + filedir() + Returns the directory that contains the current file being processed. It may be a relative or absolute + pathname, but is guaranteed to be correct for use in an INCLUDE command as follows: + + INCLUDE [filedir()]/stuff + + This includes the file "stuff" in the same directory as the current file being processed. Note that this + workaround is no longer necessary because DO stuff will achieve the same goal. + + Note that if the currently-processing reminders file was specified as a symbolic link, then filedir() re‐ + turns the directory containing the symbolic link and not the directory containing the target of the sym‐ + bolic link. You should avoid using symbolic links to files unless both the symbolic link and its target + happen to be in the same directory. + + filename() + Returns (as a STRING) the name of the current file being processed by Remind. Inside included files, re‐ + turns the name of the included file. + + getenv(s_envvar) + Similar to the getenv(2) system call. Returns a string representing the value of the specified environ‐ + ment variable. Returns "" if the environment variable is not defined. Note that the names of environ‐ + ment variables are generally case-sensitive; thus, getenv("HOME") is not the same as getenv("home"). + + hebdate(i_day, s_hebmon [,idq_yrstart [,i_jahr [,i_aflag]]]) + Support for Hebrew dates - see the section "THE HEBREW CALENDAR" + + hebday(dq_date) + Support for Hebrew dates - see the section "THE HEBREW CALENDAR" + + hebmon(dq_date) + Support for Hebrew dates - see the section "THE HEBREW CALENDAR" + + hebyear(dq_date) + Support for Hebrew dates - see the section "THE HEBREW CALENDAR" + + hex(i_n) + Returns a STRING that is the hexadecimal representation of n. There is no "0x" prefix and any letters in + the returned value are uppper-case. + + hour(tq_time) + Returns the hour component of time. + + htmlescape(s_str) + Returns a modified copy of str where "<" is replaced with "<"; ">" is replaced with ">" and "&" is + replaced with "&" + + htmlstriptags(s_str) + Returns a modified copy of str where HTML tags are stripped out. The stripping algorithm is fairly + naive; the function starts stripping characters when it encounters a "<" and it stops stripping when it + encounters a ">". + + iif(x_test1, x_arg1, [x_test2, x_arg2,...], x_default) + If test1 is true, returns arg1. Otherwise, if test2 is true, returns arg2, and so on. If all of the + test arguments are false, returns default. Note that only those arguments needed to determine the final + result are evaluated. This function accepts an odd number of arguments - note that prior to version + 03.00.05 of Remind, it accepted 3 arguments only. The 3-argument version of iif() is compatible with + previous versions of Remind. + + index(s_search, s_target [,i_start) + Returns an INT that is the location of target in the string search. Note that index uses byte positions, + not character positions, so should not be used on non-ASCII strings. Use mbindex for non-ASCII strings. + + The first byte of a string is numbered 1. If target does not exist in search, then 0 is returned. + + The optional parameter start specifies the position in search at which to start looking for target. + + mbindex(s_search, s_target [,i_start]) + Similar to index() but returns the character position rather than the byte position. Also, start is in‐ + terpreted as a 1-based character index rather than a byte index. + + isany(arg1 [,arg2, ..., argN]); + Returns 1 if the first argument arg1 is equal to any of the subsequent arguments arg2 through argN; re‐ + turns 0 otherwise. Also returns 0 if called with only one argument. + + As an example, the following two expressions are equivalent: + + (a == b) || (a == c) || (a == d) || (a == e) + + isany(a, b, c, d, e) + + isconst(x_any) + Evaluates its argument and then throws away the value, returning 1 if the expression is constant or 0 if + it is non-constant. Note that isconst does not take into account the context; if arg is a constant ex‐ + pression but evaluated in a non-constant context, isconst will still return 1. For details about con‐ + stant vs. non-constant expressions, see the section "NON-CONSTANT EXPRESSIONS" + + isdst([d_date [,t_time]]) or isdst(q_datetime) + Returns a positive number if daylight saving time is in effect on the specified date and time. Date de‐ + faults to today() and time defaults to midnight. + + Note that this function is only as reliable as the C run-time library functions. It is available start‐ + ing with version 03.00.07 of Remind. + + isleap(idq_arg) + Returns 1 if arg is a leap year, and 0 otherwise. Arg can be an INT, DATE or DATETIME object. If a DATE + or DATETIME is supplied, then the year component is used in the test. + + isomitted(dq_date) + Returns 1 if date is omitted, given the current global OMIT context. Returns 0 otherwise. (If a date‐ + time is supplied, only the date part is used.) Note that any local OMIT or OMITFUNC clauses are not + taken into account by this function. + + language() + Returns a STRING naming the compiled-in language supported by Remind. Remind used to support compiled-in + support for other languages, but now all localization is done at run-time. As such, this function always + returns "English". However, the expression _("LANGID") returns the two-character ISO 639 language code + of any language pack in effect, assuming the language pack author has written the localization correctly! + + localtoutc(q_datetime) + Given a DATETIME object interpreted in the local time zone, return a DATETIME object that expresses the + same time in UTC. + + lower(s_string) + Returns a STRING with all upper-case bytes in string converted to lower-case. Note: This function works + correctly only for ASCII strings. If you are using Unicode characters outside the ASCII set, use mblower + instead. + + mblower(s_string) + Returns a STRING with all upper-case characters in string converted to lower-case. This function works + correctly on any Unicode string. + + max(x_arg1 [,x_arg2...) + Can take any number of arguments, and returns the maximum. The arguments can be of any type, but must + all be of the same type. They are compared as with the > operator. + + min(x_arg1 [,x_arg2...) + Can take any number of arguments, and returns the minimum. The arguments can be of any type, but must + all be of the same type. They are compared as with the < operator. + + minsfromutc([d_date [,t_time]]) or minsfromutc(q_datetime) + Returns the number of minutes from Universal Time Coordinated (formerly GMT) to local time on the speci‐ + fied date and time. Date defaults to today() and time defaults to midnight. If local time is before + UTC, the result is negative. Otherwise, the result is positive. + + Note that this function is only as reliable as the C run-time library functions. It is available start‐ + ing with version 03.00.07 of Remind. + + minute(tq_time) + Returns the minute component of time. + + mon(dqis_arg) + If arg is of DATE or DATETIME type, returns a string that names the month component of the date. If arg + is an INT from 1 to 12, returns a string that names the month. If arg is a STRING, returns the name of + the month. This last case might sound silly, but for example: + + mon("Mar") + + will return "March", the full name of the month. + + monnum(dq_date) or monnum(s_str) + Returns an INT from 1 to 12, representing the month component of date. If a STRING is supplied rather + than a DATE or DATETIME, then if str is a valid month name (or minimum 3-character abbreviation of a + month name) then the corresponding month number is returned. If str is not a valid month name, then an + error occurs. + + moondate(i_phase [,d_date [,t_time]]) or moondate(i_phase, q_datetime) + This function returns the date of the first occurrence of the phase phase of the moon on or after date + and time. Phase can range from 0 to 3, with 0 signifying new moon, 1 first quarter, 2 full moon, and 3 + third quarter. If date is omitted, it defaults to today(). If time is omitted, it defaults to midnight. + + For example, the following returns the date of the next full moon: + + SET fullmoon moondate(2) + + moontime(i_phase [,d_date [,t_time]]) or moontime(i_phase, q_datetime) + This function returns the time of the first occurrence of the phase phase of the moon on or after date + and time. Phase can range from 0 to 3, with 0 signifying new moon, 1 first quarter, 2 full moon, and 3 + third quarter. If date is omitted, it defaults to today(). If time is omitted, it defaults to midnight. + Moontime() is intended to be used in conjunction with moondate(). The moondate() and moontime() func‐ + tions are accurate to within a couple of minutes of the times in "Old Farmer's Almanac" for Ottawa, On‐ + tario. + + For example, the following returns the date and time of the next full moon: + + MSG Next full moon at [moontime(2)] on [moondate(2)] + + moondatetime(i_phase [,d_date [,t_time]]) or moondatetime(i_phase, q_datetime) + This function is similar to moondate and moontime, but returns a DATETIME result. + + moonphase([d_date [,t_time]]) or moonphase(q_datetime) + This function returns the phase of the moon on date and time, which default to today() and midnight, re‐ + spectively. The returned value is an integer from 0 to 359, representing the phase of the moon in de‐ + grees. 0 is a new moon, 180 is a full moon, 90 is first-quarter, etc. + + moonrise([d_date]) + This function returns a DATETIME result giving the date and time of the first moonrise on or after mid‐ + night on date. If date is not supplied, it defaults to today(). + + Note that it is not uncommon for a day to have no moonrise, so the date part of the return value may not + be the same as the date argument. So if you want a calendar of moonrise times, you could use something + like this: + + SET mr moonrise() + IF datepart(mr) == today() + REM NOQUEUE [mr] MSG Moon rises at %3. + ELSE + REM MSG No moonrise today + ENDIF + + moonrisedir([d_date]) + This function returns an INT result giving the direction from which the moon will rise on the first moon‐ + rise on or after midnight on date. If date is not supplied, it defaults to today(). The return value + ranges from 0 to 359, where 0 is North, 90 is East, 180 is South and 270 is West. + + moonset([d_date]) + This function is analogous to moonrise() but returns the DATETIME of the next moonset on or after mid‐ + night on date. + + moonsetdir([d_date]) + This function is analogous to moonrisedir() but returns the direction of moonset. + + multitrig(s_trig1 [,s_trig2, [... s_trigN]]) + multitrig evaluates each string as a trigger, similar to evaltrig, and returns the earliest trigger date + that is on or after today(). multitrig is similar to trig but has the following difference: + + trig returns the first trigger date that would have triggered today, whereas multitrig returns the earli‐ + est trigger date later than today, regardless of whether it would have triggered today. + + If no trigger can be computed that is later than today(), then multitrig returns 1990-01-01. + + Consider the following examples, assuming that today is Sunday, 24 March 2024: + + # Returns 1990-01-01 because neither would trigger today + SET a trig("Mon", "Wed") + + # Returns 2024-03-25 because it's the earlier trigger date + SET a multitrig("Mon", "Wed") + + # Returns 2024-03-27 because it's the first that would trigger today + SET a trig("Wed +3", "Mon +3") + + # Returns 2024-03-25 because it's the earlier trigger date + SET a multitrig("Wed +3", "Mon +3") + + # Returns 1990-01-01 because all triggers have expired + SET a multitrig("2000", "2022", "1998", "2023") + + In general, multitrig works better with the Remind algorithm than trig and should be used most of the + time. As an example, this reminder is issued at the end of each quarter: + + REM [multitrig("Mar 31", "Jun 30", "Sep 30", "Dec 31")] +7 MSG \ + %"End of [ord($Tm/3)] quarter%" is %b. + + If you want the last working day of each quarter, you could use: + + PUSH-OMIT-CONTEXT + OMIT Sat Sun + REM [multitrig("Mar ~1", "Jun ~1", "Sep ~1", "Dec ~1")] +7 MSG \ + %"Last working day of [ord($Tm/3)] quarter%" is %b. + POP-OMIT-CONTEXT + + Note that unlike evaltrig, multitrig always returns a DATE and never a DATETIME. Including an AT clause + in a trigger supplied to multitrig will result in an error. + + ndawn([dq_date]) + Returns the time of "nautical dawn" on the specified date. If date is omitted, defaults to today(). If + a datetime object is supplied, only the date component is used. + + ndusk([dq_date]) + Returns the time of "nautical twilight" on the specified date. If date is omitted, defaults to today(). + + nonomitted(dq_start, dq_end [, i_step] [,s_wkday...]) + This function returns the number of non-omitted days between start and end. If start is non-omitted, + then it is counted. end is never counted. + + Note that if end is less than start, the arguments are effectively swapped, so counting always begins + from the older date. + + If the third argument to nonomitted is an INT, then it must be greater than zero, and is considered to be + the step by which nonomitted counts. For example the following expression: + + nonomitted('2023-07-01', '2023-07-29', 7) + + returns the number of non-omitted Saturdays from 2023-07-01 up to (but not including) 2023-07-29. (Both + 2023-07-01 and 2023-07-29 are Saturdays.) + + If no step argument is supplied, then a step of 1 is used. + + In addition to using the global OMIT context, you can supply additional arguments that are names of week‐ + days to be omitted. However, in a REM command, any local OMITFUNC clause is not taken into account by + this function. + + For example, the following line sets a to 11 (assuming no global OMITs): + + set a nonomitted('2007-08-01', '2007-08-16', "Sat", "Sun") + + because Thursday, 16 August 2007 is the 11th working day (not counting Saturday and Sunday) after Wednes‐ + day, 1 August 2007. + + nonomitted has various uses. For example, many schools run on a six-day cycle and the day number is not + incremented on holidays. Suppose the school year starts with Day 1 on 4 September 2007. The following + reminder will label day numbers in a calendar: + + IF today() >= '2007-09-04' + set daynum nonomitted('2007-09-04', today(), "Sat", "Sun") + REM OMIT SAT SUN SKIP CAL Day [(daynum % 6) + 1] + ENDIF + + Obviously, the answer you get from nonomitted depends on the global OMIT context. If you use movable + OMITs, you may get inconsistent results. + + Here is a more complex use for nonomitted. My garbage collection follows two interleaved 14-day cycles: + One Friday, garbage and paper recycling ("Black Box") are collected. The next Friday, garbage and plas‐ + tic recycling ("Blue Box") are collected. If any of Monday-Friday is a holiday, collection is delayed + until the Saturday. Here's a way to encode these rules: + + fset _garbhol(x) wkdaynum(x) == 5 && nonomitted(x-4, x+1) < 5 + REM 12 November 1999 *14 AFTER OMITFUNC _garbhol MSG Black Box + REM 19 November 1999 *14 AFTER OMITFUNC _garbhol MSG Blue Box + + Here's how it works: The _garbhol(x) user-defined function returns 1 if and only if (1) x is a Friday + and (2) there is at least one OMITted day from the previous Monday up to and including the Friday. + + The first REM statement sets up the 14-day black-box cycle. The AFTER keyword makes it move collection + to the Saturday if _garbhol returns 1. The second REM statement sets up the 14-day blue-box cycle with a + similar adjustment made by AFTER in conjunction with _garbhol. + + nonconst(x_arg) + Returns the argument arg unchanged, but forces the expression to be considered non-constant. For de‐ + tails, see the section "NON-CONSTANT EXPRESSIONS" + + now() Returns the current system time, as a TIME type. This may be the actual time, or a time supplied on the + command line. Note that in Calendar Mode, now() always returns 00:00. + + ord(i_num) + Returns a string that is the ordinal number num. For example, ord(2) returns "2nd", and ord(213) returns + "213th". + + In order to help with localization, if you define a function called ordx that takes a single parameter, + then calling ord(n) invokes ordx(n) and returns whatever it does. During the callback to ordx, RUN will + be disabled. + + orthodoxeaster([dqi_arg]) + If arg is an INT, then returns the date of Orthodox Easter Sunday for the specified year. If arg is a + DATE or DATETIME, then returns the date of the next Orthodox Easter Sunday on or after arg. (The time + component of a datetime is ignored.) If arg is omitted, then it defaults to today(). + + Note that orthodoxeaster computes the Orthodox Easter. For the Western Easter date, see easterdate. + + ostype() + Returns "UNIX". Remind used to run on OS/2 and MS-DOS, but does not any longer. + + pad(x_arg, s_padstr, i_len [, i_right]) + Converts the first argument arg to a string if necessary, and then if it is shorter than len bytes, pads + to to len bytes using as many copies (including partial copies) of padstr as necessary. By default, the + string is left-padded, but if right is supplied and non-zero, the string will be right-padded. + + Here are some examples: + + pad(3, "0", 2) --> "03" + pad(465, "0", 2) --> "465" + pad("foo", " ", 5) --> " foo" + pad("foo", " ", 5, 1) --> "foo " + pad("foo", "bar", 11) --> "barbarbafoo" + + mbpad(x_arg, s_padstr, i_len [, i_right]) + This is the multibyte counterpart to pad. The length is specified in characters rather than bytes. Use + mbpad rather than pad if either of the strings contains non-ASCII characters. + + plural(i_num [,s_str1 [,s_str2]]) + Can take from one to three arguments. If one argument is supplied, returns "s" if num is not 1, and "" + if num is 1. + + If two arguments are supplied, returns str1 + "s" if num is not 1. Otherwise, returns str1. + + If three arguments are supplied, returns str1 if num is 1, and str2 otherwise. + + psmoon(i_phase [,i_size [,s_note [,i_notesize]]]) + [DEPRECATED] Returns a STRING consisting of PostScript code to draw a moon in the upper-left hand corner + of the calendar box. Phase specifies the phase of the moon, and is 0 (new moon), 1 (first quarter), 2 + (full moon) or 3 (third quarter). If size is specified, it controls the radius of the moon in PostScript + units (1/72 inch.) If it is not specified or is negative, the size of the day-number font is used. + + For example, the following four lines place moon symbols on the PostScript calendar: + + REM [moondate(0)] PS [psmoon(0)] + REM [moondate(1)] PS [psmoon(1)] + REM [moondate(2)] PS [psmoon(2)] + REM [moondate(3)] PS [psmoon(3)] + + If note is specified, the text is used to annotate the moon display. The font is the same font used for + calendar entries. If notesize is given, it specifies the font size to use for the annotation, in Post‐ + Script units (1/72 inch.) If notesize is not given, it defaults to the size used for calendar entries. + (If you annotate the display, be careful not to overwrite the day number -- Remind does not check for + this.) For example, if you want the time of each new moon displayed, you could use this in your reminder + script: + + REM [moondate(0)] PS [psmoon(0, -1, moontime(0)+"")] + + Note how the time is coerced to a string by concatenating the null string. + + psshade(i_gray) or psshade(i_red, i_green, i_blue) + [DEPRECATED] Returns a STRING that consists of PostScript commands to shade a calendar box. Num can + range from 0 (completely black) to 100 (completely white.) If three arguments are given, they specify + red, green and blue intensity from 0 to 100. Here's an example of how to use this: + + REM Sat Sun PS [psshade(95)] + + The above command emits PostScript code to lightly shade the boxes for Saturday and Sunday in a Post‐ + Script calendar. + + Note that psmoon and psshade are deprecated; instead you should use the SPECIAL SHADE and SPECIAL MOON + reminders as described in "Out-of-Band Reminders." + + realcurrent() + Returns (as a DATETIME) the true date and time of day as provided by the operating system. This is in + contrast to current(), which may return a time supplied on the command line. + + realnow() + Returns the true time of day as provided by the operating system. This is in contrast to now(), which + may return a time supplied on the command line, or 00:00 in Calendar Mode. + + realtoday() + Returns the date as provided by the operating system. This is in contrast to Remind's concept of "to‐ + day", which may be changed if it is running in calendar mode, or if a date has been supplied on the com‐ + mand line. + + rows() If either standard output or standard error is a TTY, returns the height of the terminal in rows. If + neither standard output nor standard error is a TTY, attempts to open "/dev/tty" to obtain the terminal + size. If this fails, returns -1. + + sgn(i_num) + Returns -1 if num is negative, 1 if num is positive, and 0 if num is zero. + + shell(s_cmd [,i_maxlen]) + Executes cmd as a system command, and returns the first 511 characters of output resulting from cmd. Any + whitespace character in the output is converted to a space. Note that if RUN OFF has been executed, or + the -r command-line option has been used, shell() will result in an error, and cmd will not be executed. + + When shell runs cmd, it arranges for cmd's standard input file descriptor to be connected to /dev/null. + + If maxlen is specified, then shell() returns the first maxlen characters of output (rather than the first + 511). If maxlen is specified as a negative number, then it defaults to the value of the system variable + $MaxStringLen. + + shellescape(s_str) + Returns str with all shell metacharacters such as " ", "*", etc escaped with a backslash. For example: + + SET a shellescape("a b*? c&d$e") + + will set a to: + + "a\ b\*\?\ c\&d\$e" + + slide(d_start, i_amt [, i_step] [,s_wkday...]) + This function is the inverse of nonomitted. It adds amt (which can be negative) chunks of step days to + start, not counting omitted days. If step is not supplied, then it is assumed to be 1. Note that only + every stepth day is tested to see if it is omitted. The optional wkday arguments are additional weekday + names to omit. + + Consider this example: + + OMIT 14 May 2009 + SET a slide('2009-05-13', 5, "Sat", "Sun") + + In this case, a is set to 2009-05-21. That's because we slide forward by 5 days, not including Thursday, + May 14 or Saturday and Sunday, May 16 and 17. You can go backwards, too, so: + + OMIT 14 May 2009 + SET a slide('2009-05-21', -5, "Sat", "Sun") + + takes a back to 2009-05-13. + + Now consider this example: + + OMIT 14 May 2009 + SET a slide('2009-05-07', 2, 7) + + This sets a to '2009-05-28' because we skip ahead two weeks, not counting a week where the day we land on + happens to be omitted. Contrast with this: + + OMIT 13 May 2009 + SET a slide('2009-05-07', 2, 7) + + which sets a to '2009-05-21'. Although 2009-05-13 is omitted, we don't "land" on it as we step forward + in chunks of 7 days, so we never see that it is omitted. + + soleq(i_which [, dqi_start]) + The soleq function computes solstices and equinoxes. The which parameter ranges from 0 to 3, and speci‐ + fies which event we are interested in: 0 is the March equinox; 1 is the June solstice; 2 is the September + equinox and 3 is the December solstice. + + The optional start parameter can either be an integer specifying the year of the event we are interested + in, or a DATE or DATETIME object; if the latter, then soleq returns the first event on or after the date + part of the start parameter (it ignores the time component if start is a DATETIME.) If start is not sup‐ + plied, then it defaults to today(). + + The return value of soleq() is a DATETIME object specifying the date and time of the solstice or equinox + in the local time zone. It should be accurate to within 3 minutes or so in the worst case. + + See the included file $SysInclude/seasons.rem for examples of how to use soleq(). + + stdout() + Returns a string representing where Remind's standard output is going. The return values are one of the + following: "TTY" if standard-output is a terminal, "BLOCKDEV" if it is a block device (very unlikely), + "CHARDEV" if it is a character device (e.g., /dev/null), "DIR" if it is a directory (very unlikely), + "PIPE" if it is a pipe or FIFO, "SYMLINK" if it is a symlink (very unlikely), "SOCKET" if it is a socket, + or "UNKNOWN" if it could not be determined. + + The purpose of stdout() is mostly to distinguish between TTY and non-TTY output; you may wish to change + or disable colors if the output is not going to a TTY. + + strlen(s_str) + Returns the length of str in bytes. If the length of str is too large to represent as an integer, emits + a "Number too high" error. Note that strlen returns the number of bytes in the string, not the number of + characters. These numbers are the same for ASCII strings, but may be different for UTF-8 strings. + + mbstrlen(str) + Similar to strlen, but returns the length of the string in characters rather than bytes and is thus safe + for use on multi-byte strings. + + substr(s_str, i_start [,i_end]) + Returns a STRING consisting of all bytes in str from start up to and including end. Bytes are numbered + from 1. If end is not supplied, then it defaults to the length of str. Because substr uses byte indexes + rather than character indexes, it should not be used on multi-byte strings. + + mbsubstr(s_str, i_start [,i_end]) + Similar to substr but uses character indexes rather than byte indexes, and is thus safe for use on multi- + byte strings. + + sunrise([dq_date]) + Returns a TIME indicating the time of sunrise on the specified date (default today().) In high lati‐ + tudes, there may be no sunrise on a particular day, in which case sunrise() returns the INT 0 if the sun + never sets, or 1440 if it never rises. + + sunset([dq_date]) + Returns a TIME indicating the time of sunset on the specified date (default today().) In high latitudes, + there may be no sunset on a particular day, in which case sunset() returns the INT 0 if the sun never + rises, or 1440 if it never sets. + + The functions sunrise() and sunset() are based on an algorithm in "Almanac for Computers for the year + 1978" by L. E. Doggett, Nautical Almanac Office, USNO. They require the latitude and longitude to be + specified by setting the appropriate system variables. (See "System Variables".) The sun functions + should be accurate to within about 4 minutes for latitudes lower than 60 degrees. The functions are + available starting from version 03.00.07 of Remind. + + time(i_hr, i_min) + Creates a TIME with the hour and minute components specified by hr and min. + + timepart(tq_datetime) + Returns a TIME object representing the time portion of datetime. + + timezone([dq_datetime]) + Returns a string representing the local time zone name of the given DATETIME. If no argument is sup‐ + plied, Remind uses the value of current(). If a DATE rather than DATETIME is supplied, Remind uses a + time part of 00:00. + + today() + Returns Remind's notion of "today." This may be the actual system date, or a date supplied on the com‐ + mand line, or (in Calendar Mode) the date of the calendar entry currently being computed. + + trig(s_1 [,s_2, ...]) + For each string argument s_n, trig evaluates s_n as if it were a REM or IFTRIG trigger specification. If + the trigger would trigger today, then the trigger date is returned and no further triggers are evaluated. + If none of the triggers would trigger today, then the zero date 1990-01-01 is returned. + trig also has a zero-argument form; this returns the trigger date of the most recent trig function that + returned a non-zero trigger date. + + trig can be used to make more sophisticated versions of IFTRIG. For example, if you have meetings every + Monday in June and July, and you want warnings 3 days in advance, you could use: + + REM [trig("Mon Jun +3", "Mon July +3")] +3 MSG Meeting %b + + NOTE: We need to repeat the +3 delta outside of the trig function for advance warning to work properly. + This is because trig returns a date constant (the trigger date) and the REM command does not know the de‐ + tails of trig's arguments. + + Note that because Remind has short-circuit logical operators, something like: + + SET a trig("Mon +7") || trig("Fri +7") + + would set the value of trig() to the date of the following Monday. Because trig("Mon +7") always returns + true, the logical-OR operator does not bother evaluating trig("Fri +7") which therefore does not set + trig(). + + Important Note: Because trig() always returns an absolute date, it will not work properly with a SATISFY + clause. Consider this reminder: + + REM [trig("Mar", "Apr")] SATISFY [$Td == 15] MSG 15 Mar or April + + If we run Remind on 5 March 2022, we might expect the trigger date to be calculated as 15 March 2022... + but that's not what happens. Instead, the trig function is evaluated first, and it returns 2022-03-05. + So as far as Remind is concerned, the REM statement becomes: + + REM 2022-03-05 SATISFY [$Td == 15] MSG 15 Mar or April + + and the SATISFY expression is never true. So: Do not mix trig() and SATISFY. + + trigdate() + Returns the calculated trigger date of the last REM or IFTRIG command. If used in the body of a REM com‐ + mand, returns that command's trigger date. If the most recent REM command did not yield a computable + trigger date, returns the integer 0. + + trigdatetime() + Similar to trigdate(), but returns a DATETIME if the most recent triggerable REM command had an AT + clause. If there was no AT clause, returns a DATE. If no trigger could be computed, returns the integer + 0. See "MULTI-DAY EVENTS" for more information. + + trigeventstart() + Returns a DATETIME representing the start of the most recent triggerable REM command that had an AT + clause. For events without a DURATION or that do not span multiple days, returns the same as trigdate‐ + time(). If the REM command did not have an AT clause, returns the integer -1 (and differs from trigdate‐ + time() in this respect.) See "MULTI-DAY EVENTS" for more information. + + trigeventstarttz() + Similar to trigeventstart() but returns the DATETIME in the time zone specified by a TZ clause, if one + was present. If no TZ clause was present, returns the same value as trigeventstart(). + + trigeventduration() + Returns a TIME representing the duration of the most recent triggerable REM command that had an AT and a + DURATION clause. If the event does not span multiple days, returns the same thing as trigduration(). If + the REM command lacked an AT or DURATION clause, returns -1. See "MULTI-DAY EVENTS" for more informa‐ + tion. + + trigback() + Returns the "back" amount of the last REM or IFTRIG command. Returns a positive integer N if the "back" + is of the form -N, or a negative integer if it is of the form --N. If there is no "back", then returns + 0. + + trigbase() + Returns the "base" date of the last REM or IFTRIG command. If the trigger specification includes all + three of day, month and year, then the base date is the date formed from those components. If the trig‐ + ger specification lacks one of those components, then 0 is returned. + + Here is an example of how trigbase() might be used: + + REM 2025-05-05 *7 UNTIL 2025-05-26 \ + MSG Meeting: week #[(trigdate() - trigbase())/trigrep()+1] + + On 2025-05-05, it would print: "Meeting: week #1". On 2025-05-12, it would print: "Meeting: week #2" and + so on. + + trigdelta() + Returns the "delta" amount of the last REM or IFTRIG command. Returns a positive integer N if the + "delta" is of the form +N, or a negative integer if it is of the form ++N. If there is no "delta", then + returns 0. + + trigtimedelta() + Similar to trigdelta(), but returns the delta used in the AT clause of a timed reminder. + + trigrep() + Returns the "repeat" amount of the last REM or IFTRIG command. Returns a positive integer N if the "re‐ + peat" is of the form *N. If there is no "repeat", then returns 0. + + trigtimerep() + Similar to trigrep(), but returns the repeat used in the AT clause of a timed reminder. + + trigtz() + If a TZ clause was used in the last REM or IFTRIG command, returns the time zone name. Otherwise returns + the empty string. + + trigduration() + Returns (as a TIME type) the DURATION parameter of a timed reminder. If there is no DURATION parameter, + returns the integer -1. See "MULTI-DAY EVENTS" for more information. + + trigpriority() + Returns the PRIORITY of the last REM or IFTRIG command. + + triguntil() + Returns (as a DATE type) the UNTIL parameter of the last REM or IFTRIG command. If there was no UNTIL + parameter, returns the integer -1. If there is a THROUGH parameter, that will be returned by triguntil() + since "THROUGH yyyy-mm-dd" is simply syntactic sugar for "*1 UNTIL yyyy-mm-dd". + + trigscanfrom() + Returns (as a DATE type) the SCANFROM parameter of the last REM or IFTRIG command. If there was no SCAN‐ + FROM parameter, returns the integer -1. Note that FROM and SCANFROM interact; a reminder that has a + "FROM yyyy-mm-dd" parameter will act as if it has a SCANFROM parameter whose value is the maximum of + "yyyy-mm-dd" and today. + + trigfrom() + Returns (as a DATE type) the FROM parameter of the last REM or IFTRIG command. If there was no FROM pa‐ + rameter, returns the integer -1. + + triginfo(s_header) + Returns a STRING that is the INFO item associated with the header header. The header should not contain + a colon. Header name comparisons are case-insensitive. + + For example, the following will assign "At home" to the variable a and the empty string to variable b: + + REM INFO "Location: At home" MSG test + SET a triginfo("location") + SET b triginfo("no_such_header") + + trigistodo() + Returns 1 if the last REM command was a TODO type or 0 if not. + + trigcompletethrough() + Returns a DATE object that is the COMPLETE-THROUGH date of the most recent REM command. If there was no + COMPLETE-THROUGH date, returns the INT 0. + + trigmaxoverdue() + Returns an INT that is the MAX-OVERDUE value of the most recent REM command. If there was no MAX-OVERDUE + clause, returns -1. + + trigger(d_date [,t_time [,i_utcflag]]) or trigger(q_datetime [,i_utcflag]) + Returns a string suitable for use in a REM command or a SCANFROM or UNTIL clause, allowing you to calcu‐ + late trigger dates in advance. Note that in earlier versions of Remind, trigger was required to convert + a date into something the REM command could consume. However, in this version of Remind, you can omit + it. Normally, the date and time are the local date and time; however, if utcflag is non-zero, the date + and time are interpreted as UTC times, and are converted to local time. Examples: + + trigger('1993/04/01') + + returns "1 April 1993", + + trigger('1994/08/09', 12:33) + + returns "9 August 1994 AT 12:33", as does: + + trigger('1994/08/09@12:33'). + + Finally: + + trigger('1994/12/01', 03:00, 1) + + returns "30 November 1994 AT 22:00" for EST, which is 5 hours behind UTC. The value for your time zone + may differ. + + trigger() will never return a date earlier than "1 January 1990" even if the UTC flag dictates that it + should. So do not use it for reminders before about 2 January 1990 in your local time zone. I do not + anticipate this restriction being a real problem. + + trigtags() + Returns a comma-separated list of the TAGs associated with the most recent REM command that was trig‐ + gered. Returns the empty string if there were no TAGs. If there are multiple tags, they are each sepa‐ + rated by a single comma, not a comma and a space. + + trigtime() + Returns the time of the last REM command with an AT clause in the system default time zone. If the last + REM did not have an AT clause, returns the integer 0. If a REM command has an AT clause with a DURATION, + then you can compute the end time as trigtime() + trigduration(). + + trigtimetz() + Similar to trigtime() but returns the time in the time zone specified by a TZ clause, if one was present. + If no TZ clause was present, returns the same value as trigtime(). + + trigvalid() + Returns 1 if the value returned by trigdate() is valid for the most recent REM command, or 0 otherwise. + Sometimes REM commands cannot calculate a trigger date. For example, the following REM command can never + be triggered: + + REM Mon OMIT Mon SKIP MSG Impossible! + + typeof(x_arg) + Returns "STRING", "INT", "DATE", "TIME" or "DATETIME", depending on the type of arg. + + tzconvert(q_datetime, s_srczone [,s_dstzone]) + Converts datetime from the time zone named by srczone to the time zone named by dstzone. If srczone is + the empty string, then the default system time zone is used as the source zone. If dstzone is omitted or + is the empty string, the default system time zone is used as the destination zone. The return value is a + DATETIME. Time zone names are system-dependent; consult your operating system for legal values. Here is + an example: + + tzconvert('2007-07-08@01:14', "Canada/Eastern", "Canada/Pacific") + + returns + + 2007-07-07@22:14 + + If your system includes the directory /usr/share/zoneinfo, Remind will warn you if you use an invalid + time zone name for srczone or dstzone. To suppress these warnings, add a "!" to the beginning of the + time zone name. + + upper(s_string) + Returns a STRING with all lower-case bytes in string converted to upper-case. Note: This function works + correctly only for ASCII strings. If you are using Unicode characters outside the ASCII set, use mbupper + instead. + + mbupper(s_string) + Returns a STRING with all lower-case characters in string converted to upper-case. This function works + correctly on any Unicode string. + + utctolocal(q_datetime) + Given a DATETIME object interpreted in UTC, return a DATETIME object that expresses the same time in the + local time zone. + + value(s_varname [,x_default]) + Returns the value of the specified variable. For example, value("X"+"Y") returns the value of variable + XY, if it is defined. If XY is not defined, an error results. + + However, if you supply a second argument, it is returned if the varname is not defined. The expression + value("XY", 0) will return 0 if XY is not defined, and the value of XY if it is defined. Note that value + evaluates the second argument lazily; it is not evaluated if varname is defined. + + version() + Returns a string specifying the version of Remind. For version 06.02.05, returns "06.02.05". It is + guaranteed that as new versions of Remind are released, the value returned by version() will strictly in‐ + crease, according to the rules for string ordering. + + weekno([dq_date, [i_wkstart, [i_daystart]]]) + Returns the week number of the year. If no arguments are supplied, returns the ISO 8601 week number for + today(). If one argument date is supplied, then returns the ISO 8601 week number for that date. If two + arguments are supplied, then wkstart must range from 0 to 6, and represents the first day of the week + (with 0 being Sunday and 6 being Saturday.). If wkstart is not supplied, then it defaults to 1. If the + third argument daystart is supplied, then it specifies when Week 1 starts. If daystart is less than or + equal to 7, then Week 1 starts on the first wkstart on or after January daystart. Otherwise, Week 1 + starts on the first wkstart on or after December daystart. If omitted, daystart defaults to 29 (follow‐ + ing the ISO 8601 definition.) + + wkday(dqi_arg) + If arg is a DATE or DATETIME, returns a string representing the day of the week of the date. If arg is + an INT from 0 to 6, returns the corresponding weekday ("Sunday" to "Saturday"). + + wkdaynum(dq_date) or wkdaynum(s_str) + Returns a number from 0 to 6 representing the day-of-week of the specified date. (0 represents Sunday, + and 6 represents Saturday.) If a STRING is supplied, then if str is a valid weekday name (or minimum + 3-character abbreviation thereof) then the corresponding weekday number is returned. If str is not a + valid weekday name, then an error occurs. + + year(dq_date) + Returns a INT that is the year component of date. + +MACHINES WITH A 32-BIT TIME_T TYPE + Internally, the standard C library represents times as a time_t type. This is a signed integer type that counts + seconds since midnight, 1 January 1970 UTC. If the time_t type is only 32 bits in size, then the C library can‐ + not represent times after 19 January 2038 at 03:14:07 UTC. + + To work around this limitation, Remind will "fold" years greater than 2037 to a smaller year that begins on the + same day and has the same number of days. This results in a year that's very likely to have the same daylight + saving rules as the original year, unless the rules have changed in the interim (in which case the rule change + could not even be represented on a 32-bit machine, so it's moot.) + + Remind then does UTC-to-local or local-to-UTC conversions with the folded year, and then post-conversion cor‐ + rects it back. This should give correct results for astronomical functions and conversions between local and + UTC time, providing the daylight saving time rules have not changed between the folded year and the original + year. + + Note that the $FoldYear system variable is no longer that useful; Remind always folds years if it's necessary on + a 32-bit system. You can set $FoldYear to 1 to force Remind to fold years even on a 64-bit system, but that's + useful only for testing and not for production. + +MULTI-DAY EVENTS + If you specify a start time with AT and a duration with DURATION, you can create events that span multiple days. + Consider these two REM statements: + + REM 1991-02-13 AT 16:00 DURATION 72:00 MSG 72-hour event + REM 1991-02-13 THROUGH 1991-02-16 AT 16:00 MSG Four events + + The first statement creates a single event that starts on 13 February 1991 at 16:00 and runs through 16 February + 1991 at 16:00 + + The second statements creates four separate events that start at 16:00 on 13, 14, 15 and 16 February 1991 and + have indefinite duration. + + Remind handles multi-day events specially. These are the rules: + + On the first day of a multi-day event, trigdatetime() will return the starting date and time of the event, and + trigduration() will return the original DURATION. + + On each subsequent day of a multi-day event, trigdatetime() will return midnight on the day in question, and + trigduration() will return the remaining duration. Consider this example: + + #!/bin/sh + remind - 12 feb 1991 '*6' <<'EOF' + BANNER % + REM 1991-02-13 AT 16:00 DURATION 72:00 SATISFY 1 + set a trigdatetime() + set b trigduration() + set c trigeventstart() + set d trigeventduration() + MSG now=[today()] dt=[a] dur=[b] estart=[c] edur=[d]% + EOF + + The output is: + + now=1991-02-12 dt=1991-02-13@16:00 dur=72:00 estart=1991-02-13@16:00 edur=72:00 + now=1991-02-13 dt=1991-02-13@16:00 dur=72:00 estart=1991-02-13@16:00 edur=72:00 + now=1991-02-14 dt=1991-02-14@00:00 dur=64:00 estart=1991-02-13@16:00 edur=72:00 + now=1991-02-15 dt=1991-02-15@00:00 dur=40:00 estart=1991-02-13@16:00 edur=72:00 + now=1991-02-16 dt=1991-02-16@00:00 dur=16:00 estart=1991-02-13@16:00 edur=72:00 + now=1991-02-17 dt=1991-02-13@16:00 dur=72:00 estart=-1 edur=-1 + + As you see, the trigdatetime() and trigduration() functions return the start time and duration of the remaining + portion of a multi-day event, whereas trigeventstart and trigeventduration always return the original start and + duration of the multi-day event. Note also that the return value for expired reminders is not reliable; the + fact that trigeventstart and trigeventduration return -1 in that case is an implementation artifact. + + SELF-OVERLAPPING EVENTS + + A multi-day event has the possibility of "overlapping itself". When this happens, Remind prefers the later + event (only one copy of an event is ever triggered for a given date.) Consider this example: + + #!/bin/sh + remind - '*5' 10 Feb 1991 <<'EOF' + + BANNER % + REM MON at 0:00 DURATION 192:0 MSG [today()] [trigeventstart()] [trigduration()]% + + EOF + + The output is: + + 1991-02-10 1991-02-04@00:00 48:00 + 1991-02-11 1991-02-11@00:00 192:00 + 1991-02-12 1991-02-11@00:00 168:00 + 1991-02-13 1991-02-11@00:00 144:00 + 1991-02-14 1991-02-11@00:00 120:00 + + Although the event from 1991-02-04 still has 24 hours left on 1991-02-11, the fresh occurrence on 1991-02-11 + takes precedences and is the one that is triggered. + + I do not recommend constructing self-overlapping multi-day events. + +EXPRESSION PASTING + An extremely powerful feature of Remind is its macro capability, or "expression pasting." + + In almost any situation where Remind is not expecting an expression, you can "paste" an expression in. To do + this, surround the expression with square brackets. For example: + + REM [mydate] MSG foo + + This evaluates the expression "mydate", where "mydate" is presumably some pre-computed variable, and then + "pastes" the result into the command-line for the parser to process. + + If you want a literal "[" character for some reason, simply use "[[". For example: + + REM MSG Here are [[square] brackets! + + A formal description of this is: When Remind encounters a "pasted-in" expression, it evaluates the expression, + and coerces the result to a STRING. It then substitutes the string for the pasted-in expression, and continues + parsing. Note, however, that expressions are evaluated only once, not recursively. Thus, writing: + + ["[a+b]"] + + causes Remind to read the token "[a+b]". It does not interpret this as a pasted-in expression. + + You can use expression pasting almost anywhere. However, there are a few exceptions: + + o If Remind is expecting an expression, as in the SET command, or the IF command, you should not include + square brackets. For example, use: + + SET a 4+5 + and not: + SET a [4+5] + + o You cannot use expression pasting for the first token on a line. For example, the following will not + work: + + ["SET"] a 1 + + This restriction is because Remind must be able to unambiguously determine the first token of a line for + the flow-control commands (to be discussed later.) + + In fact, if Remind cannot determine the first token on a line, it assumes that it is a REM command. If + expression-pasting is used, Remind assumes it is a REM command. Thus, the following three commands are + equivalent: + + REM 12 Nov 1993 AT 13:05 MSG BOO! + 12 Nov 1993 AT 13:05 MSG BOO! + [12] ["Nov " + 1993] AT [12:05+60] MSG BOO! + + o You cannot use expression-pasting to determine the type (MSG, CAL, etc.) of a REM command. You can paste + expressions before and after the MSG, etc. keywords, but cannot do something like this: + + REM ["12 Nov 1993 AT 13:05 " + "MSG" + " BOO!"] + + However, as an escape hatch, the sequence SPECIAL type means the same thing as just type where type is + one of MSG, MSF, RUN, CAL, PS and PSFILE. This lets you do something like this: + + SET type "MSG" + REM 12 Nov 2024 SPECIAL [type] Hello + + You can use this to control the types of your reminders based on variables you set, how Remind is in‐ + voked, etc. + + COMMON PITFALLS WITH EXPRESSION PASTING + + Remember that extra spaces are not inserted when an expression is pasted. Thus, something like: + + REM[expr]MSG[expr] + + will probably fail. + + If you use an expression to calculate a delta or back, ensure that the result is a positive number. Something + like: + + REM +[mydelta] Nov 12 1993 MSG foo + + will fail if mydelta happens to be negative. + +FLOW CONTROL COMMANDS + Remind has commands that control the flow of a reminder script. Normally, reminder scripts are processed se‐ + quentially. However, IF and related commands allow you to process files conditionally, and skip sections that + you don't want interpreted. + + THE IF COMMAND + + The IF command has the following form: + + IF expr + t-command + t-command... + ELSE + f-command + f-command... + ENDIF + + Note that the commands are shown indented for clarity. Also, the ELSE portion can be omitted. IF commands can + be nested up to a depth of 64, across all levels of INCLUDE. + + If the expr evaluates to a non-zero INT, a DATE that is not 1990-01-01, a TIME that is not 00:00, a DATETIME + that is not 1990-01-01@00:00, or a non-null STRING, then the IF portion is considered true, and the t-commands + are executed. If expr evaluates to zero or null, then the f-commands (if the ELSE portion is present) are exe‐ + cuted. + + Examples: + + IF defined("want_hols") + INCLUDE /usr/share/remind/holidays + ENDIF + + IF today() > '1992/2/10' + set missed_ap "You missed it!" + ELSE + set missed_ap "Still have time..." + ENDIF + + THE IFTRIG COMMAND + + The IFTRIG command is similar to an IF command, except that it computes a trigger (as in the REM command), and + evaluates to true if a corresponding REM command would trigger. Examples: + + IFTRIG 1 Nov + ; Executed on 1 Nov + ELSE + ; Executed except on 1 Nov + ENDIF + + IFTRIG 1 -1 OMIT Sat Sun +4 + ; Executed on last working day of month, + ; and the 4 working days preceding it + ELSE + ; Executed except on above days + ENDIF + + Note that the IFTRIG command computes a trigger date, which can be retrieved with the trigdate() function. You + can use all of the normal trigger components, such as UNTIL, delta, etc. in the IFTRIG command. However, you + cannot use a type specifier such as CAL, MSG or SATISFY; attempting to do so yields a parse error. + +USER-DEFINED FUNCTIONS + In addition to the built-in functions, Remind allows you to define your own functions. The FSET command does + this for you: + + FSET fname(args) expr + + Fname is the name of the function, and follows the convention for naming variables. Args is a comma-separated + list of arguments, and expr is an expression. expr is not evaluated at the time the function is defined; in‐ + stead, it is evaluated each time the function is called. Args can be empty, in which case you define a function + taking no parameters. Here are some examples: + + FSET double(x) 2*x + FSET yeardiff(date1, date2) year(date1) - year(date2) + FSET since(x) ord($Ty - x) + + The last function is useful in birthday reminders. For example: + + REM 1 Nov +12 MSG Dean's [since(1984)] birthday is %b. + + Dean was born in 1984. The above example, on 1 November 1992, would print: + + Dean's 8th birthday is today. + + Similarly, the function is useful in anniversary reminders. For example: + + REM 4 June MSG [since(1989)] anniversary of the Tienanmen Square massacre + + Notes: + + o If you access a variable in expr that is not in the list of arguments, the global value (if any) is used. + + o Function and parameter names are significant to 64 characters. + + o The value() function always accesses the global value of a variable, even if it has the same name as an + argument. For example: + + fset func(x) value("x") + set x 1 + set y func(5) + + The above sequence sets y to 1, which is the global value of x. + + o User-defined functions may call other functions, including other user-defined functions. Recursive calls + are allowed, but they must terminate (for example, by using a short-circuit operator or function that + breaks the recursion) or an error will result after a certain maximum number of recursive calls (by de‐ + fault, 1000.) + + o If a user-defined function has the same name as a built-in function, it is ignored and the built-in func‐ + tion is used. To prevent conflicts with future versions of Remind (which may define more built-in func‐ + tions), you may wish to name all user-defined functions beginning with an underscore. + + o If a user-defined function is defined in a context where RUN is disabled, then whenever that function is + called, RUN will be disabled during its evaluation, even if it is called from a context where RUN is en‐ + abled. + + To delete a user-defined function, use FUNSET. This takes a space-separated list of user-defined functions to + delete. For example, after the command: + + FUNSET myfunc1 otherfunc thirdfunc + + it is guaranteed that no user-defined functions named myfunc1, otherfunc or thirdfunc will exist. Remind does + not issue an error if you try to FUNSET a nonexistent user-defined function; it simply does nothing in that + case. + + You can rename a user-defined function with FRENAME. This takes two names: An old name and a new name. Con‐ + sider this command: + + FRENAME func_a func_b + + If func_a does not exist, the command unsets func_b if it is defined. However, if func_a exists, then it is re‐ + named to func_b, and func_a is no longer defined. If func_b was defined prior to the FRENAME command, then that + old definition is lost. + + If either argument to the FRENAME command is the name of a built-in function, the command fails with an error + message and does nothing. + + If you define a user-defined function and then later on redefine it, Remind will issue a warning. If you do not + want this warning, then use FUNSET to remove the existing definition before you redefine the function. Alterna‐ + tively, you can use a "-" token before the function name to suppress "redefined function" warnings, as in the + following example: + + FSET - f(x) 2*x + # You must have space before and after the "-" + # This will NOT work: + # FSET -f(x) 2*x + +SAVING AND RESTORING FUNCTIONS + Occasionally, it is useful to redefine a function for a specific block of reminders, but to restore its original + definition at the end of that block. Just as with variables, functions can be pushed onto and popped off an in‐ + ternal stack. Here is an example: + + PUSH-FUNCS msgprefix + FSET msgprefix(x) "My new prefix: " + INCLUDE block_of_reminders.rem + POP-FUNCS + + The file block_of_reminders.rem would be executed with the msgprefix function defined above. After the POP- + FUNCS msgprefix would be restored to its previous definition if it had one, or simply unset if it was not previ‐ + ously defined. + + The command PUSH-FUNCS takes a space-separated list of function names. All of the named user-defined functions + will be saved to an internal stack. You can even push names that are not defined as any function. + + After a function name has been pushed, Remind will not issue a warning if you redefine it, because presumably + the purpose of pushing it in the first place is to redefine it. + + The command POP-FUNCS restores the definitions of all the user-defined functions in the corresponding PUSH-FUNCS + command. If undefined functions were pushed onto the stack, then POP-FUNCS makes those functions undefined + again. Here's one more example: + + FUNSET a + FSET b(x) 2*x + REM MSG [b(3)] # Outputs 6 + PUSH-FUNCS a b + FSET a(x) 22*x + FSET b(x) 3*x + REM MSG [a(3)] [b(3)] # Outputs 66 9 + POP-FUNCS + REM MSG [a(3)] # Undefined function "a" + REM MSG [b(3)] # Outputs 6 + +PRECISE SCHEDULING + The WARN keyword allows precise control over advance warning in a more flexible manner than the delta mechanism. + It should be followed by the name of a user-defined function, warn_function. + + If a warn_function is supplied, then it must take one argument of type INT. Remind ignores any delta, and in‐ + stead calls warn_function successively with the arguments 1, 2, 3, ... + + Warn_function's return value n is interpreted as follows: + + o If n is positive, then the reminder is triggered exactly n days before its trigger date. + + o If n is negative, then it is triggered n days before its trigger date, not counting OMITted days. + + As an example, suppose you wish to be warned of American Independence Day 5, 3, and 1 days in advance. You + could use this: + + FSET _wfun(x) choose(x, 5, 3, 1, 0) + REM 4 July WARN _wfun MSG American Independence Day is %b. + + NOTES + + 1 If an error occurs during the evaluation of warn_function, then Remind stops calling it and simply issues + the reminder on its trigger date. + + 2 If the absolute-values of the return values of warn_function are not monotonically decreasing, Remind + stops calling it and issues the reminder on its trigger date. + + 3 Warn_function should (as a matter of good style) return 0 as the final value in its sequence of return + values. However, a reminder will always be triggered on its trigger date, regardless of what warn_func‐ + tion does. + + Similarly to WARN, the SCHED keyword allows precise control over the scheduling of timed reminders. It should + be followed by the name of a user-defined function, sched_function. + + If a scheduling function is supplied, then it must take one argument of type INT. Rather than using the AT + time, time delta, and time repeat, Remind calls the scheduling function to determine when to trigger the re‐ + minder. The first time the reminder is queued, the scheduling function is called with an argument of 1. Each + time the reminder is triggered, it is re-scheduled by calling the scheduling function again. On each call, the + argument is incremented by one. + + The return value of the scheduling function must be an INT or a TIME. If the return value is a TIME, then the + reminder is re-queued to trigger at that time. If it is a positive integer n, then the reminder is re-queued to + trigger at the previous trigger time plus n minutes. Finally, if it is a negative integer or zero, then the re‐ + minder is re-queued to trigger n minutes before the AT time. Note that there must be an AT clause for the SCHED + clause to do anything. + + Here's an example: + + FSET _sfun(x) choose(x, -60, 30, 15, 10, 3, 1, 1, 1, 1, 0) + REM AT 13:00 SCHED _sfun MSG foo + + The reminder would first be triggered at 13:00-60 minutes, or at 12:00. It would next be triggered 30 minutes + later, at 12:30. Then, it would be triggered at 12:45, 12:55, 12:58, 12:59, 13:00, 13:01 and 13:02. + + NOTES + + 1 If an error occurs during the evaluation of sched_func, then Remind reverts to using the AT time and the + delta and repeat values, and never calls sched_func again. + + 2 If processing sched_func yields a time earlier than the current system time, it is repeatedly called with + increasing argument until it yields a value greater than or equal to the current time. However, if the + sequence of values calculated during the repetition is not strictly increasing, then Remind reverts to + the default behaviour and never calls sched_func again. + + 3 It is quite possible using sched_func to keep triggering a reminder even after the AT-time. However, it + is not possible to reschedule a reminder past midnight - no crossing of date boundaries is allowed. + Also, it is quite possible to not trigger a reminder on the AT time when you use a scheduling function. + However, if your scheduling function is terminated (for reasons 1 and 2) before the AT time of the re‐ + minder, it will be triggered at the AT time, because normal processing takes over. + + 4 Your scheduling functions should (as a matter of good style) return 0 when no more scheduling is re‐ + quired. See the example. + + 5 All scheduling functions are evaluated after the entire Remind script has been read in. So whatever + function definitions are in effect at the end of the script are used. + +THE SATISFY CLAUSE + The form of REM that uses SATISFY is as follows: + + REM trigger SATISFY expr + + The way this works is as follows: Remind first calculates a trigger date, in the normal fashion. Next, it sets + trigdate() to the calculated trigger date. It then evaluates expr. If the result is not the null string or + zero, processing ends. Otherwise, Remind computes the next trigger date, and re-tests expr. This iteration + continues until expr evaluates to non-zero or non-null, or until the iteration limit specified with the -x com‐ + mand-line option is reached. + + If expr is not satisfied, then trigvalid() is set to 0 and the error message "Can't compute trigger" is issued. + Otherwise, trigvalid() is set to 1. + + This is really useful only if expr involves a call to the trigdate() or related functions or system variables; + otherwise, expr will not change as Remind iterates. In fact, if expr is not a constant and does not call trig‐ + date() or related functions or system variables, then Remind will issue a warning. + + An example of the usefulness of SATISFY: Suppose you wish to be warned of every Friday the 13th. Your first + attempt may be: + + # WRONG! + REM Fri 13 +2 MSG Friday the 13th is %b. + + But this won't work. This reminder triggers on the first Friday on or after the 13th of each month. The way to + do it is with a more complicated sequence: + + REM 13 SATISFY wkdaynum(trigdate()) == 5 + IF trigvalid() + REM [trigdate()] +2 MSG \ + Friday the 13th is %b. + ENDIF + + You can write the REM statement a little more concisely: + + REM 13 SATISFY $Tw == 5 + + Let's see how this works. The SATISFY clause iterates through all the 13ths of successive months, until a trig‐ + ger date is found whose day-of-week is Friday (== 5). If a valid date was found, we use the calculated trigger + date to set up the next reminder. + + We could also have written: + + REM Fri SATISFY day(trigdate()) == 13 + + but this would result in more iterations, since "Fridays" occur more often than "13ths of the month." + + Here is another example: Suppose you want to be reminded of something on the 15th of January, April, July, and + October. You could make four separate reminders, or you could use: + + REM 15 SATISFY [isany($Tm, 1, 4, 7, 10)] MSG 15th Reminder! + + This technique of using one REM command to calculate a trigger date to be used by another command is quite pow‐ + erful. For example, suppose you wanted to OMIT Labour day, which is the first Monday in September. You could + use: + + # Note: SATISFY 1 is an idiom for "do nothing" + REM Mon 1 Sept SATISFY 1 + OMIT [trigdate()] + + CAVEAT: This only omits the next Labour Day, not all Labour Days in the future. This could cause strange re‐ + sults, as the OMIT context can change depending on the current date. For example, if you use the following com‐ + mand after the above commands: + + REM Mon AFTER msg hello + + the result will not be as you expect. Consider producing a calendar for September, 1992. Labour Day was on + Monday, 7 September, 1992. However, when Remind gets around to calculating the trigger for Tuesday, 8 Septem‐ + ber, 1992, the OMIT command will now be omitting Labour Day for 1993, and the "Mon AFTER" command will not be + triggered. (But see the description of SCANFROM in the section "DETAILS ABOUT TRIGGER COMPUTATION.") + + It is probably best to stay away from computing OMIT trigger dates unless you keep these pitfalls in mind. + + For versions of Remind starting from 03.00.07, you can include a MSG, RUN, etc. clause in a SATISFY clause as + follows: + + REM trigger_stuff SATISFY [expr] MSG body + + Note that for this case only, the expr after SATISFY must be enclosed in square brackets. It must come after + all the other components of the trigger, and immediately before the MSG, RUN, etc. keyword. If expr cannot be + satisfied, then the reminder is not triggered. + + Thus, the "Friday the 13th" example can be expressed more compactly as: + + REM 13 +2 SATISFY [$Tw == 5] MSG Friday the 13th is %b. + + And you can trigger a reminder on Mondays, Wednesdays and Thursdays occurring on odd-numbered days of the month + with the following: + + REM Mon Wed Thu SATISFY [$Td %2 ] MSG Here it is!!! + + Note that SATISFY and OMITFUNC can often be used to solve the same problem, though in different ways. Sometimes + a SATISFY is cleaner and sometimes an OMITFUNC; experiment and use whichever seems clearer. + +POSSIBLY-UNCOMPUTABLE TRIGGERS + Occasionally, you may wish to suppress the "Can't compute trigger" warnings for reminders for which a trigger + date cannot be computed. For example, the following reminder is triggered on a Monday that is not a holiday if + the following Tuesday is a holiday: + + REM Mon SKIP SATISFY [isomitted($T+1)] MSG Work between holidays + + However, if there are no Mondays after today's date that satisfy the condition, Remind will print the "Can't + compute trigger" error. To suppress this, use the MAYBE-UNCOMPUTABLE keyword: + + REM MAYBE-UNCOMPUTABLE Mon SKIP SATISFY [isomitted($T+1)] MSG Work between holidays + + It's almost never appropriate to use MAYBE-UNCOMPUTABLE, but it is provided for those rare occasions when it + makes sense. If you use MAYBE-UNCOMPUTABLE inside the evaltrig() function, then untriggerable triggers return + -1. For example: + + SET a evaltrig("MAYBE-UNCOMPUTABLE Mon SKIP OMIT Mon") + + will set a to -1. + +DEBUGGING REMINDER SCRIPTS + Although the command-line -d option is useful for debugging, it is often overkill. For example, if you turn on + the -dx option for a reminder file with many complex expressions, you'll get a huge amount of output. The DEBUG + command allows you to control the debugging flags under program control. The format is: + + DEBUG [+flagson] [-flagsoff] + + Flagson and flagsoff consist of strings of the characters "shextvlfqnu" that correspond to the debugging options + discussed in the command-line options section. If preceded with a "+", the corresponding group of debugging op‐ + tions is switched on. Otherwise, they are switched off. For example, you could use this sequence to debug a + complicated expression: + + DEBUG +x + set a very_complex_expression(many_args) + DEBUG -x + + THE DUMPVARS COMMAND + + The command DUMPVARS displays the values of variables in memory. Its format is: + + DUMPVARS [-c] [var...] + + If you supply a space-separated list of variable names, the corresponding variables are displayed. If you do + not supply a list of variables, then all variables in memory are displayed. To dump a system variable, put its + name in the list of variables to dump. If you put a lone dollar sign in the list of variables to dump, then all + system variables will be dumped. + + If you supply the -c flag, then any variable determined to be constant will have its value followed by "" + + THE ERRMSG COMMAND + + The ERRMSG command has the following format: + + ERRMSG body + + The body is passed through the substitution filter (with an implicit trigger date of today()) and printed to the + error output stream. Example: + + IF !defined("critical_var") + ERRMSG You must supply a value for "critical_var" + EXIT + ENDIF + + THE EXIT COMMAND + + The above example also shows the use of the EXIT command. This causes an unconditional exit from script pro‐ + cessing. Any queued timed reminders are discarded. If you are in calendar mode (described next), then the cal‐ + endar processing is aborted. + + If you supply an INT-type expression after the EXIT command, it is returned to the calling program as the exit + status. Otherwise, an exit status of 99 is returned. + + THE FLUSH COMMAND + + This command simply consists of the word FLUSH on a line by itself. The command flushes the standard output and + standard error streams used by Remind. This is not terribly useful to most people, but may be useful if you run + Remind as a subprocess of another program, and want to use pipes for communication. + +AGENDA MODE JSON OUTPUT + If you supply the --json argument, then Remind outputs JSON instead of the normal text output. The JSON output + consists of a single JSON array of zero or more objects. There are three possible types of objects in the ar‐ + ray: + + banner The banner object, if present, will be the first object in the array. There will be at most one banner + object. It contains a single key, banner, whose value is the banner that Remind would normally print in + Agenda Mode. + + noreminders + The noreminders object, if present, will be the final object in the array. There will be at most one + noreminders object. It contains a single keym, noreminders, whose value is the phrase "No reminders.", + possibly localized into a different language. + + event All other objects in the array are event objects. They are JSON objects that contain all of the keys de‐ + scribed in the rem2ps(1) man page section "CALENDAR ENTRIES". However, the calendar_body, plain_body and + raw_body keys will not be present. + + JSON output can be used by a front-end to display an attractive list of reminders in Agenda Mode. The "show to‐ + day's reminders" feature of tkremind uses the JSON output. + +CALENDAR MODE + If you supply the -c, -s or -p command-line option, then Remind runs in "calendar mode." In this mode, Remind + interprets the script repeatedly, performing one iteration through the whole file for each day in the calendar. + Reminders that trigger are saved in internal buffers, and then inserted into the calendar in the appropriate + places. + + If you also supply the -a option, then Remind will not include timed reminders in the calendar. + + The -p option is used in conjunction with the Rem2PS program to produce a calendar in PostScript format. For + example, the following command will send PostScript code to standard output: + + remind -p .reminders | rem2ps + + You can print a PostScript calendar by piping this to the lpr command. + + If you have a reminder script called ".reminders", and you execute this command: + + remind -c .reminders jan 1993 + + then Remind executes the script 31 times, once for each day in January. Each time it executes the script, it + increments the value of today(). Any reminders whose trigger date matches today() are entered into the calen‐ + dar. + + MSG and CAL-type reminders, by default, have their entire body inserted into the calendar. RUN-type reminders + are not normally inserted into the calendar. However, if you enclose a portion of the body in the %"...%" se‐ + quence, only that portion is inserted. For example, consider the following: + + REM 6 Jan MSG %"Dianne's birthday%" is %b + + In agenda mode, Remind would print "Dianne's birthday is today" on 6 January. However, in the calendar mode, + only the text "Dianne's birthday" is inserted into the box for 6 January. + + If you explicitly use the %"...%" sequence in a RUN-type reminder, then the text between the delimiters is in‐ + serted into the calendar. If you use the sequence %"%" in a MSG or CAL-type reminder, then no calendar entry is + produced for that reminder. + + PRESERVING VARIABLES + + Because Remind iterates through the script for each day in the calendar, slow operations may severely reduce the + speed of producing a calendar. + + For example, suppose you set the variables "me" and "hostname" as follows: + + SET me shell("whoami") + SET hostname shell("hostname") + + Normally, Remind clears all variables between iterations in calendar mode. However, if certain variables are + slow to compute, and will not change between iterations, you can "preserve" their values with the PRESERVE com‐ + mand. Also, since function definitions are preserved between calendar iterations, there is no need to redefine + them on each iteration. Thus, you could use the following sequence: + + IF ! defined("initialized") + set initialized 1 + set me shell("whoami") + set hostname shell("hostname") + fset func(x) complex_expr + preserve initialized me hostname + ENDIF + + The operation is as follows: On the first iteration through the script, "initialized" is not defined. Thus, + the commands between IF and ENDIF are executed. The PRESERVE command ensures that the values of initialized, me + and hostname are preserved for subsequent iterations. On the next iteration, the commands are skipped, since + initialized has remained defined. Thus, time-consuming operations that do not depend on the value of today() + are done only once. + + Most system variables (those whose names start with '$') are automatically preserved between calendar itera‐ + tions. + + Note that for efficiency, Remind caches the reminder script (and any INCLUDEd files) in memory when producing a + calendar. + + Timed reminders are sorted and placed into the calendar in time order. These are followed by non-timed re‐ + minders. Remind automatically places the time of timed reminders in the calendar according to the -b command- + line option. Reminders in calendar mode are sorted as if the -g option had been used; you can change the sort + order in calendar mode by explicitly using the -g option to specify a different order from the default. + + REPEATED EXECUTION + + If you supply a repeat parameter on the command line, and do not use the -c, -p, or -s options, Remind operates + in a similar manner to calendar mode. It repeatedly executes the reminder script, incrementing today() with + each iteration. The same rules about preserving variables and function definitions apply. Note that using re‐ + peat on the command line also enables the -q option and disables any -z option. As an example, if you want to + see how Remind will behave for the next week, you can type: + + remind .reminders '*7' + + If you want to print the dates of the next 1000 days, use: + + (echo 'banner %'; echo 'msg [today()]%') | remind - '*1000' + +INITIALIZING VARIABLES ON THE COMMAND LINE + The -i option is used to initialize variables on the Remind command line. The format is -ivar=expr, where expr + is any valid expression. Note that you may have to use quotes or escapes to prevent the shell from interpreting + special characters in expr. You can have as many -i options as you want on the command line, and they are + processed in order. Thus, if a variable is defined in one -i option, it can be referred to by subsequent -i op‐ + tions. + + Note that if you supply a date on the command line, it is not parsed until all options have been processed. + Thus, if you use today() in any of the -i expressions, it will return the same value as realtoday() and not the + date supplied on the command line. + + Any variables defined on the command line are preserved as with the PRESERVE command. + + You should not have any spaces between the -i option and the equal sign; otherwise, strange variable names are + created that can only be accessed with the value() or defined() functions. + + You can also define a function on the command line by using: + + -ifunc(args)=definition + + Be sure to protect special characters from shell interpretation. + +MORE ABOUT POSTSCRIPT + The PS and PSFILE reminders pass PostScript code directly to the printer. They differ in that the PS-type re‐ + minder passes its body directly to the PostScript output (after processing by the substitution filter) while the + PSFILE-type's body should simply consist of a filename. The Rem2PS program will open the file named in the PS‐ + FILE-type reminder, and include its contents in the PostScript output. + + The PostScript-type reminders for a particular day are included in the PostScript output in sorted order of pri‐ + ority. Note that the order of PostScript commands has a major impact on the appearance of the calendars. For + example, PostScript code to shade a calendar box will obliterate code to draw a moon symbol if the moon symbol + code is placed in the calendar first. For this reason, you should not provide PS or PSFILE-type reminders with + priorities; instead, you should ensure that they appear in the reminder script in the correct order. PostScript + code should draw objects working from the background to the foreground, so that foreground objects properly + overlay background ones. If you prioritize these reminders and run the script using descending sort order for + priorities, the PostScript output will not work. + + All of the PostScript code for a particular date is enclosed in a save-restore pair. However, if several Post‐ + Script-type reminders are triggered for a single day, each section of PostScript is not enclosed in a save-re‐ + store pair - instead, the entire body of included PostScript is enclosed. + + PostScript-type reminders are executed by the PostScript printer before any regular calendar entries. Thus, + regular calendar entries will overlay the PostScript-type reminders, allowing you to create shaded or graphical + backgrounds for particular days. + + Before executing your PostScript code, the origin of the PostScript coordinate system is positioned to the bot‐ + tom left-hand corner of the "box" in the calendar representing today(). This location is exactly in the middle + of the intersection of the bottom and left black lines delineating the box - you may have to account for the + thickness of these lines when calculating positions. + + Several PostScript variables are available to the PostScript code you supply. All distance and size variables + are in PostScript units (1/72 inch.) The variables are: + + LineWidth + The width of the black grid lines making up the calendar. + + Border The border between the center of the grid lines and the space used to print calendar entries. This bor‐ + der is normally blank space. + + BoxWidth and BoxHeight + The width and height of the calendar box, from center-to-center of the black gridlines. + + InBoxHeight + The height from the center of the bottom black gridline to the top of the regular calendar entry area. + The space from here to the top of the box is used only to draw the day number. + + /DayFont, /EntryFont, /SmallFont, /TitleFont and /HeadFont + The fonts used to draw the day numbers, the calendar entries, the small calendars, the calendar title + (month, year) and the day-of-the-week headings, respectively. + + DaySize, EntrySize, TitleSize and HeadSize + The sizes of the above fonts. (The size of the small calendar font is not defined here.) For example, + if you wanted to print the Hebrew date next to the regular day number in the calendar, use: + + REM PS Border BoxHeight Border sub DaySize sub moveto \ + /DayFont findfont DaySize scalefont setfont \ + ([hebday(today())] [hebmon(today())]) show + + Note how /DayFont and DaySize are used. + + Note that if you supply PostScript code, it is possible to produce invalid PostScript files. Always test your + PostScript thoroughly with a PostScript viewer before sending it to the printer. You should not use any docu‐ + ment structuring comments in your PostScript code. + +DAEMON MODE + If you use the -z command-line option, Remind runs in "daemon mode". In this mode, no "normal" reminders are + issued. Instead, only timed reminders are collected and queued, and are then issued whenever they reach their + trigger time. + + In addition, Remind wakes up every few minutes to check the modification date on the reminder script (the file‐ + name supplied on the command line.) If Remind detects that the script has changed, it re-executes itself in + daemon mode, and interprets the changed script. If Remind was compiled with support for inotify(7), then if the + command-line reminder script is really a directory, Remind also re-executes itself if any of the files in the + directory is changed. + + In daemon mode, Remind also re-reads the remind script when it detects that the system date has changed. + + In daemon mode, Remind acts as if the -f option had been used, so to run in daemon mode in the background, use: + + remind -z .reminders & + + If you use sh or bash, you may have to use the "nohup" command to ensure that the daemon is not killed when you + log out. + +PURGE MODE + If you supply the -j command-line option, Remind runs in purge mode. In this mode, it tries to purge expired + reminders from your reminder files. + + In purge mode, Remind reads your reminder file and creates a new file by appending ".purged" to the original + file name. Note that Remind never edits your original file; it always creates a new .purged file. + + If you invoke Remind against a directory instead of a file, then a .purged file is created for each *.rem file + in the directory. + + Normally, Remind does not create .purged files for INCLUDed files. However, if you supply a numeric argument + after -j, then Remind will create .purged files for the specified level of INCLUDE. For example, if you invoke + Remind with the argument -j2, then .purged files will be created for the file (or directory) specified on the + command line, any files included by them, and any files included by those files. However, .purged files will + not be created for third-or-higher level INCLUDE files. + + Determining which reminders have expired is extremely tricky. Remind does its best, but you should always com‐ + pare the .purged file to the original file and hand-merge the changes back in. + + Remind annotates the .purged file as follows: + + An expired reminder is prefixed with: #!P: Expired: + + In situations where Remind cannot reliably determine that something was expired, you may see the following com‐ + ments inserted before the problematic line: + + #!P: Cannot purge SATISFY-type reminders + + #!P: The next IF evaluated false... + #!P: REM statements in IF block not checked for purging. + + #!P: The previous IF evaluated true. + #!P: REM statements in ELSE block not checked for purging + + #!P: The next IFTRIG did not trigger. + #!P: REM statements in IFTRIG block not checked for purging. + + #!P: Next line has expired, but contains expression... please verify + + #!P: Next line may have expired, but contains non-constant expression + #!P: or a relative SCANFROM clause + + #!P! Could not parse next line: Some-Error-Message-Here + + #!P! Problem calculating trigger date + + Remind always annotates .purged files with lines beginning with "#!P". If such lines are encountered in the + original file, they are not copied to the .purged file. + + If you use the "Hush" flag -h in conjunction with the "Purge" flag -j, then Remind does not create any of the + diagnostic comments listed above. Instead, the only change it makes to the .purged file is to mark expired re‐ + minders with "#!P: Expired". + +NON-CONSTANT EXPRESSIONS + In Purge Mode, Remind will not mark a REM statement as expired if its trigger specification contains a non-con‐ + stant expression. A non-constant expression is defined as one whose value might differ from run to run, usually + because it depends on the current date, but also if it depends on something from the environment (such as the + output of the shell() function.) + + The use of any of the following in an expression causes Remind to consider it non-constant: + + o A global variable that has been assigned the result of a non-constant expression, or that has been + assigned a value in a non-constant context (to be described later.) + + o A system variable + + o Certain built-in functions that depend on the current date (for example, today()) or the environ‐ + ment (for example, shell()). + + In addition, for the purposes of safely expiring reminders in Purge Mode, Remind considers the following to be + non-constant: + + o The use of an OMITFUNC + + o The use of a relative SCANFROM + + Whenever a variable is assigned a value, Remind tracks whether or not the expression whose value it was assigned + is constant or non-constant. Additionally, variables that are assigned in a non-constant context are always as‐ + sumed to be non-constant. A non-constant context is the code in the scope of an IF statement where the IF ex‐ + pression is non-constant. + + Here are some examples that should make things clear: + + SET d '2025-06-01' # d is constant + REM [d] MSG Hello! # eligible for purging + + SET d today() - 3 # d is non-constant + REM [d] MSG Hello! # not eligible for purging + + IF wkdaynum(today()) == 3 + set d '2025-06-01' # d is non-constant + ELSE + set d '2026-01-01' # d is non-constant + ENDIF + + SET d '2025-06-01' # d is constant + IF today() > today() + 3 # This branch is never taken, but... + SET d '2029-01-01' # d is still marked non-constant + ENDIF + + # Although here d is still '2025-06-01', it is marked + # non-constant because as far as Remind is concerned, + # the IF body *might* have been executed depending on today() + + Note that within the IF...ENDIF scope, any assignments are non-constant because the code flow depends on today's + date, which could change in subsequent Remind runs. If you want to force a variable to be treated as constant, + no matter what, then use the following just before you use the variable: + + SET var const(var) + + Variables initialized on the command-line with the -i flag are always considered to be non-constant. + + If you have an expired reminder that for some reason you never want purged, simply use the built-in function + nonconst somewhere in the trigger. For example: + + REM 1992-01-01 MSG This will be purged after Jan 1 1992 + REM [nonconst('1992-01-01')] MSG This will never be purged + + REM Wed UNTIL 1993-12-31 MSG This will be purged after 1993 + REM Wed UNTIL [nonconst('1993-12-31')] MSG Never purged + + The n debugging flag prints a message to standard error whenever Remind decides that an expression is non-con‐ + stant. This can produce a large amount of output, so if you want to find out why Remind considers a specific + expression to be non-constant, it's best to use DEBUG +n before it and DEBUG -n after it to limit the amount of + output. + +SORTING REMINDERS + The -g option causes Remind to sort reminders by trigger date, time and priority before issuing them. Note that + reminders are still calculated in the order encountered in the script. However, rather than being issued imme‐ + diately, they are saved in an internal buffer. When Remind has finished processing the script, it issues the + saved reminders in sorted order. The -g option can be followed by up to four characters that must all be "a" or + "d". The first character specifies the sort order by trigger date (ascending or descending), the second speci‐ + fies the sort order by trigger time and the third specifies the sort order by priority. If the fourth character + is "d", the untimed reminders are sorted before timed reminders. The default is to sort all fields in ascending + order and to sort untimed reminders after timed reminders. + + In ascending order, reminders are issued with the most imminent first. Descending order is the reverse. Re‐ + minders are always sorted by trigger date, and reminders with the same trigger date are then sorted by trigger + time. If two reminders have the same date and time, then the priority is used to break ties. Reminders with + the same date, time and priority are issued in the order they were encountered. + + You can define a user-defined function called SORTBANNER that takes one DATE-type argument. In sort mode, the + following sequence happens: + + If Remind notices that the next reminder to issue has a different trigger date from the previous one (or if it + is the first one to be issued), then SORTBANNER is called with the trigger date as its argument. The result is + coerced to a string, and passed through the substitution filter with the appropriate trigger date. The result + is then displayed. + + Here's an example - consider the following fragment: + + # Switch off the normal banner + BANNER % + REM 11 March 1993 ++1 MSG Not so important + REM 17 March 1993 ++7 MSG Way in the future + REM 10 March 1993 MSG Important Reminder + REM 11 March 1993 ++1 MSG Not so important - B + FSET sortbanner(x) iif(x == today(), \ + "***** THINGS TO DO TODAY *****", \ + "----- Things to do %b -----") + + Running this with the -gaa option on 10 March 1993 produces the following output: + + ***** THINGS TO DO TODAY ***** + + Important Reminder + + ----- Things to do tomorrow ----- + + Not so important + + Not so important - B + + ----- Things to do in 7 days' time ----- + + Way in the future + + You can use the args() built-in function to determine whether or not SORTBANNER has been defined. (This could + be used, for example, to provide a default definition for SORTBANNER in a system-wide file included at the end + of the user's file.) Here's an example: + + # Create a default sortbanner function if it hasn't already + # been defined + if args("sortbanner") != 1 + fset sortbanner(x) "--- Things to do %b ---" + endif + +MSGPREFIX() AND MSGSUFFIX() + You can define two functions in your script called msgprefix() and msgsuffix(). They should each accept one ar‐ + gument, a number from 0 to 9999. + + In agenda mode, for MSG- and MSF-type reminders, the following sequence occurs when Remind triggers a reminder: + + o If msgprefix() is defined, it is evaluated with the priority of the reminder as its argument. The result + is printed. It is not passed through the substitution filter. + + o The body of the reminder is printed. + + o If msgsuffix() is defined, it is evaluated with the priority of the reminder as its argument. The result + is printed. It is not passed through the substitution filter. + + Here's an example: The following definition causes priority-0 reminders to be preceded by "URGENT", and prior‐ + ity-6000 reminders to be preceded by "(not important)". + + fset msgprefix(x) iif(x==0, "URGENT: ", \ + x==6000, "(not important) ", "") + + In Calendar Mode (with the -c, -s or -p options), an analogous pair of functions named calprefix() and calsuf‐ + fix() can be defined. They work with all reminders that produce an entry in the calendar (i.e., CAL- and possi‐ + bly RUN-type reminders as well as MSG-type reminders.) + + NOTES + + Normally, the body of a reminder is followed by a carriage return. Thus, the results of msgsuffix() will appear + on the next line. If you don't want this, make sure the output of msgsuffix begins with a backspace. This + places the suffix before rather than after the carriage return. (The backspace character itself is stripped + out.) Here is an example: + + FSET msgsuffix(x) char(8) + " - suffix on same line" + + If Remind has problems evaluating msgprefix(), msgsuffix() or sortbanner(), you will see a lot of error mes‐ + sages. For an example of this, define the following: + + fset msgprefix(x) x/0 + +COMPILE-TIME SUPPORT FOR OTHER LANGUAGES + Remind used to support compile-time localization to other languages, but no longer does. All localization is + now done at run-time. + +RUN-TIME SUPPORT FOR OTHER LANGUAGES + Remind has run-time support for other languages, and compile-time support has been removed in favour of run-time + support. + + A number of system variables let you translate various phrases to other languages. These system variables are: + + $Monday, $Tuesday, $Wednesday, $Thursday, $Friday, $Saturday, $Sunday + Set each of these system variables to a string representing the corresponding day's name in your lan‐ + guage. Strings must be valid UTF-8 strings. + + $January, $February, $March, $April, $May, $June, $July, $August, $September, $October, $November, $December + Set each of these system variables to a string representing the corresponding month's name in your lan‐ + guage. Strings must be valid UTF-8 strings. + + $Ago, $Am, $And, $At, $Hour, $Is, $Minute, $Now, $On, $Pm, $Today, $Tomorrow, $Was + Set each of these system variables to the translation of the corresponding English word into your lan‐ + guage. Note that $Am and $Pm should be the translations of "AM" and "PM" (morning and afternoon time in‐ + dicators) respectively. + + $Hplu, $Mplu + Set these to the suffix to add to the word for "hour" and "minute" to make them plural. In English, both + would be set to "s". + + $Fromnow + Set this to the translation of the English phrase "from now" + + Note that if you set any of the language-related system variables, they should be set in a section of your + script that always is evaluated. If you set them inside an IF statement, for example, results are unpre‐ + dictable. + + Note also that the Rem2PS back-end does not support the full range of UTF-8 characters. The TkRemind, rem2html + and rem2pdf back-ends all do support the full UTF-8 range. + +RUN-TIME MODIFICATION OF THE SUBSTITUTION FILTER + The system variables mentioned in the previous section are not typically sufficient to properly translate Re‐ + mind's output to another language. Some languages have complicated rules for AM vs PM times and others have + complex rules for making words plural. Remind therefore allows you to define a number of functions that modify + the behavior of the substitution filter at run-time. The functions are: + + subst_ampm(h) + This function is passed a single integer, namely an hour from 0 to 23. It should return a string that + indicates "AM" or "PM" or even finer gradations in some languages. + + subst_ordinal(d) + This function is passed a single integer, namely a day of the month from 1 to 31. It should return a + string that is suffixed to the day number to turn it into an ordinal number. In English, for example, + the function might return "st", "nd", "rd" or "th", depending on d. + + subst_N(alt, date, time) + This is actually a family of functions, where N is a letter or number. This function completely over‐ + rides the substitution sequence "%N". The three arguments are an integer alt which, if non-zero, indi‐ + cates that the alternate-mode substitution sequence "%*N" was encountered; date which is the trigger date + of the reminder and time which is the trigger time. + + subst_Nx(alt, date, time) + Again, this is a family of functions. It is similar to the subst_N family except it is only called if + date is two or more days away from today(). This is useful if you don't want to override the "today" or + "tomorrow" output for most substitution sequences. + + Here's an example of how you might customize your substitution filter. Suppose you want to change the "%b" se‐ + quence to substitute "the day after tomorrow" for an event two days from now. You could do this: + + FSET subst_bx(a,d,t) iif(d==today()+2, "the day after tomorrow", \ + "in " + (d-today()) + " days' time") + REM [today()+3] ++3 MSG Event 1 is %b% + REM [today()+2] ++3 MSG Event 2 is %b% + REM [today()+1] ++3 MSG Event 3 is %b% + REM [today()] ++3 MSG Event 4 is %b% + + The output of this script is: + + Event 1 is in 3 days' time + Event 2 is the day after tomorrow + Event 3 is tomorrow + Event 4 is today + + Note how Event 2's wording was changed from the normal "in 2 days' time", and note also that the "tomorrow" and + "today" events used the normal substitution---subst_bx is not called for trigger days of today or tomorrow. + + As a special case, if a subst_Nx or subst_N function returns the integer zero, then the normal substitution + mechanism is used. Therefore, the previous example could have been written more simply as: + + FSET subst_bx(a,d,t) iif(d==today()+2, "the day after tomorrow", 0) + + You can override substitution sequences that are not alphanumeric as follows: + + Override %: with subst_colon + + Override %! with subst_bang + + Override %? with subst_question + + Override %@ with subst_at + + Override %# with subst_hash + + You can define your own substitution sequences in addition to the built-in ones as follows: If you define a + function named subst_name(alt, date, time), then the sequence %{name} calls the function with alt set to 0 and + date and time to the trigger date and time, respectively. The %{name} sequence is replaced with whatever the + function returns. The sequence %*{name} is similar, but calls the function with alt set to 1. + + If you use a %{name} sequence and the function subst_name is not defined or returns an error, then %{name} is + replaced with the empty string. + + Note that when Remind invokes any callback function for a substitution sequence, RUN will be disabled. + +THE TRANSLATION TABLE + To assist with localizing reminder files, Remind maintains a table of translations. This is simple a lookup ta‐ + ble that maps one string (the original string) to a new string (the translated string.) When Remind starts exe‐ + cuting, the translation table is empty. + + To add a message to the translation table, use the TRANSLATE command (which may be abbreviated to TRANS.) The + TRANSLATE command must be followed by two quoted strings, separated from each other and from the command by + whitespace. For example, a Dutch language file might contain something like this: + + TRANSLATE "New Moon" "Nieuwe maan" + TRANSLATE "First Quarter" "Eerste kwartier" + TRANSLATE "Full Moon" "Volle maan" + TRANSLATE "Last Quarter" "Laatste kwartier" + + To actually use the translation table, make use of the _ built-in function, as follows: + + REM NOQUEUE [moondatetime(0)] MSG [_("New Moon")] (%2) + REM NOQUEUE [moondatetime(1)] MSG [_("First Quarter")] (%2) + REM NOQUEUE [moondatetime(2)] MSG [_("Full Moon")] (%2) + REM NOQUEUE [moondatetime(3)] MSG [_("Last Quarter")] (%2) + + By using TRANSLATE and _ judiciously, you can make your reminder files easy to translate. + + TRANSLATE has four additional forms: If it is followed by one quoted string instead of two, then Remind deletes + the translation table entry for that string. If it is followed by the keyword DUMP, then Remind dumps all + translation table entries to standard output. And if it is followed by CLEAR, then Remind deletes all of the + translation table entries. + + The fourth form, TRANSLATE GENERATE, dumps all of the strings that can be localized (as a series of TRANSLATE + commands) to standard output. Strings that are already localized are output with their localization; strings + that are not localized are output as: + + TRANSLATE "untranslated" "" + + If you want to add a new language, you can obtain a skeleton translation + file by running: + + echo "TRANSLATE GENERATE" | remind -h - > /tmp/skeleton.rem + + If you have an existing language file that is missing some translations, you can update it by running: + + (echo INCLUDE mylang.rem; echo TRANSLATE GENERATE) | \ + remind -h - > /tmp/mylang-update.rem + + and then editing mylang-update.rem to add in the missing translations. + + If you have some reminder scripts that use the _() built-in function or %(...) substitution sequence, you can + generate a list of needed TRANSLATE commands by running: + + remind -q -n -dq myscript.rem 2>&1 | grep ^TRANSLATE | sort | uniq + + Note that if you SET various translation-related system variables such as $Monday, $December, $Ago, etc, then + Remind also makes a corresponding translation table entry automatically. This is done for all of the transla‐ + tion-related system variables except for $Hplu and $Mplu. + + The converse applies too; creating a translation table for "December" automatically sets $December. And if you + invoke TRANSLATE CLEAR, then all translation-related system variables are set to their default values as well. + + The translation table always contains a special entry LANGID whose default value is en. Translators are encour‐ + aged to add a LANGID entry in their language files; the value should be the two-characters ISO 639 language + code. + + For example, if you write a translation file for the Dutch language, add this line: + + TRANSLATE "LANGID" "nl" + + Scripts can use _("LANGID") to query the translation language that is in effect. + + The _() function uses the following procedure to obtain the translation for a string: + + 1 Look for an exact match. If found, return. + + 2 If the original string had an upper-case letter, search for the all-lower-case equivalent. If + found, make the first letter of the result upper-case and return. + + 3 If the original string started with a lower-case letter, search for an equivalent whose first let‐ + ter is upper-case and the rest lower-case. If found, make the first letter of the result lower- + case and return. + + 4 No translation was found. Return the original string. + +LANGUAGE PACKS + Remind ships with a number of language packs, which are simply reminder scripts located in [$SysInclude]/lang. + The currently-shipping language packs are: + + da.rem (Danish), de.rem (German), es.rem (Spanish), fr.rem (French), is.rem (Icelandic), it.rem (Italian), + nl.rem (Dutch), no.rem (Norwegian), pl.rem (Polish), pt.rem (Portuguese) and ro.rem (Romanian). + + To use a language pack (in this example, de.rem), simply place this at the top of your reminders file: + + SYSINCLUDE lang/de.rem + + If you want Remind to try to find the language pack appropriate for your locale settings, use: + + SYSINCLUDE lang/auto.rem + + You are encouraged to study the language packs to see how to translate Remind into additional languages. + +THE HEBREW CALENDAR + Remind has support for the Hebrew calendar, which is a luni-solar calendar. This allows you to create reminders + for Jewish holidays, jahrzeits (anniversaries of deaths) and smachot (joyous occasions.) + + THE HEBREW YEAR + + The Hebrew year has 12 months, alternately 30 and 29 days long. The months are: Tishrey, Heshvan, Kislev, + Tevet, Shvat, Adar, Nisan, Iyar, Sivan, Tamuz, Av and Elul. If you are in a UTF-8 locale, you can also use the + UTF-8-encoded Hebrew spellings for the month names, namely: + + תשרי, חשוון, כסלו, טבת, שבט, אדר, ניסן, אייר, סיון, תמוז, אב, אלול. + + In a cycle of 19 years, there are 7 leap years, being years 3, 6, 8, 11, 14, 17 and 19 of the cycle. In a leap + year, an extra month of 30 days is added before Adar. The two Adars are called Adar A and Adar B, or in Hebrew, + 'אדר א and 'אדר ב. + + Remind also permits the following alternative spellings for Hebrew month names: + + Tishrey + Can also be spelled Tishri or Tishrei + + Heshvan + Can also be spelled Cheshvan or Kheshvan + + Shvat Can also be spelled Shevat + + Tamuz Can also be spelled Tammuz + + Adar A Can also be spelled Adar 1, Adar I, אדר א or אדר 1. + + Adar B Can also be spelled Adar 2, Adar II, אדר ב or אדר 2. + + Iyar Can also be spelled Iyyar. + + For certain religious reasons, the year cannot start on a Sunday, Wednesday or Friday. To adjust for this, a + day is taken off Kislev or added to Heshvan. Thus, a regular year can have from 353 to 355 days, and a leap + year from 383 to 385. + + When Kislev or Heshvan is short, it is called chaser, or lacking. When it is long, it is called shalem, or + full. + + The Jewish date changes at sunset. However, Remind will change the date at midnight, not sunset. So in the pe‐ + riod between sunset and midnight, Remind will be a day earlier than the true Jewish date. This should not be + much of a problem in practice. + + The computations for the Jewish calendar were based on the program "hdate" written by Amos Shapir of the Hebrew + University of Jerusalem, Israel. He also supplied the preceding explanation of the calendar. + + HEBREW DATE FUNCTIONS + + hebday(d_date) + Returns the day of the Hebrew month corresponding to the date parameter. For example, 12 April 1993 cor‐ + responds to 21 Nisan 5753. Thus, hebday('1993/04/12') returns 21. + + hebmon(d_date) + Returns the name of the Hebrew month corresponding to date. For example, hebmon('1993/04/12') returns + "Nisan". + + ivritmon(d_date) + Returns the name of the Hebrew month corresponding to date, in UTF-8-encoded Hebrew script. For example, + ivritmon('1993/04/12') returns "ניסן". + + hebyear(d_date) + Returns the Hebrew year corresponding to date. For example, hebyear('1993/04/12') returns 5753. + + hebdate(i_day, s_hebmon [,id_yrstart [,i_jahr [,i_aflag]]]) + The hebdate() function is the most complex of the Hebrew support functions. It can take from 2 to 5 ar‐ + guments. It returns a DATE corresponding to the Hebrew date. + + The day parameter can range from 1 to 30, and specifies the day of the Hebrew month. The hebmon parame‐ + ter is a string that must name one of the Hebrew months specified above. Note that the month must be + spelled out in full, and use either the English transliteration shown previously, or the Hebrew spelling + encoded in UTF-8. You can also specify "Adar A" and "Adar B." Month names are not case-sensitive. + + The yrstart parameter can either be a DATE or an INT. If it is a DATE, then the hebdate() scans for the + first Hebrew date on or after that date. For example: + + hebdate(15, "Nisan", '1990/01/01') + + returns 1990/03/30, because that is the first occurrence of 15 Nisan on or after 1 January 1990. + + If yrstart is an INT, it is interpreted as a Hebrew year. Thus: + + hebdate(22, "Kislev", 5756) + + returns 1995/12/15, because that date corresponds to 22 Kislev, 5756. Note that none of the Hebrew date + functions will work with dates outside Remind's normal range for dates. + + If yrstart is not supplied, it defaults to today(). + + The jahr modifies the behaviour of hebdate() as follows: + + If jahr is 0 (the default), then hebdate() keeps scanning until it finds a date that exactly satisfies + the other parameters. For example: + + hebdate(30, "Adar A", 1993/01/01) + + returns 1995/03/02, corresponding to 30 Adar A, 5755, because that is the next occurrence of 30 Adar A + after 1 January, 1993. This behaviour is appropriate for Purim Katan, which only appears in leap years. + + If jahr is 1, then the date is modified as follows: + + o 30 Heshvan is converted to 1 Kislev in years when Heshvan is chaser + + o 30 Kislev is converted to 1 Tevet in years when Kislev is chaser + + o 30 Adar A is converted to 1 Nisan in non-leapyears + + o Other dates in Adar A are moved to the corresponding day in Adar in non-leapyears + + This behaviour is appropriate for smachot (joyous occasions) and for some jahrzeits - see "JAHRZEITS." + + if jahr is 2, then the date is modified as follows: + + o 30 Kislev and 30 Heshvan are converted to 29 Kislev and 29 Heshvan, respectively, if the month is + chaser + + o 30 Adar A is converted to 30 Shvat in non-leapyears + + o Other dates in Adar A are moved to the corresponding day in Adar in non-leapyears + + if jahr is not 0, 1, or 2, it is interpreted as a Hebrew year, and the behaviour is calculated as de‐ + scribed in the next section, "JAHRZEITS." + + The aflag parameter modifies the behaviour of the function for dates in Adar during leap years. The + aflag is only used if yrstart is a DATE type. + + The aflag only affects date calculations if hebmon is specified as "Adar". In leap years, the following + algorithm is followed: + + o If aflag is 0, then the date is triggered in Adar B. This is the default. + + o If aflag is 1, then the date is triggered in Adar A. This may be appropriate for jahrzeits in the + Ashkenazi tradition; consult a rabbi. + + o If aflag is 2, then the date is triggered in both Adar A and Adar B of a leap year. Some Ashke‐ + nazim perform jahrzeit in both Adar A and Adar B. + + JAHRZEITS + + A jahrzeit is a yearly commemoration of someone's death. It normally takes place on the anniversary of the + death, but may be delayed if burial is delayed - consult a rabbi for more information. + + In addition, because some months change length, it is not obvious which day the anniversary of a death is. The + following rules are used: + + o If the death occurred on 30 Heshvan, and Heshvan in the year after the death is chaser, then the jahrzeit + is observed on 29 Heshvan in years when Heshvan is chaser. Otherwise, the jahrzeit is observed on 1 + Kislev when Heshvan is chaser. + + o If the death occurred on 30 Kislev, and Kislev in the year after the death is chaser, then the jahrzeit + is observed on 29 Kislev in years when Kislev is chaser. Otherwise, the jahrzeit is observed on 1 Tevet + when Kislev is chaser. + + o If the death occurred on 1-29 Adar A, it is observed on 1-29 Adar in non-leapyears. + + o If the death occurred on 30 Adar A, it is observed on 30 Shvat in a non-leapyear. + + Specifying a Hebrew year for the jahr parameter causes the correct behaviour to be selected for a death in that + year. You may also have to specify aflag, depending on your tradition. + + The jahrzeit information was supplied by Frank Yellin, who quoted "The Comprehensive Hebrew Calendar" by Arthur + Spier, and "Calendrical Calculations" by E. M. Reingold and Nachum Dershowitz. + +OUT-OF-BAND REMINDERS + The SPECIAL keyword is used to transmit "out-of-band" information to Remind backends, such as tkremind or + Rem2PS. They are used only when piping data from a remind -p line. (Note that the COLOR special is an excep‐ + tion; it works similarly to MSG when the -p option is not supplied.) + + The various SPECIALs recognized are particular for each backend; however, there are four SPECIALs that all back‐ + ends should attempt to support. They are currently supported by Rem2PS, tkremind and rem2html. + + The SHADE special replaces the psshade() function. Use it like this: + + REM Sat Sun SPECIAL SHADE 128 + REM Mon SPECIAL SHADE 255 0 0 + + The SHADE keyword is followed by either one or three numbers, from 0 to 255. If one number is supplied, it is + interpreted as a grey-scale value from black (0) to white (255). If three numbers are supplied, they are inter‐ + preted as RGB components from minimum (0) to maximum (255). The example above shades weekends a fairly dark + grey and makes Mondays a fully-saturated red. (These shadings appear in calendars produced by Rem2PS, tkremind + and rem2html.) + + The MOON special replaces the psmoon() function. Use it like this: + + REM [moondate(0)] SPECIAL MOON 0 + REM [moondate(1)] SPECIAL MOON 1 + REM [moondate(2)] SPECIAL MOON 2 + REM [moondate(3)] SPECIAL MOON 3 + + These draw little moons on the various calendars. The complete syntax of the MOON special is as follows: + + ... SPECIAL MOON phase moonsize fontsize msg + + Phase is a number from 0 to 3, with 0 representing a new moon, 1 the first quarter, 2 a full moon and 3 the last + quarter. + + moonsize is the diameter in PostScript units of the moon to draw. If omitted or supplied as -1, the backend + chooses an appropriate size. + + fontsize is the font size in PostScript units of the msg + + Msg is additional text that is placed near the moon glyph. + + Note that only the Rem2PS backend supports moonsize and fontsize; the other backends use fixed sizes. + + The COLOR special lets you place colored reminders in the calendar. Use it like this: + + REM ... SPECIAL COLOR 255 0 0 This is a bright red reminder + REM ... SPECIAL COLOR 0 128 0 This is a dark green reminder + + You can spell COLOR either the American way ("COLOR") or the British way ("COLOUR"). This manual will use the + American way. + + Immediately following COLOR should be three decimal numbers ranging from 0 to 255 specifying red, green and blue + intensities, respectively. The rest of the line is the text to put in the calendar. + + The COLOR special is "doubly special", because in agenda mode, remind treats a COLOR special just like a MSG- + type reminder. Also, if you invoke Remind with -@[n], then it approximates SPECIAL COLOR reminders on your ter‐ + minal. + + See also the documentation of the $DefaultColor system variable in the section "SYSTEM VARIABLES". + + The WEEK special lets you place annotations such as the week number in the calendar. For example, this would + number each Monday with the ISO 8601 week number. The week number is shown like this: "(Wn)" in this example, + but you can put whatever text you like after the WEEK keyword. + + REM Monday SPECIAL WEEK (W[weekno()]) + +MISCELLANEOUS + COMMAND AND KEYWORD ABBREVIATIONS + + The following tokens can be abbreviated: + + o CLEAR-OMIT-CONTEXT --> CLEAR + + o PUSH-OMIT-CONTEXT --> PUSH + + o POP-OMIT-CONTEXT --> POP + + o DUMPVARS --> DUMP + + o BANNER --> BAN + + o INCLUDE --> INC + + o MAYBE-UNCOMPUTABLE --> MAYBE + + o SCANFROM --> SCAN + + NIFTY EXAMPLES + + This section is a sampling of what you can do with Remind. + + REM 5 Feb 1991 AT 14:00 +45 *30 \ + RUN mail -s "Meeting at %2" $LOGNAME wrote Remind. The moon phase code was copied largely unmodified from "moontool" + by John Walker. The moonrise/moonset code comes from https://github.com/signetica/MoonRise by Stephen R. + Schmitt and Cyrus Rahman. The sunrise and sunset functions use ideas from programs by Michael Schwartz and Marc + T. Kaufman. The Hebrew calendar support was taken from "hdate" by Amos Shapir. The supported languages and + their translators are listed below. Languages marked "complete" support error messages in that language; all + others only support the substitution filter mechanism and month/day names. + + German -- Wolfgang Thronicke + + Dutch -- Willem Kasdorp and Erik-Jan Vens + + Finnish -- Mikko Silvonen (complete) + + French -- Laurent Duperval (complete) + + Norwegian -- Trygve Randen + + Danish -- Mogens Lynnerup + + Polish -- Jerzy Sobczyk (complete) + + Brazilian Portuguese -- Marco Paganini (complete) + + Italian -- Valerio Aimale + + Romanian -- Liviu Daia + + Spanish -- Rafa Couto + + Icelandic -- Björn Davíðsson + +BUGS + If you find a bug in Remind, please report it to: dianne@skoll.ca + + There's no good reason why read-only system variables are not implemented as functions, or why functions like + version(), etc. are not implemented as read-only system variables. + + Hebrew dates in Remind change at midnight instead of sunset. + + Remind has some built-in limits (for example, number of global OMITs.) + +BIBLIOGRAPHY + Nachum Dershowitz and Edward M. Reingold, "Calendrical Calculations", Software-Practice and Experience, Vol. + 20(9), Sept. 1990, pp 899-928. + + L. E. Doggett, Almanac for computers for the year 1978, Nautical Almanac Office, USNO. + + Richard Siegel and Michael and Sharon Strassfeld, The First Jewish Catalog, Jewish Publication Society of Amer‐ + ica. + + Jean Meeus, Astronomical Algorithms, Second Edition, Willmann-Bell, Inc. + +HOME PAGE + https://dianne.skoll.ca/projects/remind/ + +MAILING LIST + https://dianne.skoll.ca/mailman/listinfo/remind-fans + +SEE ALSO + rem(1), rem2ps(1), rem2pdf(1), tkremind(1), rem2html(1) + +Remind 2026-03-02 REMIND(1) diff --git a/dune b/dune new file mode 100644 index 0000000..3e55565 --- /dev/null +++ b/dune @@ -0,0 +1 @@ +(data_only_dirs contrib calendars) diff --git a/dune-project b/dune-project new file mode 100644 index 0000000..3f269bb --- /dev/null +++ b/dune-project @@ -0,0 +1,24 @@ +(lang dune 3.20) + +(name remind_sync) + +(generate_opam_files true) + +(source + (uri https://git.donadeo.net/pdonadeo/remind-sync)) + +(authors "Paolo Donadeo ") + +(maintainers "Maintainer Name ") + +(license MIT) + +(documentation https://git.donadeo.net/pdonadeo/remind-sync) + +(package + (name remind_sync) + (synopsis "A short synopsis") + (description "A longer description") + (depends ocaml) + (tags + ("add topics" "to describe" your project))) diff --git a/lib/dune b/lib/dune new file mode 100644 index 0000000..ffc81ba --- /dev/null +++ b/lib/dune @@ -0,0 +1,8 @@ +(library + (name remind_sync) + (modules remind_sync timedesc_augmented result_augmented utf8 icalendar_augmented ptime_augmented) + (preprocess + (pps ppx_deriving.show)) + (libraries base logs timedesc uuseg uutf icalendar ptime)) + + diff --git a/lib/icalendar_augmented.ml b/lib/icalendar_augmented.ml new file mode 100644 index 0000000..f5cabce --- /dev/null +++ b/lib/icalendar_augmented.ml @@ -0,0 +1,316 @@ +module Params = struct + include Icalendar.Params + + let pp ppf _m = Format.pp_print_string ppf "" +end + +type params = Params.t [@@deriving show] + +module Ptime = struct + include Ptime_augmented +end + +(* TODO: tag these with `Utc | `Local *) +type timestamp_utc = Ptime.t [@@deriving show] +type timestamp_local = Ptime.t [@@deriving show] +type utc_or_timestamp_local = [ `Utc of timestamp_utc | `Local of timestamp_local ] [@@deriving show] +type timestamp = [ utc_or_timestamp_local | `With_tzid of timestamp_local * (bool * string) ] [@@deriving show] +type date_or_datetime = [ `Datetime of timestamp | `Date of Ptime.date ] [@@deriving show] +type weekday = [ `Friday | `Monday | `Saturday | `Sunday | `Thursday | `Tuesday | `Wednesday ] [@@deriving show] + +type recur = + [ `Byminute of int list + | `Byday of (int * weekday) list + | `Byhour of int list + | `Bymonth of int list + | `Bymonthday of int list + | `Bysecond of int list + | `Bysetposday of int list + | `Byweek of int list + | `Byyearday of int list + | `Weekday of weekday ] +[@@deriving show] + +type freq = [ `Daily | `Hourly | `Minutely | `Monthly | `Secondly | `Weekly | `Yearly ] [@@deriving show] +type count_or_until = [ `Count of int | `Until of utc_or_timestamp_local (* TODO date or datetime *) ] [@@deriving show] +type interval = int [@@deriving show] +type recurrence = freq * count_or_until option * interval option * recur list [@@deriving show] + +type valuetype = + [ `Binary + | `Boolean + | `Caladdress + | `Date + | `Datetime + | `Duration + | `Float + | `Integer + | `Period + | `Recur + | `Text + | `Time + | `Uri + | `Utcoffset + | `Xname of string * string + | `Ianatoken of string ] + +type cutype = [ `Group | `Individual | `Resource | `Room | `Unknown | `Ianatoken of string | `Xname of string * string ] +[@@deriving show] + +type partstat = + [ `Accepted + | `Completed + | `Declined + | `Delegated + | `In_process + | `Needs_action + | `Tentative + | `Ianatoken of string + | `Xname of string * string ] +[@@deriving show] + +type role = + [ `Chair | `Nonparticipant | `Optparticipant | `Reqparticipant | `Ianatoken of string | `Xname of string * string ] +[@@deriving show] + +type relationship = [ `Parent | `Child | `Sibling | `Ianatoken of string | `Xname of string * string ] [@@deriving show] + +type fbtype = [ `Free | `Busy | `Busy_Unavailable | `Busy_Tentative | `Ianatoken of string | `Xname of string * string ] +[@@deriving show] + +type param_value = [ `Quoted of string | `String of string ] [@@deriving show] + +type _ icalparameter = + | Altrep : Uri.t icalparameter + | Cn : param_value icalparameter + | Cutype : cutype icalparameter + | Delegated_from : Uri.t list icalparameter + | Delegated_to : Uri.t list icalparameter + | Dir : Uri.t icalparameter + | Encoding : [ `Base64 ] icalparameter + | Media_type : (string * string) icalparameter + | Fbtype : fbtype icalparameter + | Language : string icalparameter + | Member : Uri.t list icalparameter + | Partstat : partstat icalparameter + | Range : [ `Thisandfuture ] icalparameter + | Related : [ `Start | `End ] icalparameter + | Reltype : relationship icalparameter + | Role : role icalparameter + | Rsvp : bool icalparameter + | Sentby : Uri.t icalparameter + | Tzid : (bool * string) icalparameter + | Valuetype : valuetype icalparameter + | Iana_param : string -> param_value list icalparameter + | Xparam : (string * string) -> param_value list icalparameter +[@@deriving show] + +type other_prop = [ `Iana_prop of string * params * string | `Xprop of (string * string) * params * string ] +[@@deriving show] + +type cal_prop = + [ `Prodid of params * string + | `Version of params * string + | `Calscale of params * string + | `Method of params * string + | other_prop ] +[@@deriving show] + +type class_ = [ `Public | `Private | `Confidential | `Ianatoken of string | `Xname of string * string ] +[@@deriving show] + +type status = + [ `Draft + | `Final + | `Cancelled + | `Needs_action + | `Completed + | `In_process + | (* `Cancelled *) + `Tentative + | `Confirmed (* | `Cancelled *) ] +[@@deriving show] + +type period = timestamp * Ptime.Span.t * bool [@@deriving show] +type period_utc = timestamp_utc * Ptime.Span.t * bool [@@deriving show] +type dates_or_datetimes = [ `Datetimes of timestamp list | `Dates of Ptime.date list ] [@@deriving show] +type dates_or_datetimes_or_periods = [ dates_or_datetimes | `Periods of period list ] [@@deriving show] + +type general_prop = + [ `Dtstamp of params * timestamp_utc + | `Uid of params * string + | `Dtstart of params * date_or_datetime + | `Class of params * class_ + | `Created of params * timestamp_utc + | `Description of params * string + | `Geo of params * (float * float) + | `Lastmod of params * timestamp_utc + | `Location of params * string + | `Organizer of params * Uri.t + | `Priority of params * int + | `Seq of params * int + | `Status of params * status + | `Summary of params * string + | `Url of params * Uri.t + | `Recur_id of params * date_or_datetime + | (* TODO: Furthermore, this property MUST be specified + as a date with local time if and only if the "DTSTART" property + contained within the recurring component is specified as a date + with local time. *) + `Rrule of params * recurrence + | `Duration of params * Ptime.Span.t + | `Attach of params * [ `Uri of Uri.t | `Binary of string ] + | `Attendee of params * Uri.t + | `Categories of params * string list + | `Comment of params * string + | `Contact of params * string + | `Exdate of params * dates_or_datetimes + | `Rstatus of params * ((int * int * int option) * string * string option) + | `Related of params * string + | `Resource of params * string list + | `Rdate of params * dates_or_datetimes_or_periods ] +[@@deriving show] + +type event_prop = + [ general_prop + | `Transparency of params * [ `Transparent | `Opaque ] + | `Dtend of params * date_or_datetime + | (* TODO: valuetype same as DTSTART *) + other_prop ] +[@@deriving show] + +type 'a alarm_struct = { + trigger : params * [ `Duration of Ptime.Span.t | `Datetime of timestamp_utc ]; + duration_repeat : ((params * Ptime.Span.t) * (params * int)) option; + summary : (params * string) option; + other : other_prop list; + special : 'a; +} +[@@deriving show] + +type audio_struct = { attach : (params * [ `Uri of Uri.t | `Binary of string ]) option } [@@deriving show] +type display_struct = { description : (params * string) option } [@@deriving show] + +type email_struct = { + description : params * string; + attendees : (params * Uri.t) list; + attach : (params * [ `Uri of Uri.t | `Binary of string ]) option; +} +[@@deriving show] + +type alarm = + [ `Audio of audio_struct alarm_struct + | `Display of display_struct alarm_struct + | `Email of email_struct alarm_struct + | `None of unit alarm_struct ] +[@@deriving show] + +type tz_prop = + [ `Dtstart_local of params * timestamp_local + | `Tzoffset_to of params * Ptime.Span.t + | `Tzoffset_from of params * Ptime.Span.t + | `Rrule of params * recurrence + | `Comment of params * string + | `Rdate of params * dates_or_datetimes_or_periods + | `Tzname of params * string + | other_prop ] +[@@deriving show] + +type timezone_prop = + [ `Timezone_id of params * (bool * string) + | `Lastmod of params * timestamp_utc + | `Tzurl of params * Uri.t + | `Standard of tz_prop list + | `Daylight of tz_prop list + | other_prop ] +[@@deriving show] + +type todo_prop = + [ general_prop + | `Completed of params * timestamp_utc + | `Percent of params * int + | `Due of params * date_or_datetime + | other_prop ] +[@@deriving show] + +type journal_prop = [ general_prop | other_prop ] [@@deriving show] + +type freebusy_prop = + [ `Dtstamp of params * timestamp_utc + | `Uid of params * string + | `Contact of params * string + | `Dtstart_utc of params * timestamp_utc + | `Dtend_utc of params * timestamp_utc + | `Organizer of params * Uri.t + | `Url of params * Uri.t + | `Attendee of params * Uri.t + | `Comment of params * string + | `Freebusy of params * period_utc list + | `Rstatus of params * ((int * int * int option) * string * string option) + | other_prop ] +[@@deriving show] + +type event = { + dtstamp : params * timestamp_utc; + uid : params * string; + dtstart : params * date_or_datetime; (* NOTE: optional if METHOD present according to RFC 5545 *) + dtend_or_duration : [ `Duration of params * Ptime.Span.t | `Dtend of params * date_or_datetime ] option; + rrule : (params * recurrence) option; (* NOTE: RFC says SHOULD NOT occur more than once *) + props : event_prop list; + alarms : alarm list; +} +[@@deriving show] + +type timezone = timezone_prop list [@@deriving show] + +type component = + [ `Event of event + | `Todo of todo_prop list * alarm list + | `Journal of journal_prop list + | `Freebusy of freebusy_prop list + | `Timezone of timezone ] +[@@deriving show] + +let conv_alarm_struct (f : 'a -> 'b) (s : 'a Icalendar.alarm_struct) : 'b alarm_struct = + { + trigger = s.trigger; + duration_repeat = s.duration_repeat; + summary = s.summary; + other = s.other; + special = f s.special; + } + +let conv_audio_struct (s : Icalendar.audio_struct) : audio_struct = { attach = s.attach } +let conv_display_struct (s : Icalendar.display_struct) : display_struct = { description = s.description } + +let conv_email_struct (s : Icalendar.email_struct) : email_struct = + { description = s.description; attendees = s.attendees; attach = s.attach } + +let conv_alarm (a : Icalendar.alarm) : alarm = + match a with + | `Audio s -> `Audio (conv_alarm_struct conv_audio_struct s) + | `Display s -> `Display (conv_alarm_struct conv_display_struct s) + | `Email s -> `Email (conv_alarm_struct conv_email_struct s) + | `None s -> `None (conv_alarm_struct Fun.id s) + +let conv_event (e : Icalendar.event) : event = + { + dtstamp = e.dtstamp; + uid = e.uid; + dtstart = e.dtstart; + dtend_or_duration = e.dtend_or_duration; + rrule = e.rrule; + props = e.props; + alarms = List.map conv_alarm e.alarms; + } + +let conv_component (c : Icalendar.component) : component = + match c with + | `Event e -> `Event (conv_event e) + | `Todo (props, alms) -> `Todo (props, List.map conv_alarm alms) + | `Journal props -> `Journal props + | `Freebusy props -> `Freebusy props + | `Timezone tz -> `Timezone tz + +let parse s = + Result.map (fun (cal_props, components) -> (cal_props, List.map conv_component components)) (Icalendar.parse s) diff --git a/lib/ptime_augmented.ml b/lib/ptime_augmented.ml new file mode 100644 index 0000000..9dbc20c --- /dev/null +++ b/lib/ptime_augmented.ml @@ -0,0 +1,3 @@ +include Ptime + +type date = int * int * int [@@deriving show] diff --git a/lib/remind_sync.ml b/lib/remind_sync.ml new file mode 100644 index 0000000..0710b21 --- /dev/null +++ b/lib/remind_sync.ml @@ -0,0 +1,5 @@ +module Icalendar = Icalendar_augmented +module Ptime = Ptime_augmented +module Result = Result_augmented +module Timedesc = Timedesc_augmented +module Utf8 = Utf8 diff --git a/lib/result_augmented.ml b/lib/result_augmented.ml new file mode 100644 index 0000000..c2f4f92 --- /dev/null +++ b/lib/result_augmented.ml @@ -0,0 +1,42 @@ +module Internal_result = struct + type ('a, 'b) t = ('a, 'b) result = Ok of 'a | Error of 'b + + let return x = Ok x + let error e = Error e + let error_string s = Error (`Error_message s) + let bind = Stdlib.Result.bind + let ok = Result.ok + + module List = struct + let map (xs : 'a list) ~(f : 'a -> ('b, 'c) t) : ('b list, 'c) t = + let rec loop ?(acc = []) xs = + match xs with + | [] -> return (List.rev acc) + | hd :: tl -> ( + match f hd with + | Ok x -> loop ~acc:(x :: acc) tl + | Error e -> Error e) + in + loop xs + + let iteri ?(start = 0) (xs : 'a list) ~(f : int -> 'a -> (unit, 'b) t) : (unit, 'b) t = + let rec loop ?(idx = start) xs = + match xs with + | [] -> return () + | hd :: tl -> begin + let res = f idx hd in + match res with + | Ok () -> loop ~idx:(idx + 1) tl + | Error e -> Error e + end + in + loop xs + end + + module Let_syntax = struct + let ( let* ) = Stdlib.Result.bind + let ( let+ ) x f = Stdlib.Result.map f x + end +end + +include Internal_result diff --git a/lib/timedesc_augmented.ml b/lib/timedesc_augmented.ml new file mode 100644 index 0000000..6f71b46 --- /dev/null +++ b/lib/timedesc_augmented.ml @@ -0,0 +1,34 @@ +include Timedesc + +type t = Timedesc.t + +module Time = struct + include Timedesc.Time + + let pp = Timedesc.Time.pp_rfc3339 () +end + +module Span = struct + include Timedesc.Span +end + +module Date = struct + include Timedesc.Date + + type t = Timedesc.Date.t + + let pp = Timedesc.Date.pp_rfc3339 + + module Ymd = struct + include Timedesc.Date.Ymd + + type error = [ `Does_not_exist | `Invalid_year of int | `Invalid_month of int | `Invalid_day of int ] + [@@deriving show] + end +end + +module Timestamp = struct + type t = Timedesc.Timestamp.t + + let pp = Timedesc.Timestamp.pp +end diff --git a/lib/utf8.ml b/lib/utf8.ml new file mode 100644 index 0000000..4daf58f --- /dev/null +++ b/lib/utf8.ml @@ -0,0 +1,202 @@ +let length = Uuseg_string.fold_utf_8 `Grapheme_cluster (fun x _ -> x + 1) 0 + +let capitalize s = + let dec = Uutf.decoder ~encoding:`UTF_8 (`String s) in + + let rec split_loop ?(acc = []) () = + match Uutf.decode dec with + | `Await -> assert false + | `End -> List.rev acc + | `Malformed _ignored -> split_loop ~acc () + | `Uchar c -> split_loop ~acc:(c :: acc) () + in + + let buf = Buffer.create 1024 in + let enc = Uutf.encoder `UTF_8 (`Buffer buf) in + let rec capital_loop ?(last_was_upper = false) xs = + match xs with + | c :: tl -> + let last_was_upper = + if Uucp.Alpha.is_alphabetic c + then begin + let f = if last_was_upper = false then Uucp.Case.Map.to_upper else Uucp.Case.Map.to_lower in + match f c with + | `Self -> + let () = Uutf.encode enc (`Uchar c) |> ignore in + true + | `Uchars u_lst -> + List.iter (fun u -> Uutf.encode enc (`Uchar u) |> ignore) u_lst; + true + end + else + let () = Uutf.encode enc (`Uchar c) |> ignore in + false + in + capital_loop ~last_was_upper tl + | [] -> + let () = Uutf.encode enc `End |> ignore in + Buffer.contents buf + in + split_loop () |> capital_loop + +let lowercase s = + let dec = Uutf.decoder ~encoding:`UTF_8 (`String s) in + + let rec split_loop ?(acc = []) () = + match Uutf.decode dec with + | `Await -> assert false + | `End -> List.rev acc + | `Malformed _ignored -> split_loop ~acc () + | `Uchar c -> split_loop ~acc:(c :: acc) () + in + + let buf = Buffer.create 1024 in + let enc = Uutf.encoder `UTF_8 (`Buffer buf) in + let rec to_lower xs = + match xs with + | c :: tl -> + if Uucp.Alpha.is_alphabetic c + then begin + match Uucp.Case.Map.to_lower c with + | `Self -> + let () = Uutf.encode enc (`Uchar c) |> ignore in + to_lower tl + | `Uchars u_lst -> + List.iter (fun u -> Uutf.encode enc (`Uchar u) |> ignore) u_lst; + to_lower tl + end + else + let () = Uutf.encode enc (`Uchar c) |> ignore in + to_lower tl + | [] -> + let () = Uutf.encode enc `End |> ignore in + Buffer.contents buf + in + split_loop () |> to_lower + +let remove_non_alphabetic s = + let dec = Uutf.decoder ~encoding:`UTF_8 (`String s) in + + let rec split_loop ?(acc = []) () = + match Uutf.decode dec with + | `Await -> assert false + | `End -> List.rev acc + | `Malformed _ignored -> split_loop ~acc () + | `Uchar c -> split_loop ~acc:(c :: acc) () + in + + let buf = Buffer.create 1024 in + let enc = Uutf.encoder `UTF_8 (`Buffer buf) in + let rec filter_loop xs = + match xs with + | c :: tl -> + if Uucp.Alpha.is_alphabetic c + then begin + let () = Uutf.encode enc (`Uchar c) |> ignore in + filter_loop tl + end + else filter_loop tl + | [] -> + let () = Uutf.encode enc `End |> ignore in + Buffer.contents buf + in + split_loop () |> filter_loop + +let split_in_chunks_of n s = + let last, chunks = + Uuseg_string.fold_utf_8 + `Grapheme_cluster + (fun (last, chunks) grapheme -> + let l = List.length last in + if l < n + then (grapheme :: last, chunks) + else if l = n + then ([grapheme], (List.rev last |> StringLabels.concat ~sep:"") :: chunks) + else assert false) + ([], []) + s + in + (List.rev last |> StringLabels.concat ~sep:"") :: chunks |> List.rev + +let utf8_clamp_at n s = + let first = + Uuseg_string.fold_utf_8 + `Grapheme_cluster + (fun acc grapheme -> if List.length acc < n then grapheme :: acc else acc) + [] + s + in + let first = String.concat "" (List.rev first) in + let l = String.length first in + let rest = String.sub s l (String.length s - l) in + (first, rest) + +let clamp_at_space_up_to n s = + let module S = StringLabels in + let module L = ListLabels in + let words = S.split_on_char ~sep:' ' s |> L.map ~f:S.trim |> L.filter ~f:(( <> ) "") in + + let words = + match words with + | first :: rest -> + let l_fst = length first in + if l_fst <= n + then first :: rest + else + (* Prima parola troppo lunga, forza lo split anche se non è sullo spazio *) + let fst, snd = utf8_clamp_at n first in + fst :: snd :: rest + | [] -> [] + in + + let rec loop acc words = + match words with + | hd :: tl -> + let l = length hd in + if l <= n + then loop (hd :: acc) tl + else + let words' = split_in_chunks_of n hd in + loop (L.rev words' @ acc) tl + | [] -> L.rev acc + in + let words = loop [] words in + + let rec loop ?(ok = []) ?(total_chars = 0) ?(total_words = 0) words = + match words with + | hd :: tl -> + let l = length hd in + if total_chars + total_words + l > n + then (L.rev ok |> S.concat ~sep:" ", S.concat ~sep:" " words) + else loop ~ok:(hd :: ok) ~total_chars:(total_chars + l) ~total_words:(total_words + 1) tl + | [] -> (L.rev ok |> S.concat ~sep:" ", "") + in + loop words + +let split_at_space_up_to n s = + let rec loop ?(acc = []) s = + let s', rest = clamp_at_space_up_to n s in + let acc = s' :: acc in + if rest = "" then List.rev acc else loop ~acc rest + in + loop s + +let recode_string ?(encoding = `UTF_8) src = + let dst = Buffer.create 4 in + let rec loop d e = + match Uutf.decode d with + | `Uchar _ as u -> + let (_ : [`Ok | `Partial]) = Uutf.encode e u in + loop d e + | `End -> + let (_ : [`Ok | `Partial]) = Uutf.encode e `End in + () + | `Malformed _ -> + let (_ : [`Ok | `Partial]) = Uutf.encode e (`Uchar Uutf.u_rep) in + loop d e + | `Await -> assert false + in + let d = Uutf.decoder ~nln:(`NLF (Uchar.of_int 10)) ~encoding (`String src) in + let e = Uutf.encoder `UTF_8 (`Buffer dst) in + let () = loop d e in + Buffer.contents dst diff --git a/remind_sync.opam b/remind_sync.opam new file mode 100644 index 0000000..a3dc098 --- /dev/null +++ b/remind_sync.opam @@ -0,0 +1,30 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "A short synopsis" +description: "A longer description" +maintainer: ["Maintainer Name "] +authors: ["Paolo Donadeo "] +license: "MIT" +tags: ["add topics" "to describe" "your" "project"] +doc: "https://git.donadeo.net/pdonadeo/remind-sync" +depends: [ + "dune" {>= "3.20"} + "ocaml" + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "https://git.donadeo.net/pdonadeo/remind-sync" +x-maintenance-intent: ["(latest)"]