Checklist
Description
Date claims before the Unix epoch are converted to seconds using integer division, which truncates toward zero. This makes a Date and an Instant representing the same point in time serialize to different NumericDate values.
For example, new Date(-500) is half a second before the epoch. Its whole epoch second is -1, which is also returned by date.toInstant().getEpochSecond(). However, the Date serializer currently writes 0 because -500 / 1000 == 0 in Java.
Expected: equivalent Date and Instant values serialize to the same epoch second (-1).
Actual: the Date serializes to 0, while the equivalent Instant serializes to -1.
Reproduction
Date date = new Date(-500);
Instant instant = date.toInstant();
String token = JWT.create()
.withClaim("date", date)
.withClaim("instant", instant)
.sign(Algorithm.none());
DecodedJWT decoded = JWT.decode(token);
System.out.println(decoded.getClaim("date").asLong()); // 0
System.out.println(decoded.getClaim("instant").asLong()); // -1
The inconsistency originates in ClaimsSerializer.dateToSeconds, which uses date.getTime() / 1000, while the Instant path uses Instant.getEpochSecond().
Additional context
Using floor division for millisecond-to-second conversion would make the Date path consistent with Instant for negative timestamps while leaving non-negative timestamps unchanged.
java-jwt version
4.6.1 (master at 29f252b)
Java version
Java 17.0.14
AI assistance disclosure: Codex assisted with identifying and structuring this report. The reproduction and analysis were reviewed and verified before submission.
Checklist
Description
Dateclaims before the Unix epoch are converted to seconds using integer division, which truncates toward zero. This makes aDateand anInstantrepresenting the same point in time serialize to different NumericDate values.For example,
new Date(-500)is half a second before the epoch. Its whole epoch second is-1, which is also returned bydate.toInstant().getEpochSecond(). However, theDateserializer currently writes0because-500 / 1000 == 0in Java.Expected: equivalent
DateandInstantvalues serialize to the same epoch second (-1).Actual: the
Dateserializes to0, while the equivalentInstantserializes to-1.Reproduction
The inconsistency originates in
ClaimsSerializer.dateToSeconds, which usesdate.getTime() / 1000, while theInstantpath usesInstant.getEpochSecond().Additional context
Using floor division for millisecond-to-second conversion would make the
Datepath consistent withInstantfor negative timestamps while leaving non-negative timestamps unchanged.java-jwt version
4.6.1 (
masterat29f252b)Java version
Java 17.0.14
AI assistance disclosure: Codex assisted with identifying and structuring this report. The reproduction and analysis were reviewed and verified before submission.