Skip to content

Commit f202c3b

Browse files
feat: introduce java.time to java-core (#3330)
## In this PR: * Modify publicly exposed methods to offer a `java.time` alternative (suffixed with `Duration`). These methods will now hold the actual implementation, whereas the old signatures will encapsulate the new ones. Example: `retryDelay(org.threeten.bp.Duration)` encapsulates `retryDelayDuration(java.time.Duration)` ## Notes ### _CLIRR_ ``` [ERROR] 7012: com.google.cloud.testing.BaseEmulatorHelper$EmulatorRunner: Method 'public int waitForDuration(java.time.Duration)' has been added to an interface ``` This new interface method was added. However, we ignore this change alert because the method has a `default` implementation. ### Addressing a behavior change in `Timestamp` #### Problem When using java.time functions, parsing a datetime string with offset produces a wrong time object (offset is not added to the effective epoch seconds). #### Context Full context in https://github.com/googleapis/sdk-platform-java/pull/3330/files#r1828424787 adoptium/jdk@c6d209b was introduced as a fix (in Java 9) meant for [this issue](https://bugs.openjdk.org/browse/JDK-8066982). In java 8, this means that the offset value is stored separately in a variable that is not respected by the parsing functions before this fix. The workaround is to use `appendZoneOrOffsetId()`, which stores the offset value in the `zone` variable of a parsing context, which is [respected as of java 8](https://github.com/adoptium/jdk8u/blob/31b88042fba46e87fba83e8bfd43ae0ecb5a9afd/jdk/src/share/classes/java/time/format/Parsed.java#L589-L591). Additionally, under the consideration of this having unwanted side effects, we expanded the test suite to test for more edge and normal cases using an offset string. We also [searched](https://bugs.openjdk.org/browse/JDK-8202948?jql=affectedVersion%20%3D%20%228%22%20AND%20text%20~%20%22offset%22) the JDK's issue tracking database and found somewhat similar issues with parsing and offsets but no workaround that addressed our specific situation. This is also cause of no backports of the [fix](adoptium/jdk@c6d209b) for Java 9 into Java 8. #### Outcome The behavior is kept the same as stated by our tests and downstream checks
1 parent c624b89 commit f202c3b

File tree

9 files changed

+247
-59
lines changed

9 files changed

+247
-59
lines changed

java-core/google-cloud-core/clirr-ignored-differences.xml

+6
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@
1212
<className>com/google/cloud/ReadChannel</className>
1313
<method>long limit()</method>
1414
</difference>
15+
<difference>
16+
<!-- Default method added to interface -->
17+
<differenceType>7012</differenceType>
18+
<className>com/google/cloud/testing/BaseEmulatorHelper$EmulatorRunner</className>
19+
<method>int waitForDuration(java.time.Duration)</method>
20+
</difference>
1521
</differences>

java-core/google-cloud-core/src/main/java/com/google/cloud/RetryOption.java

+29-10
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616

1717
package com.google.cloud;
1818

19+
import static com.google.api.gax.util.TimeConversionUtils.toJavaTimeDuration;
1920
import static com.google.common.base.Preconditions.checkNotNull;
2021

2122
import com.google.api.core.BetaApi;
23+
import com.google.api.core.ObsoleteApi;
2224
import com.google.api.gax.retrying.RetrySettings;
2325
import java.io.Serializable;
24-
import org.threeten.bp.Duration;
2526

2627
/**
2728
* This class represents an options wrapper around the {@link RetrySettings} class and is an
@@ -51,13 +52,25 @@ private RetryOption(OptionType type, Object value) {
5152
this.value = checkNotNull(value);
5253
}
5354

54-
/** See {@link RetrySettings#getTotalTimeout()}. */
55-
public static RetryOption totalTimeout(Duration totalTimeout) {
55+
/** This method is obsolete. Use {@link #totalTimeoutDuration(java.time.Duration)} instead */
56+
@ObsoleteApi("Use totalTimeouDuration() instead")
57+
public static RetryOption totalTimeout(org.threeten.bp.Duration totalTimeout) {
58+
return totalTimeoutDuration(toJavaTimeDuration(totalTimeout));
59+
}
60+
61+
/** See {@link RetrySettings#getTotalTimeoutDuration()}. */
62+
public static RetryOption totalTimeoutDuration(java.time.Duration totalTimeout) {
5663
return new RetryOption(OptionType.TOTAL_TIMEOUT, totalTimeout);
5764
}
5865

59-
/** See {@link RetrySettings#getInitialRetryDelay()}. */
60-
public static RetryOption initialRetryDelay(Duration initialRetryDelay) {
66+
/** This method is obsolete. Use {@link #initialRetryDelayDuration(java.time.Duration)} instead */
67+
@ObsoleteApi("Use initialRetryDelayDuration() instead")
68+
public static RetryOption initialRetryDelay(org.threeten.bp.Duration initialRetryDelay) {
69+
return initialRetryDelayDuration(toJavaTimeDuration(initialRetryDelay));
70+
}
71+
72+
/** See {@link RetrySettings#getInitialRetryDelayDuration()}. */
73+
public static RetryOption initialRetryDelayDuration(java.time.Duration initialRetryDelay) {
6174
return new RetryOption(OptionType.INITIAL_RETRY_DELAY, initialRetryDelay);
6275
}
6376

@@ -66,8 +79,14 @@ public static RetryOption retryDelayMultiplier(double retryDelayMultiplier) {
6679
return new RetryOption(OptionType.RETRY_DELAY_MULTIPLIER, retryDelayMultiplier);
6780
}
6881

69-
/** See {@link RetrySettings#getMaxRetryDelay()}. */
70-
public static RetryOption maxRetryDelay(Duration maxRetryDelay) {
82+
/** This method is obsolete. Use {@link #maxRetryDelayDuration(java.time.Duration)} instead */
83+
@ObsoleteApi("Use maxRetryDelayDuration() instead")
84+
public static RetryOption maxRetryDelay(org.threeten.bp.Duration maxRetryDelay) {
85+
return maxRetryDelayDuration(toJavaTimeDuration(maxRetryDelay));
86+
}
87+
88+
/** See {@link RetrySettings#getMaxRetryDelayDuration()}. */
89+
public static RetryOption maxRetryDelayDuration(java.time.Duration maxRetryDelay) {
7190
return new RetryOption(OptionType.MAX_RETRY_DELAY, maxRetryDelay);
7291
}
7392

@@ -124,16 +143,16 @@ public static RetrySettings mergeToSettings(RetrySettings settings, RetryOption.
124143
for (RetryOption option : options) {
125144
switch (option.type) {
126145
case TOTAL_TIMEOUT:
127-
builder.setTotalTimeout((Duration) option.value);
146+
builder.setTotalTimeoutDuration((java.time.Duration) option.value);
128147
break;
129148
case INITIAL_RETRY_DELAY:
130-
builder.setInitialRetryDelay((Duration) option.value);
149+
builder.setInitialRetryDelayDuration((java.time.Duration) option.value);
131150
break;
132151
case RETRY_DELAY_MULTIPLIER:
133152
builder.setRetryDelayMultiplier((Double) option.value);
134153
break;
135154
case MAX_RETRY_DELAY:
136-
builder.setMaxRetryDelay((Duration) option.value);
155+
builder.setMaxRetryDelayDuration((java.time.Duration) option.value);
137156
break;
138157
case MAX_ATTEMPTS:
139158
builder.setMaxAttempts((Integer) option.value);

java-core/google-cloud-core/src/main/java/com/google/cloud/ServiceOptions.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@
6363
import java.io.Serializable;
6464
import java.nio.charset.Charset;
6565
import java.nio.charset.StandardCharsets;
66+
import java.time.Duration;
6667
import java.util.Locale;
6768
import java.util.Map;
6869
import java.util.Objects;
6970
import java.util.ServiceLoader;
7071
import java.util.Set;
7172
import java.util.regex.Matcher;
7273
import java.util.regex.Pattern;
73-
import org.threeten.bp.Duration;
7474

7575
/**
7676
* Abstract class representing service options.
@@ -787,13 +787,13 @@ public static RetrySettings getNoRetrySettings() {
787787
private static RetrySettings.Builder getDefaultRetrySettingsBuilder() {
788788
return RetrySettings.newBuilder()
789789
.setMaxAttempts(6)
790-
.setInitialRetryDelay(Duration.ofMillis(1000L))
791-
.setMaxRetryDelay(Duration.ofMillis(32_000L))
790+
.setInitialRetryDelayDuration(Duration.ofMillis(1000L))
791+
.setMaxRetryDelayDuration(Duration.ofMillis(32_000L))
792792
.setRetryDelayMultiplier(2.0)
793-
.setTotalTimeout(Duration.ofMillis(50_000L))
794-
.setInitialRpcTimeout(Duration.ofMillis(50_000L))
793+
.setTotalTimeoutDuration(Duration.ofMillis(50_000L))
794+
.setInitialRpcTimeoutDuration(Duration.ofMillis(50_000L))
795795
.setRpcTimeoutMultiplier(1.0)
796-
.setMaxRpcTimeout(Duration.ofMillis(50_000L));
796+
.setMaxRpcTimeoutDuration(Duration.ofMillis(50_000L));
797797
}
798798

799799
protected abstract Set<String> getScopes();

java-core/google-cloud-core/src/main/java/com/google/cloud/Timestamp.java

+21-9
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,19 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020

21+
import com.google.api.core.ObsoleteApi;
2122
import com.google.protobuf.util.Timestamps;
2223
import java.io.Serializable;
24+
import java.time.Instant;
25+
import java.time.LocalDateTime;
26+
import java.time.ZoneOffset;
27+
import java.time.format.DateTimeFormatter;
28+
import java.time.format.DateTimeFormatterBuilder;
29+
import java.time.format.DateTimeParseException;
30+
import java.time.temporal.TemporalAccessor;
2331
import java.util.Date;
2432
import java.util.Objects;
2533
import java.util.concurrent.TimeUnit;
26-
import org.threeten.bp.Instant;
27-
import org.threeten.bp.LocalDateTime;
28-
import org.threeten.bp.ZoneOffset;
29-
import org.threeten.bp.format.DateTimeFormatter;
30-
import org.threeten.bp.format.DateTimeFormatterBuilder;
31-
import org.threeten.bp.format.DateTimeParseException;
32-
import org.threeten.bp.temporal.TemporalAccessor;
3334

3435
/**
3536
* Represents a timestamp with nanosecond precision. Timestamps cover the range [0001-01-01,
@@ -54,7 +55,7 @@ public final class Timestamp implements Comparable<Timestamp>, Serializable {
5455
new DateTimeFormatterBuilder()
5556
.appendOptional(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
5657
.optionalStart()
57-
.appendOffsetId()
58+
.appendZoneOrOffsetId()
5859
.optionalEnd()
5960
.toFormatter()
6061
.withZone(ZoneOffset.UTC);
@@ -189,6 +190,17 @@ public com.google.protobuf.Timestamp toProto() {
189190
return com.google.protobuf.Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
190191
}
191192

193+
/** This method is obsolete. Use {@link #parseTimestampDuration(String)} instead */
194+
@ObsoleteApi("Use parseTimestampDuration(String) instead")
195+
public static Timestamp parseTimestamp(String timestamp) {
196+
try {
197+
return parseTimestampDuration(timestamp);
198+
} catch (DateTimeParseException ex) {
199+
throw new org.threeten.bp.format.DateTimeParseException(
200+
ex.getMessage(), ex.getParsedString(), ex.getErrorIndex());
201+
}
202+
}
203+
192204
/**
193205
* Creates a Timestamp instance from the given string. Input string should be in the RFC 3339
194206
* format, like '2020-12-01T10:15:30.000Z' or with the timezone offset, such as
@@ -198,7 +210,7 @@ public com.google.protobuf.Timestamp toProto() {
198210
* @return created Timestamp
199211
* @throws DateTimeParseException if unable to parse
200212
*/
201-
public static Timestamp parseTimestamp(String timestamp) {
213+
public static Timestamp parseTimestampDuration(String timestamp) {
202214
TemporalAccessor temporalAccessor = timestampParser.parse(timestamp);
203215
Instant instant = Instant.from(temporalAccessor);
204216
return ofTimeSecondsAndNanos(instant.getEpochSecond(), instant.getNano());

java-core/google-cloud-core/src/main/java/com/google/cloud/testing/BaseEmulatorHelper.java

+49-9
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616

1717
package com.google.cloud.testing;
1818

19+
import static com.google.api.gax.util.TimeConversionUtils.toJavaTimeDuration;
20+
import static com.google.api.gax.util.TimeConversionUtils.toThreetenDuration;
21+
1922
import com.google.api.core.CurrentMillisClock;
2023
import com.google.api.core.InternalApi;
24+
import com.google.api.core.ObsoleteApi;
2125
import com.google.cloud.ExceptionHandler;
2226
import com.google.cloud.RetryHelper;
2327
import com.google.cloud.ServiceOptions;
@@ -56,7 +60,6 @@
5660
import java.util.logging.Logger;
5761
import java.util.zip.ZipEntry;
5862
import java.util.zip.ZipInputStream;
59-
import org.threeten.bp.Duration;
6063

6164
/** Utility class to start and stop a local service which is used by unit testing. */
6265
@InternalApi
@@ -112,14 +115,21 @@ protected final void startProcess(String blockUntilOutput)
112115
}
113116
}
114117

118+
/** This method is obsolete. Use {@link #waitForProcessDuration(java.time.Duration)} instead */
119+
@ObsoleteApi("Use waitForProcessDuration(java.time.Duration) instead")
120+
protected final int waitForProcess(org.threeten.bp.Duration timeout)
121+
throws IOException, InterruptedException, TimeoutException {
122+
return waitForProcessDuration(toJavaTimeDuration(timeout));
123+
}
124+
115125
/**
116126
* Waits for the local service's subprocess to terminate, and stop any possible thread listening
117127
* for its output.
118128
*/
119-
protected final int waitForProcess(Duration timeout)
129+
protected final int waitForProcessDuration(java.time.Duration timeout)
120130
throws IOException, InterruptedException, TimeoutException {
121131
if (activeRunner != null) {
122-
int exitCode = activeRunner.waitFor(timeout);
132+
int exitCode = activeRunner.waitForDuration(timeout);
123133
activeRunner = null;
124134
return exitCode;
125135
}
@@ -130,7 +140,7 @@ protected final int waitForProcess(Duration timeout)
130140
return 0;
131141
}
132142

133-
private static int waitForProcess(final Process process, Duration timeout)
143+
private static int waitForProcess(final Process process, java.time.Duration timeout)
134144
throws InterruptedException, TimeoutException {
135145
if (process == null) {
136146
return 0;
@@ -180,10 +190,17 @@ public String getProjectId() {
180190
/** Starts the local emulator. */
181191
public abstract void start() throws IOException, InterruptedException;
182192

183-
/** Stops the local emulator. */
184-
public abstract void stop(Duration timeout)
193+
/** This method is obsolete. Use {@link #stopDuration(java.time.Duration)} instead */
194+
@ObsoleteApi("Use stopDuration() instead")
195+
public abstract void stop(org.threeten.bp.Duration timeout)
185196
throws IOException, InterruptedException, TimeoutException;
186197

198+
/** Stops the local emulator. */
199+
public void stopDuration(java.time.Duration timeout)
200+
throws IOException, InterruptedException, TimeoutException {
201+
stop(toThreetenDuration(timeout));
202+
}
203+
187204
/** Resets the internal state of the emulator. */
188205
public abstract void reset() throws IOException;
189206

@@ -226,8 +243,15 @@ protected interface EmulatorRunner {
226243
/** Starts the emulator associated to this runner. */
227244
void start() throws IOException;
228245

246+
/** This method is obsolete. Use {@link #waitForDuration(java.time.Duration)} instead */
247+
@ObsoleteApi("Use waitForDuration() instead")
248+
int waitFor(org.threeten.bp.Duration timeout) throws InterruptedException, TimeoutException;
249+
229250
/** Wait for the emulator associated to this runner to terminate, returning the exit status. */
230-
int waitFor(Duration timeout) throws InterruptedException, TimeoutException;
251+
default int waitForDuration(java.time.Duration timeout)
252+
throws InterruptedException, TimeoutException {
253+
return waitFor(toThreetenDuration(timeout));
254+
}
231255

232256
/** Returns the process associated to the emulator, if any. */
233257
Process getProcess();
@@ -263,9 +287,17 @@ public void start() throws IOException {
263287
log.fine("Starting emulator via Google Cloud SDK");
264288
process = CommandWrapper.create().setCommand(commandText).setRedirectErrorStream().start();
265289
}
290+
/** This method is obsolete. Use {@link #waitForDuration(java.time.Duration)} instead */
291+
@ObsoleteApi("Use waitForDuration() instead")
292+
@Override
293+
public int waitFor(org.threeten.bp.Duration timeout)
294+
throws InterruptedException, TimeoutException {
295+
return waitForDuration(toJavaTimeDuration(timeout));
296+
}
266297

267298
@Override
268-
public int waitFor(Duration timeout) throws InterruptedException, TimeoutException {
299+
public int waitForDuration(java.time.Duration timeout)
300+
throws InterruptedException, TimeoutException {
269301
return waitForProcess(process, timeout);
270302
}
271303

@@ -374,8 +406,16 @@ public Path call() throws IOException {
374406
.start();
375407
}
376408

409+
/** This method is obsolete. Use {@link #waitForDuration(java.time.Duration)} instead */
410+
@ObsoleteApi("Use waitForDuration() instead")
377411
@Override
378-
public int waitFor(Duration timeout) throws InterruptedException, TimeoutException {
412+
public int waitFor(org.threeten.bp.Duration timeout)
413+
throws InterruptedException, TimeoutException {
414+
return waitForDuration(toJavaTimeDuration(timeout));
415+
}
416+
417+
public int waitForDuration(java.time.Duration timeout)
418+
throws InterruptedException, TimeoutException {
379419
return waitForProcess(process, timeout);
380420
}
381421

0 commit comments

Comments
 (0)