Skip to content

Commit

Permalink
jwtk#235 Replace usage of Date with Instant
Browse files Browse the repository at this point in the history
All instances of java.util.Date in code have been replaced with java.time.Instant for better time precision and timezone handling. This includes changes in variable names, method names, test cases, comments and documentation. The java.util.Date-based utilities have also been removed.
  • Loading branch information
pveeckhout committed Dec 27, 2023
1 parent fd1728c commit 13a83b6
Show file tree
Hide file tree
Showing 14 changed files with 66 additions and 106 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ would be very strange if a machine's clock was more than 5 minutes difference fr
#### Custom Clock Support

Timestamps created during parsing can now be obtained via a custom time source via an implementation of
the new `io.jsonwebtoken.Clock` interface. The default implementation simply returns `new Date()` to reflect the time
the new `io.jsonwebtoken.Clock` interface. The default implementation simply returns `new Instant()` to reflect the time
when parsing occurs, as most would expect. However, supplying your own clock could be useful, especially during test
cases to guarantee deterministic behavior.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@ String jws = Jwts.builder()
.audience("you")
.expiration(expiration) //a java.time.Instant
.notBefore(notBefore) //a java.time.Instant
.issuedAt(new Date()) // for example, now
.issuedAt(Instant.now) // for example, now
.id(UUID.randomUUID().toString()) //just an example id

