Skip to content

Convert All Usage of LocalDateTime to Instant #456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b24a0fc
Change LocalDateTime to Instant
emmyzhou-db Jun 3, 2025
6f81b4c
Update parseExpiry in CilTokenSource
emmyzhou-db Jun 4, 2025
daac1b2
Update javadoc
emmyzhou-db Jun 4, 2025
f3d4b8a
Update javadoc
emmyzhou-db Jun 4, 2025
12123a9
Retrigger tests
emmyzhou-db Jun 5, 2025
def03c5
Merge branch 'main' into emmyzhou-db/localdatetime-to-instant
parthban-db Jun 5, 2025
fdc50ef
Removed redundant date formattters
emmyzhou-db Jun 6, 2025
70934b2
Change clock supplier to use UTC time
emmyzhou-db Jun 6, 2025
1bd052f
Add support for space separated expiry strings
emmyzhou-db Jun 7, 2025
408f3b4
revert test data
emmyzhou-db Jun 7, 2025
7fccff9
Update exception handling
emmyzhou-db Jun 11, 2025
64313f8
Update Javadoc
emmyzhou-db Jun 11, 2025
447eae2
Added more tests to CilTokenSourceTest
emmyzhou-db Jun 11, 2025
66335a7
Add test to verify perserved behaviour
emmyzhou-db Jun 12, 2025
3a824eb
Generate all timezones
emmyzhou-db Jun 12, 2025
c1367bf
Merge branch 'emmyzhou-db/test_time' into emmyzhou-db/localdatetime-t…
emmyzhou-db Jun 12, 2025
4d31c1e
Merge branch 'emmyzhou-db/test_time' into emmyzhou-db/localdatetime-t…
emmyzhou-db Jun 12, 2025
95a3c6d
Update test
emmyzhou-db Jun 12, 2025
a5c65c5
update tests
emmyzhou-db Jun 12, 2025
a165b72
Date formats are generated at run-time
emmyzhou-db Jun 12, 2025
d1c1a6c
Generate date formats at run-time
emmyzhou-db Jun 12, 2025
785c400
Update stream of test cases
emmyzhou-db Jun 13, 2025
f35988d
Merge branch 'emmyzhou-db/test_time' into emmyzhou-db/localdatetime-t…
emmyzhou-db Jun 13, 2025
9d5c85e
Add comment
emmyzhou-db Jun 13, 2025
2a7cd95
Add comment
emmyzhou-db Jun 13, 2025
257998c
Update comment
emmyzhou-db Jun 13, 2025
ac99bbe
Merge branch 'emmyzhou-db/test_time' into emmyzhou-db/localdatetime-t…
emmyzhou-db Jun 13, 2025
ceea58a
update stream of tests
emmyzhou-db Jun 13, 2025
0697fae
Polish comments
emmyzhou-db Jun 13, 2025
65d7180
Merge branch 'main' into emmyzhou-db/localdatetime-to-instant
emmyzhou-db Jun 13, 2025
f58a2f6
Improve Javadoc
emmyzhou-db Jun 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
Expand Down Expand Up @@ -36,21 +38,40 @@ public CliTokenSource(
this.env = env;
}

