3939import java .sql .Time ;
4040import java .sql .Timestamp ;
4141import java .sql .Types ;
42+ import java .time .Instant ;
4243import java .util .ArrayList ;
4344import java .util .Calendar ;
4445import java .util .List ;
@@ -161,26 +162,33 @@ protected Accessor createAccessor(ColumnMetaData columnMetaData,
161162 throw new AssertionError ("bad " + columnMetaData .type .rep );
162163 }
163164 case Types .TIMESTAMP :
165+ // TIMESTAMP WITH LOCAL TIME ZONE is a standard ISO type without proper JDBC support.
166+ // It represents a global instant in time, as opposed to local clock/calendar parameters,
167+ // so avoid normalizing against the local calendar by setting that to null for this type.
168+ Calendar effectiveCalendar =
169+ "TIMESTAMP_WITH_LOCAL_TIME_ZONE" .equals (columnMetaData .type .getName ())
170+ ? null
171+ : localCalendar ;
164172 switch (columnMetaData .type .rep ) {
165173 case PRIMITIVE_LONG :
166174 case LONG :
167175 case NUMBER :
168- return new TimestampFromNumberAccessor (getter , localCalendar );
176+ return new TimestampFromNumberAccessor (getter , effectiveCalendar );
169177 case JAVA_SQL_TIMESTAMP :
170178 return new TimestampAccessor (getter );
171179 case JAVA_UTIL_DATE :
172- return new TimestampFromUtilDateAccessor (getter , localCalendar );
180+ return new TimestampFromUtilDateAccessor (getter , effectiveCalendar );
173181 default :
174182 throw new AssertionError ("bad " + columnMetaData .type .rep );
175183 }
176- case 2013 : // TIME_WITH_TIMEZONE
184+ case Types . TIME_WITH_TIMEZONE :
177185 switch (columnMetaData .type .rep ) {
178186 case STRING :
179187 return new StringAccessor (getter );
180188 default :
181189 throw new AssertionError ("bad " + columnMetaData .type .rep );
182190 }
183- case 2014 : // TIMESTAMP_WITH_TIMEZONE
191+ case Types . TIMESTAMP_WITH_TIMEZONE :
184192 switch (columnMetaData .type .rep ) {
185193 case STRING :
186194 return new StringAccessor (getter );
@@ -276,11 +284,31 @@ static Time intToTime(int v, Calendar calendar) {
276284 return new Time (v );
277285 }
278286
279- static Timestamp longToTimestamp (long v , Calendar calendar ) {
287+ /**
288+ * Interpret a {@link Number} as a {@link Timestamp}.
289+ *
290+ * If the number is a {@link BigDecimal}, assume it represents seconds since epoch with up to
291+ * nanosecond precision. If it is any other {@link Number}, truncate it to an integer and assume
292+ * it represents milliseconds since epoch.
293+ *
294+ * @param v The number to convert
295+ * @param calendar Subtract the time zone offset of this calendar from the result
296+ */
297+ static Timestamp numberToTimestamp (Number v , Calendar calendar ) {
298+ Instant instant ;
299+ if (v instanceof BigDecimal ) {
300+ // May overflow if the value is > ~292 *billion* years away from epoch in either direction.
301+ long wholeSeconds = v .longValue ();
302+ long nanoSeconds = ((BigDecimal ) v ).remainder (BigDecimal .ONE ).movePointRight (9 ).longValue ();
303+ instant = Instant .ofEpochSecond (wholeSeconds , nanoSeconds );
304+ } else {
305+ // May overflow if the value is > ~292 *million* years away from epoch in either direction.
306+ instant = Instant .ofEpochMilli (v .longValue ());
307+ }
280308 if (calendar != null ) {
281- v -= calendar .getTimeZone ().getOffset (v );
309+ instant = instant . minusMillis ( calendar .getTimeZone ().getOffset (instant . toEpochMilli ()) );
282310 }
283- return new Timestamp ( v );
311+ return Timestamp . from ( instant );
284312 }
285313
286314 /** Implementation of {@link Cursor.Accessor}. */
@@ -934,8 +962,7 @@ private DateFromNumberAccessor(Getter getter, Calendar localCalendar) {
934962 if (v == null ) {
935963 return null ;
936964 }
937- return longToTimestamp (v .longValue () * DateTimeUtils .MILLIS_PER_DAY ,
938- calendar );
965+ return numberToTimestamp (v .longValue () * DateTimeUtils .MILLIS_PER_DAY , calendar );
939966 }
940967
941968 @ Override public String getString () throws SQLException {
@@ -990,7 +1017,7 @@ private TimeFromNumberAccessor(Getter getter, Calendar localCalendar) {
9901017 if (v == null ) {
9911018 return null ;
9921019 }
993- return longToTimestamp ( v . longValue () , calendar );
1020+ return numberToTimestamp ( v , calendar );
9941021 }
9951022
9961023 @ Override public String getString () throws SQLException {
@@ -1018,10 +1045,10 @@ protected Number getNumber() throws SQLException {
10181045 * in its default representation {@code long};
10191046 * corresponds to {@link java.sql.Types#TIMESTAMP}.
10201047 */
1021- private static class TimestampFromNumberAccessor extends NumberAccessor {
1048+ static class TimestampFromNumberAccessor extends NumberAccessor {
10221049 private final Calendar localCalendar ;
10231050
1024- private TimestampFromNumberAccessor (Getter getter , Calendar localCalendar ) {
1051+ TimestampFromNumberAccessor (Getter getter , Calendar localCalendar ) {
10251052 super (getter , 0 );
10261053 this .localCalendar = localCalendar ;
10271054 }
@@ -1035,7 +1062,7 @@ private TimestampFromNumberAccessor(Getter getter, Calendar localCalendar) {
10351062 if (v == null ) {
10361063 return null ;
10371064 }
1038- return longToTimestamp ( v . longValue () , calendar );
1065+ return numberToTimestamp ( v , calendar );
10391066 }
10401067
10411068 @ Override public Date getDate (Calendar calendar ) throws SQLException {
0 commit comments