Ask Anvil

Answers to questions about automating PDFs, e-signatures, Webforms, and other paperwork problems.
PDFs
Categories

Why is the date in my generated document one day off, and how do I fix it?

The symptom

Your record holds 2026-03-01. The generated document prints February 28, 2026. The stored value is correct, your template does no date math, and the same code produced the right date on a colleague's machine. Timestamps like "signed at" look fine. Only plain calendar dates move, and they always move backward by exactly one day.

The actual cause

A calendar date is not a point in time, but JavaScript's Date treats every string like one. In the date time string format, a date-only string is interpreted as UTC, while a string that carries a time but no offset is interpreted in local time. So "2026-03-01" becomes midnight UTC, and any formatter that renders that instant in a zone behind UTC prints the day before.

const d = new Date("2026-03-01");

d.toISOString();
// "2026-03-01T00:00:00.000Z"

d.toLocaleDateString("en-US", { timeZone: "America/New_York" });
// "2/28/2026"

This survives local testing because the size of the shift depends on the runtime's zone. If your test environment runs in UTC, nothing moves and every test passes. Then a worker, a browser, or a nightly report formats the same value in US Eastern or Pacific and every date jumps back a day.

The inverse trap is the same bug pointed the other way. Adding a time without an offset, as in new Date("2026-03-01T00:00:00"), is parsed as local midnight, so in New York it is stored as 2026-03-01T05:00:00.000Z. Whichever direction you hit, the fix is the same.

The fix

Stop round-tripping calendar dates through an instant type. If the value has no meaningful time of day (effective dates, dates of birth, deadlines, expiration dates), format it from its string parts and never build a Date at all:

const MONTHS = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"];

function formatDateOnly(iso) {
  const [y, m, d] = iso.split("-").map(Number);
  return MONTHS[m - 1] + " " + d + ", " + y;
}

formatDateOnly("2026-03-01"); // "March 1, 2026"

If a Date object is unavoidable, because a library or template helper insists on one, pin the output zone to UTC so parsing and formatting agree:

new Date("2026-03-01").toLocaleDateString("en-US", {
  timeZone: "UTC",
  year: "numeric",
  month: "long",
  day: "numeric",
});
// "March 1, 2026"

In Python, the equivalent discipline is to keep the value a date and never promote it to a datetime, since a date carries no timezone and nothing can shift it. Note that %d is zero padded, so strftime prints "March 01, 2026"; assemble the string yourself if you want "March 1, 2026".

from datetime import date

d = date.fromisoformat("2026-03-01")
d.strftime("%B %d, %Y")  # 'March 01, 2026'
f"{d:%B} {d.day}, {d.year}"  # 'March 1, 2026'

Two caveats. First, apply the fix where the value is formatted, not only where it is stored. Forcing the document worker's timezone to UTC hides the bug until one client, one preview screen, or one export path formats in local time. Second, real timestamps are the opposite case: an audit line like "signed at" is a genuine instant, so convert it deliberately to a stated zone and print the zone label next to it rather than stripping the offset.

Back to All Questions

The fastest way to build software for documents

Anvil Document SDK is a comprehensive toolbox for product teams launching document flows where PDF filling, signing, and complex conditional scenarios are necessary.
Explore Anvil
Anvil Webforms