diff --git a/types.go b/types.go index 80b1b96..2c647fd 100644 --- a/types.go +++ b/types.go @@ -49,9 +49,13 @@ func newNumericDateFromSeconds(f float64) *NumericDate { // MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch // represented in NumericDate to a byte array, using the precision specified in TimePrecision. func (date NumericDate) MarshalJSON() (b []byte, err error) { + var prec int + 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', -1, 64)), nil + return []byte(strconv.FormatFloat(f, 'f', prec, 64)), nil } // UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a diff --git a/types_test.go b/types_test.go index 675f953..bc28605 100644 --- a/types_test.go +++ b/types_test.go @@ -18,12 +18,10 @@ func TestNumericDate(t *testing.T) { jwt.TimePrecision = time.Microsecond - raw := `{"iat":1516239022,"exp":1516239022.12345}` + raw := `{"iat":1516239022.000000,"exp":1516239022.123450}` - err := json.Unmarshal([]byte(raw), &s) - - if err != nil { - t.Errorf("Unexpected error: %s", err) + if err := json.Unmarshal([]byte(raw), &s); err != nil { + t.Fatalf("Unexpected error: %s", err) } b, _ := json.Marshal(s) @@ -65,3 +63,49 @@ func TestSingleArrayMarshal(t *testing.T) { t.Errorf("Serialized format of string array mismatch. Expecting: %s Got: %s", string(expected), string(b)) } } + +func TestNumericDate_MarshalJSON(t *testing.T) { + // Do not run this test in parallel because it's changing + // global state. + oldPrecision := jwt.TimePrecision + t.Cleanup(func() { + jwt.TimePrecision = oldPrecision + }) + + tt := []struct { + in time.Time + want string + precision time.Duration + }{ + {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(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(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, 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}, + } + + for i, tc := range tt { + jwt.TimePrecision = tc.precision + by, err := jwt.NewNumericDate(tc.in).MarshalJSON() + if err != nil { + t.Fatal(err) + } + if got := string(by); got != tc.want { + t.Errorf("[%d]: failed encoding: got %q want %q", i, got, tc.want) + } + } +}