Files
spencermountain-spacetime/PR.md
T
dualfroz d48b259893 fix(set): clamp hour() to 23 instead of the invalid 24
hour(n) with n >= 24 clamped n to 24, which walkTo() rejects as an invalid
hour (valid range 0-23), nulling the epoch and invalidating the object.
Clamp to 23 to match the other overflow-clamping setters (date, month).
2026-09-04 09:43:57 +02:00

2.0 KiB

Fix hour() setter clamping to an invalid hour (24)

Problem

Calling .hour(n) with n >= 24 produces an invalid spacetime whose epoch is null, instead of clamping to a valid hour:

const s = spacetime('2021-03-14 10:00:00', 'UTC')

s.hour(24).isValid()      // false
s.hour(24).epoch          // null
s.hour(25).format('iso')  // '' (empty - object is invalid)

This is inconsistent with every other overflow-clamping setter:

  • .date(40) clamps to the last day of the month,
  • .month(13) clamps to December (11),
  • .hour(-1) clamps to 0.

Only the upper bound of .hour() is broken.

Root cause

src/methods/set/set.js, in hours():

if (n >= 24) {
  n = 24
} else if (n < 0) {
  n = 0
}

Valid hours are 0-23. Clamping to 24 yields a value that src/methods/set/walk.js rejects (hour.valid: (n) => n >= 0 && n < 24), and on an invalid unit walkTo() sets s.epoch = null, invalidating the whole object. The negative branch is already correct (clamps to the valid 0); the >= 24 branch is off by one and should clamp to 23.

Fix

if (n >= 24) {
  n = 23
} else if (n < 0) {
  n = 0
}

hour(24), hour(25), hour(100) now all clamp to 23:00 on the same day, matching the behavior of the other setters.

Test

Added a test to test/set.test.js (next to the existing month/date clamp test) asserting that out-of-range hours clamp to 23, keep the object valid, keep the same day, and that negatives still clamp to 0.

Counterfactual (revert n = 23 back to n = 24):

not ok - hour(24) clamps to 23
  operator: equal
  expected: 23
  actual:   0

(the invalid object reports hour 0).

Gate

  • npm test (tape) run against src (the default dev target of test/lib/index.js): the new test passes; the full suite shows only 18 pre-existing DST/timezone failures (europe/london, atlantic/madeira, atlantic/azores, some date-dependent "dst-now") that are present verbatim without this change and are unrelated to it.
  • npm run lint (eslint ./src): exit 0.