diff --git a/src/time/format.go b/src/time/format.go index cf5d6c712dba06..c82c14c20d636a 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -659,6 +659,8 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { return t.appendFormatRFC3339(b, false) case RFC3339Nano: return t.appendFormatRFC3339(b, true) + case imfFixdate: + return t.appendFormatIMFFixdate(b) default: return t.appendFormat(b, layout) } diff --git a/src/time/format_imffixdate.go b/src/time/format_imffixdate.go new file mode 100644 index 00000000000000..8e88ebecd36846 --- /dev/null +++ b/src/time/format_imffixdate.go @@ -0,0 +1,41 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time + +// imfFixdate is the IMF-fixdate layout: the format HTTP senders are required +// to generate for header fields carrying a date, defined in RFC 9110, section +// 5.6.7 as a fixed-length, single-zone subset of the Internet Message Format +// (RFC 5322). It is the value of net/http.TimeFormat, and after RFC 3339 it is +// the most widely used machine-readable layout, which is why it is +// specialized here. +// +// "GMT" is a literal in this layout, not a zone abbreviation, so formatting +// does not convert. An HTTP-date represents an instant in UTC; supplying one +// is the caller's responsibility, as net/http does. +const imfFixdate = "Mon, 02 Jan 2006 15:04:05 GMT" + +// appendFormatIMFFixdate is a specialization of appendFormat for imfFixdate. +// It formats t in its own location, exactly as the general path does. +func (t Time) appendFormatIMFFixdate(b []byte) []byte { + _, _, abs := t.locabs() + days := abs.days() + year, month, day := days.date() + hour, min, sec := abs.clock() + + b = append(b, days.weekday().String()[:3]...) + b = append(b, ',', ' ') + b = appendInt(b, day, 2) + b = append(b, ' ') + b = append(b, month.String()[:3]...) + b = append(b, ' ') + b = appendInt(b, year, 4) + b = append(b, ' ') + b = appendInt(b, hour, 2) + b = append(b, ':') + b = appendInt(b, min, 2) + b = append(b, ':') + b = appendInt(b, sec, 2) + return append(b, " GMT"...) +} diff --git a/src/time/format_test.go b/src/time/format_test.go index 2537c765968ee2..33d8af1158dd5a 100644 --- a/src/time/format_test.go +++ b/src/time/format_test.go @@ -1091,3 +1091,25 @@ func FuzzParseRFC3339(f *testing.F) { } }) } + +// TestAppendFormatIMFFixdate checks the specialized formatter for the +// IMF-fixdate layout (RFC 9110, section 5.6.7) against the general one, across +// many instants and several locations. The non-UTC cases matter: "GMT" is a +// literal in this layout, not a zone abbreviation, so the time must not be +// converted. +func TestAppendFormatIMFFixdate(t *testing.T) { + const imfFixdate = "Mon, 02 Jan 2006 15:04:05 GMT" // net/http.TimeFormat + locs := []*Location{UTC, Local, FixedZone("CEST", 2*60*60), FixedZone("X", -11*3600-1800)} + base := Date(1990, January, 1, 0, 0, 0, 0, UTC) + var got, want [64]byte + for _, loc := range locs { + for i := range 20000 { + tm := base.Add(Duration(i) * 7919 * Second).In(loc) + g := tm.AppendFormat(got[:0], imfFixdate) + w := AppendFormatAny(tm, want[:0], imfFixdate) + if !bytes.Equal(g, w) { + t.Fatalf("loc=%v t=%v AppendFormat=%q general=%q", loc, tm, g, w) + } + } + } +} diff --git a/src/time/time_test.go b/src/time/time_test.go index 5cd7471397dac3..7db53ea332a19e 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -1545,6 +1545,22 @@ func BenchmarkFormatRFC3339Nano(b *testing.B) { } } +func BenchmarkFormatIMFFixdate(b *testing.B) { + t := Unix(1265346057, 0) + for i := 0; i < b.N; i++ { + t.Format("Mon, 02 Jan 2006 15:04:05 GMT") + } +} + +// BenchmarkFormatRFC1123 uses a layout the same length as the HTTP date +// format. +func BenchmarkFormatRFC1123(b *testing.B) { + t := Unix(1265346057, 0) + for i := 0; i < b.N; i++ { + t.Format("Mon, 02 Jan 2006 15:04:05 MST") + } +} + func BenchmarkFormatNow(b *testing.B) { // Like BenchmarkFormat, but easier, because the time zone // lookup cache is optimized for the present.