return x
case stringptr:
return KindString
- case timeLocation:
+ case timeLocation, timeTime:
return KindTime
case groupptr:
return KindGroup
return Value{num: u, any: KindBool}
}
-// Unexported version of *time.Location, just so we can store *time.Locations in
-// Values. (No user-provided value has this type.)
-type timeLocation *time.Location
+type (
+ // Unexported version of *time.Location, just so we can store *time.Locations in
+ // Values. (No user-provided value has this type.)
+ timeLocation *time.Location
+
+ // timeTime is for times where UnixNano is undefined.
+ timeTime time.Time
+)
// TimeValue returns a [Value] for a [time.Time].
// It discards the monotonic portion.
// mistaken for any other Value, time.Time or otherwise.
return Value{any: timeLocation(nil)}
}
- return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
+ nsec := v.UnixNano()
+ t := time.Unix(0, nsec)
+ if v.Equal(t) {
+ // UnixNano correctly represents the time, so use a zero-alloc representation.
+ return Value{num: uint64(nsec), any: timeLocation(v.Location())}
+ }
+ // Fall back to the general form.
+ // Strip the monotonic portion to match the other representation.
+ return Value{any: timeTime(v.Round(0))}
}
// DurationValue returns a [Value] for a [time.Duration].
return v.time()
}
+// See TimeValue to understand how times are represented.
func (v Value) time() time.Time {
- loc := v.any.(timeLocation)
- if loc == nil {
- return time.Time{}
+ switch a := v.any.(type) {
+ case timeLocation:
+ if a == nil {
+ return time.Time{}
+ }
+ return time.Unix(0, int64(v.num)).In(a)
+ case timeTime:
+ return time.Time(a)
+ default:
+ panic(fmt.Sprintf("bad time type %T", v.any))
}
- return time.Unix(0, int64(v.num)).In(loc)
}
// LogValuer returns v's value as a LogValuer. It panics
BoolValue(true),
BoolValue(false),
TimeValue(testTime),
+ TimeValue(time.Time{}),
TimeValue(time.Date(2001, 1, 2, 3, 4, 5, 0, time.UTC)),
+ TimeValue(time.Date(2300, 1, 1, 0, 0, 0, 0, time.UTC)), // overflows nanoseconds
+ TimeValue(time.Date(1715, 6, 13, 0, 25, 26, 290448384, time.UTC)), // overflowed value
AnyValue(&x),
AnyValue(&y),
GroupValue(Bool("b", true), Int("i", 3)),
}
}
-func TestZeroTime(t *testing.T) {
- z := time.Time{}
- got := TimeValue(z).Time()
- if !got.IsZero() {
- t.Errorf("got %s (%#[1]v), not zero time (%#v)", got, z)
+func TestValueTime(t *testing.T) {
+ // Validate that all representations of times work correctly.
+ for _, tm := range []time.Time{
+ time.Time{},
+ time.Unix(0, 1e15), // UnixNanos is defined
+ time.Date(2300, 1, 1, 0, 0, 0, 0, time.UTC), // overflows UnixNanos
+ } {
+ got := TimeValue(tm).Time()
+ if !got.Equal(tm) {
+ t.Errorf("got %s (%#[1]v), want %s (%#[2]v)", got, tm)
+ }
+ if g, w := got.Location(), tm.Location(); g != w {
+ t.Errorf("%s: location: got %v, want %v", tm, g, w)
+ }
}
}