2121import static org .apache .arrow .driver .jdbc .accessor .impl .calendar .ArrowFlightJdbcTimeStampVectorGetter .createGetter ;
2222
2323import java .sql .Date ;
24+ import java .sql .SQLException ;
2425import java .sql .Time ;
2526import java .sql .Timestamp ;
27+ import java .time .Instant ;
2628import java .time .LocalDateTime ;
29+ import java .time .OffsetDateTime ;
30+ import java .time .ZoneOffset ;
31+ import java .time .ZonedDateTime ;
2732import java .time .temporal .ChronoUnit ;
2833import java .util .Calendar ;
2934import java .util .TimeZone ;
3035import java .util .concurrent .TimeUnit ;
3136import java .util .function .IntSupplier ;
37+
3238import org .apache .arrow .driver .jdbc .accessor .ArrowFlightJdbcAccessor ;
3339import org .apache .arrow .driver .jdbc .accessor .ArrowFlightJdbcAccessorFactory ;
3440import org .apache .arrow .vector .TimeStampVector ;
3541import org .apache .arrow .vector .types .pojo .ArrowType ;
3642import org .apache .arrow .vector .util .DateUtility ;
3743
38- /** Accessor for the Arrow types extending from {@link TimeStampVector}. */
44+ /**
45+ * Accessor for the Arrow types extending from {@link TimeStampVector}.
46+ */
3947public class ArrowFlightJdbcTimeStampVectorAccessor extends ArrowFlightJdbcAccessor {
4048
4149 private final TimeZone timeZone ;
4250 private final Getter getter ;
4351 private final TimeUnit timeUnit ;
4452 private final LongToLocalDateTime longToLocalDateTime ;
4553 private final Holder holder ;
54+ private final boolean isZoned ;
4655
47- /** Functional interface used to convert a number (in any time resolution) to LocalDateTime. */
56+ /**
57+ * Functional interface used to convert a number (in any time resolution) to LocalDateTime.
58+ */
4859 interface LongToLocalDateTime {
4960 LocalDateTime fromLong (long value );
5061 }
5162
52- /** Instantiate a ArrowFlightJdbcTimeStampVectorAccessor for given vector. */
63+ /**
64+ * Instantiate a ArrowFlightJdbcTimeStampVectorAccessor for given vector.
65+ */
5366 public ArrowFlightJdbcTimeStampVectorAccessor (
5467 TimeStampVector vector ,
5568 IntSupplier currentRowSupplier ,
@@ -58,6 +71,7 @@ public ArrowFlightJdbcTimeStampVectorAccessor(
5871 this .holder = new Holder ();
5972 this .getter = createGetter (vector );
6073
74+ this .isZoned = getVectorIsZoned (vector );
6175 this .timeZone = getTimeZoneForVector (vector );
6276 this .timeUnit = getTimeUnitForVector (vector );
6377 this .longToLocalDateTime = getLongToLocalDateTimeForVector (vector , this .timeZone );
@@ -68,11 +82,57 @@ public Class<?> getObjectClass() {
6882 return Timestamp .class ;
6983 }
7084
85+ @ Override
86+ public <T > T getObject (final Class <T > type ) throws SQLException {
87+ final Object value ;
88+ if (type == OffsetDateTime .class ) {
89+ value = getOffsetDateTime ();
90+ } else if (type == LocalDateTime .class ) {
91+ value = getLocalDateTime (null );
92+ } else if (type == ZonedDateTime .class ) {
93+ value = getZonedDateTime ();
94+ } else if (type == Instant .class ) {
95+ value = getInstant ();
96+ } else if (type == Timestamp .class ) {
97+ value = getObject ();
98+ } else {
99+ throw new SQLException ("invalid class" );
100+ }
101+ return !type .isPrimitive () && wasNull ? null : type .cast (value );
102+ }
103+
71104 @ Override
72105 public Object getObject () {
73106 return this .getTimestamp (null );
74107 }
75108
109+ private ZonedDateTime getZonedDateTime () {
110+ LocalDateTime localDateTime = getLocalDateTime (null );
111+ if (localDateTime == null ) {
112+ return null ;
113+ }
114+
115+ return localDateTime .atZone (this .timeZone .toZoneId ());
116+ }
117+
118+ private OffsetDateTime getOffsetDateTime () {
119+ LocalDateTime localDateTime = getLocalDateTime (null );
120+ if (localDateTime == null ) {
121+ return null ;
122+ }
123+ ZoneOffset offset = this .timeZone .toZoneId ().getRules ().getOffset (localDateTime );
124+ return localDateTime .atOffset (offset );
125+ }
126+
127+ private Instant getInstant () {
128+ LocalDateTime localDateTime = getLocalDateTime (null );
129+ if (localDateTime == null ) {
130+ return null ;
131+ }
132+ ZoneOffset offset = this .timeZone .toZoneId ().getRules ().getOffset (localDateTime );
133+ return localDateTime .toInstant (offset );
134+ }
135+
76136 private LocalDateTime getLocalDateTime (Calendar calendar ) {
77137 getter .get (getCurrentRow (), holder );
78138 this .wasNull = holder .isSet == 0 ;
@@ -85,7 +145,8 @@ private LocalDateTime getLocalDateTime(Calendar calendar) {
85145
86146 LocalDateTime localDateTime = this .longToLocalDateTime .fromLong (value );
87147
88- if (calendar != null ) {
148+ // Adjust timestamp to desired calendar (if provided) only if the column includes TZ info, otherwise treat as wall-clock time
149+ if (calendar != null && this .isZoned ) {
89150 TimeZone timeZone = calendar .getTimeZone ();
90151 long millis = this .timeUnit .toMillis (value );
91152 localDateTime =
@@ -177,4 +238,11 @@ protected static TimeZone getTimeZoneForVector(TimeStampVector vector) {
177238
178239 return TimeZone .getTimeZone (timezoneName );
179240 }
241+
242+ protected static boolean getVectorIsZoned (TimeStampVector vector ) {
243+ ArrowType .Timestamp arrowType =
244+ (ArrowType .Timestamp ) vector .getField ().getFieldType ().getType ();
245+
246+ return arrowType .getTimezone () != null ;
247+ }
180248}
0 commit comments