Skip to content

ESD-1554: accept string and null dates in Time.UnmarshalJSON - #168

Open
Phil-Browne wants to merge 5 commits into
mainfrom
fix/time-unmarshal-string-dates
Open

ESD-1554: accept string and null dates in Time.UnmarshalJSON#168
Phil-Browne wants to merge 5 commits into
mainfrom
fix/time-unmarshal-string-dates

Conversation

@Phil-Browne

@Phil-Browne Phil-Browne commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Description

Time.UnmarshalJSON only decoded JSON numbers (Unix epoch milliseconds). Any date value that arrived as a string made the unmarshal error out, and that error failed the decode of the whole response, surfacing to users as an empty resource list. The Time type backs date fields on ports, VXCs, MCRs, MVEs, IXs, service keys, and locations, so this affects every list/get path.

Note: the spec is not uniform here. The product DTOs declare these dates as non-nullable epoch-millisecond integers, so for ports, VXCs, MCRs, MVEs and service keys this is defensive broadening rather than a documented mismatch. But MegaportLocation.liveDate, behind GET /v2/locations, is a documented date-time string that the SDK decodes into Location.LiveDate *Time, so at least one mapping would have failed on the old numeric-only path. That the v2 location methods are deprecated is probably why nobody hit it. Either way this costs nothing on the unchanged numeric path.

The unmarshaller now also accepts:

  • RFC3339 / ISO 8601 date strings, with or without offset, plus date-only
  • a quoted numeric epoch (e.g. "1700000000000")
  • null / empty, mapped to the zero time

The plain numeric epoch path is unchanged, so existing responses decode exactly as before (same timezone, same precision). A single string-valued date can no longer empty a list. A quoted digit string outside the year 2000-2100 epoch-millisecond window is treated as a date rather than an epoch, so a compact date like 20260629 or 20260629010203 can't silently decode to a 1970-era or far-future timestamp. Truly unparseable values still return an error.

Adds table-driven tests for each input form plus struct-level regression tests covering null on both pointer and value Time fields.

No CHANGELOG entry: the file is organized by released version with no Unreleased section, so entries are added at release time.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Contributor Agreement

[I have read and accept the CLA]

@Phil-Browne Phil-Browne changed the title fix: accept string and null dates in Time.UnmarshalJSON ESD-1554: accept string and null dates in Time.UnmarshalJSON Jun 29, 2026
@Phil-Browne
Phil-Browne force-pushed the fix/time-unmarshal-string-dates branch 3 times, most recently from fec4379 to 120f1b3 Compare June 29, 2026 16:34
Time.UnmarshalJSON only decoded JSON numbers (Unix epoch milliseconds),
so any endpoint or environment that returns an ISO 8601 date string made
the unmarshal error out. That error failed the decode of the whole
response and surfaced to users as an empty resource list.

Accept date strings (RFC3339/ISO 8601, with or without offset, plus
date-only), a quoted numeric epoch, and null/empty values (mapped to the
zero time), so a single string-valued date can no longer empty a list.
The plain numeric epoch path is unchanged, so existing responses decode
exactly as before. A quoted digit string outside the year 2000-2100 epoch
window is treated as a date, not an epoch, so a compact date cannot
silently decode to a 1970-era or far-future timestamp. Truly unparseable
values still error.

Adds table-driven tests covering each form plus the null handling for
both pointer and value Time fields.
@Phil-Browne
Phil-Browne force-pushed the fix/time-unmarshal-string-dates branch from 120f1b3 to c6e4c72 Compare June 29, 2026 17:00
@Phil-Browne
Phil-Browne marked this pull request as ready for review July 6, 2026 23:44
Jackson-based backends emit offsets like +0000, which the RFC3339
layouts reject; add a Z0700 layout covering both fractional and
whole-second forms.
Assert the full decoded instant instead of just .Year(), which would
have passed even if month/day/time were mangled.
time.RFC3339Nano precedes it in timeStringLayouts and accepts everything
it would, so the entry never matched.
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.

1 participant