The calendar turned 31 pages but the first box says 30
One entry counts down from 2026-03-01 00:00 to 2026-04-01 00:00 on a machine whose zone is America/New_York. The calendar sits 31 days apart, yet the first box on the page reads 30 days 23:00:00, and that 23 in the second box is hard to look at. The first guess was bad arithmetic; reading the code shows two definitions of a day booked into one ledger.
The front end is only those 181 template lines and 7,374 bytes, with five lines of arithmetic: line 121 takes the milliseconds of c.target - now, line 128 floors the millisecond place, line 129 runs days = Math.floor(totalSec / 86400) where the divisor is a hard-coded literal 86400, and lines 130-132 spread the remainder into hours, minutes and seconds. There is no round, ceil or toFixed, every step truncates toward zero, and this conversion reruns once per second on the countdown timer page.
Spread out, 2,674,800,000 milliseconds is 743 real hours, and cutting it 86400 seconds at a time yields only 30 whole blocks with 82800 seconds left over, read as 23 hours. The same pair of dates in the DST-free Asia/Shanghai zone is 2,678,400,000 milliseconds and 31 days 00:00. What is lost is a single hour, and the floor turns that into a missing day.
Only zones that observe DST drift apart
The groups below are measured through the script harness (Node 24 with TZ injected, FakeDate freezing the base stamp 1793070000000): the template has no time zone or DST branch anywhere, so the difference comes from the IANA rules of the machine that runs it, and every region with DST is hit.
- New York 3-01 to 4-01: 31 calendar days become
30 days 23:00; London behaves the same across the same month span. - New York 11-01 to 11-03: the clock going back hands out one extra hour, 176400000 milliseconds, 49 hours, 2 calendar days become
02 days 01:00. - Sydney 3-31 to 4-30: 2595600000 milliseconds, 721 hours, 30 calendar days become
30 days 01:00. - Control pair 3-07 to 3-09: New York 169200000 milliseconds, 47 hours become
01 days 23:00; the same dates in Shanghai are 172800000 milliseconds and02 days 00:00, matching digit for digit.
Truncation is only visible at the boundary
Feed a diff into those five lines and the boundary looks like this, the same in all four zones:
| diff milliseconds | Page output |
|---|---|
| 86,399,000 | 00 days 23:59:59 |
| 86,400,000 | 01 days 00:00:00 |
When the diff is 1000 milliseconds short of a day the first box still shows zero, and round would push it to 01 days. The four boxes can never contradict one another, because lines 130-132 are a remainder split, and the harness proved days*86400 + hours*3600 + mins*60 + secs === totalSec over eight diffs. The only disagreement is between the day box and the number of pages the calendar turned.
A passed target leaves you one sentence
Line 124 reads if (diff <= 0), zero included; line 125 zeroes the four numbers, but lines 143-144 print only the green 已到达!, and those four zeros never reach the screen. A diffMs of 0, the -3155760000000 of 100 years ago, even the -1793070000000 of 1970-01-01T08:00 all give the same sentence: no minus sign, no error, and no reverse counter reading 已过期 N 天. The card keeps the 目标: line and the delete button.
Do not read the other row of zeros as an expiry signal: while the diff is between 1 and 999 milliseconds the page shows 00 days 00:00:00 with reached=NO, and a real Chrome run reproduced exactly that at diff=268 milliseconds on the moment the entry was added; only past 1000 milliseconds does it switch to 01 seconds. There is no third branch between the two, so the last second is by design one second short.
The default leaves you 1 to 3600 seconds
The target field is the <input type="datetime-local"> on line 20, not a date field, and it has no min, max or step: date and clock are forced to arrive as a pair, in 60 second steps. Typing only 2026-12-31 is sanitized into an empty string and raises the message on line 67, 请选择目标日期时间; the message on line 70, 日期格式无效, takes something like 275760-09-13T00:00 before a real browser will print it.
The first screen prefills neither 00:00 nor the current time but the next whole hour (lines 51-52 call setHours(getHours() + 1) and then zero minutes, seconds and milliseconds): on a base of 11:00:00 it prefills 2026-10-27T12:00 with 3600 seconds left, and at 23:59:59 it prefills next day 00:00 with one second left. Press Add without touching anything and the worst case on screen is 已到达!.
One timer drives 200 entries and remembers nothing
State lives only in that array on line 44: grepping the whole file for localStorage and sessionStorage returns zero hits, so a reload drops you back on line 34, 暂无倒计时,请在上方添加. Lines 98-101 run if (timer) return; before attaching setInterval(render, 1000), so one entry and 200 entries share a single handle, and the latter build is 295582 bytes; even after all three entries expire, intervals still reads 1 and clearInterval was called zero times.
Neither does error accumulate: every tick recomputes c.target - new Date() (lines 120-121) instead of counting itself down, so if the clock is pushed 86437400 milliseconds late the reading catches up in the next repaint. Nothing sounds when a target lands, though: Audio, Notification and document.title each score zero hits, so the promise of an expiry notice, 过期提醒:倒计时结束后自动提示, has no carrier.
The same 10 years folds two ways
On the calendar, 10 years is 315,619,200,000 milliseconds and prints 3653 days 00:00; 10 years folded at 365.25 days each is 315,576,000,000 milliseconds and prints 3652 days 12:00, a gap of 43,200,000 milliseconds, which is 12 hours. A 100 year span is blunter still: 36524 days against 36525 days differ by exactly 86,400,000 milliseconds, one whole day.
Of the 13 claims the template publishes about itself, measurement agrees with eight and contradicts five, and the three hardest are second-level accuracy, the expiry notice and days online: a day here is stock counted 86400 seconds to the block, not the pages the calendar tore off.
Quick answers
Q: Is that 30 days 23 hours a miscalculation?
It is a difference of definition. Line 129 spends 86400 seconds per day and counts whole blocks, while a calendar counts how many pages flipped. The two only separate where DST is observed: springs forward costs one hour, falls back hands one hour back, and the floor renders either as a one day gap.
Q: Can a passed target show a negative number or days overdue?
It cannot. Once diff <= 0 matches, the sentence on line 144, 已到达!, is what you get, so one day ago and 100 years ago look identical. The row 00 days 00:00:00 appears only inside the final 999 milliseconds before a target, and it is not an expiry flag.
Q: I only want the gap in days, or an alarm when time is up?
For a day gap, change the definition: the on-site date difference calculator counts calendar days and never carries a 23 hour tail. For a reminder, this page cannot help: it stores no list and makes no sound, so when time is up there is only that green line on screen.