mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-28 19:50:59 +00:00
tstime: add GoDuration which JSON serializes with time.Duration.String (#15726)
The encoding/json/v2 effort may end up changing the default represention of time.Duration in JSON. See https://go.dev/issue/71631 The GoDuration type allows us to explicitly use the time.Duration.String representation regardless of whether we serialize with v1 or v2 of encoding/json. Updates tailscale/corp#27502 Signed-off-by: Joe Tsai <joetsai@digital-static.net>
This commit is contained in:
parent
898cf06898
commit
aff8f1b358
@ -6,6 +6,7 @@ package tstime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -183,3 +184,40 @@ func (StdClock) AfterFunc(d time.Duration, f func()) TimerController {
|
|||||||
func (StdClock) Since(t time.Time) time.Duration {
|
func (StdClock) Since(t time.Time) time.Duration {
|
||||||
return time.Since(t)
|
return time.Since(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GoDuration is a [time.Duration] but JSON serializes with [time.Duration.String].
|
||||||
|
//
|
||||||
|
// Note that this format is specific to Go and non-standard,
|
||||||
|
// but excels in being most humanly readable compared to alternatives.
|
||||||
|
// The wider industry still lacks consensus for the representation
|
||||||
|
// of a time duration in humanly-readable text.
|
||||||
|
// See https://go.dev/issue/71631 for more discussion.
|
||||||
|
//
|
||||||
|
// Regardless of how the industry evolves into the future,
|
||||||
|
// this type explicitly uses the Go format.
|
||||||
|
type GoDuration struct{ time.Duration }
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ encoding.TextAppender = (*GoDuration)(nil)
|
||||||
|
_ encoding.TextMarshaler = (*GoDuration)(nil)
|
||||||
|
_ encoding.TextUnmarshaler = (*GoDuration)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d GoDuration) AppendText(b []byte) ([]byte, error) {
|
||||||
|
// The String method is inlineable (see https://go.dev/cl/520602),
|
||||||
|
// so this may not allocate since the string does not escape.
|
||||||
|
return append(b, d.String()...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d GoDuration) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(d.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *GoDuration) UnmarshalText(b []byte) error {
|
||||||
|
d2, err := time.ParseDuration(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Duration = d2
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
package tstime
|
package tstime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tailscale.com/util/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseDuration(t *testing.T) {
|
func TestParseDuration(t *testing.T) {
|
||||||
@ -34,3 +37,17 @@ func TestParseDuration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGoDuration(t *testing.T) {
|
||||||
|
wantDur := GoDuration{time.Hour + time.Minute + time.Second + time.Millisecond + time.Microsecond + time.Nanosecond}
|
||||||
|
gotJSON := string(must.Get(json.Marshal(wantDur)))
|
||||||
|
wantJSON := `"1h1m1.001001001s"`
|
||||||
|
if gotJSON != wantJSON {
|
||||||
|
t.Errorf("json.Marshal(%v) = %s, want %s", wantDur, gotJSON, wantJSON)
|
||||||
|
}
|
||||||
|
var gotDur GoDuration
|
||||||
|
must.Do(json.Unmarshal([]byte(wantJSON), &gotDur))
|
||||||
|
if gotDur != wantDur {
|
||||||
|
t.Errorf("json.Unmarshal(%s) = %v, want %v", wantJSON, gotDur, wantDur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user