2222import io .netty .buffer .Unpooled ;
2323import io .netty .handler .codec .DecoderException ;
2424import io .vertx .core .buffer .Buffer ;
25+ import io .vertx .core .internal .buffer .BufferInternal ;
2526import io .vertx .core .internal .logging .Logger ;
2627import io .vertx .core .internal .logging .LoggerFactory ;
2728import io .vertx .core .json .Json ;
2829import io .vertx .core .json .JsonArray ;
2930import io .vertx .core .json .JsonObject ;
30- import io .vertx .core .internal .buffer .BufferInternal ;
3131import io .vertx .pgclient .data .*;
3232import io .vertx .pgclient .impl .util .UTF8StringEndDetector ;
3333import io .vertx .sqlclient .Tuple ;
4242import java .nio .charset .StandardCharsets ;
4343import java .text .DecimalFormat ;
4444import java .time .*;
45- import java .time .format .DateTimeFormatter ;
4645import java .time .format .DateTimeFormatterBuilder ;
4746import java .time .temporal .ChronoField ;
48- import java .time .temporal .ChronoUnit ;
4947import java .util .ArrayList ;
5048import java .util .List ;
51- import java .util .Locale ;
5249import java .util .UUID ;
5350import java .util .function .IntFunction ;
5451
5552import static java .time .format .DateTimeFormatter .ISO_LOCAL_DATE ;
5653import static java .time .format .DateTimeFormatter .ISO_LOCAL_TIME ;
54+ import static java .time .temporal .ChronoUnit .DAYS ;
55+ import static java .time .temporal .ChronoUnit .MICROS ;
5756
5857/**
5958 * @author <a href="mailto:[email protected] ">Julien Viet</a> @@ -91,8 +90,7 @@ public class DataTypeCodec {
9190 private static final Float [] empty_float_array = new Float [0 ];
9291 private static final Double [] empty_double_array = new Double [0 ];
9392 private static final LocalDate LOCAL_DATE_EPOCH = LocalDate .of (2000 , 1 , 1 );
94- private static final LocalDateTime LOCAL_DATE_TIME_EPOCH = LocalDateTime .of (2000 , 1 , 1 , 0 , 0 , 0 );
95- private static final OffsetDateTime OFFSET_DATE_TIME_EPOCH = LocalDateTime .of (2000 , 1 , 1 , 0 , 0 , 0 ).atOffset (ZoneOffset .UTC );
93+ private static final LocalDateTime LOCAL_DATE_TIME_EPOCH = LOCAL_DATE_EPOCH .atStartOfDay ();
9694 private static final Inet [] empty_inet_array = new Inet [0 ];
9795 private static final Money [] empty_money_array = new Money [0 ];
9896
@@ -1017,7 +1015,7 @@ private static void binaryEncodeDATE(LocalDate value, ByteBuf buff) {
10171015 } else if (value == LocalDate .MIN ) {
10181016 v = Integer .MIN_VALUE ;
10191017 } else {
1020- v = (int ) -value .until (LOCAL_DATE_EPOCH , ChronoUnit . DAYS );
1018+ v = (int ) -value .until (LOCAL_DATE_EPOCH , DAYS );
10211019 }
10221020 buff .writeInt (v );
10231021 }
@@ -1030,7 +1028,7 @@ private static LocalDate binaryDecodeDATE(int index, int len, ByteBuf buff) {
10301028 case Integer .MIN_VALUE :
10311029 return LocalDate .MIN ;
10321030 default :
1033- return LOCAL_DATE_EPOCH .plus (val , ChronoUnit . DAYS );
1031+ return LOCAL_DATE_EPOCH .plus (val , DAYS );
10341032 }
10351033 }
10361034
@@ -1079,30 +1077,45 @@ private static OffsetTime textDecodeTIMETZ(int index, int len, ByteBuf buff) {
10791077 return OffsetTime .parse (cs , TIMETZ_FORMAT );
10801078 }
10811079
1082- // 294277-01-09 04:00:54.775807
1083- public static final LocalDateTime LDT_PLUS_INFINITY = LOCAL_DATE_TIME_EPOCH .plus (Long .MAX_VALUE , ChronoUnit .MICROS );
1084- // 4714-11-24 00:00:00 BC
1085- public static final LocalDateTime LDT_MINUS_INFINITY = LocalDateTime .parse ("4714-11-24 00:00:00 BC" ,
1086- DateTimeFormatter .ofPattern ("yyyy-MM-dd HH:mm:ss G" , Locale .US ));
1080+ /*
1081+ * See limits for dates and timestamps
1082+ * https://github.com/postgres/postgres/blob/75e82b2f5a6f5de6b42dbd9ea73be5ff36a931b1/src/include/datatype/timestamp.h
1083+ *
1084+ * In Pg Client, we define inclusive min and max.
1085+ */
1086+ private static final long MIN_TIMESTAMP = -211813488000000000L ;
1087+ private static final LocalDateTime MIN_LDT = LOCAL_DATE_TIME_EPOCH .plus (MIN_TIMESTAMP , MICROS );
1088+ private static final long MAX_TIMESTAMP = 9223371331200000000L - 1 ;
1089+ private static final LocalDateTime MAX_LDT = LOCAL_DATE_TIME_EPOCH .plus (MAX_TIMESTAMP , MICROS );
10871090
10881091 private static void binaryEncodeTIMESTAMP (LocalDateTime value , ByteBuf buff ) {
1089- if (value .compareTo (LDT_PLUS_INFINITY ) >= 0 ) {
1090- value = LDT_PLUS_INFINITY ;
1091- } else if (value .compareTo (LDT_MINUS_INFINITY ) <= 0 ) {
1092- value = LDT_MINUS_INFINITY ;
1092+ long timestamp ;
1093+ if (MIN_LDT .isAfter (value )) {
1094+ timestamp = MIN_TIMESTAMP ;
1095+ } else if (value .isAfter (MAX_LDT )) {
1096+ timestamp = MAX_TIMESTAMP ;
1097+ } else {
1098+ // Make sure to handle microsecond resolution as PostgreSQL does when it converts textual representation of timestamps.
1099+ // Over 499 nanos after the microsecond, round up to the next microsecond.
1100+ // Otherwise, do nothing and the nanos after the microsecond will be truncated below.
1101+ int nanosAfterMicro = value .getNano () % 1000 ;
1102+ if (nanosAfterMicro > 499 ) {
1103+ value = value .plusNanos (1000 - nanosAfterMicro );
1104+ }
1105+ timestamp = Math .min (MAX_TIMESTAMP , -value .until (LOCAL_DATE_TIME_EPOCH , MICROS ));
10931106 }
1094- buff .writeLong (- value . until ( LOCAL_DATE_TIME_EPOCH , ChronoUnit . MICROS ) );
1107+ buff .writeLong (timestamp );
10951108 }
10961109
10971110 private static LocalDateTime binaryDecodeTIMESTAMP (int index , int len , ByteBuf buff ) {
1098- LocalDateTime val = LOCAL_DATE_TIME_EPOCH .plus (buff .getLong (index ), ChronoUnit .MICROS );
1099- if (LDT_PLUS_INFINITY .equals (val )) {
1100- return LocalDateTime .MAX ;
1101- } else if (LDT_MINUS_INFINITY .equals (val )) {
1111+ long timestamp = buff .getLong (index );
1112+ if (timestamp <= MIN_TIMESTAMP ) {
11021113 return LocalDateTime .MIN ;
1103- } else {
1104- return val ;
11051114 }
1115+ if (timestamp >= MAX_TIMESTAMP ) {
1116+ return LocalDateTime .MAX ;
1117+ }
1118+ return LOCAL_DATE_TIME_EPOCH .plus (timestamp , MICROS );
11061119 }
11071120
11081121 private static LocalDateTime textDecodeTIMESTAMP (int index , int len , ByteBuf buff ) {
@@ -1132,12 +1145,12 @@ private static OffsetDateTime binaryDecodeTIMESTAMPTZ(int index, int len, ByteBu
11321145 private static void binaryEncodeTIMESTAMPTZ (OffsetDateTime value , ByteBuf buff ) {
11331146 LocalDateTime ldt ;
11341147 if (value .getOffset () != ZoneOffset .UTC ) {
1135- OffsetDateTime max = OffsetDateTime .of (LDT_PLUS_INFINITY , ZoneOffset .UTC );
1136- if (value .compareTo (max ) >= 0 ) {
1148+ OffsetDateTime max = OffsetDateTime .of (MAX_LDT , ZoneOffset .UTC );
1149+ if (! value .isBefore (max )) {
11371150 ldt = LocalDateTime .MAX ;
11381151 } else {
1139- OffsetDateTime min = OffsetDateTime .of (LDT_MINUS_INFINITY , ZoneOffset .UTC );
1140- if (value .compareTo (min ) <= 0 ) {
1152+ OffsetDateTime min = OffsetDateTime .of (MIN_LDT , ZoneOffset .UTC );
1153+ if (! value .isAfter (min )) {
11411154 ldt = LocalDateTime .MIN ;
11421155 } else {
11431156 ldt = value .toInstant ().atOffset (ZoneOffset .UTC ).toLocalDateTime ();
0 commit comments