Nakodo counts most of its limits by UTC day, and one of them by Pacific day, because that is the day the YouTube Data API's quota resets on. src/lib/time.ts is 69 lines and holds both ideas, with a comment at the top that is the whole design:
// Durations and day boundaries, in one place. Limits count by UTC day unless
// a source says otherwise (YouTube's quota day is Pacific); billing periods
// are months from the subscription's start.
The UTC half is unremarkable and heavily used: DAY_MS appears at 82 sites, daysAgo() at 11, nextUtcMidnight() at 11, startOfUtcDay() at 10. The zone-aware half has exactly two functions and, today, exactly one caller.
Finding midnight in a zone you do not control
There is no date library here. The only tool is Intl.DateTimeFormat, which can tell you what the wall clock in a zone reads at a given instant:
// Milliseconds since midnight on the wall clock in `timeZone`.
function wallClockMs(timeZone: string, d: Date): number {
const parts = format(clockFormats, timeZone, { hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" }).formatToParts(d);
const get = (type: string) => Number(parts.find((p) => p.type === type)?.value ?? 0);
return ((get("hour") * 60 + get("minute")) * 60 + get("second")) * SECOND_MS + d.getMilliseconds();
}
hourCycle: "h23" is there so that midnight reads as 00 rather than 24, which is what h24 would give you and what a default locale may pick. The milliseconds come from the Date, because formatToParts has no part for them that is reliable to parse. The formatters are memoised in two Maps keyed by zone, since constructing an Intl.DateTimeFormat is the expensive bit.
Dates use the same trick with a locale chosen for its format rather than its country:
f = new Intl.DateTimeFormat("en-CA", { timeZone, ...options });
en-CA with numeric year, month and day gives 2026-10-30, which sorts and compares as a string. That is the entire reason it is Canada.
With a wall clock in hand, the start of the day is "now minus however far into the day the wall clock says we are". Except that is wrong twice a year:
// Midnight that started today in `timeZone`. On a day the clocks change, the
// wall clock is an hour off the time elapsed, which the checks correct.
export function startOfDayIn(timeZone: string, now = new Date()): Date {
const today = dateIn(timeZone, now);
let t = now.getTime() - wallClockMs(timeZone, now);
if (dateIn(timeZone, new Date(t)) !== today) t += HOUR_MS;
else if (dateIn(timeZone, new Date(t - 1)) === today) t -= HOUR_MS;
return new Date(t);
}
On a day the clocks go forward, 13 hours of wall clock is 12 hours of elapsed time, so subtracting the wall clock lands in yesterday: the first check notices the date is wrong and adds an hour. On a day the clocks go back, it lands an hour into today, and the giveaway is that the millisecond before it is also today: the second check subtracts an hour. Both corrections are an hour because daylight saving is an hour.
The other function never makes that assumption:
export function nextMidnightIn(timeZone: string, now = new Date()): Date {
const today = dateIn(timeZone, now);
let t = new Date(now.getTime() + HOUR_MS);
while (dateIn(timeZone, t) === today) t = new Date(t.getTime() + HOUR_MS);
// t is in the new day's first hour: step back to midnight.
return new Date(t.getTime() - wallClockMs(timeZone, t));
}
It steps forward an hour at a time until the date changes, then subtracts the wall clock at that new instant, which is correct whatever the offset did on the way. Two functions in one file, written on the same afternoon, and only one of them is shift-size agnostic.
How many zones punish the assumption
Measured with Node's own ICU data, over 2026, by sampling each zone's offset on the 15th of each month and looking at the spread:
const zones = Intl.supportedValuesOf("timeZone");
const off = (tz, d) => {
const p = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "longOffset" })
.formatToParts(d).find((x) => x.type === "timeZoneName").value;
const m = p.match(/GMT([+-])(\d{2}):(\d{2})/);
return m ? (m[1] === "-" ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0;
};
const odd = [];
for (const tz of zones) {
const offs = new Set();
for (let m = 0; m < 12; m++) offs.add(off(tz, new Date(Date.UTC(2026, m, 15))));
const a = [...offs].sort((x, y) => x - y);
if (a.length > 1 && (a.at(-1) - a[0]) % 60 !== 0) odd.push([tz, a.at(-1) - a[0]]);
}
console.log("total zones:", zones.length, "| non-hour DST:", odd.length, odd);
total zones: 418 | non-hour DST: 1 [ [ 'Australia/Lord_Howe', 30 ] ]
One. Lord Howe Island, a few hundred residents in the Tasman Sea, moves its clocks by half an hour. The 45-minute zones people remember (Kathmandu at +05:45, Chatham at +12:45, Eucla at +08:45) are only awkward offsets, not awkward shifts, and awkward offsets cost this code nothing because it never does arithmetic on an offset. Chatham does keep daylight saving, and it keeps it in whole hours.
Running the two functions over that one zone's transition days shows the disagreement:
-- ordinary day --
Europe/London now 2026-10-30, 12:00:00 start 2026-10-30, 00:00:00 next 2026-10-31, 00:00:00 len 24.00h
Asia/Kathmandu now 2026-10-30, 17:45:00 start 2026-10-30, 00:00:00 next 2026-10-31, 00:00:00 len 24.00h
-- US spring forward 8 Mar 2026 --
America/New_York now 2026-03-08, 14:00:00 start 2026-03-08, 00:00:00 next 2026-03-09, 00:00:00 len 23.00h
-- UK clocks back 25 Oct 2026 --
Europe/London now 2026-10-25, 12:00:00 start 2026-10-25, 00:00:00 next 2026-10-26, 00:00:00 len 25.00h
-- Lord Howe: DST starts 4 Oct 2026, ends 5 Apr 2026 --
Australia/Lord_Howe now 2026-10-04, 17:00:00 start 2026-10-04, 00:30:00 next 2026-10-05, 00:00:00 len 23.00h
Australia/Lord_Howe now 2026-04-05, 16:30:00 start 2026-04-04, 23:30:00 next 2026-04-06, 00:00:00 len 25.00h
Australia/Lord_Howe now 2026-10-05, 17:00:00 start 2026-10-05, 00:00:00 next 2026-10-06, 00:00:00 len 24.00h
Every whole-hour zone is exact, including the 45-minute offsets and both directions of a one-hour change. On Lord Howe's two transition days the correction overshoots by the half hour it did not need: the day is reported as starting at 00:30 in October and at 23:30 the previous evening in April, while nextMidnightIn gets both right. A counter keyed on that "day" would miss its first 30 minutes once a year and double-count 30 minutes once a year. The day after, both functions agree again.
Why this is a curiosity and not an incident
The zone-aware functions have one caller:
// src/lib/youtube/quota.ts
export const pacificDay = (date = new Date()) => dateIn(YOUTUBE_TIME_ZONE, date);
// When today's quota day began: the last Pacific midnight.
export const quotaDayStart = (now = new Date()) => startOfDayIn(YOUTUBE_TIME_ZONE, now);
// Five minutes after the next Pacific midnight, when deferred work can run
// again.
export const nextQuotaReset = (now = new Date()) =>
new Date(nextMidnightIn(YOUTUBE_TIME_ZONE, now).getTime() + 5 * MINUTE_MS);
YOUTUBE_TIME_ZONE is "America/Los_Angeles", which shifts by a whole hour and has done for every year this code can be asked about. Everything else in the app counts by UTC day, which has no transitions at all. So the bug is latent: real, reproducible in six lines, and currently unreachable.
The test file says the same thing in a different way. There are six tests, 37 lines, and five of them pin the corrections:
test("start of day when the clocks go back", () => {
// 1 Nov 2026: midnight is PDT (07:00Z), 05:00 PST is 13:00Z, six hours later.
assert.equal(startOfDayIn(LA, new Date("2026-11-01T13:00:00Z")).toISOString(), "2026-11-01T07:00:00.000Z");
});
$ npx tsx --test src/lib/time.test.ts
ℹ tests 6
ℹ pass 6
ℹ fail 0
Every one of them passes LA. The tests encode the correction and the assumption behind it, which is exactly what tests written beside an implementation tend to do.
What I would change
- Make
startOfDayInwork likenextMidnightIn: find the boundary by probing, not by assuming its size. Something like "step back an hour at a time until the date changes, then add the wall clock at that instant" has no magic number in it. - Or state the assumption in the signature. A function named
startOfDayIn(timeZone)promises more than a function that only ever getsAmerica/Los_Angeles. -
SECOND_MSis exported and used nowhere outside this file. The other three duration constants have 82, 26 and 16 call sites between them.
The user-facing end of all this date arithmetic is public: the retention windows it enforces are written out in the privacy policy, the plan limits it counts against are on pricing, and how it works describes the daily rhythm those counters exist to keep.
Top comments (0)