Skip to content

Commit b3ae5d9

Browse files
prashantvuvfive
authored andcommitted
Fix handling of Time values out of UnixNano range (uber-go#804)
Fixes uber-go#737, uber-go#803. Time values are encoded by storing the UnixNano representation, but this fails when the value of UnixNano is out of int64 range, see docs: https://golang.org/pkg/time/#Time.UnixNano > The result is undefined if the Unix time in nanoseconds cannot be represented by an int64 (a date before the year 1678 or after 2262) Fix this by storing values outside of UnixNano range as-is rather than using UnixNano with a new Field type.
1 parent 6484585 commit b3ae5d9

File tree

4 files changed

+38
-1
lines changed

4 files changed

+38
-1
lines changed

field.go

+8
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ import (
3232
// improves the navigability of this package's API documentation.
3333
type Field = zapcore.Field
3434

35+
var (
36+
_minTimeInt64 = time.Unix(0, math.MinInt64)
37+
_maxTimeInt64 = time.Unix(0, math.MaxInt64)
38+
)
39+
3540
// Skip constructs a no-op field, which is often useful when handling invalid
3641
// inputs in other Field constructors.
3742
func Skip() Field {
@@ -339,6 +344,9 @@ func Stringer(key string, val fmt.Stringer) Field {
339344
// Time constructs a Field with the given key and value. The encoder
340345
// controls how the time is serialized.
341346
func Time(key string, val time.Time) Field {
347+
if val.Before(_minTimeInt64) || val.After(_maxTimeInt64) {
348+
return Field{Key: key, Type: zapcore.TimeFullType, Interface: val}
349+
}
342350
return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()}
343351
}
344352

field_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package zap
2222

2323
import (
24+
"math"
2425
"net"
2526
"sync"
2627
"testing"
@@ -107,6 +108,10 @@ func TestFieldConstructors(t *testing.T) {
107108
{"String", Field{Key: "k", Type: zapcore.StringType, String: "foo"}, String("k", "foo")},
108109
{"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 0, Interface: time.UTC}, Time("k", time.Unix(0, 0).In(time.UTC))},
109110
{"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 1000, Interface: time.UTC}, Time("k", time.Unix(0, 1000).In(time.UTC))},
111+
{"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: math.MinInt64, Interface: time.UTC}, Time("k", time.Unix(0, math.MinInt64).In(time.UTC))},
112+
{"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: math.MaxInt64, Interface: time.UTC}, Time("k", time.Unix(0, math.MaxInt64).In(time.UTC))},
113+
{"Time", Field{Key: "k", Type: zapcore.TimeFullType, Interface: time.Time{}}, Time("k", time.Time{})},
114+
{"Time", Field{Key: "k", Type: zapcore.TimeFullType, Interface: time.Unix(math.MaxInt64, 0)}, Time("k", time.Unix(math.MaxInt64, 0))},
110115
{"Uint", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint("k", 1)},
111116
{"Uint64", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint64("k", 1)},
112117
{"Uint32", Field{Key: "k", Type: zapcore.Uint32Type, Integer: 1}, Uint32("k", 1)},

zapcore/field.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,11 @@ const (
6565
Int8Type
6666
// StringType indicates that the field carries a string.
6767
StringType
68-
// TimeType indicates that the field carries a time.Time.
68+
// TimeType indicates that the field carries a time.Time that is
69+
// representable by a UnixNano() stored as an int64.
6970
TimeType
71+
// TimeFullType indicates that the field carries a time.Time stored as-is.
72+
TimeFullType
7073
// Uint64Type indicates that the field carries a uint64.
7174
Uint64Type
7275
// Uint32Type indicates that the field carries a uint32.
@@ -145,6 +148,8 @@ func (f Field) AddTo(enc ObjectEncoder) {
145148
// Fall back to UTC if location is nil.
146149
enc.AddTime(f.Key, time.Unix(0, f.Integer))
147150
}
151+
case TimeFullType:
152+
enc.AddTime(f.Key, f.Interface.(time.Time))
148153
case Uint64Type:
149154
enc.AddUint64(f.Key, uint64(f.Integer))
150155
case Uint32Type:

zapcore/field_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"time"
3030

3131
"github.com/stretchr/testify/assert"
32+
"github.com/stretchr/testify/require"
3233
"go.uber.org/zap"
3334
. "go.uber.org/zap/zapcore"
3435
)
@@ -164,6 +165,14 @@ func TestFields(t *testing.T) {
164165
}
165166

166167
func TestEquals(t *testing.T) {
168+
// Values outside the UnixNano range were encoded incorrectly (#737, #803).
169+
timeOutOfRangeHigh := time.Unix(0, math.MaxInt64).Add(time.Nanosecond)
170+
timeOutOfRangeLow := time.Unix(0, math.MinInt64).Add(-time.Nanosecond)
171+
timeOutOfRangeHighNano := time.Unix(0, timeOutOfRangeHigh.UnixNano())
172+
timeOutOfRangeLowNano := time.Unix(0, timeOutOfRangeLow.UnixNano())
173+
require.False(t, timeOutOfRangeHigh.Equal(timeOutOfRangeHighNano), "should be different as value is > UnixNano range")
174+
require.False(t, timeOutOfRangeHigh.Equal(timeOutOfRangeHighNano), "should be different as value is < UnixNano range")
175+
167176
tests := []struct {
168177
a, b Field
169178
want bool
@@ -198,6 +207,16 @@ func TestEquals(t *testing.T) {
198207
b: zap.Time("k", time.Unix(1000, 1000).In(time.FixedZone("TEST", -8))),
199208
want: false,
200209
},
210+
{
211+
a: zap.Time("k", timeOutOfRangeLow),
212+
b: zap.Time("k", timeOutOfRangeLowNano),
213+
want: false,
214+
},
215+
{
216+
a: zap.Time("k", timeOutOfRangeHigh),
217+
b: zap.Time("k", timeOutOfRangeHighNano),
218+
want: false,
219+
},
201220
{
202221
a: zap.Time("k", time.Unix(1000, 1000)),
203222
b: zap.Time("k", time.Unix(1000, 2000)),

0 commit comments

Comments
 (0)