Repository navigation
/import: parseDateCell emits a date cell year below 1000 unpadded (0500-01-01 → 500-01-01), so after PR #20524 a valid ISO date cell is refused per row as invalid_date, and before it a non-day was stored #20534
Description
Filing gate: ① a product defect with a named landing site and a reach:. Finding class (a). reach: is POST /api/v1/data/:object/import with a date cell naming a year from 0001 to 0999, such as 0500-01-01. Derived at source, not driven: the two code facts below are read on origin/main (after fb386074f) and at PR #20524's head 4c5740d2d. The at-tier contract review of PR #20524 found it (record 5881202199, ① item 9 and ③ item 7) and recommended filing it.
Filed by the domain:engine execution seat 1 (session_01N8TPEsoJxPsdSdNKGnNGEN, os-warren). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens
packages/rest/src/import-coerce.ts, parseDateCell, builds a date cell's value with the year as a bare number on all three date branches:
- the fast path:
${y}-${pad2(mo)}-${pad2(d)}withy = Number(ymd[1]); - the wall-clock path:
${wall.year}-…; - the
Date/new Date(s)paths:${…getUTCFullYear()}-….
A cell 0500-01-01 therefore becomes 500-01-01.
- Before PR fix(objectql)!: a date string is written in its YYYY-MM-DD form, or refused with VALIDATION_FAILED / invalid_date (#20481) #20524 (record validator: a
datefield written as a non-ISO string ("2026/07/15") answers 201 and is stored verbatim as"2026/07/15", a non-day, on memory and SQLite, because thedatearm admits anyDate.parse-readable string #20481), thedatewrite arm admitted anyDate.parse-readable string, so500-01-01was stored as written: a non-day that sorts and compares wrongly as text (the coretemporalStorageForm: thedatearm leaves a year outside 1000..9999 unpadded — over REST the epoch-ms number for 0999-06-15 counts$gt0 /$lt7 on InMemoryDriver and SQLite (correct 6 / 0); its ISO string counts 6 / 0 #20240 class). - After PR fix(objectql)!: a date string is written in its YYYY-MM-DD form, or refused with VALIDATION_FAILED / invalid_date (#20481) #20524, the
datewrite arm admits only a leadingYYYY-MM-DD(core'sisUninterpretableTemporalComparand).500-01-01is refused per row withVALIDATION_FAILED/invalid_date. The same value written directly as0500-01-01throughPOST /api/v1/data/:objectis accepted (the year range is 0001..9999 since temporal values outside the years a four-digit text or a backend holds: adatetimecomparand for year 10000 or −1 misorders on memory/SQLite and 500s on PostgreSQL; adatein year 0000 500s on PostgreSQL; adatewrite stores+010000-…verbatim #20264), so the import refuses a date the write door takes.
Years 1000..9999 are unaffected, because their year already has four digits. The datetime branches return toISOString(), which pads, so they are unaffected too.
Why
parseDateCell predates the 0001..9999 range (#20264) and the four-digit day rule, and never pads the year. pad2 exists for month and day. The year needs a four-digit pad, which the date storage rule already uses (temporalStorageForm, #20240).
Suggested shape (⛔ not a ruling)
- Pad the year to four digits on every
datebranch ofparseDateCell. - Pin
0500-01-01and0001-01-01through/importon memory and SQLite, with a year-2026 control.
Dedupe
search_issues, run by this seat in objectstack-ai/objectstack, open and closed: "import parseDateCell date cell year below 1000 emitted unpadded 500-01-01" gives 3 hits.
- record validator: a
datefield written as a non-ISO string ("2026/07/15") answers 201 and is stored verbatim as"2026/07/15", a non-day, on memory and SQLite, because thedatearm admits anyDate.parse-readable string #20481 is the write door. - temporal values outside the years a four-digit text or a backend holds: a
datetimecomparand for year 10000 or −1 misorders on memory/SQLite and 500s on PostgreSQL; adatein year 0000 500s on PostgreSQL; adatewrite stores+010000-…verbatim #20264 (closed) is the year range. - core
temporalStorageForm: thedatearm leaves a year outside 1000..9999 unpadded — over REST the epoch-ms number for 0999-06-15 counts$gt0 /$lt7 on InMemoryDriver and SQLite (correct 6 / 0); its ISO string counts 6 / 0 #20240 (closed) is coretemporalStorageForm's unpadded year, the same class in the storage rule, not in the import reader.
None is this.
Dedupe words: parseDateCell unpadded year · import date cell year below 1000 · 0500-01-01 import 500-01-01
Activity