Notes › Life Essays › Add One Month to January 31: Only Days, Hours, Minutes, Seconds

Add One Month to January 31: Only Days, Hours, Minutes, Seconds

28 Days or 29: This Page Will Not Choose for You

On 2026-01-31 I wanted the date one month later, and my hand reached, out of habit, for the field where the month goes. There is no such field. The four input boxes accept days, hours, minutes and seconds, and nothing else; month and year are not units this tool can add, and the only arithmetic in the whole template is the millisecond shift written on line 169: (days * 86400 + hours * 3600 + mins * 60 + secs) * 1000. So "add one month to January 31" turns into a conversion I have to finish before touching the keyboard. Enter 28 days and the landing point is 2026-02-28, a Saturday, with timestamp 1772208000. Enter 29 days and the landing point is 2026-03-01, a Sunday, with timestamp 1772294400. Both answers come from the same page, they sit 86400 seconds apart, and that distance is exactly one day. Nothing on the screen says which of the two should be called one month, so the choice stays outside the tool.

No Month Input Outside the Four Boxes

Read the template once and the answer is plain. getMonth and getFullYear appear only on line 104 and line 140, both inside the functions that format a result, and both of them read the date instead of writing it. There is no calculate button and no clear button either: the page ships exactly 2 <button> elements and both of them only switch mode. An empty box and a box full of non-numeric text are treated as 0, so entering 0 days is the identity, which is why 2026-01-31 00:00:00 comes back with timestamp 1769788800. Entering 31 days moves the same base to 2026-03-03 and shifts 2678400 seconds. Neither behaviour matches what people mean when they ask for one month, and the disagreement gets louder the shorter the month is.

Base 2026-01-312026-02-282026-03-012026-03-03+28 days+29 days+31 daysdiff 86400

Fig. 1: the same start, January 31, with every bar scaled by the seconds it shifted.

Across 108 Scans No Fixed Day Count Works as a Month

I did not want to stop there, so I swept the whole grid: 12 months, day numbers 29/30/31, and offsets 28/30/31, which adds up to 108 calls. In 2026 the grid offers 36 base dates, and 7 of them do not exist at the JS layer at all: 2/29, 2/30, 2/31, 4/31, 6/31, 9/31 and 11/31 each roll onto 3/1, 3/2, 3/3, 5/1, 7/1, 10/1 and 12/1. Of the 29 dates that really exist, a fixed 28 days hits the same day of the next month 0 times, 30 days hits it 8 times and 31 days hits it 14 times. Repeat the sweep on the leap year 2028, where 30 base dates are usable, and the picture hardly changes: 28 days still hits 0 times, 30 days 8 times, 31 days 15 times.

Offset2026 hits2028 hits
28 days0 / 290 / 30
30 days8 / 298 / 30
31 days14 / 2915 / 30

The best fixed offset aligns only 15/30 of the cases, and across 12 months there is no single day count that carries the whole year.

Subtraction and Leap Days Only Know Days Too

  • 2026-03-01 minus 1 day gives 2026-02-28; 2026-01-01 minus 1 day gives 2025-12-31, timestamp 1767110400.
  • 2026-03-31 minus 30 days only gets back to 2026-03-01, and 31 days are needed to reach 2026-02-28.
  • 1970-01-01 minus 1 day gives 1969-12-31 00:00:00, timestamp -115200.
  • 2028-02-29 plus 365 days gives 2029-02-28, which is 31536000 seconds; plus 366 days gives 2029-03-01; plus 1461 days gives 2032-02-29; plus 2921 days gives 2036-02-28. Whether a leap year wants 365 or 366 is never hinted at anywhere on the page, so the year you start from decides the answer you get.

Bad Dates Do Not Raise an Error: 17 Malformed Strings Read Back Empty

Expecting a warning does not work either. I typed 17 malformed strings into Chrome, and the set includes 2026-02-30T00:00, 2026-13-01T00:00, 2026-01-32T00:00, 2026-01-31T25:00 and 13/1/2026. Read back through value, every one of them came as an empty string, the seven flags on validity went false together, and the page took the empty branch written on lines 154–158. What the screen shows is a fresh empty box, nothing more, and no message anywhere. The function layer behaves differently: new Date("2026-02-30T00:00") first rolls the date onto 2026-03-02, then one more day of arithmetic prints 2026-03-03 00:00:00, timestamp 1772467200. The same wrong input can therefore be emptied by the control and shifted by the code, and neither path tells you anything.

