{
+
+ R apply(T input) throws IOException;
+}
diff --git a/fastserde/avro-fastserde-tests-common/src/test/java/com/linkedin/avro/fastserde/logical/types/InMemoryEncoder.java b/fastserde/avro-fastserde-tests-common/src/test/java/com/linkedin/avro/fastserde/logical/types/InMemoryEncoder.java
new file mode 100644
index 000000000..b74d902cb
--- /dev/null
+++ b/fastserde/avro-fastserde-tests-common/src/test/java/com/linkedin/avro/fastserde/logical/types/InMemoryEncoder.java
@@ -0,0 +1,175 @@
+package com.linkedin.avro.fastserde.logical.types;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.apache.avro.Schema;
+import org.apache.avro.io.BinaryEncoder;
+import org.apache.avro.io.Encoder;
+import org.apache.avro.util.Utf8;
+
+import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper;
+import com.linkedin.avroutil1.compatibility.SchemaNormalization;
+
+/**
+ * Helper class which wraps {@link BinaryEncoder} and its corresponding {@code OutputStream}
+ * ({@link ByteArrayOutputStream}) so that we can simplify some repeatable code just to:
+ *
+ * InMemoryEncoder encoder = new InMemoryEncoder(data.getSchema());
+ * fastSerializer.serialize(data, encoder);
+ * return encoder.toByteArray();
+ *
+ * Additionally, it prepends 10-bytes header to the output byte-array so that it can be easily compared with
+ * built-in serialization method: {@code someSpecificRecord.toByteBuffer().toByteArray()}.
+ */
+public class InMemoryEncoder extends Encoder {
+
+ private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ private final BinaryEncoder binaryEncoder = AvroCompatibilityHelper.newBinaryEncoder(baos);
+
+ public InMemoryEncoder(Schema schema) {
+ final int v1HeaderLength = 10;
+ byte[] v1Header = ByteBuffer.wrap(new byte[v1HeaderLength])
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put(new byte[]{(byte) 0xC3, (byte) 0x01}) // BinaryMessageEncoder.V1_HEADER
+ .putLong(SchemaNormalization.parsingFingerprint64(schema))
+ .array();
+
+ baos.write(v1Header, 0, v1Header.length);
+ }
+
+ public byte[] toByteArray() {
+ try {
+ binaryEncoder.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return baos.toByteArray();
+ }
+
+ // generated delegate methods
+
+ @Override
+ public void writeNull() throws IOException {
+ binaryEncoder.writeNull();
+ }
+
+ @Override
+ public void writeString(Utf8 utf8) throws IOException {
+ binaryEncoder.writeString(utf8);
+ }
+
+ @Override
+ public void writeString(String string) throws IOException {
+ binaryEncoder.writeString(string);
+ }
+
+ @Override
+ public void writeBytes(ByteBuffer bytes) throws IOException {
+ binaryEncoder.writeBytes(bytes);
+ }
+
+ @Override
+ public void writeBytes(byte[] bytes, int start, int len) throws IOException {
+ binaryEncoder.writeBytes(bytes, start, len);
+ }
+
+ @Override
+ public void writeEnum(int e) throws IOException {
+ binaryEncoder.writeEnum(e);
+ }
+
+ @Override
+ public void writeArrayStart() throws IOException {
+ binaryEncoder.writeArrayStart();
+ }
+
+ @Override
+ public void setItemCount(long itemCount) throws IOException {
+ binaryEncoder.setItemCount(itemCount);
+ }
+
+ @Override
+ public void startItem() throws IOException {
+ binaryEncoder.startItem();
+ }
+
+ @Override
+ public void writeArrayEnd() throws IOException {
+ binaryEncoder.writeArrayEnd();
+ }
+
+ @Override
+ public void writeMapStart() throws IOException {
+ binaryEncoder.writeMapStart();
+ }
+
+ @Override
+ public void writeMapEnd() throws IOException {
+ binaryEncoder.writeMapEnd();
+ }
+
+ @Override
+ public void writeIndex(int unionIndex) throws IOException {
+ binaryEncoder.writeIndex(unionIndex);
+ }
+
+ @Override
+ public void writeBoolean(boolean b) throws IOException {
+ binaryEncoder.writeBoolean(b);
+ }
+
+ @Override
+ public void writeInt(int n) throws IOException {
+ binaryEncoder.writeInt(n);
+ }
+
+ @Override
+ public void writeLong(long n) throws IOException {
+ binaryEncoder.writeLong(n);
+ }
+
+ @Override
+ public void writeFloat(float f) throws IOException {
+ binaryEncoder.writeFloat(f);
+ }
+
+ @Override
+ public void writeDouble(double d) throws IOException {
+ binaryEncoder.writeDouble(d);
+ }
+
+ @Override
+ public void writeString(CharSequence charSequence) throws IOException {
+ binaryEncoder.writeString(charSequence);
+ }
+
+ @Override
+ public void writeBytes(byte[] bytes) throws IOException {
+ binaryEncoder.writeBytes(bytes);
+ }
+
+ @Override
+ public void writeFixed(byte[] bytes, int start, int len) throws IOException {
+ binaryEncoder.writeFixed(bytes, start, len);
+ }
+
+ @Override
+ public void writeFixed(byte[] bytes) throws IOException {
+ binaryEncoder.writeFixed(bytes);
+ }
+
+ @Override
+ public void writeFixed(ByteBuffer bytes) throws IOException {
+ binaryEncoder.writeFixed(bytes);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ binaryEncoder.flush();
+ }
+}
diff --git a/fastserde/avro-fastserde-tests-common/src/test/java/com/linkedin/avro/fastserde/logical/types/LogicalTypesFastSerdeTest.java b/fastserde/avro-fastserde-tests-common/src/test/java/com/linkedin/avro/fastserde/logical/types/LogicalTypesFastSerdeTest.java
new file mode 100644
index 000000000..d72597096
--- /dev/null
+++ b/fastserde/avro-fastserde-tests-common/src/test/java/com/linkedin/avro/fastserde/logical/types/LogicalTypesFastSerdeTest.java
@@ -0,0 +1,239 @@
+package com.linkedin.avro.fastserde.logical.types;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.apache.avro.util.Utf8;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import com.linkedin.avro.fastserde.Utils;
+import com.linkedin.avro.fastserde.generated.avro.FastSerdeLogicalTypesTest1;
+import com.linkedin.avro.fastserde.generated.avro.FastSerdeLogicalTypesWithDefaults;
+import com.linkedin.avro.fastserde.generated.avro.LocalTimestampRecord;
+import com.linkedin.avro.fastserde.generated.avro.LocalTimestampRecordWithDefaults;
+import com.linkedin.avroutil1.compatibility.AvroVersion;
+
+public class LogicalTypesFastSerdeTest extends LogicalTypesTestBase {
+
+ @DataProvider
+ public static Object[][] logicalTypesTestCases() {
+ LocalDate now = LocalDate.now();
+ LocalDate localDate = LocalDate.of(2023, 8, 11);
+
+ Map mapOfDates = mapOf(
+ new String[]{"today", "yesterday", "tomorrow"},
+ new LocalDate[]{now, now.minusDays(1), now.plusDays(1)});
+
+ Map mapOfTimestamps = mapOf(
+ new String[]{"today", "yesterday", "tomorrow"},
+ new Instant[]{toInstant(now), toInstant(now.minusDays(1)), toInstant(now.plusDays(1))});
+
+ Map mapOfDatesAndTimestamps = mapOf(
+ new String[]{"today", "yesterday", "tomorrow"},
+ new Object[]{toInstant(now), now.minusDays(1), now.plusDays(1)});
+
+ Object[] mapOfUnionsOfDateAndTimestampMillisOptions = {mapOfDates, mapOfTimestamps, mapOfDatesAndTimestamps};
+ Object[] nullableUnionOfDateAndLocalTimestampOptions = {null, now.minusDays(12), localDate.atStartOfDay()};
+ Object[] unionOfDateAndLocalTimestampOptions = {now.minusDays(12), localDate.atStartOfDay()};
+ Object[] unionOfArrayAndMapOptions = {
+ Lists.newArrayList(LocalTime.now(), LocalTime.now().plusMinutes(1)), mapOfDates};
+ Object[] nullableArrayOfDatesOptions = {
+ null, Lists.newArrayList(localDate, localDate.plusDays(11), localDate.plusDays(22))};
+ Object[] decimalOrDateOptions = {new BigDecimal("3.14"), LocalDate.of(2023, 3, 14)};
+
+ List