Skip to content

Commit f202c3b

Browse files
authoredNov 14, 2024··
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

‎java-core/google-cloud-core/src/test/java/com/google/cloud/RetryOptionTest.java

+32-13
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,30 @@
2020
import static org.junit.jupiter.api.Assertions.assertNotEquals;
2121

2222
import com.google.api.gax.retrying.RetrySettings;
23+
import java.time.Duration;
2324
import org.junit.jupiter.api.Test;
24-
import org.threeten.bp.Duration;
2525

2626
class RetryOptionTest {
27+
private static final long TOTAL_TIMEOUT_MILLIS = 420l;
28+
private static final long INITIAL_RETRY_DELAY_MILLIS = 42l;
29+
private static final long MAX_RETRY_DELAY_MILLIS = 100l;
2730

2831
private static final RetryOption TOTAL_TIMEOUT =
29-
RetryOption.totalTimeout(Duration.ofMillis(420L));
32+
RetryOption.totalTimeoutDuration(Duration.ofMillis(TOTAL_TIMEOUT_MILLIS));
3033
private static final RetryOption INITIAL_RETRY_DELAY =
31-
RetryOption.initialRetryDelay(Duration.ofMillis(42L));
34+
RetryOption.initialRetryDelayDuration(Duration.ofMillis(INITIAL_RETRY_DELAY_MILLIS));
3235
private static final RetryOption RETRY_DELAY_MULTIPLIER = RetryOption.retryDelayMultiplier(1.5);
3336
private static final RetryOption MAX_RETRY_DELAY =
34-
RetryOption.maxRetryDelay(Duration.ofMillis(100));
37+
RetryOption.maxRetryDelayDuration(Duration.ofMillis(MAX_RETRY_DELAY_MILLIS));
3538
private static final RetryOption MAX_ATTEMPTS = RetryOption.maxAttempts(100);
3639
private static final RetryOption JITTERED = RetryOption.jittered(false);
3740

3841
private static final RetrySettings retrySettings =
3942
RetrySettings.newBuilder()
40-
.setTotalTimeout(Duration.ofMillis(420L))
41-
.setInitialRetryDelay(Duration.ofMillis(42L))
43+
.setTotalTimeoutDuration(Duration.ofMillis(420L))
44+
.setInitialRetryDelayDuration(Duration.ofMillis(42L))
4245
.setRetryDelayMultiplier(1.5)
43-
.setMaxRetryDelay(Duration.ofMillis(100))
46+
.setMaxRetryDelayDuration(Duration.ofMillis(100))
4447
.setMaxAttempts(100)
4548
.setJittered(false)
4649
.build();
@@ -61,10 +64,10 @@ void testEqualsAndHashCode() {
6164
assertNotEquals(MAX_ATTEMPTS, MAX_RETRY_DELAY);
6265
assertNotEquals(JITTERED, MAX_ATTEMPTS);
6366

64-
RetryOption totalTimeout = RetryOption.totalTimeout(Duration.ofMillis(420L));
65-
RetryOption initialRetryDelay = RetryOption.initialRetryDelay(Duration.ofMillis(42L));
67+
RetryOption totalTimeout = RetryOption.totalTimeoutDuration(Duration.ofMillis(420L));
68+
RetryOption initialRetryDelay = RetryOption.initialRetryDelayDuration(Duration.ofMillis(42L));
6669
RetryOption retryDelayMultiplier = RetryOption.retryDelayMultiplier(1.5);
67-
RetryOption maxRetryDelay = RetryOption.maxRetryDelay(Duration.ofMillis(100));
70+
RetryOption maxRetryDelay = RetryOption.maxRetryDelayDuration(Duration.ofMillis(100));
6871
RetryOption maxAttempts = RetryOption.maxAttempts(100);
6972
RetryOption jittered = RetryOption.jittered(false);
7073

@@ -101,17 +104,17 @@ void testMergeToSettings() {
101104
assertEquals(retrySettings, mergedRetrySettings);
102105

103106
defRetrySettings =
104-
defRetrySettings.toBuilder().setTotalTimeout(Duration.ofMillis(420L)).build();
107+
defRetrySettings.toBuilder().setTotalTimeoutDuration(Duration.ofMillis(420L)).build();
105108
mergedRetrySettings = RetryOption.mergeToSettings(defRetrySettings, TOTAL_TIMEOUT);
106109
assertEquals(defRetrySettings, mergedRetrySettings);
107110

108111
defRetrySettings =
109-
defRetrySettings.toBuilder().setMaxRetryDelay(Duration.ofMillis(100)).build();
112+
defRetrySettings.toBuilder().setMaxRetryDelayDuration(Duration.ofMillis(100)).build();
110113
mergedRetrySettings = RetryOption.mergeToSettings(defRetrySettings, MAX_RETRY_DELAY);
111114
assertEquals(defRetrySettings, mergedRetrySettings);
112115

113116
defRetrySettings =
114-
defRetrySettings.toBuilder().setInitialRetryDelay(Duration.ofMillis(42L)).build();
117+
defRetrySettings.toBuilder().setInitialRetryDelayDuration(Duration.ofMillis(42L)).build();
115118
mergedRetrySettings = RetryOption.mergeToSettings(defRetrySettings, INITIAL_RETRY_DELAY);
116119
assertEquals(defRetrySettings, mergedRetrySettings);
117120

@@ -127,4 +130,20 @@ void testMergeToSettings() {
127130
mergedRetrySettings = RetryOption.mergeToSettings(defRetrySettings, JITTERED);
128131
assertEquals(defRetrySettings, mergedRetrySettings);
129132
}
133+
134+
@Test
135+
public void threetenMethods_producesEquivalentJavaTimeRetryOptions() {
136+
137+
final RetryOption totalTimeoutThreeten =
138+
RetryOption.totalTimeout(org.threeten.bp.Duration.ofMillis(TOTAL_TIMEOUT_MILLIS));
139+
final RetryOption initialRetryDelayThreeten =
140+
RetryOption.initialRetryDelay(
141+
org.threeten.bp.Duration.ofMillis(INITIAL_RETRY_DELAY_MILLIS));
142+
final RetryOption maxRetryDelayThreeten =
143+
RetryOption.maxRetryDelay(org.threeten.bp.Duration.ofMillis(MAX_RETRY_DELAY_MILLIS));
144+
145+
assertEquals(TOTAL_TIMEOUT, totalTimeoutThreeten);
146+
assertEquals(INITIAL_RETRY_DELAY, initialRetryDelayThreeten);
147+
assertEquals(MAX_RETRY_DELAY, maxRetryDelayThreeten);
148+
}
130149
}

‎java-core/google-cloud-core/src/test/java/com/google/cloud/SerializationTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import com.google.common.collect.ImmutableList;
2424
import com.google.common.collect.ImmutableMap;
2525
import java.io.Serializable;
26-
import org.threeten.bp.Duration;
26+
import java.time.Duration;
2727

2828
public class SerializationTest extends BaseSerializationTest {
2929

@@ -37,7 +37,7 @@ public class SerializationTest extends BaseSerializationTest {
3737
private static final Role SOME_ROLE = Role.viewer();
3838
private static final Policy SOME_IAM_POLICY = Policy.newBuilder().build();
3939
private static final RetryOption CHECKING_PERIOD =
40-
RetryOption.initialRetryDelay(Duration.ofSeconds(42));
40+
RetryOption.initialRetryDelayDuration(Duration.ofSeconds(42));
4141
private static final LabelDescriptor LABEL_DESCRIPTOR =
4242
new LabelDescriptor("project_id", ValueType.STRING, "The project id");
4343
private static final MonitoredResourceDescriptor MONITORED_RESOURCE_DESCRIPTOR =

‎java-core/google-cloud-core/src/test/java/com/google/cloud/TimestampTest.java

+68-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.jupiter.api.Assertions.assertThrows;
2323

2424
import com.google.common.testing.EqualsTester;
25+
import java.time.format.DateTimeParseException;
2526
import java.util.Calendar;
2627
import java.util.Date;
2728
import java.util.GregorianCalendar;
@@ -237,14 +238,67 @@ void parseTimestampWithoutTimeZoneOffset() {
237238

238239
@Test
239240
void parseTimestampWithTimeZoneOffset() {
240-
assertThat(Timestamp.parseTimestamp("0001-01-01T00:00:00-00:00"))
241+
// Max values
242+
assertThat(Timestamp.parseTimestampDuration("0001-01-01T00:00:00-00:00"))
241243
.isEqualTo(Timestamp.MIN_VALUE);
242-
assertThat(Timestamp.parseTimestamp("9999-12-31T23:59:59.999999999-00:00"))
244+
assertThat(Timestamp.parseTimestampDuration("9999-12-31T23:59:59.999999999-00:00"))
243245
.isEqualTo(Timestamp.MAX_VALUE);
244-
assertThat(Timestamp.parseTimestamp("2020-12-06T19:21:12.123+05:30"))
246+
// Extreme values (close to min/max)
247+
assertThat(Timestamp.parseTimestampDuration("0001-01-01T00:00:00.000000001Z"))
248+
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(Timestamp.MIN_VALUE.getSeconds(), 1));
249+
assertThat(Timestamp.parseTimestampDuration("9999-12-31T23:59:59.999999998Z"))
250+
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(Timestamp.MAX_VALUE.getSeconds(), 999999998));
251+
// Common use cases
252+
assertThat(Timestamp.parseTimestampDuration("2020-07-10T14:03:00.123-07:00"))
253+
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(1594414980, 123000000));
254+
assertThat(Timestamp.parseTimestampDuration("2020-12-06T19:21:12.123+05:30"))
245255
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(1607262672, 123000000));
246-
assertThat(Timestamp.parseTimestamp("2020-07-10T14:03:00-07:00"))
256+
// We also confirm that parsing a timestamp with nano precision will behave the same as the
257+
// threeten counterpart
258+
assertThat(Timestamp.parseTimestampDuration("2020-12-06T19:21:12.123+05:30"))
259+
.isEqualTo(Timestamp.parseTimestamp("2020-12-06T19:21:12.123+05:30"));
260+
// Timestamps with fractional seconds at nanosecond level
261+
assertThat(Timestamp.parseTimestampDuration("2020-12-06T19:21:12.123456789+05:30"))
262+
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(1607262672, 123456789));
263+
// Fractional seconds beyond nanos should throw an exception
264+
assertThrows(
265+
DateTimeParseException.class,
266+
() -> Timestamp.parseTimestampDuration("2020-12-06T19:21:12.123456789321+05:30"));
267+
// Missing components (should throw exceptions)
268+
assertThrows(
269+
DateTimeParseException.class, () -> Timestamp.parseTimestampDuration("2020-12-06"));
270+
// Whitespace should not be supported
271+
assertThrows(
272+
DateTimeParseException.class,
273+
() -> Timestamp.parseTimestampDuration(" 2020-12-06T19:21:12.123+05:30 "));
274+
// It should be case-insensitive
275+
assertThat(Timestamp.parseTimestampDuration("2020-07-10t14:03:00-07:00"))
247276
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(1594414980, 0));
277+
// Invalid time zone offsets
278+
assertThrows(
279+
DateTimeParseException.class,
280+
() -> Timestamp.parseTimestampDuration("2020-12-06T19:21:12.123+25:00"));
281+
assertThrows(
282+
DateTimeParseException.class,
283+
() -> Timestamp.parseTimestampDuration("2020-12-06T19:21:12.123-25:00"));
284+
// Int values for SecondOfMinute should be between 0 and 59
285+
assertThrows(
286+
DateTimeParseException.class,
287+
() -> Timestamp.parseTimestampDuration("2016-12-31T23:59:60Z"));
288+
}
289+
290+
@Test
291+
void parseTimestampWithZoneString() {
292+
// Valid RFC 3339 timestamps with time zone names
293+
assertThat(Timestamp.parseTimestampDuration("2020-12-06T08:51:12.123America/Toronto"))
294+
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(1607262672, 123000000));
295+
assertThat(Timestamp.parseTimestampDuration("2023-04-10T22:42:10.123456789Europe/London"))
296+
.isEqualTo(Timestamp.ofTimeSecondsAndNanos(1681162930, 123456789));
297+
298+
// Invalid time zone names
299+
assertThrows(
300+
DateTimeParseException.class,
301+
() -> Timestamp.parseTimestampDuration("2020-12-06T19:21:12.123Invalid/TimeZone"));
248302
}
249303

