Skip to content

Commit ba2ae85

Browse files
committed
address comments
1 parent 1c7cc0c commit ba2ae85

6 files changed

Lines changed: 90 additions & 10 deletions

File tree

java-bigquery/google-cloud-bigquery-jdbc/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ drivers/**
22
target-it/**
33
*logs*/**
44
**/ITBigQueryJDBCLocalTest.java
5+
**/BigQueryStatementE2EBenchmark.java
56

67
tools/**/*.class
78
tools/**/*.jfr

java-bigquery/google-cloud-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ public Time coerce(Long value) {
200200
// Note: BQ Time has a precision of up to six fractional digits (microsecond precision)
201201
// but java.sql.Time only supports up to millisecond precision. So data after milliseconds is
202202
// truncated.
203-
return new Time(value / 1000);
203+
long millisOfDay = value / 1000;
204+
long localMillis = TimeZoneCache.getLocalMillis(millisOfDay);
205+
return new Time(localMillis);
204206
}
205207
}
206208

@@ -209,7 +211,7 @@ private static class LongToTimestamp implements BigQueryCoercion<Long, Timestamp
209211
@Override
210212
public Timestamp coerce(Long value) {
211213
// Long value is in microseconds. All further calculations should account for the unit.
212-
Instant instant = Instant.ofEpochMilli(value / 1000).plusNanos((value % 1000) * 1000);
214+
Instant instant = Instant.EPOCH.plus(value, ChronoUnit.MICROS);
213215
// Timezone-agnostic conversion preserving exact point in time as mandated by JDBC spec
214216
return Timestamp.from(instant);
215217
}
@@ -252,7 +254,7 @@ public Time coerce(FieldValue fieldValue) {
252254
long millisOfDay = TimeUnit.NANOSECONDS.toMillis(localTime.toNanoOfDay());
253255
// Adjust by local timezone offset to ensure correct wall-clock representation with
254256
// millisecond precision
255-
long localMillis = millisOfDay - java.util.TimeZone.getDefault().getOffset(millisOfDay);
257+
long localMillis = TimeZoneCache.getLocalMillis(millisOfDay);
256258
return new Time(localMillis);
257259
} catch (java.time.format.DateTimeParseException e) {
258260
IllegalArgumentException ex =
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.bigquery.jdbc;
18+
19+
import com.google.api.core.InternalApi;
20+
import java.util.TimeZone;
21+
22+
@InternalApi
23+
public class TimeZoneCache {
24+
private static volatile TimeZone defaultTimeZone = TimeZone.getDefault();
25+
26+
public static void reset() {
27+
defaultTimeZone = TimeZone.getDefault();
28+
}
29+
30+
public static long getLocalMillis(long millisOfDay) {
31+
return millisOfDay - defaultTimeZone.getOffset(millisOfDay);
32+
}
33+
}

java-bigquery/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/ArrowFormatTypeBigQueryCoercionUtilityTest.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,28 @@ public void nullToTime() {
164164

165165
@Test
166166
public void longToTime() {
167-
assertThat(INSTANCE.coerceTo(Time.class, 1408452095220000L))
168-
.isEqualTo(new Time(1408452095220L));
167+
long value = 1408452095220000L;
168+
// 1408452095220000 microseconds is 1408452095220 milliseconds.
169+
// Since the test runs under UTC timezone by TimeZoneRule, expected localMillis is
170+
// 1408452095220L.
171+
assertThat(INSTANCE.coerceTo(Time.class, value)).isEqualTo(new Time(1408452095220L));
172+
}
173+
174+
@Test
175+
public void longToTimeInNonUTCTimeZone() {
176+
java.util.TimeZone originalTimeZone = java.util.TimeZone.getDefault();
177+
try {
178+
java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("America/Los_Angeles"));
179+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
180+
long value = 1408452095220000L;
181+
// 1408452095220000 microseconds is 1408452095220 milliseconds.
182+
// Under America/Los_Angeles (PDT, -7 hours offset in Aug 2014), the subtracted offset
183+
// results in 1408452095220 - (-25200000) = 1408477295220L.
184+
assertThat(INSTANCE.coerceTo(Time.class, value)).isEqualTo(new Time(1408477295220L));
185+
} finally {
186+
java.util.TimeZone.setDefault(originalTimeZone);
187+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
188+
}
169189
}
170190

171191
@Test

java-bigquery/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.cloud.bigquery.FieldValueList;
3030
import com.google.cloud.bigquery.Range;
3131
import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionException;
32+
import com.google.cloud.bigquery.jdbc.rules.TimeZoneRule;
3233
import com.google.common.collect.ImmutableList;
3334
import java.math.BigDecimal;
3435
import java.sql.Date;
@@ -38,10 +39,12 @@
3839
import java.time.LocalDate;
3940
import java.time.LocalTime;
4041
import java.time.temporal.ChronoUnit;
41-
import java.util.concurrent.TimeUnit;
4242
import org.junit.jupiter.api.Test;
43+
import org.junit.jupiter.api.extension.RegisterExtension;
4344

4445
public class FieldValueTypeBigQueryCoercionUtilityTest {
46+
@RegisterExtension public final TimeZoneRule timeZoneRule = new TimeZoneRule("UTC");
47+
4548
private static final FieldValue STRING_VALUE = FieldValue.of(PRIMITIVE, "sample-string");
4649
private static final FieldValue INTEGER_VALUE = FieldValue.of(PRIMITIVE, "345");
4750
private static final FieldValue FLOAT_VALUE = FieldValue.of(PRIMITIVE, "345.21");
@@ -315,11 +318,27 @@ public void fieldValueToTimestampWhenInnerValueIsNull() {
315318
public void fieldValueToTime() {
316319
LocalTime expectedTime = LocalTime.of(23, 59, 59);
317320
assertThat(INSTANCE.coerceTo(Time.class, TIME_VALUE)).isEqualTo(Time.valueOf(expectedTime));
318-
LocalTime expectedTimeWithNanos = LocalTime.parse("23:59:59.99999");
319-
long millisOfDay = TimeUnit.NANOSECONDS.toMillis(expectedTimeWithNanos.toNanoOfDay());
320-
long localMillis = millisOfDay - java.util.TimeZone.getDefault().getOffset(millisOfDay);
321+
// expectedTimeWithNanos has 999 milliseconds, giving 86399999 ms of day.
322+
// Since the test runs under UTC timezone by TimeZoneRule, expected localMillis is 86399999L.
321323
assertThat(INSTANCE.coerceTo(Time.class, TIME_WITH_NANOSECOND_VALUE))
322-
.isEqualTo(new Time(localMillis));
324+
.isEqualTo(new Time(86399999L));
325+
}
326+
327+
@Test
328+
public void fieldValueToTimeInNonUTCTimeZone() {
329+
java.util.TimeZone originalTimeZone = java.util.TimeZone.getDefault();
330+
try {
331+
java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("America/Los_Angeles"));
332+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
333+
// 23:59:59.99999 yields 86399999 milliseconds.
334+
// Under America/Los_Angeles on 1970-01-01 (PST, -8 hours offset),
335+
// the subtracted offset results in 86399999 - (-28800000) = 115199999L.
336+
assertThat(INSTANCE.coerceTo(Time.class, TIME_WITH_NANOSECOND_VALUE))
337+
.isEqualTo(new Time(115199999L));
338+
} finally {
339+
java.util.TimeZone.setDefault(originalTimeZone);
340+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
341+
}
323342
}
324343

325344
@Test

java-bigquery/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/rules/TimeZoneRule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ public TimeZoneRule(String timeZoneId) {
3737
public void beforeAll(ExtensionContext context) {
3838
defaultTimeZone = TimeZone.getDefault();
3939
TimeZone.setDefault(TimeZone.getTimeZone(timeZoneId));
40+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
4041
}
4142

4243
@Override
4344
public void afterAll(ExtensionContext context) {
4445
TimeZone.setDefault(defaultTimeZone);
46+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
4547
}
4648

4749
@Override
@@ -50,17 +52,20 @@ public void beforeEach(ExtensionContext context) {
5052
defaultTimeZone = TimeZone.getDefault();
5153
}
5254
TimeZone.setDefault(TimeZone.getTimeZone(timeZoneId));
55+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
5356
}
5457

5558
@Override
5659
public void afterEach(ExtensionContext context) {
5760
if (defaultTimeZone != null) {
5861
TimeZone.setDefault(defaultTimeZone);
5962
}
63+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
6064
}
6165

6266
/** Public method to enforce the rule manually */
6367
public void enforce() {
6468
TimeZone.setDefault(TimeZone.getTimeZone(timeZoneId));
69+
com.google.cloud.bigquery.jdbc.TimeZoneCache.reset();
6570
}
6671
}

0 commit comments

Comments
 (0)