Skip to content

Commit 3f5500f

Browse files
committed
fix(bigquery-jdbc): normalize setTime from Calendar zone to JVM zone
1 parent 3e7a206 commit 3f5500f

6 files changed

Lines changed: 103 additions & 12 deletions

File tree

‎java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ public void setTime(String parameterName, Time value, Calendar calendar) throws
858858
checkClosed();
859859
this.parameterHandler.setParameter(
860860
parameterName,
861-
value == null ? null : BigQueryTemporalUtility.convertTimeWithCalendar(value, calendar),
861+
value == null ? null : BigQueryTemporalUtility.convertTimeToCalendar(value, calendar),
862862
Time.class,
863863
BigQueryStatementParameterType.IN,
864864
0);

‎java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatement.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ public void setDate(int parameterIndex, Date value, Calendar calendar) throws SQ
633633
@Override
634634
public void setTime(int parameterIndex, Time value, Calendar calendar) throws SQLException {
635635
checkClosed();
636-
setTime(parameterIndex, BigQueryTemporalUtility.convertTimeWithCalendar(value, calendar));
636+
setTime(parameterIndex, BigQueryTemporalUtility.convertTimeToCalendar(value, calendar));
637637
}
638638

639639
@Override

‎java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,4 +478,28 @@ static Time convertTimeWithCalendar(Time time, Calendar cal) {
478478
targetCal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND));
479479
return new Time(targetCal.getTimeInMillis());
480480
}
481+
482+
/**
483+
* Converts a {@link Time} for writing/inbound parameter setting operations (e.g., {@code
484+
* setTime(..., Calendar)}) by extracting its local time fields as interpreted in the target
485+
* {@link Calendar}'s timezone and normalizing them back into 1970-01-01 in the system-default
486+
* timezone.
487+
*
488+
* @param time the time instant specified relative to the target Calendar
489+
* @param cal the Calendar containing the source timezone
490+
* @return the normalized Time on 1970-01-01 in the system-default timezone
491+
*/
492+
static Time convertTimeToCalendar(Time time, Calendar cal) {
493+
if (time == null || cal == null) {
494+
return time;
495+
}
496+
ZoneId systemZone = ZoneId.systemDefault();
497+
ZoneId targetZone = cal.getTimeZone().toZoneId();
498+
if (systemZone.equals(targetZone)) {
499+
return time;
500+
}
501+
LocalTime localTime = Instant.ofEpochMilli(time.getTime()).atZone(targetZone).toLocalTime();
502+
ZonedDateTime zdt = localTime.atDate(LocalDate.of(1970, 1, 1)).atZone(systemZone);
503+
return new Time(zdt.toInstant().toEpochMilli());
504+
}
481505
}

‎java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatementTest.java‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,19 +1029,22 @@ public void testSetTimeParamByName() throws SQLException {
10291029
assertEquals(expected, actual);
10301030
}
10311031

