diff --git a/pom.xml b/pom.xml
index 1536b45a..538ae000 100644
--- a/pom.xml
+++ b/pom.xml
@@ -285,6 +285,12 @@
1.35
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.9.0
+ test
+
diff --git a/src/main/java/io/tarantool/driver/mappers/converters/object/DefaultInstantToExtensionValueConverter.java b/src/main/java/io/tarantool/driver/mappers/converters/object/DefaultInstantToExtensionValueConverter.java
index 091b9e13..c25be07d 100644
--- a/src/main/java/io/tarantool/driver/mappers/converters/object/DefaultInstantToExtensionValueConverter.java
+++ b/src/main/java/io/tarantool/driver/mappers/converters/object/DefaultInstantToExtensionValueConverter.java
@@ -11,6 +11,8 @@
import java.nio.ByteOrder;
import java.time.Instant;
+import static io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToInstantConverter.DATETIME_TYPE;
+
/**
* Default {@link java.time.Instant} to {@link ExtensionValue} converter
*
@@ -21,8 +23,6 @@ public class DefaultInstantToExtensionValueConverter implements ObjectConverter<
private static final long serialVersionUID = 20221025L;
- private static final byte DATETIME_TYPE = 0x04;
-
private byte[] toBytes(Instant value) {
long seconds = value.getEpochSecond();
Integer nano = value.getNano();
diff --git a/src/main/java/io/tarantool/driver/mappers/converters/object/DefaultOffsetDateTimeToExtensionValueConverter.java b/src/main/java/io/tarantool/driver/mappers/converters/object/DefaultOffsetDateTimeToExtensionValueConverter.java
new file mode 100644
index 00000000..46f70303
--- /dev/null
+++ b/src/main/java/io/tarantool/driver/mappers/converters/object/DefaultOffsetDateTimeToExtensionValueConverter.java
@@ -0,0 +1,81 @@
+package io.tarantool.driver.mappers.converters.object;
+
+import io.tarantool.driver.mappers.converters.ObjectConverter;
+import org.msgpack.value.ExtensionValue;
+import org.msgpack.value.ValueFactory;
+
+import java.nio.ByteBuffer;
+import java.time.OffsetDateTime;
+
+import static io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToInstantConverter.DATETIME_TYPE;
+import static io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToOffsetDateTimeConverter.SECONDS_PER_MINUTE;
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+import static java.time.ZoneOffset.UTC;
+
+/**
+ * Default {@link ExtensionValue} to {@link java.time.OffsetDateTime} converter.
+ *
+ * @author Valeriy Vyrva
+ */
+public class DefaultOffsetDateTimeToExtensionValueConverter implements ObjectConverter {
+
+ private static final long serialVersionUID = 20231027114017L;
+
+ /**
+ * Will contain only requited part:
+ *
+ * - {@code 8 bytes}: Seconds since Epoch.
+ *
+ *
+ * @see struct datetime
+ */
+ private static final int BUFFER_SIZE_COMPACT = Long.BYTES;
+ /**
+ * Will contain and required and optional parts:
+ *
+ * - {@code 8 bytes}: Seconds since Epoch.
+ * - {@code 4 bytes}: Nanoseconds.
+ * - {@code 2 bytes}: Offset in minutes from UTC.
+ * - {@code 2 bytes}: Olson timezone id.
+ *
+ * The "timezone id" is not used on Java.
+ *
+ * @see struct datetime
+ */
+ private static final int BUFFER_SIZE_COMPLETE = Long.BYTES + Integer.BYTES + Short.BYTES + Short.BYTES;
+
+ @Override
+ public ExtensionValue toValue(OffsetDateTime object) {
+ return ValueFactory.newExtension(DATETIME_TYPE, toBytes(object));
+ }
+
+ /**
+ * Encode java object into protocol level representation.
+ *
+ * @param object Object to encode
+ * @return Protocol level representation
+ * @see
+ *
+ * serialization schema
+ * @see
+ * struct datetime
+ * @see
+ * datetime_pack
+ * @see
+ * datetime_unpack
+ */
+ private byte[] toBytes(OffsetDateTime object) {
+ boolean isCompact = object.getNano() == 0 && object.getOffset().equals(UTC);
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[isCompact ? BUFFER_SIZE_COMPACT : BUFFER_SIZE_COMPLETE]);
+ buffer.order(LITTLE_ENDIAN);
+ //Required part
+ buffer.putLong(object.toEpochSecond());
+ //Optional part
+ if (!isCompact) {
+ buffer.putInt(object.getNano());
+ buffer.putShort((short) (object.getOffset().getTotalSeconds() / SECONDS_PER_MINUTE));
+ }
+ return buffer.array();
+ }
+
+}
diff --git a/src/main/java/io/tarantool/driver/mappers/converters/value/defaults/DefaultExtensionValueToInstantConverter.java b/src/main/java/io/tarantool/driver/mappers/converters/value/defaults/DefaultExtensionValueToInstantConverter.java
index eb7b31e6..1f0ba4a8 100644
--- a/src/main/java/io/tarantool/driver/mappers/converters/value/defaults/DefaultExtensionValueToInstantConverter.java
+++ b/src/main/java/io/tarantool/driver/mappers/converters/value/defaults/DefaultExtensionValueToInstantConverter.java
@@ -16,7 +16,12 @@
public class DefaultExtensionValueToInstantConverter implements ValueConverter {
private static final long serialVersionUID = 20221025L;
- private static final byte DATETIME_TYPE = 0x04;
+ /**
+ * @see
+ *
+ * mp_extension_type#MP_DATETIME
+ */
+ public static final byte DATETIME_TYPE = 0x04;
private Instant fromBytes(byte[] bytes) {
int size = bytes.length;
diff --git a/src/main/java/io/tarantool/driver/mappers/converters/value/defaults/DefaultExtensionValueToOffsetDateTimeConverter.java b/src/main/java/io/tarantool/driver/mappers/converters/value/defaults/DefaultExtensionValueToOffsetDateTimeConverter.java
new file mode 100644
index 00000000..a9ef9eea
--- /dev/null
+++ b/src/main/java/io/tarantool/driver/mappers/converters/value/defaults/DefaultExtensionValueToOffsetDateTimeConverter.java
@@ -0,0 +1,62 @@
+package io.tarantool.driver.mappers.converters.value.defaults;
+
+import io.tarantool.driver.mappers.converters.ValueConverter;
+import org.msgpack.value.ExtensionValue;
+
+import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+
+import static io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToInstantConverter.DATETIME_TYPE;
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+import static java.time.ZoneOffset.UTC;
+
+/**
+ * Default {@link ExtensionValue} to {@link java.time.OffsetDateTime} converter.
+ *
+ * @author Valeriy Vyrva
+ */
+public class DefaultExtensionValueToOffsetDateTimeConverter implements ValueConverter {
+
+ private static final long serialVersionUID = 20231027114017L;
+
+ public static final int SECONDS_PER_MINUTE = 60;
+
+ @Override
+ public boolean canConvertValue(ExtensionValue value) {
+ return value.getType() == DATETIME_TYPE;
+ }
+
+ @Override
+ public OffsetDateTime fromValue(ExtensionValue value) {
+ return fromBytes(value.getData());
+ }
+
+ /**
+ * Decode protocol level representation into java object.
+ *
+ * @param value Bytes from protocol level
+ * @return Decoded value
+ * @see
+ *
+ * serialization schema
+ * @see
+ * struct datetime
+ * @see
+ * datetime_pack
+ * @see
+ * datetime_unpack
+ */
+ private OffsetDateTime fromBytes(byte[] value) {
+ ByteBuffer buffer = ByteBuffer.wrap(value);
+ buffer.order(LITTLE_ENDIAN);
+ return Instant
+ //Required part
+ .ofEpochSecond(buffer.getLong())
+ //Optional part
+ .plusNanos(buffer.hasRemaining() ? buffer.getInt() : 0)
+ .atOffset(buffer.hasRemaining() ? ZoneOffset.ofTotalSeconds(buffer.getShort() * SECONDS_PER_MINUTE) : UTC);
+ }
+
+}
diff --git a/src/main/java/io/tarantool/driver/mappers/factories/DefaultMessagePackMapperFactory.java b/src/main/java/io/tarantool/driver/mappers/factories/DefaultMessagePackMapperFactory.java
index c085b7f4..03680fc3 100644
--- a/src/main/java/io/tarantool/driver/mappers/factories/DefaultMessagePackMapperFactory.java
+++ b/src/main/java/io/tarantool/driver/mappers/factories/DefaultMessagePackMapperFactory.java
@@ -12,6 +12,7 @@
import io.tarantool.driver.mappers.converters.object.DefaultLongArrayToArrayValueConverter;
import io.tarantool.driver.mappers.converters.object.DefaultLongToIntegerValueConverter;
import io.tarantool.driver.mappers.converters.object.DefaultNilValueToNullConverter;
+import io.tarantool.driver.mappers.converters.object.DefaultOffsetDateTimeToExtensionValueConverter;
import io.tarantool.driver.mappers.converters.object.DefaultPackableObjectConverter;
import io.tarantool.driver.mappers.converters.object.DefaultShortToIntegerValueConverter;
import io.tarantool.driver.mappers.converters.object.DefaultStringToStringValueConverter;
@@ -21,6 +22,7 @@
import io.tarantool.driver.mappers.converters.value.defaults.DefaultBinaryValueToByteArrayConverter;
import io.tarantool.driver.mappers.converters.value.defaults.DefaultBooleanValueToBooleanConverter;
import io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToBigDecimalConverter;
+import io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToOffsetDateTimeConverter;
import io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToUUIDConverter;
import io.tarantool.driver.mappers.converters.value.defaults.DefaultFloatValueToDoubleConverter;
import io.tarantool.driver.mappers.converters.value.defaults.DefaultFloatValueToFloatConverter;
@@ -46,6 +48,7 @@
import java.math.BigDecimal;
import java.time.Instant;
+import java.time.OffsetDateTime;
import java.util.UUID;
/**
@@ -86,6 +89,8 @@ private DefaultMessagePackMapperFactory() {
.withValueConverter(ValueType.EXTENSION, BigDecimal.class,
new DefaultExtensionValueToBigDecimalConverter())
.withValueConverter(ValueType.EXTENSION, Instant.class, new DefaultExtensionValueToInstantConverter())
+ .withValueConverter(ValueType.EXTENSION, OffsetDateTime.class,
+ new DefaultExtensionValueToOffsetDateTimeConverter())
.withValueConverter(ValueType.NIL, Object.class, new DefaultNilValueToNullConverter())
//TODO: Potential issue https://github.com/tarantool/cartridge-java/issues/118
.withObjectConverter(Character.class, StringValue.class, new DefaultCharacterToStringValueConverter())
@@ -102,6 +107,8 @@ private DefaultMessagePackMapperFactory() {
.withObjectConverter(BigDecimal.class, ExtensionValue.class,
new DefaultBigDecimalToExtensionValueConverter())
.withObjectConverter(Instant.class, ExtensionValue.class, new DefaultInstantToExtensionValueConverter())
+ .withObjectConverter(OffsetDateTime.class, ExtensionValue.class,
+ new DefaultOffsetDateTimeToExtensionValueConverter())
.build();
}
diff --git a/src/test/java/io/tarantool/driver/integration/ConvertersWithClusterClientIT.java b/src/test/java/io/tarantool/driver/integration/ConvertersWithClusterClientIT.java
index 527069c2..8d0a713d 100644
--- a/src/test/java/io/tarantool/driver/integration/ConvertersWithClusterClientIT.java
+++ b/src/test/java/io/tarantool/driver/integration/ConvertersWithClusterClientIT.java
@@ -13,10 +13,14 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import java.time.Instant;
import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
import java.time.ZoneOffset;
+import java.util.Collections;
import java.util.UUID;
import java.nio.charset.StandardCharsets;
@@ -110,4 +114,94 @@ public void test_boxOperations_shouldWorkWithVarbinary() throws Exception {
List byteListFromTarantool = Utils.convertBytesToByteList(bytesFromTarantool);
Assertions.assertEquals(byteList, byteListFromTarantool);
}
+
+ @ParameterizedTest(name = "[{index}] {0}")
+ @CsvSource(delimiter = '|', value = {
+ "Construct 'compact' value (zero nanoseconds, offset and timezone)" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17})" +
+ " | 2023-10-25T13:55:17Z",
+ "Construct 'complete' (has nanos) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983})" +
+ " | 2023-10-25T13:55:17.071983Z",
+ "Construct 'complete' (has nanos and positive offset) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983, tzoffset = 0+180})" +
+ " | 2023-10-25T13:55:17.071983+03:00",
+ "Construct 'complete' (has nanos and negative offset) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983, tzoffset = 0-180})" +
+ " | 2023-10-25T13:55:17.071983-03:00",
+ "Construct 'complete' (has nanos and timezone) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983, tz = " +
+ "'Europe/Isle_of_Man'})" +
+ " | 2023-10-25T13:55:17.071983+01:00",
+ "Parse with default format" +
+ " | parse('1970-01-01T00:00:00Z')" +
+ " | 1970-01-01T00:00:00Z",
+ "Parse with ISO8601 format and offset" +
+ " | parse('1970-01-01T00:00:00', {format = 'iso8601', tzoffset = 180})" +
+ " | 1970-01-01T00:00:00+03:00",
+ "Parse with RFC3339 format" +
+ " | parse('2017-12-27T18:45:32.999999-05:00', {format = 'rfc3339'})" +
+ " | 2017-12-27T18:45:32.999999-05:00",
+ })
+ @EnabledIf("io.tarantool.driver.TarantoolUtils#versionWithInstant")
+ public void test_eval_shouldReturnOffsetDateTime(
+ String description, String expression, OffsetDateTime expected
+ ) throws Exception {
+ List> result = client
+ .eval("return require('datetime')." + expression)
+ .get();
+
+ Assertions.assertEquals(expected, result.get(0), description);
+ }
+
+ @ParameterizedTest(name = "[{index}] {0}")
+ @CsvSource(delimiter = '|', value = {
+ "Same 'compact' value" +
+ " | ''" +
+ " | 2023-10-25T13:55:17Z" +
+ " | 2023-10-25T13:55:17Z",
+ "Same 'complete' value" +
+ " | ''" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17.071983+03:00",
+ "Subtract day from 'complete' value" +
+ " | :sub({day = 1})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-24T13:55:17.071983+03:00",
+ "Clear timezone from 'complete' value" +
+ " | :set({tz = 'UTC'})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17.071983Z",
+ "Clear nanoseconds from 'complete' value" +
+ " | :set({nsec = 0})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17+03:00",
+ "Clear nanoseconds and timezone from 'complete' value" +
+ " | :set({nsec = 0, tz = 'UTC'})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17Z",
+ "Add nanoseconds into 'compact' value" +
+ " | :add({usec = 100500})" +
+ " | 2023-10-25T13:55:17Z" +
+ " | 2023-10-25T13:55:17.100500Z",
+ "Add nanoseconds into 'complete' (has offset) value" +
+ " | :add({usec = 100500})" +
+ " | 2023-10-25T13:55:17+03:00" +
+ " | 2023-10-25T13:55:17.100500+03:00",
+ "Add nanoseconds into 'complete' (has nanos) value" +
+ " | :add({usec = 100500})" +
+ " | 2023-10-25T13:55:17.023067+03:00" +
+ " | 2023-10-25T13:55:17.123567+03:00",
+ })
+ @EnabledIf("io.tarantool.driver.TarantoolUtils#versionWithInstant")
+ public void test_eval_shouldHandleOffsetDateTime(
+ String description, String expression, OffsetDateTime original, OffsetDateTime expected
+ ) throws Exception {
+ List> result = client
+ .eval("args = {...}; return args[1]" + expression, Collections.singleton(original))
+ .get();
+
+ Assertions.assertEquals(expected, result.get(0), description);
+ }
+
}
diff --git a/src/test/java/io/tarantool/driver/integration/ConvertersWithProxyClientIT.java b/src/test/java/io/tarantool/driver/integration/ConvertersWithProxyClientIT.java
index 30809139..20dd6b13 100644
--- a/src/test/java/io/tarantool/driver/integration/ConvertersWithProxyClientIT.java
+++ b/src/test/java/io/tarantool/driver/integration/ConvertersWithProxyClientIT.java
@@ -13,8 +13,12 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.util.Collections;
import java.util.UUID;
import java.nio.charset.StandardCharsets;
@@ -120,4 +124,94 @@ public void test_crudOperations_shouldWorkWithBytesAsString() throws Exception {
List byteListFromTarantool = Utils.convertBytesToByteList(bytesFromTarantool);
Assertions.assertEquals(byteList, byteListFromTarantool);
}
+
+ @ParameterizedTest(name = "[{index}] {0}")
+ @CsvSource(delimiter = '|', value = {
+ "Construct 'compact' value (zero nanoseconds, offset and timezone)" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17})" +
+ " | 2023-10-25T13:55:17Z",
+ "Construct 'complete' (has nanos) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983})" +
+ " | 2023-10-25T13:55:17.071983Z",
+ "Construct 'complete' (has nanos and positive offset) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983, tzoffset = 0+180})" +
+ " | 2023-10-25T13:55:17.071983+03:00",
+ "Construct 'complete' (has nanos and negative offset) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983, tzoffset = 0-180})" +
+ " | 2023-10-25T13:55:17.071983-03:00",
+ "Construct 'complete' (has nanos and timezone) value" +
+ " | new({year = 2023, month = 10, day = 25, hour = 13, min = 55, sec = 17, usec = 71983, tz = " +
+ "'Europe/Isle_of_Man'})" +
+ " | 2023-10-25T13:55:17.071983+01:00",
+ "Parse with default format" +
+ " | parse('1970-01-01T00:00:00Z')" +
+ " | 1970-01-01T00:00:00Z",
+ "Parse with ISO8601 format and offset" +
+ " | parse('1970-01-01T00:00:00', {format = 'iso8601', tzoffset = 180})" +
+ " | 1970-01-01T00:00:00+03:00",
+ "Parse with RFC3339 format" +
+ " | parse('2017-12-27T18:45:32.999999-05:00', {format = 'rfc3339'})" +
+ " | 2017-12-27T18:45:32.999999-05:00",
+ })
+ @EnabledIf("io.tarantool.driver.TarantoolUtils#versionWithInstant")
+ public void test_eval_shouldReturnOffsetDateTime(
+ String description, String expression, OffsetDateTime expected
+ ) throws Exception {
+ List> result = client
+ .eval("return require('datetime')." + expression)
+ .get();
+
+ Assertions.assertEquals(expected, result.get(0), description);
+ }
+
+ @ParameterizedTest(name = "[{index}] {0}")
+ @CsvSource(delimiter = '|', value = {
+ "Same 'compact' value" +
+ " | ''" +
+ " | 2023-10-25T13:55:17Z" +
+ " | 2023-10-25T13:55:17Z",
+ "Same 'complete' value" +
+ " | ''" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17.071983+03:00",
+ "Subtract day from 'complete' value" +
+ " | :sub({day = 1})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-24T13:55:17.071983+03:00",
+ "Clear timezone from 'complete' value" +
+ " | :set({tz = 'UTC'})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17.071983Z",
+ "Clear nanoseconds from 'complete' value" +
+ " | :set({nsec = 0})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17+03:00",
+ "Clear nanoseconds and timezone from 'complete' value" +
+ " | :set({nsec = 0, tz = 'UTC'})" +
+ " | 2023-10-25T13:55:17.071983+03:00" +
+ " | 2023-10-25T13:55:17Z",
+ "Add nanoseconds into 'compact' value" +
+ " | :add({usec = 100500})" +
+ " | 2023-10-25T13:55:17Z" +
+ " | 2023-10-25T13:55:17.100500Z",
+ "Add nanoseconds into 'complete' (has offset) value" +
+ " | :add({usec = 100500})" +
+ " | 2023-10-25T13:55:17+03:00" +
+ " | 2023-10-25T13:55:17.100500+03:00",
+ "Add nanoseconds into 'complete' (has nanos) value" +
+ " | :add({usec = 100500})" +
+ " | 2023-10-25T13:55:17.023067+03:00" +
+ " | 2023-10-25T13:55:17.123567+03:00",
+ })
+ @EnabledIf("io.tarantool.driver.TarantoolUtils#versionWithInstant")
+ public void test_eval_shouldHandleOffsetDateTime(
+ String description, String expression, OffsetDateTime original, OffsetDateTime expected
+ ) throws Exception {
+ List> result = client
+ .eval("args = {...}; return args[1]" + expression, Collections.singleton(original))
+ .get();
+
+ Assertions.assertEquals(expected, result.get(0), description);
+ }
+
}
diff --git a/src/test/java/io/tarantool/driver/mappers/DefaultOffsetDateTimeConverterTest.java b/src/test/java/io/tarantool/driver/mappers/DefaultOffsetDateTimeConverterTest.java
new file mode 100644
index 00000000..bc7527a6
--- /dev/null
+++ b/src/test/java/io/tarantool/driver/mappers/DefaultOffsetDateTimeConverterTest.java
@@ -0,0 +1,56 @@
+package io.tarantool.driver.mappers;
+
+import io.tarantool.driver.mappers.converters.object.DefaultOffsetDateTimeToExtensionValueConverter;
+import io.tarantool.driver.mappers.converters.value.defaults.DefaultExtensionValueToOffsetDateTimeConverter;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.msgpack.core.MessageBufferPacker;
+import org.msgpack.core.MessagePack;
+import org.msgpack.value.ExtensionValue;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.testcontainers.shaded.org.bouncycastle.pqc.math.linearalgebra.ByteUtils.toHexString;
+
+public class DefaultOffsetDateTimeConverterTest {
+
+ @ParameterizedTest(name = "[{index}] {0}: [{1}] should encoded as [{2}]")
+ @CsvSource({
+ "Compact (contains only seconds since Epoch)" +
+ " , 2023-10-23T17:45:17Z, D7 04 2DB1366500000000",
+ "Complete (has nanoseconds)" +
+ " , 2023-10-23T17:45:17.1983Z, D8 04 2DB1366500000000 60D1D10B 0000 0000",
+ "Complete (has positive offset)" +
+ " , 2023-10-23T20:45:17+03:00, D8 04 2DB1366500000000 00000000 B400 0000",
+ "Complete (has negative offset)" +
+ " , 2023-10-23T14:45:17-03:00, D8 04 2DB1366500000000 00000000 4CFF 0000",
+ "Complete (has nanoseconds and offset)" +
+ " , 2023-10-23T14:45:17.1983-03:00, D8 04 2DB1366500000000 60D1D10B 4CFF 0000",
+ "Byte order check" +
+ " , 2023-10-23T14:45:18.1983-03:00, D8 04 2EB1366500000000 60D1D10B 4CFF 0000",
+ })
+ void test_shouldReadValueWhatItWrite(
+ @SuppressWarnings("unused") String description, OffsetDateTime original, String expectedNetwork
+ ) throws IOException {
+ DefaultOffsetDateTimeToExtensionValueConverter encoder = new DefaultOffsetDateTimeToExtensionValueConverter();
+ DefaultExtensionValueToOffsetDateTimeConverter decoder = new DefaultExtensionValueToOffsetDateTimeConverter();
+
+ assertTrue(encoder.canConvertObject(original), "Encoder should allow to encode java object");
+
+ ExtensionValue encoded = encoder.toValue(original);
+ try (MessageBufferPacker packer = MessagePack.newDefaultBufferPacker()) {
+ packer.packValue(encoded);
+ String protocol = toHexString(packer.toByteArray()).toUpperCase();
+ assertEquals(expectedNetwork.replace(" ", ""), protocol, "Network representation");
+ }
+
+ assertTrue(decoder.canConvertValue(encoded), "Decoder should allow to decode value");
+
+ OffsetDateTime decoded = decoder.fromValue(encoded);
+ assertEquals(original, decoded, "Decoded value should be equals to original");
+ }
+
+}