Skip to content

Commit a44a65f

Browse files
anshlykovmp911de
authored andcommitted
OffsetTimeCodec
1 parent dc4c899 commit a44a65f

File tree

6 files changed

+237
-1
lines changed

6 files changed

+237
-1
lines changed

src/main/java/io/r2dbc/postgresql/codec/AbstractTemporalCodec.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.time.LocalDateTime;
2828
import java.time.LocalTime;
2929
import java.time.OffsetDateTime;
30+
import java.time.OffsetTime;
31+
import java.time.ZoneOffset;
3032
import java.time.ZonedDateTime;
3133
import java.time.temporal.Temporal;
3234
import java.util.EnumSet;
@@ -38,6 +40,7 @@
3840
import static io.r2dbc.postgresql.type.PostgresqlObjectId.TIME;
3941
import static io.r2dbc.postgresql.type.PostgresqlObjectId.TIMESTAMP;
4042
import static io.r2dbc.postgresql.type.PostgresqlObjectId.TIMESTAMPTZ;
43+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.TIMETZ;
4144

4245
/**
4346
* Codec to decode all known temporal types.
@@ -46,7 +49,7 @@
4649
*/
4750
abstract class AbstractTemporalCodec<T extends Temporal> extends AbstractCodec<T> {
4851

49-
private static final Set<PostgresqlObjectId> SUPPORTED_TYPES = EnumSet.of(DATE, TIMESTAMP, TIMESTAMPTZ, TIME);
52+
private static final Set<PostgresqlObjectId> SUPPORTED_TYPES = EnumSet.of(DATE, TIMESTAMP, TIMESTAMPTZ, TIME, TIMETZ);
5053

5154
/**
5255
* Creates a new {@link AbstractTemporalCodec}.
@@ -130,6 +133,14 @@ private Temporal decodeTemporal(ByteBuf buffer, PostgresqlObjectId dataType, @Nu
130133
}
131134

132135
return PostgresqlDateTimeFormatter.INSTANCE.parse(ByteBufUtils.decode(buffer), ZonedDateTime::from);
136+
case TIMETZ:
137+
if (FORMAT_BINARY == format) {
138+
long timeNano = buffer.readLong() * 1000;
139+
int offsetSec = -buffer.readInt();
140+
return OffsetTime.of(LocalTime.ofNanoOfDay(timeNano), ZoneOffset.ofTotalSeconds(offsetSec));
141+
}
142+
143+
return PostgresqlTimeFormatter.INSTANCE.parse(ByteBufUtils.decode(buffer), OffsetTime::from);
133144
}
134145

135146
throw new UnsupportedOperationException(String.format("Cannot decode value for type %s, format %s", dataType, format));

src/main/java/io/r2dbc/postgresql/codec/DefaultCodecs.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public DefaultCodecs(ByteBufAllocator byteBufAllocator) {
6666
new LocalTimeCodec(byteBufAllocator),
6767
new LongCodec(byteBufAllocator),
6868
new OffsetDateTimeCodec(byteBufAllocator),
69+
new OffsetTimeCodec(byteBufAllocator),
6970
new ShortCodec(byteBufAllocator),
7071
new UriCodec(byteBufAllocator),
7172
new UrlCodec(byteBufAllocator),
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2017-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.r2dbc.postgresql.codec;
18+
19+
import io.netty.buffer.ByteBuf;
20+
import io.netty.buffer.ByteBufAllocator;
21+
import io.r2dbc.postgresql.client.Parameter;
22+
import io.r2dbc.postgresql.message.Format;
23+
import io.r2dbc.postgresql.type.PostgresqlObjectId;
24+
import io.r2dbc.postgresql.util.Assert;
25+
import io.r2dbc.postgresql.util.ByteBufUtils;
26+
27+
import java.time.OffsetTime;
28+
29+
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
30+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.TIMETZ;
31+
32+
public class OffsetTimeCodec extends AbstractTemporalCodec<OffsetTime> {
33+
34+
private final ByteBufAllocator byteBufAllocator;
35+
36+
OffsetTimeCodec(ByteBufAllocator byteBufAllocator) {
37+
super(OffsetTime.class);
38+
this.byteBufAllocator = Assert.requireNonNull(byteBufAllocator, "byteBufAllocator must not be null");
39+
}
40+
41+
@Override
42+
PostgresqlObjectId getDefaultType() {
43+
return TIMETZ;
44+
}
45+
46+
@Override
47+
OffsetTime doDecode(ByteBuf buffer, PostgresqlObjectId dataType, Format format, Class<? extends OffsetTime> type) {
48+
Assert.requireNonNull(buffer, "byteBuf must not be null");
49+
50+
return decodeTemporal(buffer, dataType, format, OffsetTime.class, OffsetTime::from);
51+
}
52+
53+
@Override
54+
Parameter doEncode(OffsetTime value) {
55+
Assert.requireNonNull(value, "value must not be null");
56+
57+
return create(TIMETZ, FORMAT_TEXT, () -> ByteBufUtils.encode(this.byteBufAllocator, value.toString()));
58+
}
59+
60+
@Override
61+
public Parameter encodeNull() {
62+
return createNull(TIMETZ, FORMAT_TEXT);
63+
}
64+
65+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2017-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.r2dbc.postgresql.codec;
18+
19+
import java.time.format.DateTimeFormatter;
20+
import java.time.format.DateTimeFormatterBuilder;
21+
22+
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
23+
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
24+
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
25+
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
26+
27+
class PostgresqlTimeFormatter {
28+
29+
static final DateTimeFormatter INSTANCE =
30+
new DateTimeFormatterBuilder()
31+
.appendValue(HOUR_OF_DAY, 2)
32+
.appendLiteral(':')
33+
.appendValue(MINUTE_OF_HOUR, 2)
34+
.optionalStart()
35+
.appendLiteral(':')
36+
.appendValue(SECOND_OF_MINUTE, 2)
37+
.optionalStart()
38+
.appendFraction(NANO_OF_SECOND, 0, 9, true)
39+
.optionalEnd()
40+
.optionalStart()
41+
.appendOffset("+HH:mm", "+00")
42+
.optionalEnd()
43+
.toFormatter();
44+
45+
}

src/test/java/io/r2dbc/postgresql/AbstractCodecIntegrationTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
import java.time.LocalDateTime;
5353
import java.time.LocalTime;
5454
import java.time.OffsetDateTime;
55+
import java.time.OffsetTime;
5556
import java.time.ZoneId;
57+
import java.time.ZoneOffset;
5658
import java.time.ZonedDateTime;
5759
import java.util.Date;
5860
import java.util.LinkedHashMap;
@@ -445,6 +447,13 @@ void zonedDateTime() {
445447
}, "TIMESTAMP WITH TIME ZONE");
446448
}
447449

450+
@Test
451+
void offsetTime() {
452+
testCodec(OffsetTime.class, OffsetTime.of(LocalTime.now(), ZoneOffset.UTC), "TIMETZ");
453+
testCodec(OffsetTime.class, OffsetTime.of(LocalTime.now(), ZoneOffset.ofHoursMinutes(1, 30)), "TIMETZ");
454+
testCodec(OffsetTime.class, OffsetTime.of(LocalTime.now(), ZoneOffset.ofHoursMinutes(-3, -30)), "TIMETZ");
455+
}
456+
448457
private static <T> Mono<T> close(Connection connection) {
449458
return Mono.from(connection
450459
.close())
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2017-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.r2dbc.postgresql.codec;
18+
19+
import java.time.OffsetTime;
20+
21+
import io.r2dbc.postgresql.client.Parameter;
22+
import io.r2dbc.postgresql.client.ParameterAssert;
23+
import org.junit.jupiter.api.Test;
24+
25+
import static io.r2dbc.postgresql.client.Parameter.NULL_VALUE;
26+
import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY;
27+
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
28+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.MONEY;
29+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.TIMETZ;
30+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.VARCHAR;
31+
import static io.r2dbc.postgresql.util.ByteBufUtils.encode;
32+
import static io.r2dbc.postgresql.util.TestByteBufAllocator.TEST;
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
35+
36+
/**
37+
* Unit tests for {@link OffsetTimeCodec}.
38+
*/
39+
class OffsetTimeCodecUnitTests {
40+
41+
private static final int dataType = TIMETZ.getObjectId();
42+
43+
@Test
44+
void constructorNoByteBufAllocator() {
45+
assertThatIllegalArgumentException().isThrownBy(() -> new OffsetTimeCodec(null))
46+
.withMessage("byteBufAllocator must not be null");
47+
}
48+
49+
@Test
50+
void decode() {
51+
OffsetTime offsetTime = OffsetTime.parse("21:16:41.123456-09:00");
52+
53+
assertThat(new OffsetTimeCodec(TEST).decode(encode(TEST, "21:16:41.123456-09:00"), dataType, FORMAT_TEXT, OffsetTime.class))
54+
.isEqualTo(offsetTime);
55+
}
56+
57+
@Test
58+
void decodeNoByteBuf() {
59+
assertThat(new OffsetTimeCodec(TEST).decode(null, dataType, FORMAT_TEXT, OffsetTime.class)).isNull();
60+
}
61+
62+
@Test
63+
void doCanDecode() {
64+
OffsetTimeCodec codec = new OffsetTimeCodec(TEST);
65+
66+
assertThat(codec.doCanDecode(TIMETZ, FORMAT_BINARY)).isTrue();
67+
assertThat(codec.doCanDecode(MONEY, FORMAT_TEXT)).isFalse();
68+
assertThat(codec.doCanDecode(TIMETZ, FORMAT_TEXT)).isTrue();
69+
}
70+
71+
@Test
72+
void doCanDecodeNoFormat() {
73+
assertThatIllegalArgumentException().isThrownBy(() -> new OffsetTimeCodec(TEST).doCanDecode(VARCHAR, null))
74+
.withMessage("format must not be null");
75+
}
76+
77+
@Test
78+
void doCanDecodeNoType() {
79+
assertThatIllegalArgumentException().isThrownBy(() -> new OffsetTimeCodec(TEST).doCanDecode(null, FORMAT_TEXT))
80+
.withMessage("type must not be null");
81+
}
82+
83+
@Test
84+
void doEncode() {
85+
OffsetTime offsetTime = OffsetTime.now();
86+
87+
ParameterAssert.assertThat(new OffsetTimeCodec(TEST).doEncode(offsetTime))
88+
.hasFormat(FORMAT_TEXT)
89+
.hasType(TIMETZ.getObjectId())
90+
.hasValue(encode(TEST, offsetTime.toString()));
91+
}
92+
93+
@Test
94+
void doEncodeNoValue() {
95+
assertThatIllegalArgumentException().isThrownBy(() -> new OffsetTimeCodec(TEST).doEncode(null))
96+
.withMessage("value must not be null");
97+
}
98+
99+
@Test
100+
void encodeNull() {
101+
ParameterAssert.assertThat(new OffsetTimeCodec(TEST).encodeNull())
102+
.isEqualTo(new Parameter(FORMAT_TEXT, TIMETZ.getObjectId(), NULL_VALUE));
103+
}
104+
105+
}

0 commit comments

Comments
 (0)