A 14-day-trial calculator. Mutates the start date, adds 14 days. Looked fine until DST transitions in non-IST regions caused trials to end at 23:00 or 01:00 instead of midnight, sometimes coinciding with off-by-one display.
The vulnerable diff
// billing/trial.ts
function calculateTrialEnd(start: Date): Date {
// BUG #1: setDate mutates start. Caller's date object changes.
// BUG #2: 14 days across a DST boundary may give 14×24h not 14 calendar days.
return new Date(start.setDate(start.getDate() + 14));
}What is wrong
Two related bugs. `Date.setDate` mutates the date object — surprising to callers expecting immutability. And adding days via `setDate(getDate() + N)` is calendar-aware (handles month boundaries) but still operates in the local timezone — DST transitions can shift the time-of-day. The fix is a date library (date-fns, Luxon, Temporal) that handles both immutability and DST correctly.
The attack
Symptom on a DST-observing region:
start = 2026-03-08 12:00:00 (US/Eastern, before DST)
trialEnd computed naively: 2026-03-22 11:00:00 (after DST shifted by 1h)
Display logic shows "trial ends Sat Mar 21" (calculated days remaining = 13)Customer sees 13 days. Support tickets follow.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Logic] [Date-Math] [medium]
Two issues:
1. setDate mutates start — surprises callers.
2. 14 days across DST shifts time-of-day by ±1 hour, can cause
off-by-one displayed days.
Use a date library:
import { addDays } from "date-fns";
function calculateTrialEnd(start: Date): Date {
return addDays(start, 14); // immutable, DST-aware
}
Or Temporal (now standard):
function calculateTrialEnd(startIso: string, tz: string) {
return Temporal.ZonedDateTime.from(`${startIso}[${tz}]`)
.add({ days: 14 })
.toString();
}
Pin the user's timezone with the date if the trial end matters at
calendar-day precision (it usually does for billing).The fix
// billing/trial.ts — fixed
import { addDays } from "date-fns";
function calculateTrialEnd(start: Date): Date {
return addDays(start, 14);
}Immutable, DST-aware addition. Cluster 7 post 70 also applies — store the user's timezone and apply it where calendar-day precision matters.
Why human review missed it
Date arithmetic bugs are subtle and don't reproduce in dev (no DST transition in the test window). Mesrai catches `setDate(getDate() + N)` patterns and recommends the date-library equivalent.
Related rules + further reading
Mesrai rule pack: logic/date-mutate-and-dst — flags setDate / setMonth mutations and recommends date-library add.
date-fns: Why Immutability Matters.
Common in trial / subscription / scheduling code.
Takeaway
Use a date library. Immutable + DST-aware. setDate mutates; the math drifts at DST. Mesrai catches the pattern.