Skip to content

Fix !!timestamp resolution for years 0000-0099 - #775

Closed
sarathfrancis90 wants to merge 1 commit into
nodeca:masterfrom
sarathfrancis90:fix-timestamp-low-year
Closed

Fix !!timestamp resolution for years 0000-0099#775
sarathfrancis90 wants to merge 1 commit into
nodeca:masterfrom
sarathfrancis90:fix-timestamp-low-year

Conversation

@sarathfrancis90

Copy link
Copy Markdown

While round-tripping Date values through YAML11_SCHEMA I hit a case where a date in years 1–99 throws on reload:

const d = new Date(0); d.setUTCFullYear(50, 0, 1)
load(dump(d, { schema: YAML11_SCHEMA }), { schema: YAML11_SCHEMA })
// -> throws: cannot resolve a node with !<tag:yaml.org,2002:timestamp> explicit tag

And loading a valid low-year timestamp directly silently yields a string instead of a Date:

load('0001-01-01', { schema: YAML11_SCHEMA }) // -> '0001-01-01' (string), expected a Date

Root cause: resolveYamlTimestamp builds the date with Date.UTC(year, …), which applies JavaScript's legacy two-digit-year remap (0–99 → 1900–1999). The Date is then off by ~1900 years, so the calendar-normalization guard (getUTCFullYear() !== year) rejects the value and it falls back to a plain string. During dump that same rejection forces an explicit !!timestamp tag onto the scalar, which then can't resolve on load — hence the throw.

Fix: restore the intended year with setUTCFullYear(year, month, day) before the validity check. Passing month/day re-applies the day in the context of the real year, so invalid calendar dates like 0001-02-29 are still rejected.

Tested with npm test (all green). Extended test/core/tags/timestamp.test.mjs with low-year load cases, a leap day (0004-02-29), rejection of an invalid low-year date, and a dump → load round-trip.

Date.UTC() applies JavaScript's legacy two-digit-year remap (0-99 map to
1900-1999), so resolveYamlTimestamp built the wrong year and the
normalization guard then rejected valid low four-digit years such as
0001-01-01, leaving them as plain strings. As a side effect a Date in
years 1-99 dumped as an explicit !!timestamp and threw on reload under
YAML11_SCHEMA.

Restore the intended year with setUTCFullYear() before the calendar
validity check; passing month/day re-validates the leap day against the
real year, so invalid dates (e.g. 0001-02-29) are still rejected.
@puzrin

puzrin commented Jul 23, 2026

Copy link
Copy Markdown
Member

I understand the problem, but I don't like the implementation with a duplicated check for the year subrange. IMHO, it's worth creating a helper

function makeUtcDate (year, month, day, hour = 0, minute = 0, second = 0, fraction = 0) {
  const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction))

  // Date.UTC() treats years 0..99 as 1900..1999. Restore the parsed YAML year
  // before validating calendar normalization, e.g. reject 0001-02-29.
  date.setUTCFullYear(year, month, day)

  return date
}

That's more readable and "straight".


Tests are bloated. It's worth just adding a couple of good samples to the existing one (0001-01-01, 0050-06-15T12:30:00Z) and 0001-02-29 to the existing test of rejected examples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants