slog: correctly represent the zero Time in a Value

Storing the zero time.Time in a Value and then retrieving it
gave a non-zero time. The reason is that UnixNanos is called
to store a time.Time in a Value, and its result is undefined
for the zero time (or any time before the year 1678 or after 2262,
but I don't think we need to worry about those).

One solution, implemented in this CL, is to use a nil *time.Location
to represent the zero time. the time.Time.Location method never
returns nil, so a Value with a nil *time.Location in its `any` field
cannot be mistaken for any other Value, time.Time or otherwise.

Change-Id: Icba66f2ec16e21ed574c3336e1996ed759ab1c17
Reviewed-on: https://go-review.googlesource.com/c/exp/+/463817
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/slog/value.go b/slog/value.go
index 4e928b9..ffe5907 100644
--- a/slog/value.go
+++ b/slog/value.go
@@ -97,6 +97,13 @@
 // TimeValue returns a Value for a time.Time.
 // It discards the monotonic portion.
 func TimeValue(v time.Time) Value {
+	if v.IsZero() {
+		// UnixNano on the zero time is undefined, so represent the zero time
+		// with a nil *time.Location instead. time.Time.Location method never
+		// returns nil, so a Value with any == timeLocation(nil) cannot be
+		// mistaken for any other Value, time.Time or otherwise.
+		return Value{any: timeLocation(nil)}
+	}
 	return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
 }
 
@@ -271,7 +278,11 @@
 }
 
 func (v Value) time() time.Time {
-	return time.Unix(0, int64(v.num)).In(v.any.(timeLocation))
+	loc := v.any.(timeLocation)
+	if loc == nil {
+		return time.Time{}
+	}
+	return time.Unix(0, int64(v.num)).In(loc)
 }
 
 // LogValuer returns v's value as a LogValuer. It panics
diff --git a/slog/value_test.go b/slog/value_test.go
index 31403ca..67f8730 100644
--- a/slog/value_test.go
+++ b/slog/value_test.go
@@ -190,6 +190,14 @@
 	}
 }
 
+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)
+	}
+}
+
 type replace struct {
 	v Value
 }