Skip to content

Add MarshalText to types.Date - #162

Open
jusabe-kreditz wants to merge 1 commit into
oapi-codegen:mainfrom
jusabe-kreditz:fix-date-marshaltext
Open

jusabe-kreditz wants to merge 1 commit into
oapi-codegen:mainfrom
jusabe-kreditz:fix-date-marshaltext

Conversation

@jusabe-kreditz

Copy link
Copy Markdown

Problem

types.Date defines UnmarshalText, which parses the DateFormat layout (2006-01-02), but it defines no MarshalText. It therefore inherits time.Time's, which emits RFC 3339. The two halves of encoding.TextMarshaler / encoding.TextUnmarshaler disagree, so any codec that pairs them writes a Date it cannot read back.

encoding/xml shows it with no third-party dependency:

type body struct {
	XMLName   xml.Name   `xml:"body"`
	DateField types.Date `xml:"date"`
}

b, _ := xml.Marshal(body{DateField: types.Date{Time: time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC)}})
// <body><date>2019-04-01T00:00:00Z</date></body>

var out body
err := xml.Unmarshal(b, &out)
// parsing time "2019-04-01T00:00:00Z": extra text: "T00:00:00Z"

The same thing happens in any other text-based encoder. I hit it originally through a DynamoDB mapper (guregu/dynamo), which selects TextMarshaler to encode and TextUnmarshaler to decode: rows written with a date field could never be read back.

Fix

Add MarshalText, formatting with DateFormat so it agrees with MarshalJSON, String and UnmarshalText. This is also the method set types.Duration already carries (MarshalJSON/UnmarshalJSON, MarshalText/UnmarshalText, Bind, String) — Date was missing exactly one method from it.

Relationship to #58

#58 fixed the XML symptom with MarshalXML/UnmarshalXML (42 lines plus an encoding/xml import) and was closed by its author without review. Fixing MarshalText resolves that round trip at the root, in three lines, with no new import — and fixes every other text-based encoder at the same time. Tests here cover the XML case so that behaviour stays pinned.

Compatibility

This is an observable behaviour change for anyone relying on Date emitting RFC 3339 through a text encoder. I'd argue that output was never usable, since UnmarshalText rejects it, but flagging it explicitly so it's a deliberate call rather than a surprise. JSON output is unaffected.

Testing

  • TestDate_MarshalText, TestDate_TextRoundTrip, TestDate_XMLRoundTrip added.
  • Full module suite passes (go test ./...); no existing test depended on the RFC 3339 text output.
  • golangci-lint v2.12.2 (the version CI pins) reports 0 issues on types/.

@jusabe-kreditz
jusabe-kreditz requested a review from a team as a code owner September 14, 2026 12:22
Date defines UnmarshalText, which parses the "2006-01-02" DateFormat, but
has no MarshalText, so it inherits time.Time's RFC 3339 one. Every codec
that pairs encoding.TextMarshaler with encoding.TextUnmarshaler therefore
writes a Date it cannot read back:

    xml.Marshal   -> <date>2019-04-01T00:00:00Z</date>
    xml.Unmarshal -> parsing time "2019-04-01T00:00:00Z": extra text: "T00:00:00Z"

Format with DateFormat instead, matching MarshalJSON, String and
UnmarshalText, and matching the method set types.Duration already carries.

This also resolves the XML round trip that oapi-codegen#58 addressed with MarshalXML
and UnmarshalXML, without needing the encoding/xml import.

Co-Authored-By: Claude Opus 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

None yet

Development

Successfully merging this pull request may close these issues.

1 participant