The warning on line 164 has exactly one reachable route in a real browser, and it is the five or six digit year. The control leaves 10000-01-01T00:00 and 275760-09-13T00:00 as they were typed, and that sample set holds 8 members; new Date() marks all 8 of them Invalid, while the largest year string that still parses measured 9999. Erasing a bad string into an empty value and raising an error on the page are two different events, and here only the first one ever happens.

The Result Is Never Checked: 99979547 Days Render as NaN

Nothing after line 170 calls isNaN. With the base 2026-01-01T00:00 the largest forward value that still resolves is 99979546 days, landing on 275760-09-13 00:00:00. One day further, 99979547 days renders as NaN-NaN-NaN NaN:NaN:NaN, the timestamp field prints NaN, the weekday row stays blank, the result area flips from display:none to visible, and the elapsed line reads 已过 NaN 天 NaN 小时, which spells out NaN days and NaN hours. The ceiling travels with the base as well: after switching it to 2026-06-15T10:30 only 99979380 days are still valid and 99979381 already prints NaN, while the negative side accepts 100020453 days (-271821-04-21 00:05:43). Front and back differ by 40907 days, and the reason is that the Date object in V8 covers only ±8640000000000000 milliseconds; anything past that boundary is not a date error but a formatting accident the page is happy to display.

The Difference Excludes the First Day and Floors Every Unit

The gap between two dates is computed on lines 224–235, and the first day is not counted in it; every unit is floored on the way out. Between 2026-01-01 and 2026-01-31 the page reports 30 days / 720 hours / 43200 minutes / 2592000 seconds, although counting both endpoints gives 31 days. From 1/1 to 3/1 of 2026 the answer is 59 days, and in 2028 the same pair gives 60 days. Two timestamps inside the same minute return zero everywhere: if B holds 2026-01-01T00:00:00.500, those 500 milliseconds are dropped on line 227 and the four boxes read 0. A difference of 1 second in the reverse direction prints as -0 天 0 时 0 分 1 秒; the boxes then show -0 days / -0 hours / -0 minutes / -1 seconds, while the formula line 时间差 = 0天 0时 0分 1秒 carries no sign at all, which is how a negative gap gets presented as a positive one.

Common Questions

Q: "One month" — is it 28 days or 29?

It depends on where you want the answer to stop. To stay at the end of the month, enter 28 days and take 2026-02-28. To step into the 1st of the next month, enter 29 days and take 2026-03-01. This page returns both and labels neither as the correct one, and they sit 86400 seconds apart. Change to another month end and the answer drifts again to 30 days or 31 days, which is the pattern the hit table earlier already lays out.

Q: Why is there no hint when I mistype a date?

Because the malformed string is flattened into an empty value inside the control before any check runs, so the page only walks the empty branch on lines 154–158 and shows an empty box again; that is not an error, and it is not a success either. The message about an invalid start time is reached only by the 8 samples whose year has five or six digits. All of my evidence comes from reading value back, and the path where a person types one character at a time on a keyboard was not sampled on its own.

Q: Can I lay out all 12 months at once?

No. The page has no input unit for month or year and no batch list, and it holds 0 <table> elements and 0 <select> elements. Across the 108 scans the best fixed offset reached only 15/30, so aligning month by month means converting each month with its own length and then typing that result into the day box on the time add and subtract page.

My cheat sheet has three lines: a small month has 30 days, a large month has 31 days, and February has 28 or 29 days. The page will not do that conversion for me, but it computes both plus 28 days and plus 29 days cleanly, and the landing date is mine to pick.

📎 Related articles

Landscape Loses 25% And Portrait 43.75%: A Cell-By-Cell Geometry Audit Of 7 Collage Templates 2026-10-28 A 5% White Watermark On A Light Base Adds Nothing: The PNG Exports At 4,782 Bytes, Identical To The Base Image 2026-10-28 How many characters fit on a copybook page: the default A4 gives 20, not 100 ÷ 4 = 25 2026-10-27 A day on both the weekend list and the make-up list: which rule the workday tool obeys 2026-10-27 A Countdown Day Is 86400 Seconds, So DST Can Steal a Day 2026-10-27