From ddad50803c621b7b7fbedbe73f8cf5ad2e22277a Mon Sep 17 00:00:00 2001 From: Paolo Donadeo Date: Fri, 19 Jun 2026 23:33:24 +0200 Subject: [PATCH] fix: use deterministic hash for SCHED/WARN function names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the global sequential counter (sched_1, warn_2, …) with a polynomial hash of (UUID ^ date), so names are stable across runs and unique across calendars — eliminating "function redefined" errors when multiple .rem files are included by Remind. --- bin/remind.ml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/bin/remind.ml b/bin/remind.ml index 664b170..5d1e7e8 100644 --- a/bin/remind.ml +++ b/bin/remind.ml @@ -80,12 +80,13 @@ let empty = (* ── alarm rendering ─────────────────────────────────────────── *) -(** Global counter for generating unique WARN/SCHED function names *) -let alarm_id = ref 0 - -let next_alarm_id () = - incr alarm_id; - !alarm_id +(** Deterministic alarm function name tag derived from the event UID and date. Using both fields ensures uniqueness + across calendars (UUID is globally unique) and between a master event and its date-specific overrides (which share + the same UUID). *) +let alarm_hash rem = + let key = rem.original_uuid ^ Timedesc.Date.to_rfc3339 rem.date in + let h = String.fold_left (fun acc c -> ((acc * 1000003) + Char.code c) land 0x3FFFFFFF) 0 key in + spf "%08x" h (** Convert a Timedesc.Span.t to signed total minutes. Negative = before event; positive = after event. *) let span_to_minutes (sp : Timedesc.Span.t) : int = @@ -141,8 +142,7 @@ let render_alarm (rem : rem) : alarm_rendering = | [] -> empty_alarm | [ n ] -> { empty_alarm with time_delta = spf "+%d " n } | mins -> - let id = next_alarm_id () in - let name = spf "sched_%d" id in + let name = spf "sched_%s" (alarm_hash rem) in (* SCHED sequence: most-advance first (most negative), then 0 to stop. mins is sorted ascending (closest first), so reverse for SCHED order. *) let sched_vals = List.rev_map (fun n -> spf "%d" (-n)) mins @ [ "0" ] in @@ -155,8 +155,7 @@ let render_alarm (rem : rem) : alarm_rendering = | [] -> empty_alarm | [ n ] -> { empty_alarm with day_delta = spf "++%d " n } | days -> - let id = next_alarm_id () in - let name = spf "warn_%d" id in + let name = spf "warn_%s" (alarm_hash rem) in (* WARN sequence: furthest first (days sorted descending), then 0 to stop. *) let warn_vals = List.map string_of_int (days @ [ 0 ]) in let fset = spf "FSET %s(x) choose(x, %s)\n" name (String.concat ", " warn_vals) in