Skip to content

Commit 8c7a08e

Browse files
hotlongclaude
andcommitted
chore(changeset): /import date cells read only in ISO 8601 or the export shape (#20534)
Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9b31e7b commit 8c7a08e

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
'@objectstack/rest': minor
3+
---
4+
5+
fix(rest): `POST /api/v1/data/:object/import` reads a `date`, `datetime` or `time` cell only in ISO 8601 or the export's own `YYYY-MM-DD HH:mm:ss`, on a calendar day that exists, and keeps a `date`'s year at four digits (#20534)
6+
7+
Clause-②: no (narrowing)
8+
9+
**BREAKING for callers of the import door.** A text cell for a `date`,
10+
`datetime` or `time` field is now read only in one of these spellings, after
11+
trimming:
12+
13+
- `YYYY-MM-DD`;
14+
- `YYYY-MM-DDTHH:MM[:SS[.fraction]]`, then `Z`, a `+HH:MM` / `-HH:MM` /
15+
`+HHMM` offset, or nothing (a wall clock, read in the importing user's
16+
business timezone, as before);
17+
- `YYYY-MM-DD HH:MM[:SS[.fraction]]` with no offset, which is what the export
18+
writes for a `datetime` cell;
19+
- for a `time` field, also a bare `HH:MM` / `HH:MM:SS`.
20+
21+
The day must exist. Every other cell is that row's `invalid_date` error, with
22+
the importer's existing sentence ("is not a valid date" / "datetime" /
23+
"time"). The reader used to hand such a cell to the JavaScript date parser,
24+
which read it in the SERVER PROCESS's timezone and month-first, and rolled an
25+
impossible day into the next month, so the import reported success and stored
26+
a different value. For each shape, change the cell FROM the refused spelling
27+
TO the ISO one:
28+
29+
- **An impossible day.** FROM `2026-02-30`, `2026-02-29`, `2026-04-31` in any
30+
spelling (a `datetime` `2026-02-30` was stored as 2 March, and so was a
31+
`date` written `2026-02-30T10:00:00Z`) TO the day you mean. Nothing is
32+
rolled over.
33+
- **A locale or prose date.** FROM `07/15/2026`, `07/15/2026 10:00`,
34+
`07/08/2026`, `15 July 2026`, `Jul 15 2026 10:00` (stored hours apart on a
35+
New York and a Shanghai server, a `date` a day apart, and `07/08/2026` read
36+
as 8 July) TO `2026-07-15`, `2026-07-15 10:00`, `2026-07-08` or
37+
`2026-08-07`. No timezone and no field order is guessed. Converting a
38+
spreadsheet column to ISO (in Excel, the cell format `yyyy-mm-dd` or
39+
`yyyy-mm-dd hh:mm:ss`) before export is the fix.
40+
- **A year-first spelling that is not ISO.** FROM `2026/7/15`, `2026/07/15`,
41+
`2026-7-15`, `2026/07/15 10:00` or a one-digit hour (`2026-07-15 9:00`) TO
42+
the zero-padded, dash-separated `2026-07-15`, `2026-07-15 10:00`,
43+
`2026-07-15 09:00`. These used to import as the day they denote; they are
44+
refused with the rest because the reader accepts ISO 8601 and the export's
45+
own shape and nothing else.
46+
- **A zone after a space, or lower-case `t` / `z`.** FROM
47+
`2026-07-15 10:00Z`, `2026-07-15 10:00:00+08:00`, `2026-07-15t10:00:00z` TO
48+
`2026-07-15T10:00Z`, `2026-07-15T10:00:00+08:00`, `2026-07-15T10:00:00Z`,
49+
the spellings the create and update doors take.
50+
- **A zone-naive `24:00`.** FROM `2026-07-15 24:00` (read in the server's
51+
zone) TO `2026-07-16 00:00`. `2026-07-15T24:00:00Z`, which names its
52+
instant, reads as before.
53+
- **A number, reduced or expanded forms.** FROM a JSON number such as `2026`
54+
or an Excel serial, `2026`, `2026-07`, `+002026-07-15` TO `2026-01-01`,
55+
`2026-07-01`, `2026-07-15`.
56+
57+
**The year keeps four digits.** A `date` cell for a year from 0001 to 0999
58+
(`0500-01-01`) used to leave the reader as `500-01-01`, which the write door
59+
refuses, so the import refused a day the create door takes. It is stored as
60+
written now. A bare day read into a `datetime` field in years 0001 to 0099
61+
(`0050-01-01`) used to be stored in the 1900s (`1950-01-01T00:00:00.000Z`); it
62+
is stored in its own year now.
63+
64+
**What is not affected.** Every ISO 8601 cell and every export-shape cell on
65+
a real day reads exactly as before, including a zone-naive cell read in the
66+
business timezone and an offset-bearing cell honoured as written. An xlsx
67+
cell that Excel stores as a date is unaffected: it reaches the reader as the
68+
export shape. The dry run answers the same verdicts as the real write. A
69+
refused cell fails only its own row, and the rest of the batch imports as
70+
before. The same narrowing applies to the exported `coerceRow` helper.
71+
72+
**If you are refused.** The row's result carries `code: 'invalid_date'` and
73+
quotes the cell, so the file can be corrected and re-imported.
74+
75+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable is removed, renamed or reshaped: no spec key, no export, no stored row. The import door's cell reader accepts fewer spellings of a date inside an imported file, which is data, not metadata, so `objectstack migrate meta` has nothing to reach. The row's invalid_date error quotes the refused cell, and the repair is to write the date in ISO 8601. -->

0 commit comments

Comments
 (0)