1010import java .nio .charset .Charset ;
1111import java .nio .charset .CharsetDecoder ;
1212import java .nio .charset .StandardCharsets ;
13+ import java .time .Instant ;
14+ import java .time .LocalDate ;
15+ import java .time .LocalDateTime ;
16+ import java .time .OffsetDateTime ;
17+ import java .time .ZoneId ;
18+ import java .time .ZoneOffset ;
19+ import java .time .ZonedDateTime ;
1320import java .util .ArrayList ;
1421import java .util .HashMap ;
1522import java .util .List ;
@@ -161,11 +168,17 @@ private <T> Object decodeByType(
161168 case BOOLEAN :
162169 return Decoder .decodeBoolean (size );
163170 case UTF8_STRING :
164- return this .decodeString (size );
171+ var s = this .decodeString (size );
172+ var temporalFromString = coerceTemporalFromString (s , cls );
173+ return temporalFromString != null ? temporalFromString : s ;
165174 case DOUBLE :
166- return this .decodeDouble (size );
175+ var d = this .decodeDouble (size );
176+ var temporalFromDouble = coerceTemporalFromDouble (d , cls );
177+ return temporalFromDouble != null ? temporalFromDouble : d ;
167178 case FLOAT :
168- return this .decodeFloat (size );
179+ var f = this .decodeFloat (size );
180+ var temporalFromFloat = coerceTemporalFromDouble (f , cls );
181+ return temporalFromFloat != null ? temporalFromFloat : f ;
169182 case BYTES :
170183 return this .getByteArray (size );
171184 case UINT16 :
@@ -176,14 +189,20 @@ private <T> Object decodeByType(
176189 return coerceFromInt (this .decodeInt32 (size ), cls );
177190 case UINT64 :
178191 case UINT128 :
179- return this .decodeBigInteger (size );
192+ var bi = this .decodeBigInteger (size );
193+ var temporalFromBigInt = coerceTemporalFromBigInteger (bi , cls );
194+ return temporalFromBigInt != null ? temporalFromBigInt : bi ;
180195 default :
181196 throw new InvalidDatabaseException (
182197 "Unknown or unexpected type: " + type .name ());
183198 }
184199 }
185200
186201 private static Object coerceFromInt (int value , Class <?> target ) {
202+ var temporal = coerceTemporalFromLong ((long ) value , target );
203+ if (temporal != null ) {
204+ return temporal ;
205+ }
187206 if (target .equals (Object .class )
188207 || target .equals (Integer .TYPE )
189208 || target .equals (Integer .class )) {
@@ -218,6 +237,10 @@ private static Object coerceFromInt(int value, Class<?> target) {
218237 }
219238
220239 private static Object coerceFromLong (long value , Class <?> target ) {
240+ var temporal = coerceTemporalFromLong (value , target );
241+ if (temporal != null ) {
242+ return temporal ;
243+ }
221244 if (target .equals (Object .class ) || target .equals (Long .TYPE ) || target .equals (Long .class )) {
222245 return value ;
223246 }
@@ -251,6 +274,108 @@ private static Object coerceFromLong(long value, Class<?> target) {
251274 return value ;
252275 }
253276
277+ private static boolean isTemporalTarget (Class <?> target ) {
278+ return target .equals (Instant .class )
279+ || target .equals (LocalDate .class )
280+ || target .equals (LocalDateTime .class )
281+ || target .equals (OffsetDateTime .class )
282+ || target .equals (ZonedDateTime .class );
283+ }
284+
285+ private static Object coerceTemporalFromLong (long value , Class <?> target ) {
286+ if (!isTemporalTarget (target )) {
287+ return null ;
288+ }
289+ // Heuristic: >= 10^12 -> milliseconds, else seconds
290+ boolean millis = Math .abs (value ) >= 1_000_000_000_000L ;
291+ Instant instant = millis ? Instant .ofEpochMilli (value ) : Instant .ofEpochSecond (value );
292+ return temporalFromInstant (instant , target );
293+ }
294+
295+ private static Object coerceTemporalFromBigInteger (BigInteger value , Class <?> target ) {
296+ if (!isTemporalTarget (target )) {
297+ return null ;
298+ }
299+ var abs = value .abs ();
300+ boolean millis = abs .compareTo (BigInteger .valueOf (1_000_000_000_000L )) >= 0 ;
301+ Instant instant = millis
302+ ? Instant .ofEpochMilli (value .longValue ())
303+ : Instant .ofEpochSecond (value .longValue ());
304+ return temporalFromInstant (instant , target );
305+ }
306+
307+ private static Object coerceTemporalFromDouble (double value , Class <?> target ) {
308+ if (!isTemporalTarget (target )) {
309+ return null ;
310+ }
311+ long seconds = (long ) Math .floor (value );
312+ long nanos = Math .round ((value - seconds ) * 1_000_000_000.0 );
313+ Instant instant = Instant .ofEpochSecond (seconds , nanos );
314+ return temporalFromInstant (instant , target );
315+ }
316+
317+ private static Object coerceTemporalFromString (String s , Class <?> target ) {
318+ if (!isTemporalTarget (target )) {
319+ return null ;
320+ }
321+ try {
322+ // Try exact parser for target first, with fallbacks to Instant/Offset/Zoned
323+ if (target .equals (Instant .class )) {
324+ try {
325+ return Instant .parse (s );
326+ } catch (Exception e ) {
327+ return OffsetDateTime .parse (s ).toInstant ();
328+ }
329+ }
330+ if (target .equals (LocalDate .class )) {
331+ try {
332+ return LocalDate .parse (s );
333+ } catch (Exception e ) {
334+ return OffsetDateTime .parse (s ).toLocalDate ();
335+ }
336+ }
337+ if (target .equals (LocalDateTime .class )) {
338+ try {
339+ return LocalDateTime .parse (s );
340+ } catch (Exception e1 ) {
341+ try {
342+ return LocalDateTime .ofInstant (Instant .parse (s ), ZoneOffset .UTC );
343+ } catch (Exception e2 ) {
344+ return OffsetDateTime .parse (s ).toLocalDateTime ();
345+ }
346+ }
347+ }
348+ if (target .equals (OffsetDateTime .class )) {
349+ return OffsetDateTime .parse (s );
350+ }
351+ if (target .equals (ZonedDateTime .class )) {
352+ return ZonedDateTime .parse (s );
353+ }
354+ } catch (Exception ignore ) {
355+ return null ;
356+ }
357+ return null ;
358+ }
359+
360+ private static Object temporalFromInstant (Instant instant , Class <?> target ) {
361+ if (target .equals (Instant .class )) {
362+ return instant ;
363+ }
364+ if (target .equals (LocalDate .class )) {
365+ return LocalDateTime .ofInstant (instant , ZoneOffset .UTC ).toLocalDate ();
366+ }
367+ if (target .equals (LocalDateTime .class )) {
368+ return LocalDateTime .ofInstant (instant , ZoneOffset .UTC );
369+ }
370+ if (target .equals (OffsetDateTime .class )) {
371+ return OffsetDateTime .ofInstant (instant , ZoneOffset .UTC );
372+ }
373+ if (target .equals (ZonedDateTime .class )) {
374+ return ZonedDateTime .ofInstant (instant , ZoneId .of ("UTC" ));
375+ }
376+ return null ;
377+ }
378+
254379 private String decodeString (long size ) throws CharacterCodingException {
255380 var oldLimit = buffer .limit ();
256381 buffer .limit (buffer .position () + size );
0 commit comments