/// ... etc ...
Expand Down Expand Up @@ -1416,7 +1416,7 @@ Clock clock = new MyClock();
Jwts.parser().clock(myClock) //... etc ...
```
The `JwtParser`'s default `Clock` implementation simply returns `new Date()` to reflect the time when parsing occurs,
The `JwtParser`'s default `Clock` implementation simply returns `Instant.now()` to reflect the time when parsing occurs,
as most would expect. However, supplying your own clock could be useful, especially when writing test cases to
guarantee deterministic behavior.

Expand Down
4 changes: 2 additions & 2 deletions api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {

/**
* Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT.
* The parser uses a default Clock implementation that simply returns {@code new Date()} when called.
* The parser uses a default Clock implementation that simply returns {@code new Instant()} when called.
*
* @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT.
* @return the parser builder for method chaining.
Expand All @@ -235,7 +235,7 @@ public interface JwtParserBuilder extends Builder<JwtParser> {

/**
* Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT.
* The parser uses a default Clock implementation that simply returns {@code new Date()} when called.
* The parser uses a default Clock implementation that simply returns {@code new Instant()} when called.
*
* @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT.
* @return the parser builder for method chaining.
Expand Down
10 changes: 5 additions & 5 deletions api/src/main/java/io/jsonwebtoken/lang/DateFormats.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ private DateFormats() {
* Return an ISO-8601-formatted string with millisecond precision representing the
* specified {@code instant}. Will always convert to UTC timezone.
*
* @param instant the date for which to create an ISO-8601-formatted string
* @return the date represented as an ISO-8601-formatted string in UTC timezone with millisecond precision.
* @param instant the instant for which to create an ISO-8601-formatted string
* @return the instant represented as an ISO-8601-formatted string in UTC timezone with millisecond precision.
*/
public static String formatIso8601(Instant instant) {
return formatIso8601(instant, true);
Expand All @@ -56,7 +56,7 @@ public static String formatIso8601(Instant instant) {
*
* @param instant the instant for which to create an ISO-8601-formatted string
* @param includeMillis whether to include millisecond notation within the string.
* @return the date represented as an ISO-8601-formatted string in UTC timezone with optional millisecond precision.
* @return the instant represented as an ISO-8601-formatted string in UTC timezone with optional millisecond precision.
*/
public static String formatIso8601(Instant instant, boolean includeMillis) {
Assert.notNull(instant, "Instant argument cannot be null.");
Expand All @@ -67,8 +67,8 @@ public static String formatIso8601(Instant instant, boolean includeMillis) {
}

/**
* Parse the specified ISO-8601-formatted date string and return the corresponding {@link Instant} instance. The
* date string may optionally contain millisecond notation, and those milliseconds will be represented accordingly.
* Parse the specified ISO-8601-formatted date string and return the corresponding {@link Instant} instance.
* The date string may optionally contain millisecond notation, and those milliseconds will be represented accordingly.
*
* @param s the ISO-8601-formatted string to parse
* @return the string's corresponding {@link Instant} instance.
Expand Down
24 changes: 12 additions & 12 deletions api/src/test/groovy/io/jsonwebtoken/lang/DateFormatsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,36 @@ class DateFormatsTest {
@Test
void testParseIso8601DateWithMillisZuluOffset() {
String dateString = "2023-12-25T15:30:00.123Z"
Instant parsedDate = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedDate)
Instant parsedInstant = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedInstant)
final expectedInstant = OffsetDateTime.of(2023, 12, 25, 15, 30, 0, 123000000, ZoneOffset.UTC).toInstant()
assertEquals expectedInstant, parsedDate
assertEquals expectedInstant, parsedInstant
}

@Test
void testParseIso8601DateWithMillisNonZuluOffset() {
String dateString = "2023-12-25T15:30:00.123-01:00"
Instant parsedDate = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedDate)
Instant parsedInstant = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedInstant)
final expectedInstant = OffsetDateTime.of(2023, 12, 25, 15, 30, 0, 123000000, ZoneOffset.ofHours(-1)).toInstant()
assertEquals expectedInstant, parsedDate
assertEquals expectedInstant, parsedInstant
}

@Test
void testParseIso8601DateWithoutMillisZuluOffset() {
String dateString = "2023-12-25T15:30:00Z"
Instant parsedDate = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedDate)
Instant parsedInstant = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedInstant)
final expectedInstant = OffsetDateTime.of(2023, 12, 25, 15, 30, 0, 0, ZoneOffset.UTC).toInstant()
assertEquals expectedInstant, parsedDate
assertEquals expectedInstant, parsedInstant
}

@Test
void testParseIso8601DateWithoutMillisNonZuluOffset() {
String dateString = "2023-12-25T15:30:00+01:00"
Instant parsedDate = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedDate)
assertEquals OffsetDateTime.of(2023,12,25,15,30,0, 0, ZoneOffset.ofHours(1)).toInstant(), parsedDate
Instant parsedInstant = DateFormats.parseIso8601Date(dateString)
assertNotNull(parsedInstant)
assertEquals OffsetDateTime.of(2023,12,25,15,30,0, 0, ZoneOffset.ofHours(1)).toInstant(), parsedInstant
}

@Test(expected = DateTimeParseException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import io.jsonwebtoken.lang.Maps
import org.junit.Before
import org.junit.Test

import java.time.Instant

import static org.junit.Assert.*

class JacksonDeserializerTest {
Expand Down Expand Up @@ -99,15 +101,15 @@ class JacksonDeserializerTest {
CustomBean expectedCustomBean = new CustomBean()
.setByteArrayValue("bytes".getBytes("UTF-8"))
.setByteValue(0xF as byte)
.setDateValue(new Date(currentTime))
.setInstantValue(Instant.ofEpochMilli(currentTime))
.setIntValue(11)
.setShortValue(22 as short)
.setLongValue(33L)
.setStringValue("s-value")
.setNestedValue(new CustomBean()
.setByteArrayValue("bytes2".getBytes("UTF-8"))
.setByteValue(0xA as byte)
.setDateValue(new Date(currentTime + 1))
.setInstantValue(Instant.ofEpochMilli(currentTime + 1))
.setIntValue(111)
.setShortValue(222 as short)
.setLongValue(333L)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
*/
package io.jsonwebtoken.jackson.io.stubs

import java.time.Instant

class CustomBean {

private String stringValue
private int intValue
private Date dateValue
private Instant instantValue
private short shortValue
private long longValue
private byte byteValue
Expand All @@ -44,12 +46,12 @@ class CustomBean {
return this
}

Date getDateValue() {
return dateValue
Instant getInstantValue() {
return instantValue
}

CustomBean setDateValue(Date dateValue) {
this.dateValue = dateValue
CustomBean setInstantValue(Instant instantValue) {
this.instantValue = instantValue
return this
}

Expand Down Expand Up @@ -109,7 +111,7 @@ class CustomBean {
if (longValue != that.longValue) return false
if (shortValue != that.shortValue) return false
if (!Arrays.equals(byteArrayValue, that.byteArrayValue)) return false
if (dateValue != that.dateValue) return false
if (instantValue != that.instantValue) return false
if (nestedValue != that.nestedValue) return false
if (stringValue != that.stringValue) return false

Expand All @@ -120,7 +122,7 @@ class CustomBean {
int result
result = stringValue.hashCode()
result = 31 * result + intValue
result = 31 * result + dateValue.hashCode()
result = 31 * result + instantValue.hashCode()
result = 31 * result + (int) shortValue
result = 31 * result + (int) (longValue ^ (longValue >>> 32))
result = 31 * result + (int) byteValue
Expand All @@ -135,7 +137,7 @@ class CustomBean {
return "CustomBean{" +
"stringValue='" + stringValue + '\'' +
", intValue=" + intValue +
", dateValue=" + dateValue?.time +
", instantValue=" + instantValue?.toEpochMilli() +
", shortValue=" + shortValue +
", longValue=" + longValue +
", byteValue=" + byteValue +
Expand Down
10 changes: 5 additions & 5 deletions impl/src/main/java/io/jsonwebtoken/impl/FixedClock.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,27 @@ public class FixedClock implements Clock {

/**
* Creates a new fixed clock using <code>new {@link Instant instant}()</code> as the seed timestamp. All calls to
* {@link #now now()} will always return this seed Date.
* {@link #now now()} will always return this seed Instant.
*/
public FixedClock() {
this(Instant.now());
}

/**
* Creates a new fixed clock using the specified seed timestamp. All calls to
* {@link #now now()} will always return this seed Date.
* {@link #now now()} will always return this seed Instant.
*
* @param now the specified Date to always return from all calls to {@link #now now()}.
* @param now the specified Instant to always return from all calls to {@link #now now()}.
*/
public FixedClock(Instant now) {
this.now = now;
}

/**
* Creates a new fixed clock using the specified seed timestamp. All calls to
* {@link #now now()} will always return this seed Date.
* {@link #now now()} will always return this seed Instant.
*
* @param timeInMillis the specified Date in milliseconds to always return from all calls to {@link #now now()}.
* @param timeInMillis the specified Instant in milliseconds to always return from all calls to {@link #now now()}.
*/
public FixedClock(long timeInMillis) {
this(Instant.ofEpochMilli(timeInMillis));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static Instant toSpecInstant(Object value) {
if (value instanceof String) {
try {
value = Long.parseLong((String) value);
} catch (NumberFormatException ignored) { // will try in the fallback toDate method call below
} catch (NumberFormatException ignored) { // will try in the fallback toInstant method call below
}
}
if (value instanceof Number) {
Expand All @@ -54,7 +54,7 @@ public static Instant toSpecInstant(Object value) {
/**
* Returns a {@link Instant} equivalent of the specified object value using heuristics.
*
* @param v the object value to represent as a Date.
* @param v the object value to represent as an Instant.
* @return a {@link Instant} equivalent of the specified object value using heuristics.
*/
public static Instant toInstant(Object v) {
Expand Down
31 changes: 0 additions & 31 deletions impl/src/test/groovy/io/jsonwebtoken/DateTestUtils.groovy

This file was deleted.

6 changes: 3 additions & 3 deletions impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ class JwtParserTest {

@Test
void testParseRequireCustomDate_Missing_Fail() {
def aDate = new Date(System.currentTimeMillis())
def anInstant = Instant.now()

byte[] key = randomKey()

Expand All @@ -1542,12 +1542,12 @@ class JwtParserTest {

try {
Jwts.parser().setSigningKey(key).
require("aDate", aDate).
require("anInstant", anInstant).
build().
parseSignedClaims(compact)
fail()
} catch (MissingClaimException e) {
String msg = "Missing 'aDate' claim. Expected value: $aDate"
String msg = "Missing 'anInstant' claim. Expected value: $anInstant"
assertEquals msg, e.getMessage()
}
}
Expand Down
32 changes: 10 additions & 22 deletions impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,17 @@ import static org.junit.Assert.*

class JwtsTest {

private static Date dateWithOnlySecondPrecision(long millis) {
long seconds = (millis / 1000) as long
long secondOnlyPrecisionMillis = seconds * 1000
return new Date(secondOnlyPrecisionMillis)
}

private static Instant instantWithOnlySecondPrecision(Instant instant) {
return instant.truncatedTo(ChronoUnit.SECONDS)
}

private static int later() {
def date = laterDate(10000)
def seconds = date.getTime() / 1000
return seconds as int
}

private static Date laterDate(int seconds) {
def millis = seconds * 1000L
def time = System.currentTimeMillis() + millis
return dateWithOnlySecondPrecision(time)
private static long later() {
def instant = laterInstant(10000L)
return instant.getEpochSecond();
}

private static Instant laterInstant(int seconds) {
private static Instant laterInstant(long seconds) {
return instantWithOnlySecondPrecision(Instant.now().plusSeconds(seconds))
}

Expand Down Expand Up @@ -417,8 +405,8 @@ class JwtsTest {
Instant then = laterInstant(10000)
String compact = Jwts.builder().setExpiration(then).compact()
Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims
def claimedDate = claims.getExpiration()
assertEquals then, claimedDate
def claimedInstant = claims.getExpiration()
assertEquals then, claimedInstant

compact = Jwts.builder().setIssuer("Me")
.setExpiration(then) //set it
Expand All @@ -434,8 +422,8 @@ class JwtsTest {
Instant now = instantWithOnlySecondPrecision(Instant.now()) //jwt exp only supports *seconds* since epoch:
String compact = Jwts.builder().setNotBefore(now).compact()
Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims
def claimedDate = claims.getNotBefore()
assertEquals now, claimedDate
def claimedInstant = claims.getNotBefore()
assertEquals now, claimedInstant

compact = Jwts.builder().setIssuer("Me")
.setNotBefore(now) //set it
Expand All @@ -451,8 +439,8 @@ class JwtsTest {
Instant now = instantWithOnlySecondPrecision(Instant.now()) //jwt exp only supports *seconds* since epoch:
String compact = Jwts.builder().setIssuedAt(now).compact()
Claims claims = Jwts.parser().unsecured().build().parse(compact).payload as Claims
def claimedDate = claims.getIssuedAt()
assertEquals now, claimedDate
def claimedInstant = claims.getIssuedAt()
assertEquals now, claimedInstant

compact = Jwts.builder().setIssuer("Me")
.setIssuedAt(now) //set it
Expand Down

0 comments on commit 13a83b6

Please sign in to comment.