Skip to content

ReminderRouter derives "today" as the UTC day: "remind me today" silently schedules for tomorrow every evening in negative-offset zones #1923

Description

@MatiasBarboza

What's broken

hooks/ReminderRouter.hook.ts derives due dates with:

function iso(d: Date): string {
  return d.toISOString().slice(0, 10);
}

toISOString() is always UTC, so between 21:00 and 23:59 in UTC-3 (and any negative-offset zone's evening), "remind me today" files an issue due tomorrow. It fails silently every night — the issue is created, the date is just wrong, so nothing ever alarms.

Version / location

  • v7.40.4 (current tag), LifeOS/install/hooks/ReminderRouter.hook.ts:122-124 (iso()), used by parseRelativeDate for today/tomorrow/in-N-days/next-weekday.
  • The class is wider: grep -rn "toISOString().slice(0, 10)\|toISOString().split" hooks/ LIFEOS/TOOLS/ LIFEOS/PULSE/ finds ~33 sites at the tag; most are human-facing days (due dates, created: frontmatter, "Last Updated" stamps, filenames, "Today is X" prompts to models) where the UTC day is the wrong answer for any non-UTC operator.

Repro (clean tree, no patches)

// bun repro.ts — uses only the tag's code path
const now = new Date("2026-08-01T01:06:00Z"); // == 2026-07-31 22:06 in America/Montevideo
console.log(now.toISOString().slice(0, 10));   // "2026-08-01" — but the operator's "today" is 2026-07-31

Negative control: the same derivation at midday (2026-08-20T15:00:00Z, 12:00 UTC-3) returns the correct local day — which is why the bug survives casual testing: it only exists at night, and its symptom is a reminder that quietly arrives a day late.

Root cause

The codebase has no primitive for "the LOCAL calendar day of an instant". hooks/lib/time.ts already reads principal.timezone from settings.json and has getPSTDate() (local today), but nothing converts an arbitrary Date to the principal's day, so call sites reach for toISOString().slice(0, 10).

Suggested fix

Add the missing primitive to hooks/lib/time.ts (timezone as a parameter so it's testable — bun test runs in UTC, where an environment-reading version can never fail):

export function localISODate(date: Date = new Date(), timezone: string = getTimezone()): string {
  return new Intl.DateTimeFormat('en-CA', {
    timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit',
  }).format(date);
}

Then migrate the human-facing sites (iso() in ReminderRouter first). Two classes of site should NOT be converted: absolute event seals (full-ISO ts fields in logs) and pure date-string arithmetic that anchors on T00:00:00Z and returns to a string (e.g. AppleHealthImport.windowStart).

Running this fix in production since 2026-08-20 (26 files migrated, test suite green, regression tests red against the unpatched derivation). Happy to PR if useful.

Prior art

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions