Skip to content

fix(contact-logs): correct timezone handling on Contact_Date save/edit#63

Merged
chriskehayias merged 2 commits into
mainfrom
fix/contact-log-timezone
May 21, 2026
Merged

fix(contact-logs): correct timezone handling on Contact_Date save/edit#63
chriskehayias merged 2 commits into
mainfrom
fix/contact-log-timezone

Conversation

@chriskehayias

Copy link
Copy Markdown
Contributor

Summary

  • Customer reported Contact Log entries saving with a wrong date/time and each edit shifting the date back another day. Root cause was a double conversion: the form appended T00:00:00.000Z to the date string, and ContactLogService then ran new Date(...).getFullYear() on the result — producing the SQL string in the Node server's local timezone, not the MP domain's. Editing re-applied the same transform.
  • Ministry Platform stores datetimes as wall-clock values in the domain's configured time zone (exposed via getDomainInfo().TimeZoneName), not UTC. This PR adds a reusable DomainTimezoneService, threads MP timezone through the contact-log read/write paths, and upgrades the Add/Edit form to a <input type="datetime-local"> defaulting to the current moment in MP-TZ.
  • A new reference doc .claude/references/ministryplatform.datetimehandling.md documents the rule, the service API, write/read recipes, anti-patterns, the Windows↔IANA mapping, and test mocking guidance. CLAUDE.md gains a Key Development Practice (Cleaning Up and adding Claude #10) and a Reference Documents link so future MP datetime work picks this up.

What changed

New

  • src/services/domainTimezoneService.ts — singleton with getMpTimezone() / toMpSqlDatetime() / parseMpDatetime(), Windows→IANA mapping, cached per process.
  • src/services/domainTimezoneService.test.ts — 16 tests; isolation via mockReset() (queue-leak gotcha documented in the reference doc).
  • src/components/shared-actions/domain.tsgetMpTimezone() server action for client display.
  • .claude/references/ministryplatform.datetimehandling.md — the rule, recipes, anti-patterns, testing guidance.

Fixed

  • src/components/contact-logs/contact-logs.tsx — stopped appending T00:00:00.000Z on create/update; threaded mpTimezone prop; formatDateTime uses Intl.DateTimeFormat({ timeZone }); form upgraded to datetime-local defaulting to MP-TZ "now"; fresh "now" seeded each open.
  • src/services/contactLogService.ts — removed the new Date(x).getFullYear() round-trip; Contact_Date flows through DomainTimezoneService.toMpSqlDatetime(); Zod still validates the rest of the record.
  • src/components/contact-lookup-details/contact-lookup-details.tsx + page — pass mpTimezone down.
  • src/services/contactLogService.test.ts — rewrote cases that previously asserted the broken Zod-rejection behavior; added a 3-edit round-trip regression that proves no drift.

Docs

Test plan

  • npm run test:run — 258/258 passing
  • npm run lint — clean
  • npx tsc --noEmit — no new errors (7 pre-existing in helper.test.ts unrelated)
  • Timezone tests pass under both TZ=UTC and TZ=America/Los_Angeles
  • Manual verification on /contactlookup:
    • Create a Contact Log late evening (e.g. 11:33 PM local) — saved value matches what you entered (no day shift)
    • Edit the saved entry without changing fields, save again — date/time does NOT drift
    • Repeat edit at least 3× to confirm round-trip stability
    • Add Log dialog opens with the current MP-TZ moment pre-filled

🤖 Generated with Claude Code

chriskehayias and others added 2 commits May 21, 2026 02:38
Customer reported Contact Log entries saving at the wrong date/time and each
edit shifting the date back another day. Root cause was a double conversion:
the form appended `T00:00:00.000Z` to the date string, and ContactLogService
ran `new Date(...).getFullYear()` on the result — producing the SQL string in
the Node server's local timezone, not the MP domain's. Editing re-applied the
same transform to the already-shifted date.

Ministry Platform stores datetimes as wall-clock values in the domain's
configured time zone (exposed via `getDomainInfo().TimeZoneName`), not UTC.

Changes:

- Add DomainTimezoneService (`src/services/domainTimezoneService.ts`) — a
  singleton wrapping `MPHelper.getDomainInfo()` with cached IANA zone
  resolution. Includes a Windows→IANA mapping table since MP's API typically
  returns Windows zone names ("Eastern Standard Time") and `Intl` requires
  IANA ("America/New_York"). Public API: `getMpTimezone()`,
  `toMpSqlDatetime(value)`, `parseMpDatetime(value)`.

- Add shared server action `getMpTimezone()` in `shared-actions/domain.ts`
  for client-side display.

- Fix ContactLogService — `Contact_Date` now flows through
  `toMpSqlDatetime()`; Zod still validates the rest of the record. The
  `new Date(...).getFullYear()` round-trip is gone.

- Fix `contact-logs.tsx` — stop appending UTC marker on submit; thread
  `mpTimezone` prop through page → ContactLookupDetails → ContactLogs;
  `formatDateTime` uses `Intl.DateTimeFormat({ timeZone })` so MP wall-clock
  values render correctly regardless of the user's browser zone.

- Upgrade Add/Edit form to `<input type="datetime-local">` defaulting to
  current moment in MP-TZ wall-clock. Fresh "now" is seeded each time the
  Add Log dialog opens. Edit pre-fill preserves both date and time.

- Add reference doc `.claude/references/ministryplatform.datetimehandling.md`
  covering why MP isn't UTC, the service API, write/read recipes, the
  anti-patterns this commit removes, and test mocking guidance.

- Update `CLAUDE.md` with Key Development Practice #10 and a Reference
  Documents link so future MP date work picks this up.

Tests:
- 16 new tests for `DomainTimezoneService`, isolated via `mockReset()` and
  passing under both `TZ=UTC` and `TZ=America/Los_Angeles`.
- Rewrote `contactLogService.test.ts` cases that previously asserted the
  broken Zod-rejection behavior; added a 3-edit round-trip regression that
  proves the date no longer drifts.
- Full suite: 258/258 passing; lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue is fixed and documented; the TODO's contents are now captured in the
commit that landed the fix and in `.claude/references/ministryplatform.datetimehandling.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.89796% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/services/domainTimezoneService.ts 94.11% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

@chriskehayias chriskehayias merged commit bc631f6 into main May 21, 2026
2 checks passed
@chriskehayias chriskehayias deleted the fix/contact-log-timezone branch May 21, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant