A cancel-by-date calculator that gets clamped month math right (so it doesn't break on Jan 31 + 1 month)
Most trial-reminder tools just tell you "day 7 of 14." I built Trial Guard to compute the one date that actually matters — the last safe day to cancel — and then get that date out of the browser tab and into a calendar, because a banner you have to remember to look at on the right day is not a reminder system.
Three dates, not one
A signup trial has three distinct dates that are easy to conflate:
- Trial ends — the last day of the trial window itself
- Billing date — the day the card actually gets charged (one day after trial end)
- Cancel-by date — billing date minus a safety buffer the user sets (default a couple of days), so "cancel today" doesn't mean "cancel at 11:59pm and hope"
If the buffer is longer than the trial itself, the cancel-by date would land before the trial even started — the tool clamps it to the start date instead and surfaces a note explaining why, rather than showing a date that makes no sense.
The month-length trap
The naive way to add "1 month" to a date is date.setMonth(date.getMonth() + 1). That breaks the moment someone starts a trial on the 31st of a month: adding one month to Jan 31 in JavaScript doesn't land on Feb 28, it silently rolls forward into March, because Date normalizes an out-of-range day. For a tool whose entire job is telling you the correct day, a silent one-to-three-day drift is exactly the bug you can't have.
The fix is a clamped-add helper: compute the target month, work out that month's actual last day (new Date(year, month+1, 0).getDate()), and cap the day-of-month to whichever is smaller — the original day or the target month's last day. Jan 31 plus one month becomes Feb 28 (or Feb 29 in a leap year), not Mar 3. The same helper handles week- and day-denominated trials by just adding milliseconds instead, so the clamping logic only kicks in for the month case where it's actually needed.
A verdict, not just a countdown
Rather than rendering a bare number, compute() resolves the dates into one of a handful of states: already billed (trial-end date is in the past), inside the buffer window (cancel now), the last safe day, tomorrow's the deadline, or a plain days-remaining count. Each state gets its own copy and its own color on a countdown gauge, because "cancel today" and "you have three weeks" are different actions and should not look like the same UI element with a different number in it.
Getting the date out of the tab
A date sitting in a browser tab you'll close in ten seconds is not a reminder. The tool offers two exports:
- A one-tap Google Calendar deep link, built entirely from URL parameters (
calendar.google.com/calendar/render?action=TEMPLATE&dates=...). The fiddly part: Google's all-day event end date is exclusive, so the link has to add one calendar day to the cancel-by date or the event shows up ending a day early. - A hand-built
.icsfile for every other calendar app, generated client-side as a plain string (BEGIN:VCALENDAR... DTSTART;VALUE=DATE:...) with no library — an ICS file for a single all-day event is a small enough spec that pulling in a dependency for it isn't worth it.
Handing off instead of starting over
Once the tool has a service name and a decision to cancel, it doesn't just say "good luck" — it links to Cancel Helper with the service name pre-filled via a ?service= query parameter, so the next step in the actual task (writing the cancellation message) doesn't make the user retype what they just typed here.
Live tool: https://trial-guard.pages.dev/?utm_source=devto&utm_medium=backlink
Happy to go deeper on the calendar-export edge cases or the clamped date math if useful — drop a comment.
Top comments (0)