IGNITE-13669: Support temporal types. (#216)

diff --git a/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java b/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java
index 9d69a14..d15e72f 100644
--- a/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java
+++ b/modules/api/src/main/java/org/apache/ignite/schema/ColumnType.java
@@ -57,10 +57,22 @@
     /** 128-bit UUID. */
     public static final ColumnType UUID = new ColumnType(ColumnTypeSpec.UUID);
 
+    /** Timezone-free three-part value representing a year, month, and day. */
+    public static final ColumnType DATE = new ColumnType(ColumnTypeSpec.DATE);
+
+    /** String varlen type of unlimited length. */
+    private static final VarLenColumnType UNLIMITED_STRING = stringOf(0);
+
+    /** Blob varlen type of unlimited length. */
+    private static final VarLenColumnType UNLIMITED_BLOB = blobOf(0);
+
+    /** Number type with unlimited precision. */
+    public static final NumberColumnType UNLIMITED_NUMBER = new NumberColumnType(ColumnTypeSpec.NUMBER, NumberColumnType.UNLIMITED_PRECISION);
+
     /**
-     * Bitmask type factory method.
+     * Returns bit mask type.
      *
-     * @param bits Bitmask size in bits.
+     * @param bits Bit mask size in bits.
      * @return Bitmap type.
      */
     public static VarLenColumnType bitmaskOf(int bits) {
@@ -68,16 +80,16 @@
     }
 
     /**
-     * String factory method.
+     * Returns string type of unlimited length.
      *
      * @return String type.
      */
     public static VarLenColumnType string() {
-        return stringOf(0);
+        return UNLIMITED_STRING;
     }
 
     /**
-     * String factory method for fix-sized string type.
+     * Return string type of limited size.
      *
      * @param length String length in chars.
      * @return String type.
@@ -87,16 +99,17 @@
     }
 
     /**
-     * Blob type factory method.
+     * Returns blob type of unlimited length.
      *
      * @return Blob type.
+     * @see #blobOf(int)
      */
     public static VarLenColumnType blobOf() {
-        return blobOf(0);
+        return UNLIMITED_BLOB;
     }
 
     /**
-     * Blob type factory method for fix-sized blob.
+     * Return blob type of limited length.
      *
      * @param length Blob length in bytes.
      * @return Blob type.
@@ -106,11 +119,11 @@
     }
 
     /**
-     * Number type factory method.
+     * Returns number type with given precision.
      *
      * @param precision Precision of value.
      * @return Number type.
-     * @throws IllegalArgumentException If precision is not positive.
+     * @throws IllegalArgumentException If precision value was invalid.
      */
     public static NumberColumnType numberOf(int precision) {
         if (precision <= 0)
@@ -120,21 +133,22 @@
     }
 
     /**
-     * Number type factory method.
+     * Returns number type with the default precision.
      *
      * @return Number type.
+     * @see #numberOf(int)
      */
     public static NumberColumnType numberOf() {
-        return new NumberColumnType(ColumnTypeSpec.NUMBER, NumberColumnType.UNLIMITED_PRECISION);
+        return UNLIMITED_NUMBER;
     }
 
     /**
-     * Decimal type factory method.
+     * Returns decimal type with given precision and scale.
      *
      * @param precision Precision.
      * @param scale Scale.
      * @return Decimal type.
-     * @throws IllegalArgumentException If precision or scale are invalid.
+     * @throws IllegalArgumentException If precision and/or scale values were invalid.
      */
     public static DecimalColumnType decimalOf(int precision, int scale) {
         if (precision <= 0)
@@ -145,15 +159,16 @@
 
         if (precision < scale)
             throw new IllegalArgumentException("Precision [" + precision + "] must be" +
-                " not lower than scale [ " + scale + " ].");
+                                                   " not lower than scale [ " + scale + " ].");
 
         return new DecimalColumnType(ColumnTypeSpec.DECIMAL, precision, scale);
     }
 
     /**
-     * Decimal type factory method with default precision and scale values.
+     * Returns decimal type with default precision and scale values.
      *
      * @return Decimal type.
+     * @see #decimalOf(int, int)
      */
     public static DecimalColumnType decimalOf() {
         return new DecimalColumnType(
@@ -164,6 +179,93 @@
     }
 
     /**
+     * Returns timezone-free type representing a time of day in hours, minutes, seconds, and fractional seconds
+     * with the default precision of 6 (microseconds).
+     *
+     * @return Native type.
+     * @see TemporalColumnType#DEFAULT_PRECISION
+     * @see #time(int)
+     */
+    public static TemporalColumnType time() {
+        return new TemporalColumnType(ColumnTypeSpec.TIME, TemporalColumnType.DEFAULT_PRECISION);
+    }
+
+    /**
+     * Returns timezone-free type representing a time of day in hours, minutes, seconds, and fractional seconds.
+     * <p>
+     * Precision is a number of digits in fractional seconds part,
+     * from 0 - whole seconds precision up to 9 - nanoseconds precision.
+     *
+     * @param precision The number of digits in fractional seconds part. Accepted values are in range [0-9].
+     * @return Native type.
+     * @throws IllegalArgumentException If precision value was invalid.
+     */
+    public static TemporalColumnType time(int precision) {
+        if (precision < 0 || precision > 9)
+            throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+        return new TemporalColumnType(ColumnTypeSpec.TIME, precision);
+    }
+
+    /**
+     * Returns timezone-free datetime encoded as (date, time) with the default time precision of 6 (microseconds).
+     *
+     * @return Native type.
+     * @see TemporalColumnType#DEFAULT_PRECISION
+     * @see #datetime(int)
+     */
+    public static TemporalColumnType datetime() {
+        return new TemporalColumnType(ColumnTypeSpec.DATETIME, TemporalColumnType.DEFAULT_PRECISION);
+    }
+
+    /**
+     * Returns timezone-free datetime encoded as (date, time).
+     * <p>
+     * Precision is a number of digits in fractional seconds part of time,
+     * from 0 - whole seconds precision up to 9 - nanoseconds precision.
+     *
+     * @param precision The number of digits in fractional seconds part. Accepted values are in range [0-9].
+     * @return Native type.
+     * @throws IllegalArgumentException If precision value was invalid.
+     */
+    public static TemporalColumnType datetime(int precision) {
+        if (precision < 0 || precision > 9)
+            throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+        return new TemporalColumnType(ColumnTypeSpec.DATETIME, precision);
+    }
+
+    /**
+     * Returns point in time as number of ticks since Jan 1, 1970 00:00:00.000 (with no timezone)
+     * with the default precision of 6 (microseconds).
+     *
+     * @return Native type.
+     * @see TemporalColumnType#DEFAULT_PRECISION
+     * @see #timestamp(int)
+     */
+    public static TemporalColumnType timestamp() {
+        return new TemporalColumnType(ColumnTypeSpec.TIMESTAMP, TemporalColumnType.DEFAULT_PRECISION);
+    }
+
+    /**
+     * Returns point in time as number of ticks since Jan 1, 1970 00:00:00.000 (with no timezone).
+     * Ticks that are stored can be precised to second, millisecond, microsecond or nanosecond.
+     * <p>
+     * Precision is a number of digits in fractional seconds part of time,
+     * from 0 - whole seconds precision up to 9 - nanoseconds precision.
+     *
+     * @param precision The number of digits in fractional seconds part. Accepted values are in range [0-9].
+     * @return Native type.
+     * @throws IllegalArgumentException If precision value was invalid.
+     */
+    public static TemporalColumnType timestamp(int precision) {
+        if (precision < 0 || precision > 9)
+            throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+        return new TemporalColumnType(ColumnTypeSpec.TIMESTAMP, precision);
+    }
+
+    /**
      * Column type of variable length.
      */
     public static class VarLenColumnType extends ColumnType {
@@ -274,8 +376,7 @@
 
             DecimalColumnType type = (DecimalColumnType)o;
 
-            return precision == type.precision &&
-                scale == type.scale;
+            return precision == type.precision && scale == type.scale;
         }
 
         /** {@inheritDoc} */
@@ -289,7 +390,7 @@
      */
     public static class NumberColumnType extends ColumnType {
         /** Undefined precision. */
-        public static final int UNLIMITED_PRECISION = 0;
+        private static final int UNLIMITED_PRECISION = 0;
 
         /** Max precision of value. If -1, column has no precision restrictions. */
         private final int precision;
@@ -309,7 +410,7 @@
         /**
          * Returns column precision.
          *
-         * @return Max value precision.
+         * @return Max number of digits.
          */
         public int precision() {
             return precision;
@@ -319,11 +420,71 @@
         @Override public boolean equals(Object o) {
             if (this == o)
                 return true;
+
             if (o == null || getClass() != o.getClass())
                 return false;
+
             if (!super.equals(o))
                 return false;
+
             NumberColumnType type = (NumberColumnType)o;
+
+            return precision == type.precision;
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(super.hashCode(), precision);
+        }
+    }
+
+    /**
+     * Column type of variable length.
+     */
+    public static class TemporalColumnType extends ColumnType {
+        /** Default temporal type precision: microseconds. */
+        public static final int DEFAULT_PRECISION = 6;
+
+        /** Fractional seconds precision. */
+        private final int precision;
+
+        /**
+         * Creates temporal type.
+         *
+         * @param typeSpec Type spec.
+         * @param precision Fractional seconds meaningful digits. Allowed values are 0-9,
+         * where {@code 0} means second precision, {@code 9} means 1-ns precision.
+         */
+        private TemporalColumnType(ColumnTypeSpec typeSpec, int precision) {
+            super(typeSpec);
+
+            assert precision >= 0 && precision < 10;
+
+            this.precision = precision;
+        }
+
+        /**
+         * Return column precision.
+         *
+         * @return Number of fractional seconds meaningful digits.
+         */
+        public int precision() {
+            return precision;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            if (!super.equals(o))
+                return false;
+
+            TemporalColumnType type = (TemporalColumnType)o;
+
             return precision == type.precision;
         }
 
@@ -370,6 +531,18 @@
         /** A decimal floating-point number. */
         DECIMAL,
 
+        /** Timezone-free date. */
+        DATE,
+
+        /** Timezone-free time with precision. */
+        TIME,
+
+        /** Timezone-free datetime. */
+        DATETIME,
+
+        /** Number of ticks since Jan 1, 1970 00:00:00.000 (with no timezone). Tick unit depends on precision. */
+        TIMESTAMP,
+
         /** 128-bit UUID. */
         UUID,
 
diff --git a/modules/api/src/main/java/org/apache/ignite/table/Tuple.java b/modules/api/src/main/java/org/apache/ignite/table/Tuple.java
index adc38e4..fd0e346 100644
--- a/modules/api/src/main/java/org/apache/ignite/table/Tuple.java
+++ b/modules/api/src/main/java/org/apache/ignite/table/Tuple.java
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.table;
 
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.UUID;
 import org.apache.ignite.binary.BinaryObject;
@@ -237,4 +241,68 @@
      * @return Column value.
      */
     BitSet bitmaskValue(int columnIndex);
+
+    /**
+     * Gets {@code LocalDate} column value.
+     *
+     * @param columnName Column name.
+     * @return Column value.
+     */
+    LocalDate dateValue(String columnName);
+
+    /**
+     * Gets {@code LocalDate} column value.
+     *
+     * @param columnIndex Column index.
+     * @return Column value.
+     */
+    LocalDate dateValue(int columnIndex);
+
+    /**
+     * Gets {@code LocalTime} column value.
+     *
+     * @param columnName Column name.
+     * @return Column value.
+     */
+    LocalTime timeValue(String columnName);
+
+    /**
+     * Gets {@code LocalTime} column value.
+     *
+     * @param columnIndex Column index.
+     * @return Column value.
+     */
+    LocalTime timeValue(int columnIndex);
+
+    /**
+     * Gets {@code LocalDateTime} column value.
+     *
+     * @param columnName Column name.
+     * @return Column value.
+     */
+    LocalDateTime datetimeValue(String columnName);
+
+    /**
+     * Gets {@code LocalDateTime} column value.
+     *
+     * @param columnIndex Column index.
+     * @return Column value.
+     */
+    LocalDateTime datetimeValue(int columnIndex);
+
+    /**
+     * Gets {@code Instant} column value.
+     *
+     * @param columnName Column name.
+     * @return Column value.
+     */
+    Instant timestampValue(String columnName);
+
+    /**
+     * Gets {@code Instant} column value.
+     *
+     * @param columnIndex Column index.
+     * @return Column value.
+     */
+    Instant timestampValue(int columnIndex);
 }
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java
index 45d1280..dbda15f 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientDataType.java
@@ -53,4 +53,16 @@
 
     /** BitMask. */
     public static final int BITMASK = 11;
+
+    /** Date. */
+    public static final int DATE = 12;
+
+    /** Time. */
+    public static final int TIME = 13;
+
+    /** DateTime. */
+    public static final int DATETIME = 14;
+
+    /** Timestamp. */
+    public static final int TIMESTAMP = 15;
 }
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java
index facefa2..8b3254d 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessagePacker.java
@@ -22,6 +22,10 @@
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.UUID;
 import io.netty.buffer.ByteBuf;
@@ -348,6 +352,59 @@
     }
 
     /**
+     * Writes a date.
+     *
+     * @param val Date value.
+     * @return This instance.
+     * @throws UnsupportedOperationException Not supported.
+     */
+    public ClientMessagePacker packDate(LocalDate val) {
+        assert !closed : "Packer is closed";
+
+        throw new UnsupportedOperationException("TODO: IGNITE-15163");
+    }
+
+    /**
+     * Writes a time.
+     *
+     * @param val Time value.
+     * @return This instance.
+     * @throws UnsupportedOperationException Not supported.
+     */
+    public ClientMessagePacker packTime(LocalTime val) {
+        assert !closed : "Packer is closed";
+
+        throw new UnsupportedOperationException("TODO: IGNITE-15163");
+    }
+
+    /**
+     * Writes a datetime.
+     *
+     * @param val Datetime value.
+     * @return This instance.
+     * @throws UnsupportedOperationException Not supported.
+     */
+    public ClientMessagePacker packDateTime(LocalDateTime val) {
+        packDate(val.toLocalDate());
+        packTime(val.toLocalTime());
+
+        return this;
+    }
+
+    /**
+     * Writes a timestamp.
+     *
+     * @param val Timestamp value.
+     * @return This instance.
+     * @throws UnsupportedOperationException Not supported.
+     */
+    public ClientMessagePacker packTimestamp(Instant val) {
+        assert !closed : "Packer is closed";
+
+        throw new UnsupportedOperationException("TODO: IGNITE-15163");
+    }
+
+    /**
      * Packs an object.
      *
      * @param val Object value.
@@ -384,6 +441,18 @@
         if (val instanceof BitSet)
             return packBitSet((BitSet) val);
 
+        if (val instanceof LocalDate)
+            return packDate((LocalDate)val);
+
+        if (val instanceof LocalTime)
+            return packTime((LocalTime)val);
+
+        if (val instanceof LocalDateTime)
+            return packDateTime((LocalDateTime)val);
+
+        if (val instanceof Instant)
+            return packTimestamp((Instant)val);
+
         // TODO: Support all basic types IGNITE-15163
         throw new UnsupportedOperationException("Unsupported type, can't serialize: " + val.getClass());
     }
diff --git a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java
index a058c9c..cdf1e18 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/client/proto/ClientMessageUnpacker.java
@@ -22,6 +22,10 @@
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.UUID;
 import io.netty.buffer.ByteBuf;
@@ -38,6 +42,8 @@
 
 import static org.apache.ignite.client.proto.ClientDataType.BITMASK;
 import static org.apache.ignite.client.proto.ClientDataType.BYTES;
+import static org.apache.ignite.client.proto.ClientDataType.DATE;
+import static org.apache.ignite.client.proto.ClientDataType.DATETIME;
 import static org.apache.ignite.client.proto.ClientDataType.DECIMAL;
 import static org.apache.ignite.client.proto.ClientDataType.DOUBLE;
 import static org.apache.ignite.client.proto.ClientDataType.FLOAT;
@@ -46,6 +52,8 @@
 import static org.apache.ignite.client.proto.ClientDataType.INT64;
 import static org.apache.ignite.client.proto.ClientDataType.INT8;
 import static org.apache.ignite.client.proto.ClientDataType.STRING;
+import static org.apache.ignite.client.proto.ClientDataType.TIME;
+import static org.apache.ignite.client.proto.ClientDataType.TIMESTAMP;
 
 /**
  * Ignite-specific MsgPack extension based on Netty ByteBuf.
@@ -362,6 +370,54 @@
     }
 
     /**
+     * Reads a date.
+     *
+     * @return Date value.
+     * @throws UnsupportedOperationException Not supported yet.
+     */
+    public LocalDate unpackDate() {
+        assert refCnt > 0 : "Unpacker is closed";
+
+        throw new UnsupportedOperationException("TODO: IGNITE-15163");
+    }
+
+    /**
+     * Reads a time.
+     *
+     * @return Time value.
+     * @throws UnsupportedOperationException Not supported yet.
+     */
+    public LocalTime unpackTime() {
+        assert refCnt > 0 : "Unpacker is closed";
+
+        throw new UnsupportedOperationException("TODO: IGNITE-15163");
+    }
+
+    /**
+     * Reads a datetime.
+     *
+     * @return Datetime value.
+     * @throws UnsupportedOperationException Not supported yet.
+     */
+    public LocalDateTime unpackDateTime() {
+        assert refCnt > 0 : "Unpacker is closed";
+
+        throw new UnsupportedOperationException("TODO: IGNITE-15163");
+    }
+
+    /**
+     * Reads a timestamp.
+     *
+     * @return Timestamp value.
+     * @throws UnsupportedOperationException Not supported yet.
+     */
+    public Instant unpackTimestamp() {
+        assert refCnt > 0 : "Unpacker is closed";
+
+        throw new UnsupportedOperationException("TODO: IGNITE-15163");
+    }
+
+    /**
      * Unpacks an object based on the specified type.
      *
      * @param dataType Data type code.
@@ -408,6 +464,18 @@
 
             case BITMASK:
                 return unpackBitSet();
+
+            case DATE:
+                return unpackDate();
+
+            case TIME:
+                return unpackTime();
+
+            case DATETIME:
+                return unpackDateTime();
+
+            case TIMESTAMP:
+                return unpackTimestamp();
         }
 
         throw new IgniteException("Unknown client data type: " + dataType);
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
index 9cc3dc3..3bff8d6 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java
@@ -18,6 +18,10 @@
 package org.apache.ignite.client.handler.requests.table;
 
 import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
@@ -49,7 +53,7 @@
      * @param schemaVer Schema version.
      * @param schema Schema.
      */
