Skip to content

Commit 0557e69

Browse files
authored
feat(bigquery-jdbc): implement TypeRegistry and TypeDescriptor (#13947)
1 parent d945a69 commit 0557e69

3 files changed

Lines changed: 627 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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 java.sql.Date;
20+
import java.sql.Time;
21+
import java.sql.Timestamp;
22+
import java.time.Instant;
23+
import java.time.LocalDate;
24+
import java.time.LocalDateTime;
25+
import java.time.LocalTime;
26+
import java.time.ZoneId;
27+
import java.util.Calendar;
28+
29+
/**
30+
* A highly optimized utility for bridging BigQuery's civil time and absolute time semantics to
31+
* legacy JDBC Date/Time/Timestamp classes using JSR-310 timezone anchoring.
32+
*/
33+
final class BigQueryTemporalUtility {
34+
35+
private BigQueryTemporalUtility() {}
36+
37+
/**
38+
* Converts a BigQuery civil DATETIME string into an absolute Timestamp by anchoring it to the
39+
* provided timezone (or JVM default if null).
40+
*/
41+
public static Timestamp boxDateTime(String val, ZoneId zoneId) {
42+
ZoneId targetZone = zoneId != null ? zoneId : ZoneId.systemDefault();
43+
String isoString = val.replace(' ', 'T');
44+
return Timestamp.from(LocalDateTime.parse(isoString).atZone(targetZone).toInstant());
45+
}
46+
47+
/**
48+
* Converts a BigQuery civil DATE string into an absolute Date by anchoring it to midnight of the
49+
* provided timezone (or JVM default if null).
50+
*/
51+
public static Date boxDate(String val, ZoneId zoneId) {
52+
ZoneId targetZone = zoneId != null ? zoneId : ZoneId.systemDefault();
53+
return new Date(LocalDate.parse(val).atStartOfDay(targetZone).toInstant().toEpochMilli());
54+
}
55+
56+
/**
57+
* Converts a BigQuery civil TIME string into an absolute Time. If a ZoneId is provided (e.g. from
58+
* the legacy JDBC 3.0 getTime(col, Calendar) API), this uses legacy Calendar manipulation to
59+
* strictly mimic older JVM historical DST quirks for 1970. If no ZoneId is provided (e.g. modern
60+
* JDBC 4.2 getObject(col, LocalTime.class)), this uses pure JSR-310 math which guarantees
61+
* perfectly accurate modern conversions.
62+
*/
63+
public static Time boxTime(String val, ZoneId zoneId) {
64+
LocalTime localTime = LocalTime.parse(val);
65+
66+
if (zoneId == null) {
67+
// JDBC 4.2 Modern API (no Calendar provided):
68+
// Use pure JSR-310 math for perfectly accurate modern conversions without Calendar quirks.
69+
return new Time(
70+
localTime
71+
.atDate(LocalDate.of(1970, 1, 1))
72+
.atZone(ZoneId.systemDefault())
73+
.toInstant()
74+
.toEpochMilli());
75+
}
76+
77+
// Legacy JDBC 3.0 API (Calendar provided):
78+
// Use legacy Calendar manipulation to intentionally replicate old JVM historical DST quirks
79+
// for January 1, 1970, ensuring strict backwards compatibility for legacy ORMs.
80+
Calendar targetCal = Calendar.getInstance(java.util.TimeZone.getTimeZone(zoneId));
81+
targetCal.set(Calendar.YEAR, 1970);
82+
targetCal.set(Calendar.MONTH, Calendar.JANUARY);
83+
targetCal.set(Calendar.DAY_OF_MONTH, 1);
84+
targetCal.set(Calendar.HOUR_OF_DAY, localTime.getHour());
85+
targetCal.set(Calendar.MINUTE, localTime.getMinute());
86+
targetCal.set(Calendar.SECOND, localTime.getSecond());
87+
targetCal.set(Calendar.MILLISECOND, localTime.getNano() / 1_000_000);
88+
89+
return new Time(targetCal.getTimeInMillis());
90+
}
91+
92+
/**
93+
* Converts a BigQuery absolute TIMESTAMP string into a legacy Timestamp. Because it is absolute,
94+
* the Calendar timezone is explicitly ignored per JDBC 4.2 spec.
95+
*/
96+
public static Timestamp boxTimestamp(String val) {
97+
String iso = val;
98+
// Handle the " UTC" suffix format
99+
if (iso.endsWith(" UTC")) {
100+
iso = iso.substring(0, iso.length() - 4) + "Z";
101+
}
102+
// Replace the date-time space separator with 'T' (e.g. 2023-10-01 12:00:00 ->
103+
// 2023-10-01T12:00:00)
104+
if (iso.length() > 10 && iso.charAt(10) == ' ') {
105+
iso = iso.substring(0, 10) + 'T' + iso.substring(11);
106+
}
107+
108+
try {
109+
return Timestamp.from(Instant.parse(iso));
110+
} catch (java.time.format.DateTimeParseException e) {
111+
// Fallback for non-standard formats
112+
return Timestamp.valueOf(val);
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)