mirror of
https://github.com/golang/go
synced 2024-11-22 23:50:03 -07:00
time: make time.Time print a valid Go string with %#v
Previously calling fmt.Sprintf("%#v", t) on a time.Time value would yield a result like: time.Time{wall:0x0, ext:63724924180, loc:(*time.Location)(nil)} which does not compile when embedded in a Go program, and does not tell you what value is represented at a glance. This change adds a GoString method that returns much more legible output: "time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.UTC)" which gives you more information about the time.Time and also can be usefully embedded in a Go program without additional work. Update Quote() to hex escape non-ASCII characters (copying logic from strconv), which makes it safer to embed them in the output of GoString(). Fixes #39034. Change-Id: Ic985bafe4e556f64e82223c643f65143c9a45c3b Reviewed-on: https://go-review.googlesource.com/c/go/+/267017 Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com>
This commit is contained in:
parent
fadad851a3
commit
bb09f8a29b
@ -205,6 +205,15 @@ that can be used to pass values between C and Go safely. See
|
||||
</p>
|
||||
</dl><!-- net/http -->
|
||||
|
||||
<dl id="time"><dt><a href="/pkg/time/">time</a></dt>
|
||||
<dd>
|
||||
<p><!-- CL 260858 -->
|
||||
time.Time now has a <a href="/pkg/time/#Time.GoString">GoString</a>
|
||||
method that will return a more useful value for times when printed with
|
||||
the <code>"%#v"</code> format specifier in the fmt package.
|
||||
</p>
|
||||
</dd>
|
||||
</dl><!-- time -->
|
||||
<p>
|
||||
TODO: complete this section
|
||||
</p>
|
||||
|
@ -477,6 +477,60 @@ func (t Time) String() string {
|
||||
return s
|
||||
}
|
||||
|
||||
// GoString implements fmt.GoStringer and formats t to be printed in Go source
|
||||
// code.
|
||||
func (t Time) GoString() string {
|
||||
buf := []byte("time.Date(")
|
||||
buf = appendInt(buf, t.Year(), 0)
|
||||
month := t.Month()
|
||||
if January <= month && month <= December {
|
||||
buf = append(buf, ", time."...)
|
||||
buf = append(buf, t.Month().String()...)
|
||||
} else {
|
||||
// It's difficult to construct a time.Time with a date outside the
|
||||
// standard range but we might as well try to handle the case.
|
||||
buf = appendInt(buf, int(month), 0)
|
||||
}
|
||||
buf = append(buf, ", "...)
|
||||
buf = appendInt(buf, t.Day(), 0)
|
||||
buf = append(buf, ", "...)
|
||||
buf = appendInt(buf, t.Hour(), 0)
|
||||
buf = append(buf, ", "...)
|
||||
buf = appendInt(buf, t.Minute(), 0)
|
||||
buf = append(buf, ", "...)
|
||||
buf = appendInt(buf, t.Second(), 0)
|
||||
buf = append(buf, ", "...)
|
||||
buf = appendInt(buf, t.Nanosecond(), 0)
|
||||
buf = append(buf, ", "...)
|
||||
switch loc := t.Location(); loc {
|
||||
case UTC, nil:
|
||||
buf = append(buf, "time.UTC"...)
|
||||
case Local:
|
||||
buf = append(buf, "time.Local"...)
|
||||
default:
|
||||
// there are several options for how we could display this, none of
|
||||
// which are great:
|
||||
//
|
||||
// - use Location(loc.name), which is not technically valid syntax
|
||||
// - use LoadLocation(loc.name), which will cause a syntax error when
|
||||
// embedded and also would require us to escape the string without
|
||||
// importing fmt or strconv
|
||||
// - try to use FixedZone, which would also require escaping the name
|
||||
// and would represent e.g. "America/Los_Angeles" daylight saving time
|
||||
// shifts inaccurately
|
||||
// - use the pointer format, which is no worse than you'd get with the
|
||||
// old fmt.Sprintf("%#v", t) format.
|
||||
//
|
||||
// Of these, Location(loc.name) is the least disruptive. This is an edge
|
||||
// case we hope not to hit too often.
|
||||
buf = append(buf, `time.Location(`...)
|
||||
buf = append(buf, []byte(quote(loc.name))...)
|
||||
buf = append(buf, `)`...)
|
||||
}
|
||||
buf = append(buf, ')')
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// Format returns a textual representation of the time value formatted
|
||||
// according to layout, which defines the format by showing how the reference
|
||||
// time, defined to be
|
||||
@ -688,14 +742,33 @@ type ParseError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// These are borrowed from unicode/utf8 and strconv and replicate behavior in
|
||||
// that package, since we can't take a dependency on either.
|
||||
const runeSelf = 0x80
|
||||
const lowerhex = "0123456789abcdef"
|
||||
|
||||
func quote(s string) string {
|
||||
buf := make([]byte, 0, len(s)+2) // +2 for surrounding quotes
|
||||
buf = append(buf, '"')
|
||||
for _, c := range s {
|
||||
if c == '"' || c == '\\' {
|
||||
buf = append(buf, '\\')
|
||||
buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes
|
||||
buf[0] = '"'
|
||||
for i, c := range s {
|
||||
if c >= runeSelf || c < ' ' {
|
||||
// This means you are asking us to parse a time.Duration or
|
||||
// time.Location with unprintable or non-ASCII characters in it.
|
||||
// We don't expect to hit this case very often. We could try to
|
||||
// reproduce strconv.Quote's behavior with full fidelity but
|
||||
// given how rarely we expect to hit these edge cases, speed and
|
||||
// conciseness are better.
|
||||
for j := 0; j < len(string(c)) && j < len(s); j++ {
|
||||
buf = append(buf, `\x`...)
|
||||
buf = append(buf, lowerhex[s[i+j]>>4])
|
||||
buf = append(buf, lowerhex[s[i+j]&0xF])
|
||||
}
|
||||
} else {
|
||||
if c == '"' || c == '\\' {
|
||||
buf = append(buf, '\\')
|
||||
}
|
||||
buf = append(buf, string(c)...)
|
||||
}
|
||||
buf = append(buf, string(c)...)
|
||||
}
|
||||
buf = append(buf, '"')
|
||||
return string(buf)
|
||||
|
@ -129,6 +129,31 @@ func TestFormat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var goStringTests = []struct {
|
||||
in Time
|
||||
want string
|
||||
}{
|
||||
{Date(2009, February, 5, 5, 0, 57, 12345600, UTC),
|
||||
"time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.UTC)"},
|
||||
{Date(2009, February, 5, 5, 0, 57, 12345600, Local),
|
||||
"time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.Local)"},
|
||||
{Date(2009, February, 5, 5, 0, 57, 12345600, FixedZone("Europe/Berlin", 3*60*60)),
|
||||
`time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.Location("Europe/Berlin"))`,
|
||||
},
|
||||
{Date(2009, February, 5, 5, 0, 57, 12345600, FixedZone("Non-ASCII character ⏰", 3*60*60)),
|
||||
`time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.Location("Non-ASCII character \xe2\x8f\xb0"))`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestGoString(t *testing.T) {
|
||||
// The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2009
|
||||
for _, tt := range goStringTests {
|
||||
if tt.in.GoString() != tt.want {
|
||||
t.Errorf("GoString (%q): got %q want %q", tt.in, tt.in.GoString(), tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// issue 12440.
|
||||
func TestFormatSingleDigits(t *testing.T) {
|
||||
time := Date(2001, 2, 3, 4, 5, 6, 700000000, UTC)
|
||||
@ -796,10 +821,13 @@ func TestQuote(t *testing.T) {
|
||||
{`abc"xyz"`, `"abc\"xyz\""`},
|
||||
{"", `""`},
|
||||
{"abc", `"abc"`},
|
||||
{`☺`, `"\xe2\x98\xba"`},
|
||||
{`☺ hello ☺ hello`, `"\xe2\x98\xba hello \xe2\x98\xba hello"`},
|
||||
{"\x04", `"\x04"`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if q := Quote(tt.s); q != tt.want {
|
||||
t.Errorf("Quote(%q) = %q, want %q", tt.s, q, tt.want)
|
||||
t.Errorf("Quote(%q) = got %q, want %q", tt.s, q, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user