-    public static void writeSchema(ClientMessagePacker packer,int schemaVer, SchemaDescriptor schema) {
+    public static void writeSchema(ClientMessagePacker packer, int schemaVer, SchemaDescriptor schema) {
         packer.packInt(schemaVer);
 
         if (schema == null) {
@@ -85,7 +89,7 @@
             return;
         }
 
-        var schema = ((SchemaAware) tuple).schema();
+        var schema = ((SchemaAware)tuple).schema();
 
         writeTuple(packer, tuple, schema);
     }
@@ -99,9 +103,9 @@
      * @throws IgniteException on failed serialization.
      */
     public static void writeTuple(
-            ClientMessagePacker packer,
-            Tuple tuple,
-            SchemaDescriptor schema
+        ClientMessagePacker packer,
+        Tuple tuple,
+        SchemaDescriptor schema
     ) {
         writeTuple(packer, tuple, schema, false, false);
     }
@@ -116,10 +120,10 @@
      * @throws IgniteException on failed serialization.
      */
     public static void writeTuple(
-            ClientMessagePacker packer,
-            Tuple tuple,
-            SchemaDescriptor schema,
-            boolean skipHeader
+        ClientMessagePacker packer,
+        Tuple tuple,
+        SchemaDescriptor schema,
+        boolean skipHeader
     ) {
         writeTuple(packer, tuple, schema, skipHeader, false);
     }
@@ -135,11 +139,11 @@
      * @throws IgniteException on failed serialization.
      */
     public static void writeTuple(
-            ClientMessagePacker packer,
-            Tuple tuple,
-            SchemaDescriptor schema,
-            boolean skipHeader,
-            boolean keyOnly
+        ClientMessagePacker packer,
+        Tuple tuple,
+        SchemaDescriptor schema,
+        boolean skipHeader,
+        boolean keyOnly
     ) {
         if (tuple == null) {
             packer.packNil();
@@ -189,12 +193,13 @@
 
         for (Tuple tuple : tuples) {
             if (schema == null) {
-                schema = ((SchemaAware) tuple).schema();
+                schema = ((SchemaAware)tuple).schema();
 
                 packer.packInt(schema.version());
                 packer.packInt(tuples.size());
-            } else
-                assert schema.version() == ((SchemaAware) tuple).schema().version();
+            }
+            else
+                assert schema.version() == ((SchemaAware)tuple).schema().version();
 
             writeTuple(packer, tuple, schema, true, keyOnly);
         }
@@ -257,10 +262,10 @@
      * @return Tuple.
      */
     public static Tuple readTuple(
-            ClientMessageUnpacker unpacker,
-            TableImpl table,
-            boolean keyOnly,
-            SchemaDescriptor schema
+        ClientMessageUnpacker unpacker,
+        TableImpl table,
+        boolean keyOnly,
+        SchemaDescriptor schema
     ) {
         var builder = table.tupleBuilder();
 
@@ -367,6 +372,18 @@
 
             case BITMASK:
                 return ClientDataType.BITMASK;
+
+            case DATE:
+                return ClientDataType.DATE;
+
+            case TIME:
+                return ClientDataType.TIME;
+
+            case DATETIME:
+                return ClientDataType.DATETIME;
+
+            case TIMESTAMP:
+                return ClientDataType.TIMESTAMP;
         }
 
         throw new IgniteException("Unsupported native type: " + spec);
@@ -382,43 +399,43 @@
 
         switch (col.type().spec()) {
             case INT8:
-                packer.packByte((byte) val);
+                packer.packByte((byte)val);
                 break;
 
             case INT16:
-                packer.packShort((short) val);
+                packer.packShort((short)val);
                 break;
 
             case INT32:
-                packer.packInt((int) val);
+                packer.packInt((int)val);
                 break;
 
             case INT64:
-                packer.packLong((long) val);
+                packer.packLong((long)val);
                 break;
 
             case FLOAT:
-                packer.packFloat((float) val);
+                packer.packFloat((float)val);
                 break;
 
             case DOUBLE:
-                packer.packDouble((double) val);
+                packer.packDouble((double)val);
                 break;
 
             case DECIMAL:
-                packer.packDecimal((BigDecimal) val);
+                packer.packDecimal((BigDecimal)val);
                 break;
 
             case UUID:
-                packer.packUuid((UUID) val);
+                packer.packUuid((UUID)val);
                 break;
 
             case STRING:
-                packer.packString((String) val);
+                packer.packString((String)val);
                 break;
 
             case BYTES:
-                byte[] bytes = (byte[]) val;
+                byte[] bytes = (byte[])val;
                 packer.packBinaryHeader(bytes.length);
                 packer.writePayload(bytes);
                 break;
@@ -427,6 +444,22 @@
                 packer.packBitSet((BitSet)val);
                 break;
 
+            case DATE:
+                packer.packDate((LocalDate)val);
+                break;
+
+            case TIME:
+                packer.packTime((LocalTime)val);
+                break;
+
+            case DATETIME:
+                packer.packDateTime((LocalDateTime)val);
+                break;
+
+            case TIMESTAMP:
+                packer.packTimestamp((Instant)val);
+                break;
+
             default:
                 throw new IgniteException("Data type not supported: " + col.type());
         }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java
index 62a49d6..97a97f9 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/table/ClientTupleBuilder.java
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.internal.client.table;
 
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.Iterator;
 import java.util.UUID;
@@ -212,6 +216,46 @@
     }
 
     /** {@inheritDoc} */
+    @Override public LocalDate dateValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDate dateValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDateTime datetimeValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDateTime datetimeValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
     @NotNull @Override public Iterator<Object> iterator() {
         return new Iterator<>() {
             /** Current column index. */
diff --git a/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java b/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java
index 592988f..dbde506 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/ClientTupleBuilderTest.java
@@ -17,9 +17,13 @@
 
 package org.apache.ignite.client;
 
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
 import java.util.BitSet;
 import java.util.UUID;
-
 import org.apache.ignite.client.proto.ClientDataType;
 import org.apache.ignite.internal.client.table.ClientColumn;
 import org.apache.ignite.internal.client.table.ClientSchema;
@@ -138,10 +142,19 @@
                 new ClientColumn("uuid", ClientDataType.UUID, false, false, 6),
                 new ClientColumn("str", ClientDataType.STRING, false, false, 7),
                 new ClientColumn("bits", ClientDataType.BITMASK, false, false, 8),
+                new ClientColumn("time", ClientDataType.TIME, false, false, 9),
+                new ClientColumn("date", ClientDataType.DATE, false, false, 10),
+                new ClientColumn("datetime", ClientDataType.DATETIME, false, false, 11),
+                new ClientColumn("timestamp", ClientDataType.TIMESTAMP, false, false, 12)
         });
 
         var uuid = UUID.randomUUID();
 
+        var date = LocalDate.of(1995, Month.MAY, 23);
+        var time = LocalTime.of(17, 0, 1, 222_333_444);
+        var datetime = LocalDateTime.of(1995, Month.MAY, 23, 17, 0, 1, 222_333_444);
+        var timestamp = Instant.now();
+
         var builder = new ClientTupleBuilder(schema)
                 .set("i8", (byte)1)
                 .set("i16", (short)2)
@@ -151,7 +164,11 @@
                 .set("double", (double)6.6)
                 .set("uuid", uuid)
                 .set("str", "8")
-                .set("bits", new BitSet(3));
+                .set("bits", new BitSet(3))
+                .set("date", date)
+                .set("time", time)
+                .set("datetime", datetime)
+                .set("timestamp", timestamp);
 
         var tuple = builder.build();
 
@@ -181,6 +198,11 @@
 
         assertEquals(0, tuple.bitmaskValue(8).length());
         assertEquals(0, tuple.bitmaskValue("bits").length());
+
+        assertEquals(date, tuple.dateValue("date"));
+        assertEquals(time, tuple.timeValue("time"));
+        assertEquals(datetime, tuple.datetimeValue("datetime"));
+        assertEquals(timestamp, tuple.timestampValue("timestamp"));
     }
 
     private static ClientTupleBuilder getBuilder() {
diff --git a/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java b/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
index b48a1da..81fa756 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/CustomTuple.java
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.client;
 
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.Iterator;
 import java.util.UUID;
@@ -51,8 +55,10 @@
 
     @Override public String columnName(int columnIndex) {
         switch (columnIndex) {
-            case 0: return "id";
-            case 1: return "name";
+            case 0:
+                return "id";
+            case 1:
+                return "name";
         }
 
         return null;
@@ -60,8 +66,10 @@
 
     @Override public Integer columnIndex(String columnName) {
         switch (columnName) {
-            case "id": return 0;
-            case "name": return 1;
+            case "id":
+                return 0;
+            case "name":
+                return 1;
         }
 
         return null;
@@ -69,8 +77,10 @@
 
     @Override public <T> T valueOrDefault(String columnName, T def) {
         switch (columnName) {
-            case "id": return (T) id;
-            case "name": return (T) name;
+            case "id":
+                return (T)id;
+            case "name":
+                return (T)name;
         }
 
         return def;
@@ -82,8 +92,10 @@
 
     @Override public <T> T value(int columnIndex) {
         switch (columnIndex) {
-            case 0: return (T) id;
-            case 1: return (T) name;
+            case 0:
+                return (T)id;
+            case 1:
+                return (T)name;
         }
 
         return null;
@@ -169,6 +181,38 @@
         throw new UnsupportedOperationException();
     }
 
+    @Override public LocalDate dateValue(String columnName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public LocalDate dateValue(int columnIndex) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public LocalTime timeValue(String columnName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public LocalTime timeValue(int columnIndex) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public LocalDateTime datetimeValue(String columnName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public LocalDateTime datetimeValue(int columnIndex) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Instant timestampValue(String columnName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override public Instant timestampValue(int columnIndex) {
+        throw new UnsupportedOperationException();
+    }
+
     @NotNull @Override public Iterator<Object> iterator() {
         throw new UnsupportedOperationException();
     }
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
index 8624633..be3da0f 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
@@ -19,6 +19,10 @@
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import org.apache.ignite.internal.schema.row.Row;
 import org.apache.ignite.internal.tostring.S;
@@ -150,6 +154,46 @@
         @Override public Object objectValue(Row tup, int colIdx) {
             return tup.numberValue(colIdx);
         }
+    },
+
+    /**
+     * Native type representing a timezone-free date.
+     */
+    DATE("date", true) {
+        /** {@inheritDoc} */
+        @Override public Object objectValue(Row tup, int colIdx) {
+            return tup.dateValue(colIdx);
+        }
+    },
+
+    /**
+     * Native type representing a timezone-free time.
+     */
+    TIME("time", true) {
+        /** {@inheritDoc} */
+        @Override public Object objectValue(Row tup, int colIdx) {
+            return tup.timeValue(colIdx);
+        }
+    },
+
+    /**
+     * Native type representing a timezone-free datetime.
+     */
+    DATETIME("datetime", true) {
+        /** {@inheritDoc} */
+        @Override public Object objectValue(Row tup, int colIdx) {
+            return tup.dateTimeValue(colIdx);
+        }
+    },
+
+    /**
+     * Native type representing a timestamp in milliseconds since Jan 1, 1970 00:00:00.000 (with no timezone).
+     */
+    TIMESTAMP("timestamp", true) {
+        /** {@inheritDoc} */
+        @Override public Object objectValue(Row tup, int colIdx) {
+            return tup.timestampValue(colIdx);
+        }
     };
 
     /** Flag indicating whether this type specifies a fixed-length type. */
@@ -234,7 +278,17 @@
         else if (cls == Double.class)
             return NativeTypeSpec.DOUBLE;
 
-        // Other types
+        // Temporal types.
+        else if (cls == LocalDate.class)
+            return NativeTypeSpec.DATE;
+        else if (cls == LocalTime.class)
+            return NativeTypeSpec.TIME;
+        else if (cls == LocalDateTime.class)
+            return NativeTypeSpec.DATETIME;
+        else if (cls == Instant.class)
+            return NativeTypeSpec.TIMESTAMP;
+
+        // Other types.
         else if (cls == byte[].class)
             return NativeTypeSpec.BYTES;
         else if (cls == String.class)
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java
index 00fb486..508be27 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypes.java
@@ -53,6 +53,9 @@
     /** */
     public static final NativeType BYTES = new VarlenNativeType(NativeTypeSpec.BYTES, Integer.MAX_VALUE);
 
+    /** Timezone-free three-part value representing a year, month, and day. */
+    public static final NativeType DATE = new NativeType(NativeTypeSpec.DATE, 3);
+
     /** Don't allow to create an instance. */
     private NativeTypes() {
     }
@@ -109,6 +112,66 @@
     }
 
     /**
+     * Creates a TIME type with given precision.
+     *
+     * @param precision Fractional seconds meaningful digits. Allowed values are 0-9 for second to nanosecond precision.
+     * @return Native type.
+     */
+    public static NativeType time(int precision) {
+        return TemporalNativeType.time(precision);
+    }
+
+    /**
+     * Creates DATETIME type as pair (date, time).
+     *
+     * @param precision Fractional seconds meaningful digits. Allowed values are 0-9 for second to nanosecond precision.
+     * @return Native type.
+     */
+    public static NativeType datetime(int precision) {
+        return TemporalNativeType.datetime(precision);
+    }
+
+    /**
+     * Creates TIMESTAMP type.
+     *
+     * @param precision Fractional seconds meaningful digits. Allowed values are 0-9 for second to nanosecond precision.
+     * @return Native type.
+     */
+    public static NativeType timestamp(int precision) {
+        return TemporalNativeType.timestamp(precision);
+    }
+
+    /**
+     * Creates a TIME type with default precision.
+     *
+     * @return Native type.
+     * @see #time(int)
+     */
+    public static NativeType time() {
+        return TemporalNativeType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+    }
+
+    /**
+     * Creates DATETIME type with default precision.
+     *
+     * @return Native type.
+     * @see #datetime(int)
+     */
+    public static NativeType datetime() {
+        return TemporalNativeType.datetime(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+    }
+
+    /**
+     * Creates TIMESTAMP type with default precision.
+     *
+     * @return Native type.
+     * @see #timestamp(int)
+     */
+    public static NativeType timestamp() {
+        return TemporalNativeType.timestamp(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+    }
+
+    /**
      * Return the native type for specified object.
      *
      * @param val Object to map to native type.
@@ -142,6 +205,18 @@
             case UUID:
                 return UUID;
 
+            case DATE:
+                return DATE;
+
+            case TIME:
+                return time();
+
+            case DATETIME:
+                return datetime();
+
+            case TIMESTAMP:
+                return timestamp();
+
             case STRING:
                 return stringOf(((CharSequence)val).length());
 
@@ -204,6 +279,18 @@
             case UUID:
                 return UUID;
 
+            case DATE:
+                return DATE;
+
+            case TIME:
+                return time(((ColumnType.TemporalColumnType)type).precision());
+
+            case DATETIME:
+                return datetime(((ColumnType.TemporalColumnType)type).precision());
+
+            case TIMESTAMP:
+                return timestamp(((ColumnType.TemporalColumnType)type).precision());
+
             case BITMASK:
                 return new BitmaskNativeType(((ColumnType.VarLenColumnType)type).length());
 
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/TemporalNativeType.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/TemporalNativeType.java
new file mode 100644
index 0000000..ec29856
--- /dev/null
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/TemporalNativeType.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import org.apache.ignite.internal.tostring.S;
+
+/**
+ * Temporal native type.
+ */
+public class TemporalNativeType extends NativeType {
+    /**
+     * Creates TIME type.
+     *
+     * @param precision Fractional seconds precision.
+     * @return Native type.
+     */
+    static TemporalNativeType time(int precision) {
+        int size = (precision > 3) ? 6 : 4;
+
+        return new TemporalNativeType(NativeTypeSpec.TIME, size, precision);
+    }
+
+    /**
+     * Creates DATETIME type.
+     *
+     * @param precision Fractional seconds precision.
+     * @return Native type.
+     */
+    static TemporalNativeType datetime(int precision) {
+        int size = NativeTypes.DATE.sizeInBytes() + ((precision > 3) ? 6 : 4);
+
+        return new TemporalNativeType(NativeTypeSpec.DATETIME, size, precision);
+    }
+
+    /**
+     * Creates TIMESTAMP type.
+     *
+     * @param precision Fractional seconds precision.
+     * @return Native type.
+     */
+    static TemporalNativeType timestamp(int precision) {
+        int size = (precision == 0) ? 8 : 12;
+
+        return new TemporalNativeType(NativeTypeSpec.TIMESTAMP, size, precision);
+    }
+
+    /** Fractional seconds precision. */
+    private final int precision;
+
+    /**
+     * Creates temporal type.
+     *
+     * @param typeSpec Type spec.
+     * @param precision Fractional seconds precision.
+     */
+    private TemporalNativeType(NativeTypeSpec typeSpec, int size, int precision) {
+        super(typeSpec, size);
+
+        if (precision < 0 || precision > 9)
+            throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+
+        this.precision = precision;
+    }
+
+    /**
+     * Return fractional seconds precision.
+     *
+     * @return Precicion;
+     */
+    public int precision() {
+        return precision;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean mismatch(NativeType type) {
+        return super.mismatch(type) || precision < ((TemporalNativeType)type).precision;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(TemporalNativeType.class.getSimpleName(), "name", spec(), "precision", precision);
+    }
+}
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java
index 0927c25..9b0c5ca 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverter.java
@@ -19,6 +19,10 @@
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -90,15 +94,19 @@
         putType(ColumnType.FLOAT);
         putType(ColumnType.DOUBLE);
         putType(ColumnType.UUID);
+        putType(ColumnType.DATE);
     }
 
-    /** */
+    /**
+     * @param type Column type.
+     */
     private static void putType(ColumnType type) {
         types.put(type.typeSpec().name(), type);
     }
 
     /**
      * Convert SortedIndexColumn to IndexColumnChange.
+     *
      * @param col IndexColumnChange.
      * @param colInit IndexColumnChange to fulfill.
      * @return IndexColumnChange to get result from.
@@ -294,6 +302,15 @@
 
                     break;
 
+                case "TIME":
+                case "DATETIME":
+                case "TIMESTAMP":
+                    ColumnType.TemporalColumnType temporalColType = (ColumnType.TemporalColumnType)colType;
+
+                    colTypeChg.changePrecision(temporalColType.precision());
+
+                    break;
+
                 default:
                     throw new IllegalArgumentException("Unknown type " + colType.typeSpec().name());
             }
@@ -340,6 +357,15 @@
                 case "NUMBER":
                     return ColumnType.numberOf(colTypeView.precision());
 
+                case "TIME":
+                    return ColumnType.time(colTypeView.precision());
+
+                case "DATETIME":
+                    return ColumnType.datetime(colTypeView.precision());
+
+                case "TIMESTAMP":
+                    return ColumnType.timestamp(colTypeView.precision());
+
                 default:
                     throw new IllegalArgumentException("Unknown type " + typeName);
             }
@@ -455,7 +481,7 @@
 
         LinkedHashMap<String, Column> colsMap = new LinkedHashMap<>(colsView.size());
 
-        columns.forEach((i,v) -> colsMap.put(v.name(), v));
+        columns.forEach((i, v) -> colsMap.put(v.name(), v));
 
         return new SchemaTableImpl(schemaName, tableName, colsMap, indices);
     }
@@ -549,7 +575,7 @@
         else if (cls == double.class)
             return ColumnType.DOUBLE;
 
-            // Boxed primitives.
+        // Boxed primitives.
         else if (cls == Byte.class)
             return ColumnType.INT8;
         else if (cls == Short.class)
@@ -563,7 +589,17 @@
         else if (cls == Double.class)
             return ColumnType.DOUBLE;
 
-            // Other types
+        // Temporal types.
+        else if (cls == LocalDate.class)
+            return ColumnType.DATE;
+        else if (cls == LocalTime.class)
+            return ColumnType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+        else if (cls == LocalDateTime.class)
+            return ColumnType.datetime(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+        else if (cls == Instant.class)
+            return ColumnType.timestamp(ColumnType.TemporalColumnType.DEFAULT_PRECISION);
+
+        // Other types
         else if (cls == String.class)
             return ColumnType.string();
         else if (cls == UUID.class)
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java
index 8287a0d..059bb7c 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/configuration/SchemaDescriptorConverter.java
@@ -21,6 +21,10 @@
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.RoundingMode;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
@@ -95,21 +99,39 @@
             case BITMASK:
                 return NativeTypes.bitmaskOf(((ColumnType.VarLenColumnType)colType).length());
 
-            case STRING:
+            case STRING: {
                 int strLen = ((ColumnType.VarLenColumnType)colType).length();
 
                 if (strLen == 0)
                     strLen = Integer.MAX_VALUE;
 
                 return NativeTypes.stringOf(strLen);
-
-            case BLOB:
+            }
+            case BLOB: {
                 int blobLen = ((ColumnType.VarLenColumnType)colType).length();
 
                 if (blobLen == 0)
                     blobLen = Integer.MAX_VALUE;
 
                 return NativeTypes.blobOf(blobLen);
+            }
+            case DATE:
+                return NativeTypes.DATE;
+            case TIME: {
+                ColumnType.TemporalColumnType temporalType = (ColumnType.TemporalColumnType)colType;
+
+                return NativeTypes.time(temporalType.precision());
+            }
+            case DATETIME: {
+                ColumnType.TemporalColumnType temporalType = (ColumnType.TemporalColumnType)colType;
+
+                return NativeTypes.datetime(temporalType.precision());
+            }
+            case TIMESTAMP: {
+                ColumnType.TemporalColumnType temporalType = (ColumnType.TemporalColumnType)colType;
+
+                return NativeTypes.timestamp(temporalType.precision());
+            }
 
             case NUMBER: {
                 ColumnType.NumberColumnType numberType = (ColumnType.NumberColumnType)colType;
@@ -161,12 +183,20 @@
                 return Double.parseDouble(dflt);
             case DECIMAL:
                 return new BigDecimal(dflt).setScale(((DecimalNativeType)type).scale(), RoundingMode.HALF_UP);
+            case NUMBER:
+                return new BigInteger(dflt);
             case STRING:
                 return dflt;
             case UUID:
                 return java.util.UUID.fromString(dflt);
-            case NUMBER:
-                return new BigInteger(dflt);
+            case DATE:
+                return LocalDate.parse(dflt);
+            case TIME:
+                return LocalTime.parse(dflt);
+            case DATETIME:
+                return LocalDateTime.parse(dflt);
+            case TIMESTAMP:
+                return Instant.parse(dflt);
             default:
                 throw new SchemaException("Default value is not supported for type: type=" + type.toString());
         }
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
index eccddcb..2c6b443 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/registry/UpgradingRowAdapter.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.schema.registry;
 
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.BitSet;
 import java.util.UUID;
 import org.apache.ignite.internal.schema.BinaryRow;
@@ -72,13 +73,7 @@
         return mapper.map(colIdx);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public byte byteValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -90,13 +85,7 @@
         return mappedId < 0 ? (byte)column.defaultValue() : super.byteValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public Byte byteValueBoxed(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -108,13 +97,7 @@
         return mappedId < 0 ? (Byte)column.defaultValue() : super.byteValueBoxed(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public short shortValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -126,13 +109,7 @@
         return mappedId < 0 ? (short)column.defaultValue() : super.shortValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public Short shortValueBoxed(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -144,13 +121,7 @@
         return mappedId < 0 ? (Short)column.defaultValue() : super.shortValueBoxed(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public int intValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -162,13 +133,7 @@
         return mappedId < 0 ? (int)column.defaultValue() : super.intValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public Integer intValueBoxed(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -180,13 +145,7 @@
         return mappedId < 0 ? (Integer)column.defaultValue() : super.intValueBoxed(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public long longValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -198,13 +157,7 @@
         return mappedId < 0 ? (long)column.defaultValue() : super.longValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public Long longValueBoxed(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -216,13 +169,7 @@
         return mappedId < 0 ? (Long)column.defaultValue() : super.longValueBoxed(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public float floatValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -234,13 +181,7 @@
         return mappedId < 0 ? (float)column.defaultValue() : super.floatValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public Float floatValueBoxed(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -252,13 +193,7 @@
         return mappedId < 0 ? (Float)column.defaultValue() : super.floatValueBoxed(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public double doubleValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -270,13 +205,8 @@
         return mappedId < 0 ? (double)column.defaultValue() : super.doubleValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+
+    /** {@inheritDoc} */
     @Override public Double doubleValueBoxed(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -288,25 +218,31 @@
         return mappedId < 0 ? (Double)column.defaultValue() : super.doubleValueBoxed(mappedId);
     }
 
-    /**
-     * Reads value from specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public BigDecimal decimalValue(int colIdx) throws InvalidTypeException {
-        // TODO: IGNITE-13668 decimal support
-        return null;
+        int mappedId = mapColumn(colIdx);
+
+        Column column = mappedId < 0 ? mapper.mappedColumn(colIdx) : super.rowSchema().column(mappedId);
+
+        if (NativeTypeSpec.DECIMAL != column.type().spec())
+            throw new SchemaException("Type conversion is not supported yet.");
+
+        return mappedId < 0 ? (BigDecimal)column.defaultValue() : super.decimalValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
+    @Override public BigInteger numberValue(int colIdx) throws InvalidTypeException {
+        int mappedId = mapColumn(colIdx);
+
+        Column column = mappedId < 0 ? mapper.mappedColumn(colIdx) : super.rowSchema().column(mappedId);
+
+        if (NativeTypeSpec.NUMBER != column.type().spec())
+            throw new SchemaException("Type conversion is not supported yet.");
+
+        return mappedId < 0 ? (BigInteger)column.defaultValue() : super.numberValue(mappedId);
+    }
+
+    /** {@inheritDoc} */
     @Override public String stringValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -318,13 +254,7 @@
         return mappedId < 0 ? (String)column.defaultValue() : super.stringValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public byte[] bytesValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -336,13 +266,7 @@
         return mappedId < 0 ? (byte[])column.defaultValue() : super.bytesValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+    /** {@inheritDoc} */
     @Override public UUID uuidValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
@@ -354,13 +278,8 @@
         return mappedId < 0 ? (UUID)column.defaultValue() : super.uuidValue(mappedId);
     }
 
-    /**
-     * Reads value for specified column.
-     *
-     * @param colIdx Column index.
-     * @return Column value.
-     * @throws InvalidTypeException If actual column type does not match the requested column type.
-     */
+
+    /** {@inheritDoc} */
     @Override public BitSet bitmaskValue(int colIdx) throws InvalidTypeException {
         int mappedId = mapColumn(colIdx);
 
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
index 3cb1af4..c254fdc 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/Row.java
@@ -22,6 +22,10 @@
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.UUID;
 import org.apache.ignite.internal.schema.BinaryRow;
@@ -31,13 +35,19 @@
 import org.apache.ignite.internal.schema.InvalidTypeException;
 import org.apache.ignite.internal.schema.NativeTypeSpec;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.TemporalNativeType;
 
 /**
  * Schema-aware row.
  * <p>
  * The class contains non-generic methods to read boxed and unboxed primitives based on the schema column types.
  * Any type conversions and coercions should be implemented outside the row by the key-value or query runtime.
+ * <p>
  * When a non-boxed primitive is read from a null column value, it is converted to the primitive type default value.
+ * <p>
+ * Natively supported temporal types are decoded automatically after read.
+ *
+ * @see TemporalTypesHelper
  */
 public class Row implements BinaryRow {
     /** Schema descriptor. */
@@ -238,7 +248,7 @@
         long offLen = findColumn(col, NativeTypeSpec.DECIMAL);
 
         if (offLen < 0)
-            return offLen == -1 ? null : (BigDecimal)rowSchema().column(col).defaultValue();
+            return null;
 
         int off = offset(offLen);
         int len = length(offLen);
@@ -261,7 +271,7 @@
         long offLen = findColumn(col, NativeTypeSpec.NUMBER);
 
         if (offLen < 0)
-            return offLen == -1 ? null : (BigInteger)rowSchema().column(col).defaultValue();
+            return null;
 
         int off = offset(offLen);
         int len = length(offLen);
@@ -348,10 +358,121 @@
     }
 
     /**
-     * @return Row flags.
+     * Reads value for specified column.
+     *
+     * @param col Column index.
+     * @return Column value.
+     * @throws InvalidTypeException If actual column type does not match the requested column type.
      */
-    private boolean hasFlag(int flag) {
-        return ((readShort(FLAGS_FIELD_OFFSET) & flag)) != 0;
+    public LocalDate dateValue(int col) throws InvalidTypeException {
+        long offLen = findColumn(col, NativeTypeSpec.DATE);
+
+        if (offLen < 0)
+            return null;
+
+        int off = offset(offLen);
+
+        return readDate(off);
+    }
+
+    /**
+     * Reads value for specified column.
+     *
+     * @param col Column index.
+     * @return Column value.
+     * @throws InvalidTypeException If actual column type does not match the requested column type.
+     */
+    public LocalTime timeValue(int col) throws InvalidTypeException {
+        long offLen = findColumn(col, NativeTypeSpec.TIME);
+
+        if (offLen < 0)
+            return null;
+
+        int off = offset(offLen);
+
+        TemporalNativeType type = (TemporalNativeType)schema.column(col).type();
+
+        return readTime(off, type);
+    }
+
+    /**
+     * Reads value for specified column.
+     *
+     * @param col Column index.
+     * @return Column value.
+     * @throws InvalidTypeException If actual column type does not match the requested column type.
+     */
+    public LocalDateTime dateTimeValue(int col) throws InvalidTypeException {
+        long offLen = findColumn(col, NativeTypeSpec.DATETIME);
+
+        if (offLen < 0)
+            return null;
+
+        int off = offset(offLen);
+
+        TemporalNativeType type = (TemporalNativeType)schema.column(col).type();
+
+        return LocalDateTime.of(readDate(off), readTime(off + 3, type));
+    }
+
+    /**
+     * Reads value for specified column.
+     *
+     * @param col Column index.
+     * @return Column value.
+     * @throws InvalidTypeException If actual column type does not match the requested column type.
+     */
+    public Instant timestampValue(int col) throws InvalidTypeException {
+        long offLen = findColumn(col, NativeTypeSpec.TIMESTAMP);
+
+        if (offLen < 0)
+            return null;
+
+        int off = offset(offLen);
+
+        TemporalNativeType type = (TemporalNativeType)schema.column(col).type();
+
+        long seconds = readLong(off);
+        int nanos = 0;
+
+        if (type.precision() != 0)
+            nanos = readInteger(off + 8);
+
+        return Instant.ofEpochSecond(seconds, nanos);
+    }
+
+    /**
+     * Reads and decode time column value.
+     *
+     * @param off Offset
+     * @param type Temporal type precision.
+     * @return LocalTime value.
+     */
+    private LocalTime readTime(int off, TemporalNativeType type) {
+        long time = Integer.toUnsignedLong(readInteger(off));
+
+        if (type.precision() > 3) {
+            time <<= 16;
+            time |= Short.toUnsignedLong(readShort(off + 4));
+            time = (time >>> TemporalTypesHelper.NANOSECOND_PART_LEN) << 32 | (time & TemporalTypesHelper.NANOSECOND_PART_MASK);
+        }
+        else // Decompress
+            time = (time >>> TemporalTypesHelper.MILLISECOND_PART_LEN) << 32 | (time & TemporalTypesHelper.MILLISECOND_PART_MASK);
+
+        return TemporalTypesHelper.decodeTime(type, time);
+    }
+
+    /**
+     * Reads and decode date column value.
+     *
+     * @param off Offset
+     * @return LocalDate value.
+     */
+    private LocalDate readDate(int off) {
+        int date = Short.toUnsignedInt(readShort(off)) << 8;
+        date |= Byte.toUnsignedInt(readByte(off + 2));
+
+        return TemporalTypesHelper.decodeDate(date);
     }
 
     /**
@@ -364,10 +485,13 @@
      *
      * @param colIdx Column index.
      * @param type Expected column type.
-     * @return Encoded offset + length of the column.
+     * @return {@code -1} if value is {@code null} for a column,
+     * or {@link Long#MAX_VALUE} if column is unknown,
+     * otherwise encoded offset + length of the column.
      * @see #offset(long)
      * @see #length(long)
      * @see InvalidTypeException If actual column type does not match the requested column type.
+     * @see org.apache.ignite.internal.schema.registry.UpgradingRowAdapter
      */
     protected long findColumn(int colIdx, NativeTypeSpec type) throws InvalidTypeException {
         // Get base offset (key start or value start) for the given column.
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
index edd3c4e..261943e 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
@@ -23,6 +23,10 @@
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.UUID;
@@ -39,6 +43,7 @@
 import org.apache.ignite.internal.schema.NativeTypes;
 import org.apache.ignite.internal.schema.NumberNativeType;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.TemporalNativeType;
 
 import static org.apache.ignite.internal.schema.BinaryRow.RowFlags.KEY_FLAGS_OFFSET;
 import static org.apache.ignite.internal.schema.BinaryRow.RowFlags.VAL_FLAGS_OFFSET;
@@ -46,8 +51,14 @@
 /**
  * Utility class to build rows using column appending pattern. The external user of this class must consult
  * with the schema and provide the columns in strict internal column sort order during the row construction.
+ * <p>
  * Additionally, the user of this class should pre-calculate the resulting row size when possible to avoid
- * unnecessary data copies and allow some size-optimizations can be applied.
+ * unnecessary data copies and allow some size optimizations to be applied.
+ * <p>
+ * Natively supported temporal types are encoded automatically with preserving sort order before writing.
+ *
+ * @see #utf8EncodedLength(CharSequence)
+ * @see TemporalTypesHelper
  */
 public class RowAssembler {
     /** Schema. */
@@ -593,6 +604,101 @@
     }
 
     /**
+     * Appends LocalDate value for the current column to the chunk.
+     *
+     * @param val Column value.
+     * @return {@code this} for chaining.
+     */
+    public RowAssembler appendDate(LocalDate val) {
+        checkType(NativeTypes.DATE);
+
+        int date = TemporalTypesHelper.encodeDate(val);
+
+        writeDate(curOff, date);
+
+        if (isKeyChunk())
+            keyHash += 31 * keyHash + val.hashCode();
+
+        shiftColumn(NativeTypes.DATE.sizeInBytes());
+
+        return this;
+    }
+
+    /**
+     * Appends LocalTime value for the current column to the chunk.
+     *
+     * @param val Column value.
+     * @return {@code this} for chaining.
+     */
+    public RowAssembler appendTime(LocalTime val) {
+        checkType(NativeTypeSpec.TIME);
+
+        TemporalNativeType type = (TemporalNativeType)curCols.column(curCol).type();
+
+        writeTime(buf, curOff, val, type);
+
+        if (isKeyChunk())
+            keyHash += 31 * keyHash + val.hashCode();
+
+        shiftColumn(type.sizeInBytes());
+
+        return this;
+    }
+
+    /**
+     * Appends LocalDateTime value for the current column to the chunk.
+     *
+     * @param val Column value.
+     * @return {@code this} for chaining.
+     */
+    public RowAssembler appendDateTime(LocalDateTime val) {
+        checkType(NativeTypeSpec.DATETIME);
+
+        TemporalNativeType type = (TemporalNativeType)curCols.column(curCol).type();
+
+        int date = TemporalTypesHelper.encodeDate(val.toLocalDate());
+
+        writeDate(curOff, date);
+        writeTime(buf, curOff + 3, val.toLocalTime(), type);
+
+        if (isKeyChunk())
+            keyHash += 31 * keyHash + val.hashCode();
+
+        shiftColumn(type.sizeInBytes());
+
+        return this;
+    }
+
+    /**
+     * Appends Instant value for the current column to the chunk.
+     *
+     * @param val Column value.
+     * @return {@code this} for chaining.
+     */
+    public RowAssembler appendTimestamp(Instant val) {
+        checkType(NativeTypeSpec.TIMESTAMP);
+
+        TemporalNativeType type = (TemporalNativeType)curCols.column(curCol).type();
+
+        long seconds = val.getEpochSecond();
+        int nanos = TemporalTypesHelper.normalizeNanos(val.getNano(), type.precision());
+
+        buf.putLong(curOff, seconds);
+
+        if (type.precision() != 0) // Write only meaningful bytes.
+            buf.putInt(curOff + 8, nanos);
+
+        if (isKeyChunk()) {
+            keyHash += 31 * keyHash + Long.hashCode(seconds);
+            keyHash += 31 * keyHash + Integer.hashCode(nanos);
+        }
+
+        shiftColumn(type.sizeInBytes());
+
+        return this;
+    }
+
+    /**
      * @return Serialized row.
      */
     public BinaryRow build() {
@@ -653,6 +759,41 @@
     }
 
     /**
+     * Writes date.
+     *
+     * @param off Offset.
+     * @param date Compacted date.
+     */
+    private void writeDate(int off, int date) {
+        buf.putShort(off, (short)(date >>> 8));
+        buf.put(off + 2, (byte)(date & 0xFF));
+    }
+
+    /**
+     * Writes time.
+     *
+     * @param buf
+     * @param off Offset.
+     * @param val Time.
+     * @param type Native type.
+     */
+    static void writeTime(ExpandableByteBuf buf, int off, LocalTime val, TemporalNativeType type) {
+        long time = TemporalTypesHelper.encodeTime(type, val);
+
+        if (type.precision() > 3) {
+            time = ((time >>> 32) << TemporalTypesHelper.NANOSECOND_PART_LEN) | (time & TemporalTypesHelper.NANOSECOND_PART_MASK);
+
+            buf.putInt(off, (int)(time >>> 16));
+            buf.putShort(off + 4, (short)(time & 0xFFFF_FFFFL));
+        }
+        else {
+            time = ((time >>> 32) << TemporalTypesHelper.MILLISECOND_PART_LEN) | (time & TemporalTypesHelper.MILLISECOND_PART_MASK);
+
+            buf.putInt(off, (int)time);
+        }
+    }
+
+    /**
      * Checks that the type being appended matches the column type.
      *
      * @param type Type spec that is attempted to be appended.
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
new file mode 100644
index 0000000..fce400d
--- /dev/null
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/TemporalTypesHelper.java
@@ -0,0 +1,296 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema.row;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import org.apache.ignite.internal.schema.TemporalNativeType;
+
+/**
+ * Helper class for temporal type conversions.
+ * <p>
+ * Provides methods to encode/decode temporal types in a compact way for further writing to row.
+ * Conversion preserves natural type order.
+ * <p>
+ * DATE is a fixed-length type which compacted representation keeps ordering, value is signed and fit into a 3-bytes.
+ * Thus, DATE value can be compared by bytes where first byte is signed and others - unsigned.
+ * Thus temporal functions, like YEAR(), can easily extracts fields with a mask,
+ * <p>
+ * Date compact structure:
+ * ┌──────────────┬─────────┬────────┐
+ * │ Year(signed) │ Month   │ Day    │
+ * ├──────────────┼─────────┼────────┤
+ * │ 15 bits      │ 4 bits  │ 5 bits │
+ * └──────────────┴─────────┴────────┘
+ * <p>
+ * TIME is a fixed-length type supporting accuracy from 1 second up to 1 nanosecond.
+ * Compacted time representation keeps ordering, values fits to 4-6 bytes value.
+ * The first 18 bits is used for hours, minutes and seconds, and the last bits for fractional seconds:
+ * 14 for millisecond precision and 30 for nanosecond.
+ * Values of a type of any intermediate precisions is normalized to the type,
+ * then stored as shortest possible structure without a precision lost.
+ * <p>
+ * Time compact structure:
+ * ┌─────────┬─────────┬──────────┬─────────────┐
+ * │ Hours   │ Minutes │ Seconds  │ Sub-seconds │
+ * ├─────────┼─────────┼──────────┼─────────────┤
+ * │ 6 bit   │ 6 bits  │ 6 bit    │ 14 bits     │ - 32-bits in total.
+ * │ 6 bit   │ 6 bits  │ 6 bit    │ 30 bits     │ - 48-bits in total.
+ * └─────────┴─────────┴──────────┴─────────────┘
+ * <p>
+ * DATETIME is just a concatenation of DATE and TIME values.
+ * <p>
+ * TIMESTAMP has similar structure to {@link java.time.Instant} and supports precision from 1 second up to 1 nanosecond.
+ * Fractional seconds part is stored in a separate bit sequence which is omitted for {@code 0} accuracy.
+ * <p>
+ * Total value size is 8/12 bytes depending on the type precision.
+ * <p>
+ * Timestamp compact structure:
+ * ┌──────────────────────────┬─────────────┐
+ * │ Seconds since the epoch  │ Sub-seconds │
+ * ├──────────────────────────┼─────────────┤
+ * │    64 bits               │ 0/32 bits   │
+ * └──────────────────────────┴─────────────┘
+ *
+ * @see org.apache.ignite.internal.schema.row.Row
+ * @see org.apache.ignite.internal.schema.row.RowAssembler
+ */
+public class TemporalTypesHelper {
+    /** Month field length. */
+    public static final int MONTH_FIELD_LENGTH = 4;
+
+    /** Day field length. */
+    public static final int DAY_FIELD_LENGTH = 5;
+
+    /** Hours field length. */
+    public static final int HOUR_FIELD_LENGTH = 5;
+
+    /** Minutes field length. */
+    public static final int MINUTES_FIELD_LENGTH = 6;
+
+    /** Seconds field length. */
+    public static final int SECONDS_FIELD_LENGTH = 6;
+
+    /** Max year boundary. */
+    public static final int MAX_YEAR = (1 << 14) - 1;
+
+    /** Min year boundary. */
+    public static final int MIN_YEAR = -(1 << 14);
+
+    /** Fractional part length for millis precision. */
+    public static final int MILLISECOND_PART_LEN = 14;
+
+    /** Fractional part mask for millis precision. */
+    public static final long MILLISECOND_PART_MASK = (1L << MILLISECOND_PART_LEN) - 1;
+
+    /** Fractional part length for nanos precision. */
+    public static final int NANOSECOND_PART_LEN = 30;
+
+    /** Fractional part mask for nanos precision. */
+    public static final long NANOSECOND_PART_MASK = (1L << NANOSECOND_PART_LEN) - 1;
+
+    /**
+     * @param len Mask length in bits.
+     * @return Mask.
+     */
+    private static int mask(int len) {
+        return (1 << len) - 1;
+    }
+
+    /**
+     * Compact LocalDate.
+     *
+     * @param date Date.
+     * @return Encoded date.
+     */
+    public static int encodeDate(LocalDate date) {
+        int val = date.getYear() << MONTH_FIELD_LENGTH;
+        val = (val | date.getMonthValue()) << DAY_FIELD_LENGTH;
+        val |= date.getDayOfMonth();
+
+        return val & (0x00FF_FFFF);
+    }
+
+    /**
+     * Expands to LocalDate.
+     *
+     * @param date Encoded date.
+     * @return LocalDate instance.
+     */
+    public static LocalDate decodeDate(int date) {
+        date = (date << 8) >> 8; // Restore sign.
+
+        int day = (date) & mask(DAY_FIELD_LENGTH);
+        int mon = (date >>= DAY_FIELD_LENGTH) & mask(MONTH_FIELD_LENGTH);
+        int year = (date >> MONTH_FIELD_LENGTH); // Sign matters.
+
+        return LocalDate.of(year, mon, day);
+    }
+
+    /**
+     * Encode LocalTime to long as concatenation of 2 int values:
+     * encoded time with precision of seconds and fractional seconds.
+     *
+     * @param type Native temporal type.
+     * @param localTime Time.
+     * @return Encoded local time.
+     * @see #NANOSECOND_PART_LEN
+     * @see #MILLISECOND_PART_LEN
+     */
+    public static long encodeTime(TemporalNativeType type, LocalTime localTime) {
+        int time = localTime.getHour() << (MINUTES_FIELD_LENGTH + SECONDS_FIELD_LENGTH);
+        time |= localTime.getMinute() << SECONDS_FIELD_LENGTH;
+        time |= localTime.getSecond();
+
+        int fractional = truncateTo(type.precision(), localTime.getNano());
+
+        return ((long)time << 32) | fractional;
+    }
+
+    /**
+     * Decode to LocalTime.
+     *
+     * @param type Type.
+     * @param time Encoded time.
+     * @return LocalTime instance.
+     */
+    public static LocalTime decodeTime(TemporalNativeType type, long time) {
+        int fractional = (int)time;
+        int time0 = (int)(time >>> 32);
+
+        int sec = time0 & mask(SECONDS_FIELD_LENGTH);
+        int min = (time0 >>>= SECONDS_FIELD_LENGTH) & mask(MINUTES_FIELD_LENGTH);
+        int hour = (time0 >>> MINUTES_FIELD_LENGTH) & mask(HOUR_FIELD_LENGTH);
+
+        // Convert to nanoseconds.
+        switch (type.precision()) {
+            case 0:
+                break;
+            case 1:
+            case 2:
+            case 3: {
+                fractional *= 1_000_000;
+                break;
+            }
+            case 4:
+            case 5:
+            case 6: {
+                fractional *= 1_000;
+                break;
+            }
+            default:
+                break;
+        }
+
+        return LocalTime.of(hour, min, sec, fractional);
+    }
+
+    /**
+     * Normalize nanoseconds regarding the precision.
+     *
+     * @param nanos Nanoseconds.
+     * @param precision Meaningful digits.
+     * @return Normalized nanoseconds.
+     */
+    public static int normalizeNanos(int nanos, int precision) {
+        switch (precision) {
+            case 0:
+                nanos = 0;
+                break;
+            case 1:
+                nanos = (nanos / 100_000_000) * 100_000_000; // 100ms precision.
+                break;
+            case 2:
+                nanos = (nanos / 10_000_000) * 10_000_000; // 10ms precision.
+                break;
+            case 3: {
+                nanos = (nanos / 1_000_000) * 1_000_000; // 1ms precision.
+                break;
+            }
+            case 4: {
+                nanos = (nanos / 100_000) * 100_000; // 100mcs precision.
+                break;
+            }
+            case 5: {
+                nanos = (nanos / 10_000) * 10_000; // 10mcs precision.
+                break;
+            }
+            case 6: {
+                nanos = (nanos / 1_000) * 1_000; // 1mcs precision.
+                break;
+            }
+            case 7: {
+                nanos = (nanos / 100) * 100; // 100ns precision.
+                break;
+            }
+            case 8: {
+                nanos = (nanos / 10) * 10; // 10ns precision.
+                break;
+            }
+            case 9: {
+                nanos = nanos; // 1ns precision
+                break;
+            }
+            default: // Should never get here.
+                throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+        }
+
+        return nanos;
+    }
+
+    /**
+     * Normalize to given precision and truncate to meaningful time unit.
+     *
+     * @param precision Precision.
+     * @param nanos Seconds' fractional part.
+     * @return Truncated fractional seconds (millis, micros or nanos).
+     */
+    private static int truncateTo(int precision, int nanos) {
+        switch (precision) {
+            case 0:
+                return 0;
+            case 1:
+                return (nanos / 100_000_000) * 100; // 100ms precision.
+            case 2:
+                return (nanos / 10_000_000) * 10; // 10ms precision.
+            case 3: {
+                return nanos / 1_000_000; // 1ms precision.
+            }
+            case 4: {
+                return (nanos / 100_000) * 100; // 100mcs precision.
+            }
+            case 5: {
+                return (nanos / 10_000) * 10; // 10mcs precision.
+            }
+            case 6: {
+                return nanos / 1_000; // 1mcs precision.
+            }
+            case 7: {
+                return (nanos / 100) * 100; // 100ns precision.
+            }
+            case 8: {
+                return (nanos / 10) * 10; // 10ns precision.
+            }
+            case 9: {
+                return nanos; // 1ns precision
+            }
+            default: // Should never get here.
+                throw new IllegalArgumentException("Unsupported fractional seconds precision: " + precision);
+        }
+    }
+}
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java
index 1e3d845..c6d30bc 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/ColumnTest.java
@@ -21,6 +21,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.apache.ignite.internal.schema.NativeTypes.BYTES;
+import static org.apache.ignite.internal.schema.NativeTypes.DATE;
 import static org.apache.ignite.internal.schema.NativeTypes.INT32;
 import static org.apache.ignite.internal.schema.NativeTypes.STRING;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -37,15 +38,17 @@
         Column[] cols = new Column[] {
             new Column("C", BYTES, false),
             new Column("B", INT32, false),
+            new Column("A", DATE, false),
             new Column("AD", STRING, false),
             new Column("AA", STRING, false),
         };
 
         Arrays.sort(cols);
 
-        assertEquals("B", cols[0].name());
-        assertEquals("C", cols[1].name());
-        assertEquals("AA", cols[2].name());
-        assertEquals("AD", cols[3].name());
+        assertEquals("A", cols[0].name());
+        assertEquals("B", cols[1].name());
+        assertEquals("C", cols[2].name());
+        assertEquals("AA", cols[3].name());
+        assertEquals("AD", cols[4].name());
     }
 }
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java
index 3876557..5c732bc 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/NativeTypeTest.java
@@ -21,6 +21,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.apache.ignite.internal.schema.NativeTypes.BYTES;
+import static org.apache.ignite.internal.schema.NativeTypes.DATE;
 import static org.apache.ignite.internal.schema.NativeTypes.DOUBLE;
 import static org.apache.ignite.internal.schema.NativeTypes.FLOAT;
 import static org.apache.ignite.internal.schema.NativeTypes.INT16;
@@ -28,7 +29,18 @@
 import static org.apache.ignite.internal.schema.NativeTypes.INT64;
 import static org.apache.ignite.internal.schema.NativeTypes.INT8;
 import static org.apache.ignite.internal.schema.NativeTypes.STRING;
+import static org.apache.ignite.internal.schema.NativeTypes.UUID;
+import static org.apache.ignite.internal.schema.NativeTypes.bitmaskOf;
+import static org.apache.ignite.internal.schema.NativeTypes.blobOf;
+import static org.apache.ignite.internal.schema.NativeTypes.datetime;
+import static org.apache.ignite.internal.schema.NativeTypes.decimalOf;
+import static org.apache.ignite.internal.schema.NativeTypes.from;
+import static org.apache.ignite.internal.schema.NativeTypes.numberOf;
+import static org.apache.ignite.internal.schema.NativeTypes.stringOf;
+import static org.apache.ignite.internal.schema.NativeTypes.time;
+import static org.apache.ignite.internal.schema.NativeTypes.timestamp;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -43,14 +55,14 @@
         assertTrue(INT8.compareTo(STRING) < 0);
         assertTrue(INT8.compareTo(BYTES) < 0);
 
-        assertTrue(NativeTypes.INT32.compareTo(STRING) < 0);
-        assertTrue(NativeTypes.INT32.compareTo(BYTES) < 0);
+        assertTrue(INT32.compareTo(STRING) < 0);
+        assertTrue(INT32.compareTo(BYTES) < 0);
 
-        assertTrue(NativeTypes.INT64.compareTo(STRING) < 0);
-        assertTrue(NativeTypes.INT64.compareTo(BYTES) < 0);
+        assertTrue(INT64.compareTo(STRING) < 0);
+        assertTrue(INT64.compareTo(BYTES) < 0);
 
-        assertTrue(NativeTypes.UUID.compareTo(STRING) < 0);
-        assertTrue(NativeTypes.UUID.compareTo(BYTES) < 0);
+        assertTrue(UUID.compareTo(STRING) < 0);
+        assertTrue(UUID.compareTo(BYTES) < 0);
     }
 
     /**
@@ -58,9 +70,22 @@
      */
     @Test
     public void compareFixlenTypesBySize() {
-        assertTrue(NativeTypes.INT16.compareTo(NativeTypes.INT32) < 0);
-        assertTrue(NativeTypes.INT32.compareTo(NativeTypes.INT64) < 0);
-        assertTrue(NativeTypes.INT64.compareTo(NativeTypes.UUID) < 0);
+        assertTrue(INT16.compareTo(INT32) < 0);
+        assertTrue(INT32.compareTo(INT64) < 0);
+        assertTrue(INT64.compareTo(UUID) < 0);
+
+        assertTrue(INT16.compareTo(DATE) < 0);
+        assertTrue(DATE.compareTo(INT32) < 0);
+
+        assertTrue(DATE.compareTo(time(0)) < 0);
+        assertTrue(INT32.compareTo(time(4)) < 0);
+        assertTrue(time(3).compareTo(time(4)) < 0);
+        assertTrue(time(9).compareTo(datetime(0)) < 0);
+
+        assertTrue(datetime(3).compareTo(INT64) < 0);
+        assertTrue(INT64.compareTo(datetime(4)) < 0);
+
+        assertTrue(INT64.compareTo(timestamp(1)) < 0);
     }
 
     /**
@@ -68,7 +93,54 @@
      */
     @Test
     public void compareFixlenTypesByDesc() {
-        assertTrue(NativeTypes.FLOAT.compareTo(NativeTypes.INT32) < 0);
+        assertTrue(FLOAT.compareTo(INT32) < 0);
+        assertTrue(datetime(0).compareTo(INT64) < 0);
+        assertTrue(INT32.compareTo(time(0)) < 0);
+        assertTrue(INT32.compareTo(time(3)) < 0);
+        assertTrue(INT64.compareTo(timestamp(0)) < 0);
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void validateTemporalTypesLength() {
+        assertEquals(3, DATE.sizeInBytes());
+
+        assertEquals(6, time().sizeInBytes());
+        assertEquals(4, time(0).sizeInBytes());
+        assertEquals(4, time(3).sizeInBytes());
+        assertEquals(6, time(4).sizeInBytes());
+        assertEquals(6, time(9).sizeInBytes());
+
+        assertEquals(9, datetime().sizeInBytes());
+        assertEquals(7, datetime(0).sizeInBytes());
+        assertEquals(7, datetime(3).sizeInBytes());
+        assertEquals(9, datetime(4).sizeInBytes());
+        assertEquals(9, datetime(9).sizeInBytes());
+
+        assertEquals(12, timestamp().sizeInBytes());
+        assertEquals(8, timestamp(0).sizeInBytes());
+        assertEquals(12, timestamp(1).sizeInBytes());
+        assertEquals(12, timestamp(9).sizeInBytes());
+
+        assertEquals(0, datetime().compareTo(datetime(6)));
+        assertEquals(0, time().compareTo(time(6)));
+        assertEquals(0, timestamp().compareTo(timestamp(6)));
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void invalidTemporalTypes() {
+        assertThrows(IllegalArgumentException.class, () -> time(-1));
+        assertThrows(IllegalArgumentException.class, () -> timestamp(-1));
+        assertThrows(IllegalArgumentException.class, () -> datetime(-1));
+
+        assertThrows(IllegalArgumentException.class, () -> time(10));
+        assertThrows(IllegalArgumentException.class, () -> timestamp(10));
+        assertThrows(IllegalArgumentException.class, () -> datetime(10));
     }
 
     /**
@@ -84,21 +156,32 @@
      */
     @Test
     public void createNativeTypeFromColumnType() {
-        assertEquals(INT8, NativeTypes.from(ColumnType.INT8));
-        assertEquals(INT16, NativeTypes.from(ColumnType.INT16));
-        assertEquals(INT32, NativeTypes.from(ColumnType.INT32));
-        assertEquals(INT64, NativeTypes.from(ColumnType.INT64));
-        assertEquals(FLOAT, NativeTypes.from(ColumnType.FLOAT));
-        assertEquals(DOUBLE, NativeTypes.from(ColumnType.DOUBLE));
-        assertEquals(BYTES, NativeTypes.from(ColumnType.blobOf()));
-        assertEquals(STRING, NativeTypes.from(ColumnType.string()));
+        assertEquals(INT8, from(ColumnType.INT8));
+        assertEquals(INT16, from(ColumnType.INT16));
+        assertEquals(INT32, from(ColumnType.INT32));
+        assertEquals(INT64, from(ColumnType.INT64));
+        assertEquals(FLOAT, from(ColumnType.FLOAT));
+        assertEquals(DOUBLE, from(ColumnType.DOUBLE));
+        assertEquals(DATE, from(ColumnType.DATE));
+        assertEquals(BYTES, from(ColumnType.blobOf()));
+        assertEquals(STRING, from(ColumnType.string()));
+
+        assertEquals(time(), from(ColumnType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION)));
+        assertEquals(datetime(), from(ColumnType.datetime(ColumnType.TemporalColumnType.DEFAULT_PRECISION)));
+        assertEquals(timestamp(), from(ColumnType.timestamp(ColumnType.TemporalColumnType.DEFAULT_PRECISION)));
 
         for (int i = 1; i < 800; i += 100) {
-            assertEquals(NativeTypes.blobOf(i), NativeTypes.from(ColumnType.blobOf(i)));
-            assertEquals(NativeTypes.stringOf(i), NativeTypes.from(ColumnType.stringOf(i)));
-            assertEquals(NativeTypes.bitmaskOf(i), NativeTypes.from(ColumnType.bitmaskOf(i)));
-            assertEquals(NativeTypes.numberOf(i), NativeTypes.from(ColumnType.numberOf(i)));
-            assertEquals(NativeTypes.decimalOf(i, i), NativeTypes.from(ColumnType.decimalOf(i, i)));
+            assertEquals(blobOf(i), from(ColumnType.blobOf(i)));
+            assertEquals(stringOf(i), from(ColumnType.stringOf(i)));
+            assertEquals(bitmaskOf(i), from(ColumnType.bitmaskOf(i)));
+            assertEquals(numberOf(i), from(ColumnType.numberOf(i)));
+            assertEquals(decimalOf(i, i), from(ColumnType.decimalOf(i, i)));
+        }
+
+        for (int i = 0; i <= 9; i++) {
+            assertEquals(time(i), from(ColumnType.time(i)));
+            assertEquals(datetime(i), from(ColumnType.datetime(i)));
+            assertEquals(timestamp(i), from(ColumnType.timestamp(i)));
         }
     }
 }
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
index 59728f6..6244b2c 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/RowTest.java
@@ -19,6 +19,10 @@
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
@@ -33,6 +37,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.apache.ignite.internal.schema.NativeTypes.BYTES;
+import static org.apache.ignite.internal.schema.NativeTypes.DATE;
 import static org.apache.ignite.internal.schema.NativeTypes.DOUBLE;
 import static org.apache.ignite.internal.schema.NativeTypes.FLOAT;
 import static org.apache.ignite.internal.schema.NativeTypes.INT16;
@@ -41,6 +46,9 @@
 import static org.apache.ignite.internal.schema.NativeTypes.INT8;
 import static org.apache.ignite.internal.schema.NativeTypes.STRING;
 import static org.apache.ignite.internal.schema.NativeTypes.UUID;
+import static org.apache.ignite.internal.schema.NativeTypes.datetime;
+import static org.apache.ignite.internal.schema.NativeTypes.time;
+import static org.apache.ignite.internal.schema.NativeTypes.timestamp;
 import static org.apache.ignite.internal.schema.TestUtils.randomBytes;
 import static org.apache.ignite.internal.schema.TestUtils.randomString;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -70,7 +78,7 @@
      */
     @Test
     public void nullableFixSizedColumns() {
-        Column[] keyCols = new Column[] {
+        Column[] keyCols = new Column[]{
             new Column("keyByteCol", INT8, false),
             new Column("keyShortCol", INT16, false),
             new Column("keyIntCol", INT32, false),
@@ -78,11 +86,15 @@
             new Column("keyFloatCol", FLOAT, false),
             new Column("keyDoubleCol", DOUBLE, false),
             new Column("keyUuidCol", UUID, false),
+            new Column("keyDateCol", DATE, false),
+            new Column("keyTimeCol", time(), false),
+            new Column("keyDateTimeCol", datetime(), false),
+            new Column("keyTimeStampCol", timestamp(), false),
             new Column("keyBitmask1Col", NativeTypes.bitmaskOf(4), false),
             new Column("keyBitmask2Col", NativeTypes.bitmaskOf(22), false)
         };
 
-        Column[] valCols = new Column[] {
+        Column[] valCols = new Column[]{
             new Column("valByteCol", INT8, false),
             new Column("valShortCol", INT16, false),
             new Column("valIntCol", INT32, false),
@@ -90,6 +102,10 @@
             new Column("valFloatCol", FLOAT, false),
             new Column("valDoubleCol", DOUBLE, false),
             new Column("valUuidCol", UUID, false),
+            new Column("valDateCol", DATE, false),
+            new Column("valTimeCol", time(), false),
+            new Column("valDateTimeCol", datetime(), false),
+            new Column("valTimeStampCol", timestamp(), false),
             new Column("valBitmask1Col", NativeTypes.bitmaskOf(4), false),
             new Column("valBitmask2Col", NativeTypes.bitmaskOf(22), false)
         };
@@ -102,7 +118,7 @@
      */
     @Test
     public void fixSizedColumns() {
-        Column[] keyCols = new Column[] {
+        Column[] keyCols = new Column[]{
             new Column("keyByteCol", INT8, true),
             new Column("keyShortCol", INT16, true),
             new Column("keyIntCol", INT32, true),
@@ -110,11 +126,15 @@
             new Column("keyFloatCol", FLOAT, true),
             new Column("keyDoubleCol", DOUBLE, true),
             new Column("keyUuidCol", UUID, true),
+            new Column("keyDateCol", DATE, true),
+            new Column("keyTimeCol", time(), true),
+            new Column("keyDateTimeCol", datetime(), true),
+            new Column("keyTimeStampCol", timestamp(), true),
             new Column("keyBitmask1Col", NativeTypes.bitmaskOf(4), true),
             new Column("keyBitmask2Col", NativeTypes.bitmaskOf(22), true),
         };
 
-        Column[] valCols = new Column[] {
+        Column[] valCols = new Column[]{
             new Column("valByteCol", INT8, true),
             new Column("valShortCol", INT16, true),
             new Column("valIntCol", INT32, true),
@@ -122,6 +142,10 @@
             new Column("valFloatCol", FLOAT, true),
             new Column("valDoubleCol", DOUBLE, true),
             new Column("valUuidCol", UUID, true),
+            new Column("valDateCol", DATE, true),
+            new Column("valTimeCol", time(), true),
+            new Column("valDateTimeCol", datetime(), true),
+            new Column("valTimeStampCol", timestamp(), true),
             new Column("valBitmask1Col", NativeTypes.bitmaskOf(4), true),
             new Column("valBitmask2Col", NativeTypes.bitmaskOf(22), true),
         };
@@ -134,22 +158,24 @@
      */
     @Test
     public void mixedColumns() {
-        Column[] keyCols = new Column[] {
+        Column[] keyCols = new Column[]{
             new Column("keyByteCol", INT8, false),
             new Column("keyShortCol", INT16, false),
             new Column("keyIntCol", INT32, false),
             new Column("keyLongCol", INT64, false),
+            new Column("keyDateTimeCol", datetime(), false),
             new Column("keyBytesCol", BYTES, false),
             new Column("keyStringCol", STRING, false),
             new Column("keyNumberCol", NativeTypes.numberOf(9), false),
             new Column("keyDecimalCol", NativeTypes.decimalOf(20, 3), false),
         };
 
-        Column[] valCols = new Column[] {
-            new Column("keyByteCol", INT8, true),
-            new Column("keyShortCol", INT16, true),
-            new Column("keyIntCol", INT32, true),
-            new Column("keyLongCol", INT64, true),
+        Column[] valCols = new Column[]{
+            new Column("valByteCol", INT8, true),
+            new Column("valShortCol", INT16, true),
+            new Column("valIntCol", INT32, true),
+            new Column("valLongCol", INT64, true),
+            new Column("valDateTimeCol", datetime(), true),
             new Column("valBytesCol", BYTES, true),
             new Column("valStringCol", STRING, true),
             new Column("valNumberCol", NativeTypes.numberOf(9), true),
@@ -160,18 +186,44 @@
     }
 
     /**
+     * Check row serialization for schema with various columns.
+     */
+    @Test
+    public void temporalColumns() {
+        Column[] keyCols = new Column[]{
+            new Column("keyTimestampCol1", timestamp(0), false),
+            new Column("keyTimestampCol2", timestamp(1), false),
+            new Column("keyTimestampCol3", timestamp(4), false),
+            new Column("keyTimestampCol4", timestamp(9), false),
+        };
+
+        Column[] valCols = new Column[]{
+            new Column("valDateTimeCol1", datetime(0), false),
+            new Column("valDateTimeCol2", datetime(1), true),
+            new Column("valDateTimeCol3", datetime(4), false),
+            new Column("valDateTimeCol4", datetime(9), true),
+            new Column("valTimeCol1", time(0), true),
+            new Column("valTimeCol2", time(1), true),
+            new Column("valTimeCol3", time(4), false),
+            new Column("valTimeCol4", time(9), false),
+        };
+
+        checkSchema(keyCols, valCols);
+    }
+
+    /**
      * Check row serialization for schema with non-nullable varlen columns only.
      */
     @Test
     public void varlenColumns() {
-        Column[] keyCols = new Column[] {
+        Column[] keyCols = new Column[]{
             new Column("keyBytesCol", BYTES, false),
             new Column("keyStringCol", STRING, false),
             new Column("keyNumberCol", NativeTypes.numberOf(9), false),
             new Column("keyDecimalCol", NativeTypes.decimalOf(20, 3), false),
         };
 
-        Column[] valCols = new Column[] {
+        Column[] valCols = new Column[]{
             new Column("valBytesCol", BYTES, false),
             new Column("valStringCol", STRING, false),
             new Column("valNumberCol", NativeTypes.numberOf(9), false),
@@ -186,12 +238,12 @@
      */
     @Test
     public void nullableVarlenColumns() {
-        Column[] keyCols = new Column[] {
+        Column[] keyCols = new Column[]{
             new Column("keyBytesCol", BYTES, true),
             new Column("keyStringCol", STRING, true),
         };
 
-        Column[] valCols = new Column[] {
+        Column[] valCols = new Column[]{
             new Column("valBytesCol", BYTES, true),
             new Column("valStringCol", STRING, true),
             new Column("valNumberCol", NativeTypes.numberOf(9), true),
@@ -520,6 +572,22 @@
                         asm.appendBitmask((BitSet)vals[i]);
                         break;
 
+                    case DATE:
+                        asm.appendDate((LocalDate)vals[i]);
+                        break;
+
+                    case TIME:
+                        asm.appendTime((LocalTime)vals[i]);
+                        break;
+
+                    case DATETIME:
+                        asm.appendDateTime((LocalDateTime)vals[i]);
+                        break;
+
+                    case TIMESTAMP:
+                        asm.appendTimestamp((Instant)vals[i]);
+                        break;
+
                     default:
                         throw new IllegalStateException("Unsupported test type: " + type);
                 }
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/TemporalTypesTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TemporalTypesTest.java
new file mode 100644
index 0000000..5c6bbe1
--- /dev/null
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TemporalTypesTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.schema;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import org.apache.ignite.internal.schema.row.TemporalTypesHelper;
+import org.apache.ignite.schema.ColumnType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test temporal type compaction.
+ */
+public class TemporalTypesTest {
+    /**
+     * Check date boundaries.
+     */
+    @Test
+    void testDate() {
+        checkDate(LocalDate.of(0, 1, 1));
+        checkDate(LocalDate.of(-1, 1, 11));
+
+        LocalDate maxDate = LocalDate.of(TemporalTypesHelper.MAX_YEAR, 12, 31);
+        LocalDate minDate = LocalDate.of(TemporalTypesHelper.MIN_YEAR, 1, 1);
+
+        checkDate(maxDate);
+        checkDate(minDate);
+
+        assertThrows(AssertionError.class, () -> checkDate(maxDate.plusDays(1)));
+        assertThrows(AssertionError.class, () -> checkDate(minDate.minusDays(1)));
+    }
+
+    /**
+     * Check time boundaries.
+     */
+    @Test
+    void testTime() {
+        for (int i = 0; i <= 9; i++) {
+            checkTime(TemporalNativeType.time(i), LocalTime.MAX.withNano(TemporalTypesHelper.normalizeNanos(LocalTime.MAX.getNano(), i))); // Seconds precision.
+            checkTime(TemporalNativeType.time(i), LocalTime.MIN);
+        }
+
+        checkTime(TemporalNativeType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION), LocalTime.MAX.truncatedTo(ChronoUnit.MICROS));
+        checkTime(TemporalNativeType.time(9), LocalTime.MAX);
+
+        assertThrows(AssertionError.class, () -> checkTime(TemporalNativeType.time(ColumnType.TemporalColumnType.DEFAULT_PRECISION), LocalTime.MAX));
+        assertThrows(AssertionError.class, () -> checkTime(TemporalNativeType.time(0), LocalTime.MAX));
+        assertThrows(AssertionError.class, () -> checkTime(TemporalNativeType.time(8), LocalTime.MAX));
+    }
+
+    /**
+     * @param date Date.
+     */
+    private void checkDate(LocalDate date) {
+        assertEquals(date, TemporalTypesHelper.decodeDate(TemporalTypesHelper.encodeDate(date)));
+    }
+
+    /**
+     * @param type Type to validate against.
+     * @param time Time value.
+     */
+    private void checkTime(TemporalNativeType type, LocalTime time) {
+        assertEquals(time, TemporalTypesHelper.decodeTime(type, TemporalTypesHelper.encodeTime(type, time)));
+    }
+}
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
index 878fb76..6d7152f 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
@@ -19,9 +19,19 @@
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Year;
+import java.time.temporal.ChronoUnit;
 import java.util.BitSet;
 import java.util.Random;
 
+import static org.apache.ignite.internal.schema.row.TemporalTypesHelper.MAX_YEAR;
+import static org.apache.ignite.internal.schema.row.TemporalTypesHelper.MIN_YEAR;
+import static org.apache.ignite.internal.schema.row.TemporalTypesHelper.normalizeNanos;
+
 /**
  * Test utility class.
  */
@@ -74,6 +84,30 @@
                 return randomBitSet(rnd, maskType.bits());
             }
 
+            case DATE: {
+                Year year = Year.of(rnd.nextInt(MAX_YEAR - MIN_YEAR) + MIN_YEAR);
+
+                return LocalDate.ofYearDay(year.getValue(), rnd.nextInt(year.length()) + 1);
+            }
+
+            case TIME:
+                return LocalTime.of(rnd.nextInt(24), rnd.nextInt(60), rnd.nextInt(60),
+                    normalizeNanos(rnd.nextInt(1_000_000_000), ((TemporalNativeType)type).precision()));
+
+            case DATETIME: {
+                Year year = Year.of(rnd.nextInt(MAX_YEAR - MIN_YEAR) + MIN_YEAR);
+
+                LocalDate date = LocalDate.ofYearDay(year.getValue(), rnd.nextInt(year.length()) + 1);
+                LocalTime time = LocalTime.of(rnd.nextInt(24), rnd.nextInt(60), rnd.nextInt(60),
+                    normalizeNanos(rnd.nextInt(1_000_000_000), ((TemporalNativeType)type).precision()));
+
+                return LocalDateTime.of(date, time);
+            }
+
+            case TIMESTAMP:
+                return Instant.ofEpochMilli(rnd.nextLong()).truncatedTo(ChronoUnit.SECONDS)
+                    .plusNanos(normalizeNanos(rnd.nextInt(1_000_000_000), ((TemporalNativeType)type).precision()));
+
             default:
                 throw new IllegalArgumentException("Unsupported type: " + type);
         }
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java b/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java
index d3ddd3a..a58d960 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/RowChunkAdapter.java
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.internal.table;
 
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.Iterator;
 import java.util.UUID;
@@ -225,6 +229,66 @@
     }
 
     /** {@inheritDoc} */
+    @Override public LocalDate dateValue(String columnName) {
+        Column col = columnByName(columnName);
+
+        return row().dateValue(col.schemaIndex());
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDate dateValue(int columnIndex) {
+        schema().validateColumnIndex(columnIndex);
+
+        return row().dateValue(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(String columnName) {
+        Column col = columnByName(columnName);
+
+        return row().timeValue(col.schemaIndex());
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(int columnIndex) {
+        schema().validateColumnIndex(columnIndex);
+
+        return row().timeValue(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDateTime datetimeValue(String columnName) {
+        Column col = columnByName(columnName);
+
+        return row().dateTimeValue(col.schemaIndex());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return
+     */
+    @Override public LocalDateTime datetimeValue(int columnIndex) {
+        schema().validateColumnIndex(columnIndex);
+
+        return row().dateTimeValue(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(String columnName) {
+        Column col = columnByName(columnName);
+
+        return row().timestampValue(col.schemaIndex());
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(int columnIndex) {
+        schema().validateColumnIndex(columnIndex);
+
+        return row().timestampValue(columnIndex);
+    }
+
+    /** {@inheritDoc} */
     @NotNull @Override public Iterator<Object> iterator() {
         return new Iterator<>() {
             /** Current column index. */
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java b/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java
index d208129..4ced975 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/TupleBuilderImpl.java
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.internal.table;
 
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -224,6 +228,46 @@
     }
 
     /** {@inheritDoc} */
+    @Override public LocalDate dateValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDate dateValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDateTime datetimeValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDateTime datetimeValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(int columnIndex) {
+        return value(columnIndex);
+    }
+
+    /** {@inheritDoc} */
     @Override public SchemaDescriptor schema() {
         return schemaDesc;
     }
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
index 440d7b7..dceee30 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/impl/TestTupleBuilder.java
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.internal.table.impl;
 
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.BitSet;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -180,6 +184,46 @@
     }
 
     /** {@inheritDoc} */
+    @Override public LocalDate dateValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDate dateValue(int columnIndex) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalTime timeValue(int columnIndex) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDateTime datetimeValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public LocalDateTime datetimeValue(int columnIndex) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(String columnName) {
+        return value(columnName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Instant timestampValue(int columnIndex) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
     @NotNull @Override public Iterator<Object> iterator() {
         throw new UnsupportedOperationException();
     }