static LocalDateTime parseExpiry(String expiry) {
/**
* Parses an expiry time string and returns the corresponding {@link Instant}.
*
* <p>The expiry time string is always in UTC. Any time zone or offset information present in the
* input is ignored.
*
* <p>The method attempts to parse the input using several common date-time formats, including
* ISO-8601 and patterns with varying sub-second precision.
*
* @param expiry the expiry time string to parse, which must represent a UTC time
* @return the parsed {@link Instant}
* @throws DateTimeParseException if the input string cannot be parsed
*/
static Instant parseExpiry(String expiry) {
DateTimeParseException lastException = null;
// Try to parse the expiry as an ISO-8601 string in UTC first
try {
return Instant.parse(expiry);
} catch (DateTimeParseException e) {
lastException = e;
}

String multiplePrecisionPattern =
"[SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]";
List<String> datePatterns =
Arrays.asList(
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm:ss." + multiplePrecisionPattern,
"yyyy-MM-dd'T'HH:mm:ss." + multiplePrecisionPattern + "XXX",
"yyyy-MM-dd'T'HH:mm:ss." + multiplePrecisionPattern + "'Z'");
DateTimeParseException lastException = null;
"yyyy-MM-dd'T'HH:mm:ss." + multiplePrecisionPattern + "XXX");
for (String pattern : datePatterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
LocalDateTime dateTime = LocalDateTime.parse(expiry, formatter);
return dateTime;
return dateTime.atZone(ZoneOffset.UTC).toInstant();
} catch (DateTimeParseException e) {
lastException = e;
}
Expand Down Expand Up @@ -83,7 +104,7 @@ protected Token refresh() {
String tokenType = jsonNode.get(tokenTypeField).asText();
String accessToken = jsonNode.get(accessTokenField).asText();
String expiry = jsonNode.get(expiryField).asText();
LocalDateTime expiresOn = parseExpiry(expiry);
Instant expiresOn = parseExpiry(expiry);
return new Token(accessToken, tokenType, expiresOn);
} catch (DatabricksException e) {
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.databricks.sdk.core.DatabricksException;
import com.databricks.sdk.core.http.HttpClient;
import com.google.common.base.Strings;
import java.time.LocalDateTime;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -166,7 +166,7 @@ public Token refresh() {
throw e;
}

LocalDateTime expiry = LocalDateTime.now().plusSeconds(response.getExpiresIn());
Instant expiry = Instant.now().plusSeconds(response.getExpiresIn());
return new Token(
response.getAccessToken(), response.getTokenType(), response.getRefreshToken(), expiry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.databricks.sdk.core.DatabricksException;
import com.databricks.sdk.core.http.HttpClient;
import java.time.LocalDateTime;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -87,7 +87,7 @@ protected Token refresh() {
throw e;
}

LocalDateTime expiry = LocalDateTime.now().plusSeconds(oauthResponse.getExpiresIn());
Instant expiry = Instant.now().plusSeconds(oauthResponse.getExpiresIn());
return new Token(
oauthResponse.getAccessToken(),
oauthResponse.getTokenType(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.Instant;

/**
* {@code OidcTokenSource} is responsible for obtaining OAuth tokens using the OpenID Connect (OIDC)
Expand Down Expand Up @@ -77,7 +77,7 @@ protected Token refresh() {
if (resp.getErrorCode() != null) {
throw new IllegalArgumentException(resp.getErrorCode() + ": " + resp.getErrorSummary());
}
LocalDateTime expiry = LocalDateTime.now().plusSeconds(resp.getExpiresIn());
Instant expiry = Instant.now().plusSeconds(resp.getExpiresIn());
return new Token(resp.getAccessToken(), resp.getTokenType(), resp.getRefreshToken(), expiry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import com.databricks.sdk.core.http.FormRequest;
import com.databricks.sdk.core.http.HttpClient;
import com.databricks.sdk.core.http.Request;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import org.apache.http.HttpHeaders;
Expand Down Expand Up @@ -70,7 +69,7 @@ protected static Token retrieveToken(
if (resp.getErrorCode() != null) {
throw new IllegalArgumentException(resp.getErrorCode() + ": " + resp.getErrorSummary());
}
LocalDateTime expiry = LocalDateTime.now().plus(resp.getExpiresIn(), ChronoUnit.SECONDS);
Instant expiry = Instant.now().plusSeconds(resp.getExpiresIn());
return new Token(resp.getAccessToken(), resp.getTokenType(), resp.getRefreshToken(), expiry);
} catch (Exception e) {
throw new DatabricksException("Failed to refresh credentials: " + e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import com.databricks.sdk.core.utils.SystemClockSupplier;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.Instant;
import java.util.Objects;

public class Token {
Expand All @@ -19,21 +18,20 @@ public class Token {
* The expiry time of the token.
*
* <p>OAuth token responses include the duration of the lifetime of the access token. When the
* token is retrieved, this is converted to a LocalDateTime tracking the expiry time of the token
* with respect to the current clock.
* token is retrieved, this is converted to an Instant tracking the expiry time of the token with
* respect to the current clock.
*/
@JsonProperty private LocalDateTime expiry;
@JsonProperty private Instant expiry;

private final ClockSupplier clockSupplier;

/** Constructor for non-refreshable tokens (e.g. M2M). */
public Token(String accessToken, String tokenType, LocalDateTime expiry) {
public Token(String accessToken, String tokenType, Instant expiry) {
this(accessToken, tokenType, null, expiry, new SystemClockSupplier());
}

/** Constructor for non-refreshable tokens (e.g. M2M) with ClockSupplier */
public Token(
String accessToken, String tokenType, LocalDateTime expiry, ClockSupplier clockSupplier) {
public Token(String accessToken, String tokenType, Instant expiry, ClockSupplier clockSupplier) {
this(accessToken, tokenType, null, expiry, clockSupplier);
}

Expand All @@ -43,7 +41,7 @@ public Token(
@JsonProperty("accessToken") String accessToken,
@JsonProperty("tokenType") String tokenType,
@JsonProperty("refreshToken") String refreshToken,
@JsonProperty("expiry") LocalDateTime expiry) {
@JsonProperty("expiry") Instant expiry) {
this(accessToken, tokenType, refreshToken, expiry, new SystemClockSupplier());
}

Expand All @@ -52,7 +50,7 @@ public Token(
String accessToken,
String tokenType,
String refreshToken,
LocalDateTime expiry,
Instant expiry,
ClockSupplier clockSupplier) {
Objects.requireNonNull(accessToken, "accessToken must be defined");
Objects.requireNonNull(tokenType, "tokenType must be defined");
Expand All @@ -71,8 +69,8 @@ public boolean isExpired() {
}
// Azure Databricks rejects tokens that expire in 30 seconds or less,
// so we refresh the token 40 seconds before it expires.
LocalDateTime potentiallyExpired = expiry.minus(40, ChronoUnit.SECONDS);
LocalDateTime now = LocalDateTime.now(clockSupplier.getClock());
Instant potentiallyExpired = expiry.minusSeconds(40);
Instant now = Instant.now(clockSupplier.getClock());
return potentiallyExpired.isBefore(now);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import com.databricks.sdk.core.oauth.Token;
import com.databricks.sdk.core.oauth.TokenSource;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
Expand All @@ -25,7 +24,7 @@ class AzureCliCredentialsProviderTest {
private static CliTokenSource mockTokenSource() {
CliTokenSource tokenSource = Mockito.mock(CliTokenSource.class);
Mockito.when(tokenSource.getToken())
.thenReturn(new Token(TOKEN, TOKEN_TYPE, LocalDateTime.now()));
.thenReturn(new Token(TOKEN, TOKEN_TYPE, java.time.Instant.now()));
return tokenSource;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.time.LocalDateTime;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import org.junit.jupiter.api.Test;

public class CliTokenSourceTest {

@Test
public void testParseExpiryWithoutTruncate() {
LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612218Z");
assertEquals(LocalDateTime.of(2023, 7, 17, 9, 2, 22, 330612218), parsedDateTime);
Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612218Z");
assertEquals(Instant.parse("2023-07-17T09:02:22.330612218Z"), parsedInstant);
}

@Test
public void testParseExpiryWithTruncate() {
LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.33061221Z");
assertEquals(LocalDateTime.of(2023, 7, 17, 9, 2, 22, 330612210), parsedDateTime);
Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.33061221Z");
assertEquals(Instant.parse("2023-07-17T09:02:22.330612210Z"), parsedInstant);
}

@Test
public void testParseExpiryWithTruncateAndLessNanoSecondDigits() {
LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612Z");
assertEquals(LocalDateTime.of(2023, 7, 17, 9, 2, 22, 330612000), parsedDateTime);
Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612Z");
assertEquals(Instant.parse("2023-07-17T09:02:22.330612000Z"), parsedInstant);
}

@Test
Expand All @@ -34,4 +34,25 @@ public void testParseExpiryWithMoreThanNineNanoSecondDigits() {
assert (e.getMessage().contains("could not be parsed"));
}
}

@Test
public void testParseExpiryWithSpaceFormat() {
Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17 09:02:22");
assertEquals(Instant.parse("2023-07-17T09:02:22Z"), parsedInstant);
}

@Test
public void testParseExpiryWithSpaceFormatAndMillis() {
Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17 09:02:22.123");
assertEquals(Instant.parse("2023-07-17T09:02:22.123Z"), parsedInstant);
}

@Test
public void testParseExpiryWithInvalidFormat() {
try {
CliTokenSource.parseExpiry("17-07-2023 09:02:22");
} catch (DateTimeParseException e) {
assert (e.getMessage().contains("could not be parsed"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import com.databricks.sdk.core.oauth.TokenSource;
import com.databricks.sdk.core.utils.Environment;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -232,7 +231,7 @@ public void testGetTokenSourceWithOAuth() {
HttpClient httpClient = mock(HttpClient.class);
TokenSource mockTokenSource = mock(TokenSource.class);
when(mockTokenSource.getToken())
.thenReturn(new Token("test-token", "Bearer", LocalDateTime.now().plusHours(1)));
.thenReturn(new Token("test-token", "Bearer", java.time.Instant.now().plusSeconds(3600)));
OAuthHeaderFactory mockHeaderFactory = OAuthHeaderFactory.fromTokenSource(mockTokenSource);
CredentialsProvider mockProvider = mock(CredentialsProvider.class);
when(mockProvider.authType()).thenReturn("test");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.databricks.sdk.core.http.Response;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.Instant;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -29,8 +29,7 @@ public class DataPlaneTokenSourceTest {

private static Stream<Arguments> provideDataPlaneTokenScenarios() throws Exception {
// Mock DatabricksOAuthTokenSource for control plane token
Token cpToken =
new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, LocalDateTime.now().plusSeconds(600));
Token cpToken = new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, Instant.now().plusSeconds(600));
DatabricksOAuthTokenSource mockCpTokenSource = mock(DatabricksOAuthTokenSource.class);
when(mockCpTokenSource.getToken()).thenReturn(cpToken);

Expand Down Expand Up @@ -81,9 +80,8 @@ private static Stream<Arguments> provideDataPlaneTokenScenarios() throws Excepti
"dp-access-token1",
TEST_TOKEN_TYPE,
TEST_REFRESH_TOKEN,
LocalDateTime.now().plusSeconds(TEST_EXPIRES_IN)),
null // No exception
),
Instant.now().plusSeconds(TEST_EXPIRES_IN)),
null),
Arguments.of(
"Success: endpoint2/auth2 (different cache key)",
TEST_ENDPOINT_2,
Expand All @@ -95,7 +93,7 @@ private static Stream<Arguments> provideDataPlaneTokenScenarios() throws Excepti
"dp-access-token2",
TEST_TOKEN_TYPE,
TEST_REFRESH_TOKEN,
LocalDateTime.now().plusSeconds(TEST_EXPIRES_IN)),
Instant.now().plusSeconds(TEST_EXPIRES_IN)),
null),
Arguments.of(
"Error response from endpoint",
Expand Down Expand Up @@ -203,7 +201,7 @@ void testDataPlaneTokenSource(
@Test
void testEndpointTokenSourceCaching() throws Exception {
Token cpToken =
new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, LocalDateTime.now().plusSeconds(3600));
new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, Instant.now().plusSeconds(3600));
DatabricksOAuthTokenSource mockCpTokenSource = mock(DatabricksOAuthTokenSource.class);
when(mockCpTokenSource.getToken()).thenReturn(cpToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import com.databricks.sdk.core.http.Response;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.Instant;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
Expand Down Expand Up @@ -48,7 +48,7 @@ private static Stream<Arguments> provideEndpointTokenScenarios() throws Exceptio
String malformedJson = "{not valid json}";

// Mock DatabricksOAuthTokenSource for control plane token
Token cpToken = new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, LocalDateTime.now().plusMinutes(10));
Token cpToken = new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, Instant.now().plusSeconds(600));
DatabricksOAuthTokenSource mockCpTokenSource = mock(DatabricksOAuthTokenSource.class);
when(mockCpTokenSource.getToken()).thenReturn(cpToken);

Expand Down
Loading
Loading