250304
@Test
@@ -281,4 +335,14 @@ void comparable() {
281335
void serialization() {
282336
reserializeAndAssert(Timestamp.parseTimestamp("9999-12-31T23:59:59.999999999Z"));
283337
}
338+
339+
@Test
340+
void parseInvalidTimestampThreetenThrowsThreetenException() {
341+
assertThrows(
342+
org.threeten.bp.format.DateTimeParseException.class,
343+
() -> Timestamp.parseTimestamp("00x1-01-01T00:00:00"));
344+
assertThrows(
345+
java.time.format.DateTimeParseException.class,
346+
() -> Timestamp.parseTimestampDuration("00x1-01-01T00:00:00"));
347+
}
284348
}

‎java-core/google-cloud-core/src/test/java/com/google/cloud/testing/BaseEmulatorHelperTest.java

+34-6
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
import java.net.URL;
2929
import java.net.URLConnection;
3030
import java.net.URLStreamHandler;
31+
import java.time.Duration;
3132
import java.util.List;
3233
import java.util.concurrent.TimeoutException;
3334
import java.util.logging.Logger;
3435
import org.easymock.EasyMock;
3536
import org.junit.jupiter.api.Test;
36-
import org.threeten.bp.Duration;
3737

3838
class BaseEmulatorHelperTest {
3939

@@ -71,10 +71,18 @@ public void start() throws IOException, InterruptedException {
7171
}
7272

7373
@Override
74-
public void stop(Duration timeout) throws IOException, InterruptedException, TimeoutException {
74+
public void stop(org.threeten.bp.Duration timeout)
75+
throws IOException, InterruptedException, TimeoutException {
76+
// we call the threeten method directly to confirm behavior
7577
waitForProcess(timeout);
7678
}
7779

80+
@Override
81+
public void stopDuration(Duration timeout)
82+
throws IOException, InterruptedException, TimeoutException {
83+
super.stopDuration(timeout);
84+
}
85+
7886
@Override
7987
public void reset() throws IOException {
8088
// do nothing
@@ -91,13 +99,33 @@ void testEmulatorHelper() throws IOException, InterruptedException, TimeoutExcep
9199
emulatorRunner.start();
92100
EasyMock.expectLastCall();
93101
EasyMock.expect(emulatorRunner.getProcess()).andReturn(process);
94-
emulatorRunner.waitFor(Duration.ofMinutes(1));
102+
emulatorRunner.waitForDuration(Duration.ofMinutes(1));
103+
EasyMock.expectLastCall().andReturn(0);
104+
EasyMock.replay(process, emulatorRunner);
105+
TestEmulatorHelper helper =
106+
new TestEmulatorHelper(ImmutableList.of(emulatorRunner), BLOCK_UNTIL);
107+
helper.start();
108+
helper.stopDuration(Duration.ofMinutes(1));
109+
EasyMock.verify();
110+
}
111+
112+
@Test
113+
void testEmulatorHelperThreeten() throws IOException, InterruptedException, TimeoutException {
114+
Process process = EasyMock.createStrictMock(Process.class);
115+
InputStream stream = new ByteArrayInputStream(BLOCK_UNTIL.getBytes(Charsets.UTF_8));
116+
EmulatorRunner emulatorRunner = EasyMock.createStrictMock(EmulatorRunner.class);
117+
EasyMock.expect(process.getInputStream()).andReturn(stream);
118+
EasyMock.expect(emulatorRunner.isAvailable()).andReturn(true);
119+
emulatorRunner.start();
120+
EasyMock.expectLastCall();
121+
EasyMock.expect(emulatorRunner.getProcess()).andReturn(process);
122+
emulatorRunner.waitForDuration(java.time.Duration.ofMinutes(1));
95123
EasyMock.expectLastCall().andReturn(0);
96124
EasyMock.replay(process, emulatorRunner);
97125
TestEmulatorHelper helper =
98126
new TestEmulatorHelper(ImmutableList.of(emulatorRunner), BLOCK_UNTIL);
99127
helper.start();
100-
helper.stop(Duration.ofMinutes(1));
128+
helper.stop(org.threeten.bp.Duration.ofMinutes(1));
101129
EasyMock.verify();
102130
}
103131

@@ -157,13 +185,13 @@ void testEmulatorHelperMultipleRunners()
157185
secondRunner.start();
158186
EasyMock.expectLastCall();
159187
EasyMock.expect(secondRunner.getProcess()).andReturn(process);
160-
secondRunner.waitFor(Duration.ofMinutes(1));
188+
secondRunner.waitForDuration(Duration.ofMinutes(1));
161189
EasyMock.expectLastCall().andReturn(0);
162190
EasyMock.replay(process, secondRunner);
163191
TestEmulatorHelper helper =
164192
new TestEmulatorHelper(ImmutableList.of(firstRunner, secondRunner), BLOCK_UNTIL);
165193
helper.start();
166-
helper.stop(Duration.ofMinutes(1));
194+
helper.stopDuration(Duration.ofMinutes(1));
167195
EasyMock.verify();
168196
}
169197

0 commit comments

Comments
 (0)
Please sign in to comment.