You write your diary at 7 a.m., and the app labels it "yesterday's entry." Your streak drops from 12 to 1. I hear this kind of report from people who had an AI build a diary or habit tracker.
It works, and then it is wrong only some of the time. Staring at the screen won't find it. The cause is usually one line about how a date gets turned into text.
First, confirm what is happening
On a phone set to Japan time, the bad window is midnight to 9 a.m. In that window the app decides it is still "yesterday." After 9 a.m. it looks fine again, which is why testing at night never reproduces it.
The line behind it is a form AI tends to write:
// Builds a date key such as "2026-11-07". A commonly generated form
const dateKey = (d) => d.toISOString().slice(0, 10);toISOString() returns the time converted to UTC, not your device's clock. Japan is nine hours ahead of UTC, so 8:59 a.m. in Japan is still 11:59 p.m. of the previous day in UTC.
Three ways to turn the same moment into a key
With the device time zone set to Japan, I ran this script on Node 22.
// dk.mjs — three ways to turn one instant into a date key
const pad = (n) => String(n).padStart(2, "0");
const keyUtc = (d) => d.toISOString().slice(0, 10);
const keyLocal = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
const keyIntl = (d, tz) => new Intl.DateTimeFormat("sv-SE", { timeZone: tz }).format(d);
const samples = ["2026-11-07T00:20:00+09:00", "2026-11-07T08:59:00+09:00",
"2026-11-07T09:00:00+09:00", "2026-11-07T23:50:00+09:00"];
for (const s of samples) {
const d = new Date(s);
console.log(s.slice(11, 16), "utc:", keyUtc(d), "local:", keyLocal(d), "intl:", keyIntl(d, "Asia/Tokyo"));
}TZ=Asia/Tokyo node dk.mjs printed:
00:20 utc: 2026-11-06 local: 2026-11-07 intl: 2026-11-07
08:59 utc: 2026-11-06 local: 2026-11-07 intl: 2026-11-07
09:00 utc: 2026-11-07 local: 2026-11-07 intl: 2026-11-07
23:50 utc: 2026-11-07 local: 2026-11-07 intl: 2026-11-07
At 00:20 and 08:59 only the utc column says November 6. It flips to November 7 at exactly 9:00, which matches the complaint.
The streak breaks because "yesterday" is built the same way
Fixing the key alone can still leave the streak wrong. That happens when the line that builds yesterday's key looks like this:
// Yesterday's key: subtracts 24 hours from a UTC-based key
const bad = (d) => keyUtc(new Date(d.getTime() - 86400000));
// Steps back one day on the device calendar instead
const yesterdayKey = (d) => { const y = new Date(d); y.setDate(y.getDate() - 1); return keyLocal(y); };At 00:20 on November 7, yesterdayKey returned 2026-11-06 while bad returned 2026-11-05. The UTC key was already a day behind, and subtracting 24 hours moved it back a second day. The app then sees today's entry but an empty "yesterday," and the streak resets to 1.
Subtracting 24 hours can also land on the wrong day in regions with daylight saving time. Stepping back with setDate on the calendar avoids that worry.
The fix: decide the date key in exactly one place
There are only a few places to touch.
- Keep a single function in the app that builds date keys.
- Build it from the device calendar (
getFullYearand friends), or fromIntl.DateTimeFormatwith the time zone stated explicitly. - Don't use
toISOString().slice(0, 10)for date keys. It is a format for sending an instant to a server.
Which one you pick depends on one question: if a user travels abroad, whose midnight should the day roll over at? Use the device calendar if they should keep counting by their own daily life, or state Asia/Tokyo if it should stay fixed to Japan time.
Build date text in one function, after deciding the time zone. I put that line into the first prompt of any record-keeping app.
A prompt you can hand to the AI as is
For an app that already exists, this narrows the search:
Find every place that builds a date key (a YYYY-MM-DD string) and replace them
with one shared function, toDateKey.
toDateKey must use the device's local year, month and day, and must not use
toISOString().slice(0, 10).
Do "today" and "yesterday" checks only with that function and setDate(getDate() - 1).
After the change, write tests for 00:20, 08:59, 09:00 and 23:50 and confirm
the streak does not break.
Please keep the last sentence. Looking at the screen is a poor way to check what happens before 9 a.m.
What I checked, and what I didn't
The output above comes from Node 22.23 with the time zone switched between Japan and UTC. With UTC, the local column also read November 6 at 00:20, so the device-calendar form simply follows whatever the device is set to.
I did not test this on a real iPhone or Android phone together with a screen generated by Rork. If your app handles dates in several places, apply the prompt above and then try it once on your own device in the morning.
The same "edge of the record" issue shows up for month boundaries in three lines to add to a budget app's first prompt. For adding changes in small steps, see how to add features one prompt at a time.
As an indie developer, anything with a daily rollover is a spot you can't avoid. I'd start by searching your project for toISOString and seeing whether any hit is building a date key.