1032-
@Test
1033-
public void testSetTimeCalParamByName() throws SQLException {
1032+
@ParameterizedTest
1033+
@ValueSource(strings = {"UTC", "America/New_York", "Asia/Tokyo"})
1034+
public void testSetTimeCalParamByName(String zoneId) throws SQLException {
10341035
BigQueryCallableStatement statement =
10351036
new BigQueryCallableStatement(bigQueryConnection, "call testProc('?')");
10361037
assertNotNull(statement);
1037-
Time expectedTime = Time.valueOf("12:34:56");
1038-
Calendar expectedCal = Calendar.getInstance();
1038+
Calendar expectedCal = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
1039+
expectedCal.clear();
1040+
expectedCal.set(1970, Calendar.JANUARY, 1, 12, 34, 56);
1041+
Time expectedTime = new Time(expectedCal.getTimeInMillis());
10391042

10401043
statement.setTime(PARAM_KEY, expectedTime, expectedCal);
10411044
Time actual = statement.getTime(PARAM_KEY);
1042-
assertNotNull(actual);
1045+
assertEquals("12:34:56", actual.toString());
10431046
actual = statement.getTime(PARAM_KEY, expectedCal);
1044-
assertNotNull(actual);
1047+
assertEquals(expectedTime, actual);
10451048
}
10461049

10471050
@Test

‎java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatementSettersTest.java‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,20 @@ public void testSetDateWithCalendar() throws Exception {
224224
assertEquals(originalMillis, cal.getTimeInMillis());
225225
}
226226

227-
@Test
228-
public void testSetTimeWithCalendar() throws Exception {
229-
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
227+
@ParameterizedTest
228+
@ValueSource(strings = {"UTC", "America/New_York", "Asia/Tokyo"})
229+
public void testSetTimeWithCalendar(String zoneId) throws Exception {
230+
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
230231
cal.setTimeInMillis(1000000L);
231232
long originalMillis = cal.getTimeInMillis();
232-
Time time = new Time(43200000L); // 12:00:00
233+
Calendar sourceCal = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
234+
sourceCal.clear();
235+
sourceCal.set(1970, Calendar.JANUARY, 1, 12, 34, 56);
236+
Time time = new Time(sourceCal.getTimeInMillis());
233237

234238
preparedStatement.setTime(1, time, cal);
235239
assertEquals(Time.class, preparedStatement.parameterHandler.getType(1));
240+
assertEquals("12:34:56", preparedStatement.parameterHandler.getParameter(1).toString());
236241
assertEquals(originalMillis, cal.getTimeInMillis());
237242
}
238243

‎java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtilityTest.java‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,65 @@ public void testConvertTimeWithCalendar() {
148148
assertThat(resultCal.get(Calendar.SECOND)).isEqualTo(45);
149149
}
150150

151+
@Test
152+
public void testConvertTimeToCalendar() {
153+
Time time = Time.valueOf("15:30:45");
154+
Calendar cal = Calendar.getInstance();
155+
156+
// Null checks
157+
assertThat(BigQueryTemporalUtility.convertTimeToCalendar(null, cal)).isNull();
158+
assertThat(BigQueryTemporalUtility.convertTimeToCalendar(time, null)).isSameInstanceAs(time);
159+
160+
// Same timezone fast-path
161+
Calendar sameZoneCal = Calendar.getInstance(TimeZone.getDefault());
162+
assertThat(BigQueryTemporalUtility.convertTimeToCalendar(time, sameZoneCal))
163+
.isSameInstanceAs(time);
164+
165+
// Different timezone
166+
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
167+
Calendar tokyoCal = Calendar.getInstance(TimeZone.getTimeZone(tokyoZone));
168+
long tokyoTimeMillis =
169+
LocalTime.of(15, 30, 45)
170+
.atDate(LocalDate.of(1970, 1, 1))
171+
.atZone(tokyoZone)
172+
.toInstant()
173+
.toEpochMilli();
174+
Time result =
175+
BigQueryTemporalUtility.convertTimeToCalendar(new Time(tokyoTimeMillis), tokyoCal);
176+
long expectedSystemMillis =
177+
LocalTime.of(15, 30, 45)
178+
.atDate(LocalDate.of(1970, 1, 1))
179+
.atZone(ZoneId.systemDefault())
180+
.toInstant()
181+
.toEpochMilli();
182+
assertThat(result.getTime()).isEqualTo(expectedSystemMillis);
183+
assertThat(result.toString()).isEqualTo("15:30:45");
184+
}
185+
186+
@Test
187+
public void testCrossTimezoneTimeRoundtrip() {
188+
ZoneId writeZone = ZoneId.of("America/Los_Angeles");
189+
ZoneId readZone = ZoneId.of("Asia/Tokyo");
190+
Calendar writeCal = Calendar.getInstance(TimeZone.getTimeZone(writeZone));
191+
Calendar readCal = Calendar.getInstance(TimeZone.getTimeZone(readZone));
192+
193+
Time inputTime =
194+
new Time(
195+
LocalTime.of(23, 15, 30)
196+
.atDate(LocalDate.of(1970, 1, 1))
197+
.atZone(writeZone)
198+
.toInstant()
199+
.toEpochMilli());
200+
Time normalizedTime = BigQueryTemporalUtility.convertTimeToCalendar(inputTime, writeCal);
201+
Time readTime = BigQueryTemporalUtility.convertTimeWithCalendar(normalizedTime, readCal);
202+
203+
Calendar verifyCal = Calendar.getInstance(TimeZone.getTimeZone(readZone));
204+
verifyCal.setTime(readTime);
205+
assertThat(verifyCal.get(Calendar.HOUR_OF_DAY)).isEqualTo(23);
206+
assertThat(verifyCal.get(Calendar.MINUTE)).isEqualTo(15);
207+
assertThat(verifyCal.get(Calendar.SECOND)).isEqualTo(30);
208+
}
209+
151210
@Test
152211
public void testBoxDateTimeAndDate() {
153212
Timestamp ts =

0 commit comments

Comments
 (0)