diff --git a/src/main/java/io/r2dbc/postgresql/codec/AbstractTemporalCodec.java b/src/main/java/io/r2dbc/postgresql/codec/AbstractTemporalCodec.java index 993e4bf0..80c02309 100644 --- a/src/main/java/io/r2dbc/postgresql/codec/AbstractTemporalCodec.java +++ b/src/main/java/io/r2dbc/postgresql/codec/AbstractTemporalCodec.java @@ -118,7 +118,7 @@ private Temporal decodeTemporal(ByteBuf buffer, PostgresqlObjectId dataType, @Nu return EpochTime.fromLong(buffer.readLong()).toLocalDateTime(); } - return PostgresqlDateTimeFormatter.INSTANCE.parse(ByteBufUtils.decode(buffer), LocalDateTime::from); + return PostgresqlDateTimeFormatter.parse(ByteBufUtils.decode(buffer), LocalDateTime::from); case DATE: case DATE_ARRAY: if (FORMAT_BINARY == format) { @@ -139,7 +139,7 @@ private Temporal decodeTemporal(ByteBuf buffer, PostgresqlObjectId dataType, @Nu return EpochTime.fromLong(buffer.readLong()).toInstant().atOffset(OffsetDateTime.now().getOffset()); } - return PostgresqlDateTimeFormatter.INSTANCE.parse(ByteBufUtils.decode(buffer), ZonedDateTime::from); + return PostgresqlDateTimeFormatter.parse(ByteBufUtils.decode(buffer), ZonedDateTime::from); case TIMETZ: case TIMETZ_ARRAY: if (FORMAT_BINARY == format) { @@ -148,7 +148,7 @@ private Temporal decodeTemporal(ByteBuf buffer, PostgresqlObjectId dataType, @Nu return OffsetTime.of(LocalTime.ofNanoOfDay(timeNano), ZoneOffset.ofTotalSeconds(offsetSec)); } - return PostgresqlTimeFormatter.INSTANCE.parse(ByteBufUtils.decode(buffer), OffsetTime::from); + return PostgresqlTimeFormatter.parse(ByteBufUtils.decode(buffer), OffsetTime::from); } throw new UnsupportedOperationException(String.format("Cannot decode value for type %s, format %s", dataType, format)); diff --git a/src/main/java/io/r2dbc/postgresql/codec/PostgresqlDateTimeFormatter.java b/src/main/java/io/r2dbc/postgresql/codec/PostgresqlDateTimeFormatter.java index ca96eaca..08ec4741 100644 --- a/src/main/java/io/r2dbc/postgresql/codec/PostgresqlDateTimeFormatter.java +++ b/src/main/java/io/r2dbc/postgresql/codec/PostgresqlDateTimeFormatter.java @@ -18,7 +18,9 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; import java.time.format.SignStyle; +import java.time.temporal.TemporalQuery; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.HOUR_OF_DAY; @@ -30,7 +32,7 @@ class PostgresqlDateTimeFormatter { - static final DateTimeFormatter INSTANCE = + private static final DateTimeFormatter FULL_OFFSET = new DateTimeFormatterBuilder() .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) .appendLiteral('-') @@ -48,8 +50,58 @@ class PostgresqlDateTimeFormatter { .appendFraction(NANO_OF_SECOND, 0, 9, true) .optionalEnd() .optionalStart() - .appendOffset("+HH:mm:ss", "+00:00:00") + .appendOffset("+HH:MM:ss", "+00:00:00") .optionalEnd() .toFormatter(); + private static final DateTimeFormatter SHORT_OFFSET = + new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral('-') + .appendValue(MONTH_OF_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_MONTH, 2) + .appendLiteral(' ') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(NANO_OF_SECOND, 0, 9, true) + .optionalEnd() + .optionalStart() + .appendOffset("+HH:mm", "+00:00") + .optionalEnd() + .toFormatter(); + + /** + * Fully parses the text producing an object of the specified type. + * Attempts to parse the text using {@link #SHORT_OFFSET} first, then {@link #FULL_OFFSET}. + *
+ * Most applications should use this method for parsing. + * It parses the entire text to produce the required date-time. + * For example: + *
+ * LocalDateTime dt = parse(str, LocalDateTime::from); + *+ * If the parse completes without reading the entire length of the text, + * or a problem occurs during parsing or merging, then an exception is thrown. + * + * @param
+ * Most applications should use this method for parsing. + * It parses the entire text to produce the required date-time. + * For example: + *
+ * OffsetTime dt = parse(str, OffsetTime::from); + *+ * If the parse completes without reading the entire length of the text, + * or a problem occurs during parsing or merging, then an exception is thrown. + * + * @param