DEV Community

Slate
Slate

Posted on

toISOString().slice(0, 10) is not today's date

A user reported that an invoice they issued on the 1st of the month was dated the 31st of the previous month. VAT reports in Israel are filed by calendar month, so this was not cosmetic. The invoice landed in the wrong reporting period.

The bug was one line:

const issueDate = new Date().toISOString().slice(0, 10);
Enter fullscreen mode Exit fullscreen mode

toISOString() returns UTC. Israel is UTC+2 (UTC+3 in summer). Issue a document at 00:30 local time and UTC still says yesterday. The user gets a legal document carrying the wrong date, and nobody notices until an accountant does.

The fix is to format in the business's own timezone, not the server's and not UTC:

function localDateString(timeZone = 'Asia/Jerusalem') {
  return new Intl.DateTimeFormat('en-CA', { timeZone }).format(new Date());
}
// "2026-07-31", en-CA locale gives YYYY-MM-DD directly
Enter fullscreen mode Exit fullscreen mode

Same rule on the backend. A Node server deployed in Frankfurt runs on UTC, so new Date().getDate() there is just as wrong as toISOString().

Two lessons that outlived the bug:

First, a fiscal date is a calendar day, not a timestamp. Store it as a plain YYYY-MM-DD string, not a Date. The moment you store midnight-UTC datetimes for date-only facts, every serialization boundary (database driver, JSON, ORM) is a new chance to shift the day. The "when exactly did this happen" question belongs to a separate audit timestamp in full ISO, with the timezone kept.

Second, decide which timezone owns the date once, and put the helper next to that decision. We centralized it into one localDateString() util on the client and one jerusalemToday() on the server, and banned raw toISOString().slice in review. The bug never came back.

This came out of building Slate, invoicing software for Israeli businesses, where a document's date decides which VAT period it belongs to. Any app that puts a date on a legal or financial record has this bug waiting if it formats dates through UTC.

Top comments (1)

Collapse
 
nark3d profile image
Adam Lewis

A lint rule would hold that better than a review convention. no-restricted-syntax matching toISOString().slice catches it on the next person's first commit, rather than depending on a reviewer who happened to be paying attention that day.

The type point works from the other direction. Once a fiscal date is its own type rather than a Date, passing a timestamp where a calendar day belongs stops compiling, and nobody has to remember the rule at all.