Skip to content

fix(type): parse string.date.epoch.parse input as milliseconds - #1669

Merged
ssalbdivad merged 1 commit into
arktypeio:mainfrom
breken-ai:fix/epoch-parse-timestamp
Oct 1, 2026
Merged

ssalbdivad merged 1 commit into
arktypeio:mainfrom
breken-ai:fix/epoch-parse-timestamp

Conversation

@breken-ai

Copy link
Copy Markdown
Contributor

string.date.epoch.parse passes the validated integer string straight to new Date(). With a string argument, Date runs its date-string parser instead of reading the value as a timestamp. So the keyword returns the wrong date, or an Invalid Date, for input it has just accepted:

const parseEpoch = type("string.date.epoch.parse")

parseEpoch("0")             // 2000-01-01T00:00 local time (expected 1970-01-01T00:00:00.000Z)
parseEpoch("-5")            // 2001-05-01 local time
parseEpoch("1700000000000") // Invalid Date (expected 2023-11-14T22:13:20.000Z)

This means something like type({ createdAt: "string.date.epoch.parse" }) on a query string or CSV column accepts the value and then hands the caller a date that is wrong or NaN, with no error. The root string.date.epoch validator is already correct: it parses the string as a number and checks it against number.epoch. Only the morph skipped that step.

Fix: convert the string with Number.parseInt before building the Date (the input is already validated as a well-formed integer string in the safe epoch range). One line in ark/type/keywords/string.ts.

Test: added string.date.epoch.parse to ark/type/__tests__/keywords/date.test.ts. It covers "0", a current millisecond timestamp, a negative timestamp and a non-integer rejection. The dates are compared as ISO strings, so the test doesn't depend on the host timezone.

  • Before the fix: 1 failing ('2000-01-01T05:00:00.000Z' vs '1970-01-01T00:00:00.000Z')
  • After the fix: 4/4 passing in date.test.ts

Checklist:

  • Code is up-to-date with the main branch (based on 42c17df)
  • You've successfully run pnpm prChecks locally: I ran part of it. pnpm test passes (1752 passing), tsc is clean, and prettier and eslint --max-warnings=0 pass on the changed files. I did not run the benches, buildRepo or testTsVersions.
  • There are new or updated unit tests validating the change

I used an AI coding assistant to find and fix this, and I checked the change and the before/after test results myself.

The morph passed the validated integer string straight to new Date(),
which treats it as a date string instead of a timestamp. "0" became
2000-01-01 in local time and "1700000000000" became an Invalid Date.

Convert the string to a number first so it is read as a Unix timestamp
in milliseconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the complete diff at 0dcd3da was reviewed: a one-line fix to the string.date.epoch.parse morph plus a regression test.

  • string.date.epoch.parse now reads its input as milliseconds — ark/type/keywords/string.ts:306 changes new Date(s) to new Date(Number.parseInt(s)), so the already-validated integer string is interpreted as a Unix timestamp rather than handed to Date's string parser (which returned a wrong or Invalid date for accepted input).
  • Regression test added — ark/type/__tests__/keywords/date.test.ts:36 pins "0", a millisecond timestamp, and a negative timestamp to exact ISO strings, and rejects "1.5".

I confirmed the fix is correct and the test is genuine, not theatre:

  • The input is validated by epochRoot (stringInteger.root narrowed through number.epoch), so by the time the morph runs the string is a well-formed integer in the safe timestamp range [-8_640_000_000_000_000, 8_640_000_000_000_000] — Number.parseInt cannot truncate or overflow here, matching the identical parse already done in the epochRoot narrow at string.ts:283.
  • Reverting only string.ts to the base makes the new test fail ('2000-01-01T05:00:00.000Z' vs '1970-01-01T00:00:00.000Z'); restored, all four keywords/date tests pass. prettier --check is clean on both changed files.

The behavior change is a bug fix (the keyword's documented contract is a Unix timestamp), so no doc update is needed; the release PR can carry the changelog note.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@ssalbdivad ssalbdivad left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, thanks for the fix!

@ssalbdivad
ssalbdivad merged commit 14ef64c into arktypeio:main Oct 1, 2026
7 checks passed
@ssalbdivad ssalbdivad mentioned this pull request Oct 1, 2026
4 tasks done
ssalbdivad added a commit that referenced this pull request Oct 1, 2026
Bumps all publishable packages and documents the changes merged since 2.2.6:

- fix `this[]` in self-referential object types (#1618, fixes #1406,
  @yharaskrik)
- parse string.date.epoch.parse input as milliseconds (#1669, @breken-ai)
- keep uppercase UUIDs valid in string.uuid JSON Schema (#1670,
  @breken-ai)
- render nested bigints correctly in error messages (#1626, fixes #1477,
  @chatman-media)
- release unused parse ids from the global registry (#1673, fixes #1584
  for equivalent types, @isaacwasserman)
- fix crashes on Hermes and Turbopack (#1674, fixes #1645 and #1643)

#1590 only adds tests, so it has no changelog entry.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done (merged or closed)

Development

Successfully merging this pull request may close these issues.

2 participants