diff --git a/types.go b/types.go index 2c647fd2..ac8e140e 100644 --- a/types.go +++ b/types.go @@ -53,9 +53,23 @@ func (date NumericDate) MarshalJSON() (b []byte, err error) { if TimePrecision < time.Second { prec = int(math.Log10(float64(time.Second) / float64(TimePrecision))) } - f := float64(date.Truncate(TimePrecision).UnixNano()) / float64(time.Second) - - return []byte(strconv.FormatFloat(f, 'f', prec, 64)), nil + truncatedDate := date.Truncate(TimePrecision) + + // For very large timestamps, UnixNano would overflow an int64, but this + // function requires nanosecond level precision, so we have to use the + // following technique to get round the issue: + // 1. Take the normal unix timestamp to form the whole number part of the + // output, + // 2. Take the result of the Nanosecond function, which retuns the offset + // within the second of the particular unix time instance, to form the + // decimal part of the output + // 3. Concatenate them to produce the final result + seconds := strconv.FormatInt(truncatedDate.Unix(), 10) + nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64) + + output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...) + + return output, nil } // UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a diff --git a/types_test.go b/types_test.go index bc28605a..b26c2bef 100644 --- a/types_test.go +++ b/types_test.go @@ -2,6 +2,7 @@ package jwt_test import ( "encoding/json" + "math" "testing" "time" @@ -79,23 +80,38 @@ func TestNumericDate_MarshalJSON(t *testing.T) { }{ {time.Unix(5243700879, 0), "5243700879", time.Second}, {time.Unix(5243700879, 0), "5243700879.000", time.Millisecond}, - {time.Unix(5243700879, 0), "5243700879.000001", time.Microsecond}, - {time.Unix(5243700879, 0), "5243700879.000000954", time.Nanosecond}, + {time.Unix(5243700879, 0), "5243700879.000000", time.Microsecond}, + {time.Unix(5243700879, 0), "5243700879.000000000", time.Nanosecond}, // {time.Unix(4239425898, 0), "4239425898", time.Second}, {time.Unix(4239425898, 0), "4239425898.000", time.Millisecond}, {time.Unix(4239425898, 0), "4239425898.000000", time.Microsecond}, {time.Unix(4239425898, 0), "4239425898.000000000", time.Nanosecond}, // + {time.Unix(253402271999, 0), "253402271999", time.Second}, + {time.Unix(253402271999, 0), "253402271999.000", time.Millisecond}, + {time.Unix(253402271999, 0), "253402271999.000000", time.Microsecond}, + {time.Unix(253402271999, 0), "253402271999.000000000", time.Nanosecond}, + // {time.Unix(0, 1644285000210402000), "1644285000", time.Second}, {time.Unix(0, 1644285000210402000), "1644285000.210", time.Millisecond}, {time.Unix(0, 1644285000210402000), "1644285000.210402", time.Microsecond}, - {time.Unix(0, 1644285000210402000), "1644285000.210402012", time.Nanosecond}, + {time.Unix(0, 1644285000210402000), "1644285000.210402000", time.Nanosecond}, // {time.Unix(0, 1644285315063096000), "1644285315", time.Second}, {time.Unix(0, 1644285315063096000), "1644285315.063", time.Millisecond}, {time.Unix(0, 1644285315063096000), "1644285315.063096", time.Microsecond}, - {time.Unix(0, 1644285315063096000), "1644285315.063096046", time.Nanosecond}, + {time.Unix(0, 1644285315063096000), "1644285315.063096000", time.Nanosecond}, + // Maximum time that a go time.Time can represent + {time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second}, + {time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999", time.Millisecond}, + {time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999", time.Microsecond}, + {time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999999", time.Nanosecond}, + // Strange precisions + {time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second}, + {time.Unix(math.MaxInt64, 999999999), "9223372036854775756", time.Minute}, + {time.Unix(math.MaxInt64, 999999999), "9223372036854774016", time.Hour}, + {time.Unix(math.MaxInt64, 999999999), "9223372036854745216", 24 * time.Hour}, } for i, tc := range tt {