Skip to content

Commit 45ba267

Browse files
committed
fix(core): readsAsWallClock answers from the spec's ClockTimeValueSchema
The private wall-clock regex in temporal-comparand.ts was a second spelling of the time-of-day set the spec's ClockTimeValueSchema defines. It now asks that schema, so one rule answers the comparand door, the record validator's time arm and the spec's time default gate. Pins: 23:59:59.999 read, 24:00 and 10:00Z refused, through isUninterpretableTemporalComparand. Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6c96b37 commit 45ba267

2 files changed

Lines changed: 27 additions & 4 deletions

File tree

‎packages/core/src/utils/temporal-comparand.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,21 @@ describe('[#20480] isUninterpretableTemporalComparand — a time column\'s insta
372372
}
373373
});
374374
});
375+
376+
// [#20771] The bare-wall-clock half of the `time` rule is the spec's
377+
// `ClockTimeValueSchema` (`@objectstack/spec/data`), not a second regex in
378+
// core: the private `readsAsWallClock` asks that schema. These are the
379+
// boundary cases on each side, asserted through the one public door that
380+
// reaches `readsAsWallClock`. None of them is an ISO instant, so the
381+
// instant half of the rule cannot rescue one: the verdict is the wall-clock
382+
// half's alone.
383+
describe('[#20771] isUninterpretableTemporalComparand — the wall-clock half is the spec\'s time-of-day rule', () => {
384+
it('reads the last wall clock of the day, to the fraction', () => {
385+
expect(isUninterpretableTemporalComparand('time', '23:59:59.999')).toBe(false);
386+
});
387+
388+
it('refuses the hour past the last one, and a time of day with a zone', () => {
389+
expect(isUninterpretableTemporalComparand('time', '24:00')).toBe(true);
390+
expect(isUninterpretableTemporalComparand('time', '10:00Z')).toBe(true);
391+
});
392+
});

‎packages/core/src/utils/temporal-comparand.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
* test), so it is left alone too.
135135
*/
136136

137-
import { classifyFilterToken } from '@objectstack/spec/data';
137+
import { ClockTimeValueSchema, classifyFilterToken } from '@objectstack/spec/data';
138138
import { isOutsideTemporalYearRange, temporalStorageForm } from './temporal-storage-form.js';
139139

140140
/** Which temporal storage rule a declared field takes. */
@@ -246,11 +246,16 @@ function readsAsCalendarDay(s: string): boolean {
246246
* `temporalStorageForm`'s `time` reading: a bare wall clock whose components are in
247247
* range. Out-of-range (`25:00`) is uninterpretable — the rule it mirrors
248248
* returns such a value untouched rather than wrapping it.
249+
*
250+
* [#20771] The set is the spec's: `ClockTimeValueSchema`, the `time` stored
251+
* form — `HH:MM[:SS[.fraction]]`, hours 00..23, minutes and seconds 00..59, no
252+
* time zone (ADR-0053 D-C1). This function asks that schema rather than
253+
* spelling a second regex, so the comparand door, the record validator's
254+
* `time` arm and the spec's `time` default gate answer from one rule and
255+
* cannot drift apart on an edit to either side.
249256
*/
250257
function readsAsWallClock(s: string): boolean {
251-
const m = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?$/.exec(s);
252-
if (!m) return false;
253-
return Number(m[1]) <= 23 && Number(m[2]) <= 59 && Number(m[3] ?? '0') <= 59;
258+
return ClockTimeValueSchema.safeParse(s).success;
254259
}
255260

256261
/**

0 commit comments

Comments
 (0)