[FEATURE] Lossless java.time support in getObject(col, Class) for DATE/TIMESTAMP (BC and pre-1582 values)
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
Research direction
Start by reviewing the open pull request #1781 and compare its changes with the requested JDBC 4.2 mappings for getObject(int/String, Class). The issue names no source files or tests; its failing examples cover DATE and TIMESTAMP values, including BC and pre-1582 dates. Done means the requested java.time mappings preserve Arrow source values, unsupported mappings raise SQLException, and existing getters keep their behavior.
Written by the indexing model from the issue text.
Description
Is your feature request related to a problem? Please describe.
Databricks SQL stores DATE and TIMESTAMP on the proleptic Gregorian calendar (Spark 3+), and the server sends exact epoch-days and epoch-microseconds over Arrow. The driver converts them to java.sql.Date / java.sql.Timestamp with Date.valueOf(LocalDate) / Timestamp.valueOf(LocalDateTime). Those legacy types use the hybrid Julian/Gregorian calendar and have no BC era, so values before 1582-10-15 cannot be returned exactly. This is the same behaviour as Spark's own toJavaTimestamp/toJavaDate, and we are not asking to change it.
The problem is that the driver currently has no lossless alternative:
getObject(col, Instant.class),getObject(col, LocalDateTime.class)andgetObject(col, OffsetDateTime.class)return ajava.lang.String(theconvertSqlTypeToSpecificJavaTypefallback). The caller gets aClassCastException.getObject(col, LocalDate.class)is derived fromjava.sql.Date.toLocalDate(), so it inherits the same loss.
Observed on 3.4.4 (also 3.1.1) against a SQL warehouse, with JVM and session in UTC and Arrow results (the default). Server values come from UNIX_MICROS / UNIX_DATE:
| Value | Server | Driver |
|---|---|---|
TO_TIMESTAMP('2016 bc', 'yyyy G') |
-125754422400000000 µs |
getTimestamp().toInstant() = -2016-12-15T00:00:00Z (−17 days); toString() = 2016-01-01 00:00:00.0 (era lost) |
TIMESTAMP '1000-01-01 00:00:00 UTC' |
-30610224000000000 µs |
toInstant() = 1000-01-06T00:00:00Z (+5 days) |
CAST(... 2016 bc ... AS DATE) |
-1455491 days |
getDate() = 2016-01-01 (AD); getObject(col, LocalDate.class) = 2016-01-01 |
DATE '1582-10-05' |
-141437 days |
getDate() = 1582-10-15; getObject(col, LocalDate.class) = 1582-10-15 |
getObject(ts, Instant/LocalDateTime/OffsetDateTime) |
n/a | returns String "2016-01-01 00:00:00.0" |
Applications that need exact historical values (for example, ingesting tables containing BC or pre-1582 dates) can't get them from the driver today.
Describe the solution you'd like
Support the JDBC 4.2 java.time mappings in getObject(int/String, Class), populated straight from the source value (Arrow vector / server string), without going through java.sql types:
| Column | LocalDate |
LocalDateTime |
Instant |
OffsetDateTime |
|---|---|---|---|---|
| DATE | exact | atStartOfDay() |
error | error |
| TIMESTAMP_NTZ | date part | exact | error (no zone) | error (no zone) |
| TIMESTAMP | session-local date | session-local fields (java.time zone rules, matching Databricks rendering) | exact | exact, UTC offset |
- Leave existing getters (
getDate,getTimestamp,getString,getObject(int)and theCalendaroverloads) unchanged, so there is no behaviour change for existing users. - Requests that can't be satisfied (for example,
Instantfrom TIMESTAMP_NTZ, or a temporal class on a non-temporal column) raise a clearSQLExceptioninstead of returning aString. - Arrow paths are exact. Text result paths are best effort: SEA
JSON_ARRAYresults already truncate TIMESTAMP to milliseconds and drop the BC era on the server (TO_TIMESTAMP('2016 bc','yyyy G')arrives as2016-01-01T00:00:00.000Z).
This is what pgjdbc, Trino, mssql-jdbc, MySQL Connector/J and Arrow Flight SQL JDBC do: legacy types stay field-based, and java.time is lossless. It also matches Spark's own lossless route (spark.sql.datetime.java8API.enabled).
I'm happy to contribute a PR. Proposed split:
- Read side: the
getObject(col, java.time.*)support above. - Write side, separately:
setObject(OffsetDateTime), which is currently unsupported.- Explicit ISO formatting with signed years in place of
toString(). - Binding
LocalDateTimeasTIMESTAMP_NTZ.
Describe alternatives or workarounds you've considered
- Make
java.sql.Timestampinstant-preserving (build it from epoch millis rather thanTimestamp.valueOf). Rejected:- It changes
toString()/getString()for every pre-1582 value;1000-01-01would print as0999-12-27. That breaks read →setTimestampround-trips. - It adds minute-level drift for pre-1900 values on non-UTC JVMs (
java.util.TimeZonehas no local mean time). - It still can't represent BC, year 0 or 1582-10-05..14.
- It diverges from Spark and the other drivers above.
- It changes
- Calendar-mode connection property. Same trade-offs, and it adds a mode to the driver.
- Rewriting queries to return
unix_micros(col)/unix_date(col)in the application. This works but changes result schemas, and needs per-query special handling for nulls and TIMESTAMP_NTZ.
Additional context
Failing test (each one reads a value back and checks it is unchanged). Connection and session are in UTC. The comment on each assertion is what 3.4.4 actually returns:
@Test void dateInGregorianGap() throws SQLException {
ResultSet rs = stmt.executeQuery("SELECT DATE '1582-10-05'"); rs.next();
assertEquals(LocalDate.of(1582, 10, 5), rs.getObject(1, LocalDate.class));
// actual: 1582-10-15 (moved 10 days forward)
}
@Test void bcDate() throws SQLException {
ResultSet rs = stmt.executeQuery("SELECT DATE '-2015-01-01'"); rs.next(); // 2016 BC
assertEquals(LocalDate.of(-2015, 1, 1), rs.getObject(1, LocalDate.class));
// actual: 2016-01-01 (BC turned into AD)
}
@Test void timestampBefore1582() throws SQLException {
ResultSet rs = stmt.executeQuery("SELECT TIMESTAMP '1000-01-01 00:00:00 UTC'"); rs.next();
assertEquals(Instant.parse("1000-01-01T00:00:00Z"), rs.getTimestamp(1).toInstant());
// actual: 1000-01-06T00:00:00Z (5 days late)
}
@Test void getObjectInstant() throws SQLException {
ResultSet rs = stmt.executeQuery("SELECT TIMESTAMP '1000-01-01 00:00:00 UTC'"); rs.next();
assertEquals(Instant.class, rs.getObject(1, Instant.class).getClass());
// actual: java.lang.String "1000-01-01 00:00:00.0" (not an Instant)
}
Environment: macOS, Java 17, Databricks JDBC 3.4.4 (also reproduced on 3.1.1), Databricks SQL warehouse (DBSQL 2026.44).
- Dominant language
- Java
- Stars
- 41
- Forks
- 49
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 27
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from databricks/databricks-jdbc
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
databricks/databricks-jdbc#1782 ·
Maintainers usually reply within 1 day
-
[BUG] SEA metadata SHOW ... LIKE under-escapes backslashes; schema/table names containing '\' never matchPossibly taken @gibkim claimed this 6 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
databricks/databricks-jdbc#1598 ·
Maintainers usually reply within 1 day
-
engineer-bot
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
databricks/databricks-jdbc#1568 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 68/100
databricks/databricks-jdbc#1770 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
databricks/databricks-jdbc#1763 ·
Maintainers usually reply within 1 day
All issues in databricks/databricks-jdbc
Similar issues
-
type: possible bug
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
-
面渣逆袭Spring事务传播机制勘误Open
Difficulty 1/5 Under an hour Newbie friendliness 80/100
itwanger/toBeBetterJavaer#218 ·
-
Markdown and Astro language servers do not start when the project path contains a non-ASCII character or a spacePossibly taken @RoiSoleil claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
eclipse-wildwebdeveloper/wildwebdeveloper#2277 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
johanhaleby/occurrent#1462 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
SasanLabs/VulnerableApp#912 ·
Maintainers usually reply within 1 day