This is an automated email from the ASF dual-hosted git repository.

ppa pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 71b24675d2 IGNITE-21551 Sql. Enable TIMESTAMP_WITH_LOCAL_TIME_ZONE 
type (#3229)
71b24675d2 is described below

commit 71b24675d2def00a9485b26ce2e6af51b41c5864
Author: Pavel Pereslegin <xxt...@gmail.com>
AuthorDate: Mon Feb 26 15:20:59 2024 +0300

    IGNITE-21551 Sql. Enable TIMESTAMP_WITH_LOCAL_TIME_ZONE type (#3229)
---
 .../main/java/org/apache/ignite/sql/Session.java   |  16 ++
 .../ignite/internal/client/sql/ClientSession.java  |   8 +
 .../internal/client/sql/ClientSessionBuilder.java  |   7 +
 .../apache/ignite/client/fakes/FakeSession.java    |   8 +
 .../ignite/client/fakes/FakeSessionBuilder.java    |   7 +
 .../internal/table/ItPublicApiColocationTest.java  |   4 +-
 .../internal/ClusterPerClassIntegrationTest.java   |  14 +-
 .../internal/ClusterPerTestIntegrationTest.java    |   2 +-
 .../ignite/internal/sql/api/ItCommonApiTest.java   |  39 ++-
 .../internal/sql/engine/ItCreateTableDdlTest.java  |   6 +-
 .../internal/sql/engine/ItDataTypesTest.java       | 122 ++++----
 .../internal/sql/engine/ItFunctionsTest.java       |  57 ++--
 .../ignite/internal/sql/engine/ItIntervalTest.java | 318 +++++++++++++++++----
 .../ignite/internal/sql/engine/ItMetadataTest.java |  14 +-
 .../src/main/codegen/includes/parserImpls.ftl      |  10 +-
 .../internal/sql/api/SessionBuilderImpl.java       |  11 +
 .../ignite/internal/sql/api/SessionImpl.java       |  17 +-
 .../ignite/internal/sql/engine/QueryProperty.java  |   2 +
 .../internal/sql/engine/SqlQueryProcessor.java     |  20 +-
 .../internal/sql/engine/exec/ExecutionContext.java |  22 +-
 .../sql/engine/exec/ExecutionServiceImpl.java      |  13 +-
 .../sql/engine/exec/exp/ExpressionFactoryImpl.java |   7 +
 .../sql/engine/exec/exp/IgniteSqlFunctions.java    |  18 +-
 .../internal/sql/engine/exec/exp/RexImpTable.java  |   2 +
 .../sql/engine/exec/exp/RexToLixTranslator.java    |  17 +-
 .../sql/engine/message/QueryStartRequest.java      |   5 +
 .../internal/sql/engine/util/BaseQueryContext.java |  28 +-
 .../engine/util/IgniteCustomAssignmentsRules.java  |  14 +-
 .../internal/sql/engine/util/IgniteMethod.java     |   4 +
 .../ignite/internal/sql/engine/util/TypeUtils.java |   3 +-
 .../ignite/internal/sql/api/SessionImplTest.java   |  35 ++-
 .../sql/engine/exec/ExecutionServiceImplTest.java  |  33 +--
 .../sql/engine/exec/RuntimeSortedIndexTest.java    |   4 +-
 .../engine/exec/exp/IgniteSqlFunctionsTest.java    |  34 +++
 .../sql/engine/exec/rel/AbstractExecutionTest.java |   4 +-
 .../engine/exec/rel/MergeJoinExecutionTest.java    |   5 +-
 .../sql/engine/framework/TestBuilders.java         |   4 +-
 .../internal/sql/engine/framework/TestNode.java    |   2 +
 .../sql/engine/planner/CastResolutionTest.java     |  37 +--
 .../internal/sql/engine/sql/SqlDdlParserTest.java  |  19 +-
 .../internal/sql/engine/util/QueryChecker.java     |   3 +
 .../internal/sql/engine/util/QueryCheckerImpl.java |  33 ++-
 42 files changed, 763 insertions(+), 265 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/sql/Session.java 
b/modules/api/src/main/java/org/apache/ignite/sql/Session.java
index a969942acc..1d64ed1915 100644
--- a/modules/api/src/main/java/org/apache/ignite/sql/Session.java
+++ b/modules/api/src/main/java/org/apache/ignite/sql/Session.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.sql;
 
+import java.time.ZoneId;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow;
 import java.util.concurrent.TimeUnit;
@@ -283,6 +284,13 @@ public interface Session extends AutoCloseable {
      */
     int defaultPageSize();
 
+    /**
+     * Returns time zone used for this session.
+     *
+     * @return Time zone used for this session.
+     */
+    ZoneId timeZoneId();
+
     /**
      * Returns a session property.
      *
@@ -398,6 +406,14 @@ public interface Session extends AutoCloseable {
          */
         SessionBuilder defaultPageSize(int pageSize);
 
+        /**
+         * Sets a default time zone for this session.
+         *
+         * @param timeZoneId Time zone ID.
+         * @return {@code this} for chaining.
+         */
+        SessionBuilder timeZoneId(ZoneId timeZoneId);
+
         /**
          * Returns a session property.
          *
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java
index ce21e337e9..bb7bfdff5a 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java
@@ -21,6 +21,7 @@ import static 
org.apache.ignite.internal.client.ClientUtils.sync;
 import static org.apache.ignite.internal.client.table.ClientTable.writeTx;
 import static 
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
 
+import java.time.ZoneId;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
@@ -283,6 +284,13 @@ public class ClientSession implements AbstractSession {
         return defaultPageSize == null ? 0 : defaultPageSize;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public ZoneId timeZoneId() {
+        // TODO https://issues.apache.org/jira/browse/IGNITE-21568
+        throw new UnsupportedOperationException();
+    }
+
     /** {@inheritDoc} */
     @Override
     public @Nullable Object property(String name) {
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSessionBuilder.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSessionBuilder.java
index ad8e3c3d63..ca168b26dc 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSessionBuilder.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSessionBuilder.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.client.sql;
 
+import java.time.ZoneId;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -119,6 +120,12 @@ public class ClientSessionBuilder implements 
SessionBuilder {
         return this;
     }
 
+    @Override
+    public SessionBuilder timeZoneId(ZoneId timeZoneId) {
+        // TODO https://issues.apache.org/jira/browse/IGNITE-21568
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public @Nullable Object property(String name) {
         return properties.get(name);
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSession.java 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSession.java
index 9a0e79eed1..9a42b8db2e 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSession.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSession.java
@@ -21,6 +21,7 @@ import static 
org.apache.ignite.internal.client.ClientUtils.sync;
 import static 
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
 import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
 
+import java.time.ZoneId;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
@@ -239,6 +240,13 @@ public class FakeSession implements AbstractSession {
         return defaultPageSize;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public ZoneId timeZoneId() {
+        // TODO https://issues.apache.org/jira/browse/IGNITE-21568
+        throw new UnsupportedOperationException();
+    }
+
     /** {@inheritDoc} */
     @Override
     public @Nullable Object property(String name) {
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSessionBuilder.java
 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSessionBuilder.java
index 3d8876167a..c704377a13 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSessionBuilder.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeSessionBuilder.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.client.fakes;
 
+import java.time.ZoneId;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -107,6 +108,12 @@ public class FakeSessionBuilder implements SessionBuilder {
         return this;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public SessionBuilder timeZoneId(ZoneId timeZoneId) {
+        throw new UnsupportedOperationException();
+    }
+
     /** {@inheritDoc} */
     @Override
     public @Nullable Object property(String name) {
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItPublicApiColocationTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItPublicApiColocationTest.java
index 6669649d90..45985a1c22 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItPublicApiColocationTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItPublicApiColocationTest.java
@@ -66,9 +66,7 @@ public class ItPublicApiColocationTest extends 
ClusterPerClassIntegrationTest {
      */
     private static final Set<NativeTypeSpec> EXCLUDED_TYPES = Stream.of(
             NativeTypeSpec.BITMASK,
-            NativeTypeSpec.NUMBER,
-            // TODO: Remove after IGNITE-19274
-            NativeTypeSpec.TIMESTAMP)
+            NativeTypeSpec.NUMBER)
             .collect(Collectors.toSet());
 
     @AfterEach
diff --git 
a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
 
b/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
index 32233e6e49..ca84a41922 100644
--- 
a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
+++ 
b/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerClassIntegrationTest.java
@@ -24,6 +24,7 @@ import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCo
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.nio.file.Path;
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -356,13 +357,14 @@ public abstract class ClusterPerClassIntegrationTest 
extends IgniteIntegrationTe
      *
      * @param node Ignite instance to run a query.
      * @param tx Transaction to run a given query. Can be {@code null} to run 
within implicit transaction.
+     * @param zoneId Client time zone.
      * @param sql Query to be run.
      * @param args Dynamic parameters for a given query.
      * @return List of lists, where outer list represents a rows, internal 
lists represents a columns.
      */
-    public static List<List<Object>> sql(Ignite node, @Nullable Transaction 
tx, String sql, Object... args) {
+    public static List<List<Object>> sql(Ignite node, @Nullable Transaction 
tx, @Nullable ZoneId zoneId, String sql, Object... args) {
         try (
-                Session session = node.sql().createSession();
+                Session session = 
node.sql().sessionBuilder().timeZoneId(zoneId).build();
                 ResultSet<SqlRow> rs = session.execute(tx, sql, args)
         ) {
             return getAllResultSet(rs);
@@ -374,12 +376,16 @@ public abstract class ClusterPerClassIntegrationTest 
extends IgniteIntegrationTe
     }
 
     protected static List<List<Object>> sql(int nodeIndex, @Nullable 
Transaction tx, String sql, Object[] args) {
+        return sql(nodeIndex, tx, null, sql, args);
+    }
+
+    protected static List<List<Object>> sql(int nodeIndex, @Nullable 
Transaction tx, @Nullable ZoneId zoneId, String sql, Object[] args) {
         IgniteImpl node = CLUSTER.node(nodeIndex);
 
         if (!AWAIT_INDEX_AVAILABILITY.get()) {
-            return sql(node, tx, sql, args);
+            return sql(node, tx, zoneId, sql, args);
         } else {
-            return executeAwaitingIndexes(node, (n) -> sql(n, tx, sql, args));
+            return executeAwaitingIndexes(node, (n) -> sql(n, tx, zoneId, sql, 
args));
         }
     }
 
diff --git 
a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerTestIntegrationTest.java
 
b/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerTestIntegrationTest.java
index d115619117..6362c04674 100644
--- 
a/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerTestIntegrationTest.java
+++ 
b/modules/runner/src/testFixtures/java/org/apache/ignite/internal/ClusterPerTestIntegrationTest.java
@@ -213,6 +213,6 @@ public abstract class ClusterPerTestIntegrationTest extends 
IgniteIntegrationTes
     protected final List<List<Object>> executeSql(int nodeIndex, String sql, 
Object... args) {
         IgniteImpl ignite = node(nodeIndex);
 
-        return ClusterPerClassIntegrationTest.sql(ignite, null, sql, args);
+        return ClusterPerClassIntegrationTest.sql(ignite, null, null, sql, 
args);
     }
 }
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java
index 08ded62911..041d54e619 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItCommonApiTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.api;
 
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -28,6 +29,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import org.apache.ignite.Ignite;
@@ -102,41 +105,49 @@ public class ItCommonApiTest extends 
BaseSqlIntegrationTest {
         //String tsStr = "2023-03-29T08:22:33.005007Z";
         String tsStr = "2023-03-29T08:22:33.005Z";
 
-        Instant ins = Instant.parse(tsStr);
+        LocalDateTime localDate = LocalDateTime.parse(tsStr, 
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
+        Instant instant = localDate.atZone(ZoneId.systemDefault()).toInstant();
 
-        sql("CREATE TABLE timestamps(id INTEGER PRIMARY KEY, i TIMESTAMP(9))");
+        sql("CREATE TABLE timestamps(id INTEGER PRIMARY KEY, i TIMESTAMP(9), 
i_tz TIMESTAMP WITH LOCAL TIME ZONE)");
 
-        // TODO: IGNITE-19274 Add column with TIMESTAMP WITH LOCAL TIME ZONE
-        sql(String.format("CREATE TABLE %s(\"%s\" INTEGER PRIMARY KEY, 
\"TIMESTAMP\" TIMESTAMP(9))", kvTblName, keyCol));
+        sql(format("CREATE TABLE {}("
+                        + "\"{}\" INTEGER PRIMARY KEY, "
+                        + "\"TIMESTAMP\" TIMESTAMP(9), "
+                        + "\"TIMESTAMP_TZ\" TIMESTAMP(9) WITH LOCAL TIME 
ZONE)", kvTblName, keyCol));
 
         Table tbl = node.tables().table(kvTblName);
 
+        localDate = LocalDateTime.of(2023, 3, 29, 8, 22, 33, 5000000);
+
         Tuple rec = Tuple.create()
                 .set("KEY", 1)
-                .set("TIMESTAMP", LocalDateTime.of(2023, 3, 29, 8, 22, 33, 
5000000));
+                .set("TIMESTAMP", localDate)
+                .set("TIMESTAMP_TZ", instant);
 
         tbl.recordView().insert(null, rec);
 
         // TODO: https://issues.apache.org/jira/browse/IGNITE-19161 Can`t 
insert timestamp representing in ISO_INSTANT format
-        tsStr = tsStr.replace("T", " ").substring(0, tsStr.length() - 1);
+        String tsValue = tsStr.replace("T", " ").substring(0, tsStr.length() - 
1);
 
-        sql("INSERT INTO timestamps VALUES (101, TIMESTAMP '" + tsStr + "')");
+        sql(format("INSERT INTO timestamps VALUES (101, TIMESTAMP '{}', 
TIMESTAMP WITH LOCAL TIME ZONE '{}')", tsValue, tsValue));
 
         try (Session ses = node.sql().createSession()) {
             // for projection pop up
-            ResultSet<SqlRow> res = ses.execute(null, "SELECT i, id FROM 
timestamps");
-
-            String srtRepr = ins.toString();
+            ResultSet<SqlRow> res = ses.execute(null, "SELECT i, i_tz, id FROM 
timestamps");
 
-            String expDateTimeStr = srtRepr.substring(0, srtRepr.length() - 1);
+            SqlRow row = res.next();
 
-            assertEquals(expDateTimeStr, 
res.next().datetimeValue(0).toString());
+            assertEquals(localDate, row.datetimeValue("i"));
+            assertEquals(instant, row.timestampValue("i_tz"));
 
-            String query = "select \"KEY\", \"TIMESTAMP\" from 
TBL_ALL_COLUMNS_SQL ORDER BY KEY";
+            String query = "select \"KEY\", \"TIMESTAMP\", \"TIMESTAMP_TZ\" 
from TBL_ALL_COLUMNS_SQL ORDER BY KEY";
 
             res = ses.execute(null, query);
 
-            assertEquals(expDateTimeStr, 
res.next().datetimeValue(1).toString());
+            row = res.next();
+
+            assertEquals(localDate, row.datetimeValue("TIMESTAMP"));
+            assertEquals(instant, row.timestampValue("TIMESTAMP_TZ"));
         }
     }
 
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
index 4ffbe37093..7e966c3e6c 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
@@ -232,9 +232,7 @@ public class ItCreateTableDdlTest extends 
BaseSqlIntegrationTest {
 
         Set<NativeTypeSpec> unsupportedTypes = Set.of(
                 // TODO https://issues.apache.org/jira/browse/IGNITE-18431
-                NativeTypeSpec.BITMASK,
-                // TODO https://issues.apache.org/jira/browse/IGNITE-19274
-                NativeTypeSpec.TIMESTAMP
+                NativeTypeSpec.BITMASK
         );
 
         // List of columns for 'ADD COLUMN' statement.
@@ -256,7 +254,7 @@ public class ItCreateTableDdlTest extends 
BaseSqlIntegrationTest {
                 dropColumnsList.app(',');
             }
 
-            addColumnsList.app("c").app(i).app(' 
').app(relDataType.getSqlTypeName());
+            addColumnsList.app("c").app(i).app(' 
').app(relDataType.getSqlTypeName().getSpaceName());
             dropColumnsList.app("c").app(i);
         }
 
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
index b3517b88db..c17f979aaa 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
@@ -26,8 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.params.provider.Arguments.arguments;
 
 import java.math.BigDecimal;
+import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -194,48 +198,74 @@ public class ItDataTypesTest extends 
BaseSqlIntegrationTest {
      */
     @Test
     public void testDateTime() {
-        assertQuery("SELECT date 
'1992-01-19'").returns(sqlDate("1992-01-19")).check();
-        assertQuery("SELECT date '1992-01-18' + interval (1) 
days").returns(sqlDate("1992-01-19")).check();
-        assertQuery("SELECT date '1992-01-18' + interval (24) 
hours").returns(sqlDate("1992-01-19")).check();
-        assertQuery("SELECT timestamp '1992-01-18 02:30:00' + interval (25) 
hours")
-                .returns(sqlDateTime("1992-01-19T03:30:00")).check();
-        assertQuery("SELECT timestamp '1992-01-18 02:30:00' + interval (23) 
hours")
-                .returns(sqlDateTime("1992-01-19T01:30:00.000")).check();
-        assertQuery("SELECT timestamp '1992-01-18 02:30:00' + interval (24) 
hours")
-                .returns(sqlDateTime("1992-01-19T02:30:00.000")).check();
-
-        assertQuery("SELECT date 
'1992-03-29'").returns(sqlDate("1992-03-29")).check();
-        assertQuery("SELECT date '1992-03-28' + interval (1) 
days").returns(sqlDate("1992-03-29")).check();
-        assertQuery("SELECT date '1992-03-28' + interval (24) 
hours").returns(sqlDate("1992-03-29")).check();
-        assertQuery("SELECT timestamp '1992-03-28 02:30:00' + interval (25) 
hours")
-                .returns(sqlDateTime("1992-03-29T03:30:00.000")).check();
-        assertQuery("SELECT timestamp '1992-03-28 02:30:00' + interval (23) 
hours")
-                .returns(sqlDateTime("1992-03-29T01:30:00.000")).check();
-        assertQuery("SELECT timestamp '1992-03-28 02:30:00' + interval (24) 
hours")
-                .returns(sqlDateTime("1992-03-29T02:30:00.000")).check();
-
-        assertQuery("SELECT date 
'1992-09-27'").returns(sqlDate("1992-09-27")).check();
-        assertQuery("SELECT date '1992-09-26' + interval (1) 
days").returns(sqlDate("1992-09-27")).check();
-        assertQuery("SELECT date '1992-09-26' + interval (24) 
hours").returns(sqlDate("1992-09-27")).check();
-        assertQuery("SELECT timestamp '1992-09-26 02:30:00' + interval (25) 
hours")
-                .returns(sqlDateTime("1992-09-27T03:30:00.000")).check();
-        assertQuery("SELECT timestamp '1992-09-26 02:30:00' + interval (23) 
hours")
-                .returns(sqlDateTime("1992-09-27T01:30:00.000")).check();
-        assertQuery("SELECT timestamp '1992-09-26 02:30:00' + interval (24) 
hours")
-                .returns(sqlDateTime("1992-09-27T02:30:00.000")).check();
-
-        assertQuery("SELECT date 
'2021-11-07'").returns(sqlDate("2021-11-07")).check();
-        assertQuery("SELECT date '2021-11-06' + interval (1) 
days").returns(sqlDate("2021-11-07")).check();
-        assertQuery("SELECT date '2021-11-06' + interval (24) 
hours").returns(sqlDate("2021-11-07")).check();
-        assertQuery("SELECT timestamp '2021-11-06 01:30:00' + interval (25) 
hours")
-                .returns(sqlDateTime("2021-11-07T02:30:00.000")).check();
-        // Check string representation here, since after timestamp calculation 
we have '2021-11-07T01:30:00.000-0800'
-        // but Timestamp.valueOf method converts '2021-11-07 01:30:00' in 
'America/Los_Angeles' time zone to
-        // '2021-11-07T01:30:00.000-0700' (we pass through '2021-11-07 
01:30:00' twice after DST ended).
-        assertQuery("SELECT (timestamp '2021-11-06 02:30:00' + interval (23) 
hours)::varchar")
-                .returns("2021-11-07 01:30:00").check();
-        assertQuery("SELECT (timestamp '2021-11-06 01:30:00' + interval (24) 
hours)::varchar")
-                .returns("2021-11-07 01:30:00").check();
+        assertQuery("SELECT time '12:34:56'")
+                .returns(LocalTime.parse("12:34:56"))
+                .check();
+
+        assertQuery("SELECT date '1992-01-19'")
+                .returns(LocalDate.parse("1992-01-19"))
+                .check();
+
+        assertQuery("SELECT timestamp '1992-01-18 02:30:00.123'")
+                .returns(LocalDateTime.parse("1992-01-18T02:30:00.123"))
+                .check();
+
+        assertQuery("SELECT timestamp with local time zone '1992-01-18 
02:30:00.123'")
+                .withTimeZoneId(ZoneId.of("America/New_York"))
+                .returns(Instant.parse("1992-01-18T07:30:00.123Z"))
+                .check();
+    }
+
+    /**
+     * Checks the conversion from {@link SqlTypeName#TIMESTAMP} to {@link 
SqlTypeName#TIMESTAMP_WITH_LOCAL_TIME_ZONE}
+     * and vise versa using dynamic parameters.
+     */
+    @Test
+    public void testTimestampConversion() {
+        String date = "1970-01-01";
+        String time = "00:00:00.056";
+
+        String tsStr = date + ' ' + time;
+        String isoStr = date + 'T' + time;
+
+        sql("CREATE TABLE timestamps(id INT PRIMARY KEY, ts TIMESTAMP, ts_tz 
TIMESTAMP WITH LOCAL TIME ZONE)");
+
+        ZoneId zoneId = ZoneId.of("America/New_York");
+
+        LocalDateTime localDateTime = LocalDateTime.parse(isoStr);
+        Instant instant = localDateTime.atZone(zoneId).toInstant();
+
+        assertQuery(format("INSERT INTO timestamps VALUES(1, '{}', '{}')", 
tsStr, tsStr))
+                .withTimeZoneId(zoneId)
+                .returns(1L)
+                .check();
+        assertQuery("SELECT ts, ts_tz FROM timestamps WHERE id=?")
+                .withParam(1)
+                .returns(localDateTime, instant)
+                .check();
+
+        // Instant -> LocalDateTime
+        assertQuery("INSERT INTO timestamps VALUES(2, ?, ?)")
+                .withTimeZoneId(zoneId)
+                .withParams(instant, instant)
+                .returns(1L)
+                .check();
+        assertQuery("SELECT ts, ts_tz FROM timestamps WHERE id=?")
+                .withParam(2)
+                .returns(localDateTime, instant)
+                .check();
+
+        // LocalDateTime -> Instant
+        assertQuery("INSERT INTO timestamps VALUES(3, ?, ?)")
+                .withTimeZoneId(zoneId)
+                .withParams(localDateTime, localDateTime)
+                .returns(1L)
+                .check();
+        assertQuery("SELECT ts, ts_tz FROM timestamps WHERE id=?")
+                .withParam(3)
+                // TODO Conversion loses precision 
https://issues.apache.org/jira/browse/IGNITE-21567
+                .returns(localDateTime, 
instant.truncatedTo(ChronoUnit.SECONDS))
+                .check();
     }
 
     /** Test decimal scale for dynamic parameters. */
@@ -688,12 +718,4 @@ public class ItDataTypesTest extends 
BaseSqlIntegrationTest {
             assertThat(err.getMessage(), containsString(result.error));
         }
     }
-
-    private LocalDate sqlDate(String str) {
-        return LocalDate.parse(str);
-    }
-
-    private LocalDateTime sqlDateTime(String str) {
-        return LocalDateTime.parse(str);
-    }
 }
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
index a58be5b466..868253f5a3 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
@@ -28,15 +28,21 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.math.BigDecimal;
+import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
 import java.time.temporal.Temporal;
+import java.util.List;
 import java.util.function.Function;
 import java.util.stream.Stream;
 import org.apache.calcite.sql.validate.SqlValidatorException;
 import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
 import org.apache.ignite.internal.sql.engine.util.MetadataMatcher;
+import org.apache.ignite.internal.util.ArrayUtils;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.sql.ColumnType;
@@ -44,6 +50,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Test Ignite SQL functions.
@@ -75,25 +82,41 @@ public class ItFunctionsTest extends BaseSqlIntegrationTest 
{
         assertQuery("SELECT OCTET_LENGTH(NULL)").returns(NULL_RESULT).check();
     }
 
-    @Test
-    public void testCurrentDateTimeTimeStamp() {
-        checkDateTimeQuery("SELECT CURRENT_DATE", Clock.DATE_CLOCK, 
LocalDate.class);
-        checkDateTimeQuery("SELECT CURRENT_TIME", Clock.TIME_CLOCK, 
LocalTime.class);
-        checkDateTimeQuery("SELECT CURRENT_TIMESTAMP", Clock.DATE_TIME_CLOCK, 
LocalDateTime.class);
-        checkDateTimeQuery("SELECT LOCALTIME", Clock.TIME_CLOCK, 
LocalTime.class);
-        checkDateTimeQuery("SELECT LOCALTIMESTAMP", Clock.DATE_TIME_CLOCK, 
LocalDateTime.class);
-        checkDateTimeQuery("SELECT {fn CURDATE()}", Clock.DATE_CLOCK, 
LocalDate.class);
-        checkDateTimeQuery("SELECT {fn CURTIME()}", Clock.TIME_CLOCK, 
LocalTime.class);
-        checkDateTimeQuery("SELECT {fn NOW()}", Clock.DATE_TIME_CLOCK, 
LocalDateTime.class);
+    @ParameterizedTest(name = "use default time zone: {0}")
+    @ValueSource(booleans = {true, false})
+    public void testCurrentDateTimeTimeStamp(boolean useDefaultTimeZone) {
+        ZoneId zoneId = ZoneId.systemDefault();
+
+        if (!useDefaultTimeZone) {
+            ZoneId utcZone = ZoneId.ofOffset("GMT", ZoneOffset.UTC);
+            Instant now = Instant.now();
+
+            if 
(now.atZone(zoneId).toLocalDateTime().equals(now.atZone(utcZone).toLocalDateTime()))
 {
+                zoneId = ZoneId.ofOffset("GMT", ZoneOffset.of("+01:00"));
+            } else {
+                zoneId = utcZone;
+            }
+        }
+
+        checkDateTimeQuery("SELECT CURRENT_DATE", Clock.DATE_CLOCK, 
LocalDate.class, zoneId);
+        checkDateTimeQuery("SELECT CURRENT_TIME", Clock.TIME_CLOCK, 
LocalTime.class, zoneId);
+        checkDateTimeQuery("SELECT CURRENT_TIMESTAMP", Clock.DATE_TIME_CLOCK, 
LocalDateTime.class, zoneId);
+        checkDateTimeQuery("SELECT LOCALTIME", Clock.TIME_CLOCK, 
LocalTime.class, zoneId);
+        checkDateTimeQuery("SELECT LOCALTIMESTAMP", Clock.DATE_TIME_CLOCK, 
LocalDateTime.class, zoneId);
+        checkDateTimeQuery("SELECT {fn CURDATE()}", Clock.DATE_CLOCK, 
LocalDate.class, zoneId);
+        checkDateTimeQuery("SELECT {fn CURTIME()}", Clock.TIME_CLOCK, 
LocalTime.class, zoneId);
+        checkDateTimeQuery("SELECT {fn NOW()}", Clock.DATE_TIME_CLOCK, 
LocalDateTime.class, zoneId);
     }
 
-    private static <T extends Temporal & Comparable<? super T>> void 
checkDateTimeQuery(String sql, Clock<T> clock, Class<T> cls) {
+    private static <T extends Temporal & Comparable<? super T>> void 
checkDateTimeQuery(
+            String sql, Clock<T> clock, Class<T> cls, ZoneId timeZone
+    ) {
         while (true) {
-            T tsBeg = clock.now();
+            T tsBeg = clock.now(timeZone);
 
-            var res = sql(sql);
+            List<List<Object>> res = sql(0, null, timeZone, sql, 
ArrayUtils.OBJECT_EMPTY_ARRAY);
 
-            T tsEnd = clock.now();
+            T tsEnd = clock.now(timeZone);
 
             // Date changed, time comparison may return wrong result.
             if (tsBeg.compareTo(tsEnd) > 0) {
@@ -560,7 +583,7 @@ public class ItFunctionsTest extends BaseSqlIntegrationTest 
{
         /**
          * A clock reporting a local time.
          */
-        Clock<LocalTime> TIME_CLOCK = LocalTime::now;
+        Clock<LocalTime> TIME_CLOCK = zoneId -> 
LocalTime.now(zoneId).truncatedTo(ChronoUnit.MILLIS);
 
         /**
          * A clock reporting a local date.
@@ -570,13 +593,13 @@ public class ItFunctionsTest extends 
BaseSqlIntegrationTest {
         /**
          * A clock reporting a local datetime.
          */
-        Clock<LocalDateTime> DATE_TIME_CLOCK = LocalDateTime::now;
+        Clock<LocalDateTime> DATE_TIME_CLOCK = zoneId -> 
LocalDateTime.now(zoneId).truncatedTo(ChronoUnit.MILLIS);
 
         /**
          * Returns a temporal value representing the current moment.
          *
          * @return Current moment representing by a temporal value.
          */
-        T now();
+        T now(ZoneId zoneId);
     }
 }
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
index 36706a6552..100f84a9f4 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItIntervalTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.engine;
 
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static 
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -28,12 +29,28 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.Period;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.ignite.internal.lang.IgniteInternalException;
 import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.apache.ignite.lang.IgniteException;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /** Interval coverage tests. */
 public class ItIntervalTest extends BaseSqlIntegrationTest {
@@ -227,52 +244,24 @@ public class ItIntervalTest extends 
BaseSqlIntegrationTest {
                 .check();
     }
 
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("dateTimeIntervalTestCases")
+    public void 
testBasicDateTimeIntervalArithmetic(DateTimeIntervalBasicTestCase testCase) {
+        assertQuery(testCase.query())
+                .withTimeZoneId(DateTimeIntervalBasicTestCase.TIME_ZONE_ID)
+                .returns(testCase.expected())
+                .check();
+    }
+
     /**
-     * Test interval arithmetic.
+     * Test date and time interval arithmetic.
      */
     @Test
-    public void testIntervalArithmetic() {
+    public void testDateTimeIntervalArithmetic() {
         // Date +/- interval.
-        assertEquals(LocalDate.parse("2021-01-02"), eval("DATE '2021-01-01' + 
INTERVAL 1 DAY"));
-        assertEquals(LocalDate.parse("2020-12-31"), eval("DATE '2021-01-01' - 
INTERVAL 1 DAY"));
         assertEquals(LocalDate.parse("2020-12-31"), eval("DATE '2021-01-01' + 
INTERVAL -1 DAY"));
-        assertEquals(LocalDate.parse("2021-02-01"), eval("DATE '2021-01-01' + 
INTERVAL 1 MONTH"));
-        assertEquals(LocalDate.parse("2022-01-01"), eval("DATE '2021-01-01' + 
INTERVAL 1 YEAR"));
         assertEquals(LocalDate.parse("2022-02-01"), eval("DATE '2021-01-01' + 
INTERVAL '1-1' YEAR TO MONTH"));
 
-        // Timestamp +/- interval.
-        assertEquals(LocalDateTime.parse("2021-01-01T00:00:01"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 SECOND"));
-        assertEquals(LocalDateTime.parse("2021-01-01T00:00:01.123"),
-                eval("TIMESTAMP '2021-01-01 00:00:00.123' + INTERVAL 1 
SECOND"));
-        assertEquals(LocalDateTime.parse("2021-01-01T00:00:01.123"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL '1.123' 
SECOND"));
-        assertEquals(LocalDateTime.parse("2021-01-01T00:00:01.246"),
-                eval("TIMESTAMP '2021-01-01 00:00:00.123' + INTERVAL '1.123' 
SECOND"));
-        assertEquals(LocalDateTime.parse("2020-12-31T23:59:59"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' - INTERVAL 1 SECOND"));
-        assertEquals(LocalDateTime.parse("2020-12-31T23:59:59"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL -1 SECOND"));
-        assertEquals(LocalDateTime.parse("2021-01-01T00:01:00"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 MINUTE"));
-        assertEquals(LocalDateTime.parse("2021-01-01T01:00:00"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 HOUR"));
-        assertEquals(LocalDateTime.parse("2021-01-02T00:00:00"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 DAY"));
-        assertEquals(LocalDateTime.parse("2021-02-01T00:00:00"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 MONTH"));
-        assertEquals(LocalDateTime.parse("2022-01-01T00:00:00"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL 1 YEAR"));
-        assertEquals(LocalDateTime.parse("2021-01-02T01:01:01.123"),
-                eval("TIMESTAMP '2021-01-01 00:00:00' + INTERVAL '1 1:1:1.123' 
DAY TO SECOND"));
-        assertEquals(LocalDateTime.parse("2022-02-01T01:01:01.123"),
-                eval("TIMESTAMP '2021-01-01 01:01:01.123' + INTERVAL '1-1' 
YEAR TO MONTH"));
-
-        // Time +/- interval.
-        assertEquals(LocalTime.parse("00:00:01"), eval("TIME '00:00:00' + 
INTERVAL 1 SECOND"));
-        assertEquals(LocalTime.parse("00:01:00"), eval("TIME '00:00:00' + 
INTERVAL 1 MINUTE"));
-        assertEquals(LocalTime.parse("01:00:00"), eval("TIME '00:00:00' + 
INTERVAL 1 HOUR"));
-
         // Date - date as interval.
         assertEquals(Duration.ofDays(1), eval("(DATE '2021-01-02' - DATE 
'2021-01-01') DAYS"));
         assertEquals(Duration.ofDays(-1), eval("(DATE '2021-01-01' - DATE 
'2021-01-02') DAYS"));
@@ -282,35 +271,82 @@ public class ItIntervalTest extends 
BaseSqlIntegrationTest {
         assertEquals(Period.ofMonths(-1), eval("(DATE '2021-01-01' - DATE 
'2021-02-01') MONTHS"));
         assertEquals(Period.ofMonths(0), eval("(DATE '2021-01-20' - DATE 
'2021-01-01') MONTHS"));
 
+        // Time - time as interval.
+        assertEquals(Duration.ofHours(1), eval("(TIME '02:00:00' - TIME 
'01:00:00') HOURS"));
+        assertEquals(Duration.ofMinutes(1), eval("(TIME '00:02:00' - TIME 
'00:01:00') HOURS"));
+        assertEquals(Duration.ofMinutes(1), eval("(TIME '00:02:00' - TIME 
'00:01:00') MINUTES"));
+        assertEquals(Duration.ofSeconds(1), eval("(TIME '00:00:02' - TIME 
'00:00:01') SECONDS"));
+        assertEquals(Duration.ofMillis(123), eval("(TIME '00:00:01.123' - TIME 
'00:00:01') SECONDS"));
+    }
+
+    /** Timestamp [with local time zone] interval arithmetic. */
+    @ParameterizedTest
+    @EnumSource(value = SqlTypeName.class, names = {"TIMESTAMP", 
"TIMESTAMP_WITH_LOCAL_TIME_ZONE"})
+    public void testTimestampIntervalArithmetic(SqlTypeName sqlTypeName) {
+        String typeName = sqlTypeName.getSpaceName();
+
         // Timestamp - timestamp as interval.
         assertEquals(Duration.ofDays(1),
-                eval("(TIMESTAMP '2021-01-02 00:00:00' - TIMESTAMP '2021-01-01 
00:00:00') DAYS"));
+                eval(format("({} '2021-01-02 00:00:00' - {} '2021-01-01 
00:00:00') DAYS", typeName, typeName)));
         assertEquals(Duration.ofDays(-1),
-                eval("(TIMESTAMP '2021-01-01 00:00:00' - TIMESTAMP '2021-01-02 
00:00:00') DAYS"));
+                eval(format("({} '2021-01-01 00:00:00' - {} '2021-01-02 
00:00:00') DAYS", typeName, typeName)));
         assertEquals(Duration.ofHours(1),
-                eval("(TIMESTAMP '2021-01-01 01:00:00' - TIMESTAMP '2021-01-01 
00:00:00') HOURS"));
+                eval(format("({} '2021-01-01 01:00:00' - {} '2021-01-01 
00:00:00') HOURS", typeName, typeName)));
         assertEquals(Duration.ofMinutes(1),
-                eval("(TIMESTAMP '2021-01-01 00:01:00' - TIMESTAMP '2021-01-01 
00:00:00') MINUTES"));
+                eval(format("({} '2021-01-01 00:01:00' - {} '2021-01-01 
00:00:00') MINUTES", typeName, typeName)));
         assertEquals(Duration.ofSeconds(1),
-                eval("(TIMESTAMP '2021-01-01 00:00:01' - TIMESTAMP '2021-01-01 
00:00:00') SECONDS"));
+                eval(format("({} '2021-01-01 00:00:01' - {} '2021-01-01 
00:00:00') SECONDS", typeName, typeName)));
         assertEquals(Duration.ofMillis(123),
-                eval("(TIMESTAMP '2021-01-01 00:00:00.123' - TIMESTAMP 
'2021-01-01 00:00:00') SECONDS"));
+                eval(format("({} '2021-01-01 00:00:00.123' - {} '2021-01-01 
00:00:00') SECONDS", typeName, typeName)));
         assertEquals(Period.ofYears(1),
-                eval("(TIMESTAMP '2022-01-01 00:00:00' - TIMESTAMP '2021-01-01 
00:00:00') YEARS"));
+                eval(format("({} '2022-01-01 00:00:00' - {} '2021-01-01 
00:00:00') YEARS", typeName, typeName)));
         assertEquals(Period.ofMonths(1),
-                eval("(TIMESTAMP '2021-02-01 00:00:00' - TIMESTAMP '2021-01-01 
00:00:00') MONTHS"));
+                eval(format("({} '2021-02-01 00:00:00' - {} '2021-01-01 
00:00:00') MONTHS", typeName, typeName)));
         assertEquals(Period.ofMonths(-1),
-                eval("(TIMESTAMP '2021-01-01 00:00:00' - TIMESTAMP '2021-02-01 
00:00:00') MONTHS"));
+                eval(format("({} '2021-01-01 00:00:00' - {} '2021-02-01 
00:00:00') MONTHS", typeName, typeName)));
         assertEquals(Period.ofMonths(0),
-                eval("(TIMESTAMP '2021-01-20 00:00:00' - TIMESTAMP '2021-01-01 
00:00:00') MONTHS"));
-
-        // Time - time as interval.
-        assertEquals(Duration.ofHours(1), eval("(TIME '02:00:00' - TIME 
'01:00:00') HOURS"));
-        assertEquals(Duration.ofMinutes(1), eval("(TIME '00:02:00' - TIME 
'00:01:00') HOURS"));
-        assertEquals(Duration.ofMinutes(1), eval("(TIME '00:02:00' - TIME 
'00:01:00') MINUTES"));
-        assertEquals(Duration.ofSeconds(1), eval("(TIME '00:00:02' - TIME 
'00:00:01') SECONDS"));
-        assertEquals(Duration.ofMillis(123), eval("(TIME '00:00:01.123' - TIME 
'00:00:01') SECONDS"));
+                eval(format("({} '2021-01-20 00:00:00' - {} '2021-01-01 
00:00:00') MONTHS", typeName, typeName)));
+
+        // Check string representation here, since after timestamp calculation 
we have '2021-11-07T01:30:00.000-0800'
+        // but Timestamp.valueOf method converts '2021-11-07 01:30:00' in 
'America/Los_Angeles' time zone to
+        // '2021-11-07T01:30:00.000-0700' (we pass through '2021-11-07 
01:30:00' twice after DST ended).
+        ZoneId zoneId = ZoneId.systemDefault();
+        String tzSuffix = sqlTypeName == 
SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE ? ' ' + zoneId.getId() : "";
+
+        assertQuery(format("SELECT ({} '2021-11-06 02:30:00' + interval (23) 
hours)::varchar", typeName))
+                .withTimeZoneId(zoneId)
+                .returns("2021-11-07 01:30:00" + tzSuffix).check();
+
+        assertQuery(format("SELECT ({} '2021-11-06 01:30:00' + interval (24) 
hours)::varchar", typeName))
+                .withTimeZoneId(zoneId)
+                .returns("2021-11-07 01:30:00" + tzSuffix).check();
+
+        // Timestamp - interval.
+        BiConsumer<String, String> timestampChecker = (expression, expected) 
-> {
+            ZoneId timeZoneId = ZoneId.systemDefault();
+
+            Function<String, Object> validator = sqlTypeName == 
SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE
+                    ? ts -> 
LocalDateTime.parse(ts).atZone(timeZoneId).toInstant()
+                    : LocalDateTime::parse;
+
+            assertQuery(format(expression, sqlTypeName.getSpaceName()))
+                    .withTimeZoneId(timeZoneId)
+                    .returns(validator.apply(expected))
+                    .check();
+        };
+
+        timestampChecker.accept("SELECT {} '2021-01-01 00:00:00' + INTERVAL 
'1.123' SECOND", "2021-01-01T00:00:01.123");
+        timestampChecker.accept("SELECT {} '2021-01-01 00:00:00.123' + 
INTERVAL '1.123' SECOND", "2021-01-01T00:00:01.246");
+        timestampChecker.accept("SELECT {} '2021-01-01 00:00:00' + INTERVAL '1 
1:1:1.123' DAY TO SECOND", "2021-01-02T01:01:01.123");
+
+        // TODO Enable this case after 
https://issues.apache.org/jira/browse/IGNITE-21557
+        if (sqlTypeName != SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
+            timestampChecker.accept("SELECT {} '2021-01-01 01:01:01.123' + 
INTERVAL '1-1' YEAR TO MONTH", "2022-02-01T01:01:01.123");
+        }
+    }
 
+    @Test
+    public void testIntervalArithmetic() {
         // Interval +/- interval.
         assertEquals(Duration.ofSeconds(2), eval("INTERVAL 1 SECONDS + 
INTERVAL 1 SECONDS"));
         assertEquals(Duration.ofSeconds(1), eval("INTERVAL 2 SECONDS - 
INTERVAL 1 SECONDS"));
@@ -408,4 +444,174 @@ public class ItIntervalTest extends 
BaseSqlIntegrationTest {
 
         
assertTrue(ex.getMessage().toLowerCase().contains(errMsg.toLowerCase()));
     }
+
+    private static List<DateTimeIntervalBasicTestCase> 
dateTimeIntervalTestCases() {
+        SqlTypeName[] types = {
+                SqlTypeName.TIME,
+                SqlTypeName.DATE,
+                SqlTypeName.TIMESTAMP,
+                SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE
+        };
+
+        Map<ChronoUnit, int[]> timeUnitData = new LinkedHashMap<>();
+
+        timeUnitData.put(ChronoUnit.SECONDS, new int[]{1, 59, 60, 61, 100, 
1_000, 10_000});
+        timeUnitData.put(ChronoUnit.MINUTES, new int[]{1, 59, 60, 61, 100, 
1_000, 10_000});
+        timeUnitData.put(ChronoUnit.HOURS, new int[]{1, 23, 24, 25, 48, 96, 
1_000, 10_000});
+        timeUnitData.put(ChronoUnit.DAYS, new int[]{1, 29, 30, 31, 100, 1_000, 
10_000});
+        timeUnitData.put(ChronoUnit.MONTHS, new int[]{1, 11, 12, 13, 100, 
1_000});
+        timeUnitData.put(ChronoUnit.YEARS, new int[]{1, 10, 100, 1_000});
+
+        List<DateTimeIntervalBasicTestCase> testCases = new ArrayList<>();
+
+        for (SqlTypeName typeName : types) {
+            for (Entry<ChronoUnit, int[]> entry : timeUnitData.entrySet()) {
+                ChronoUnit unit = entry.getKey();
+                // TODO Remove after 
https://issues.apache.org/jira/browse/IGNITE-21557
+                if (typeName == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE
+                        && (unit == ChronoUnit.MONTHS || unit == 
ChronoUnit.YEARS)) {
+                    continue;
+                }
+
+                // TODO Remove after 
https://issues.apache.org/jira/browse/IGNITE-21589
+                if (typeName == SqlTypeName.TIME && (unit == ChronoUnit.HOURS 
|| unit == ChronoUnit.DAYS)) {
+                    continue;
+                }
+
+                for (int amount : entry.getValue()) {
+                    
testCases.add(DateTimeIntervalBasicTestCase.newTestCase(typeName, unit, 
amount));
+                    
testCases.add(DateTimeIntervalBasicTestCase.newTestCase(typeName, unit, 
-amount));
+                }
+            }
+        }
+
+        return testCases;
+    }
+
+    abstract static class DateTimeIntervalBasicTestCase {
+        private static final ZoneId TIME_ZONE_ID = ZoneId.of("Asia/Nicosia");
+        private static final DateTimeFormatter dateTimeFormatter = 
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
+        private static final String dateString = "1992-01-19 00:00:00.123";
+        private static final LocalDateTime testLocalDate = 
LocalDateTime.parse(dateString, dateTimeFormatter);
+
+        final SqlTypeName type;
+        final ChronoUnit unit;
+        final int amount;
+
+        private String query;
+
+        private DateTimeIntervalBasicTestCase(SqlTypeName type, ChronoUnit 
unit, int amount) {
+            this.type = type;
+            this.unit = unit;
+            this.amount = amount;
+        }
+
+        public abstract Temporal expected();
+
+        public String query() {
+            if (query == null) {
+                String intervalSubstring = (amount > 0 ? '+' : '-') + " 
interval (" + Math.abs(amount) + ") " + unit;
+                query = format("SELECT {} '{}'{}", type.getSpaceName(), 
sqlDateLiteral(), intervalSubstring);
+            }
+
+            return query;
+        }
+
+        String sqlDateLiteral() {
+            return dateString;
+        }
+
+        static DateTimeIntervalBasicTestCase newTestCase(SqlTypeName type, 
ChronoUnit unit, Integer amount) {
+            switch (type) {
+                case TIMESTAMP:
+                    return new SqlTimestampIntervalIntervalTestCase(unit, 
amount);
+
+                case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                    return new SqlTimestampTzIntervalIntervalTestCase(unit, 
amount);
+
+                case DATE:
+                    return new SqlDateIntervalIntervalTestCase(unit, amount);
+
+                case TIME:
+                    return new SqlTimeIntervalIntervalTestCase(unit, amount);
+
+                default:
+                    throw new UnsupportedOperationException("Not implemented: 
" + type);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return query();
+        }
+
+        private static class SqlTimestampIntervalIntervalTestCase extends 
DateTimeIntervalBasicTestCase {
+            private SqlTimestampIntervalIntervalTestCase(ChronoUnit unit, 
Integer amount) {
+                super(SqlTypeName.TIMESTAMP, unit, amount);
+            }
+
+            @Override
+            public LocalDateTime expected() {
+                return testLocalDate.plus(amount, unit);
+            }
+        }
+
+        private static class SqlTimestampTzIntervalIntervalTestCase extends 
DateTimeIntervalBasicTestCase {
+            private SqlTimestampTzIntervalIntervalTestCase(ChronoUnit unit, 
Integer amount) {
+                super(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, unit, 
amount);
+            }
+
+            @Override
+            public Temporal expected() {
+                return 
testLocalDate.atZone(TIME_ZONE_ID).toInstant().plus(amount, unit);
+            }
+        }
+
+        private static class SqlDateIntervalIntervalTestCase extends 
DateTimeIntervalBasicTestCase {
+            final LocalDateTime testLocalDateEndOfDay;
+
+            SqlDateIntervalIntervalTestCase(ChronoUnit unit, Integer amount) {
+                super(SqlTypeName.DATE, unit, amount);
+
+                // DateTime + 23:59:59.999
+                testLocalDateEndOfDay = testLocalDate
+                        .plus(1, ChronoUnit.DAYS)
+                        .minus(1 + 
testLocalDate.get(ChronoField.MILLI_OF_SECOND), ChronoUnit.MILLIS);
+            }
+
+            @Override
+            String sqlDateLiteral() {
+                return dateString.substring(0, 10);
+            }
+
+            @Override
+            public Temporal expected() {
+                return (amount < 0 ? testLocalDateEndOfDay : 
testLocalDate).plus(amount, unit).toLocalDate();
+            }
+        }
+
+        private static class SqlTimeIntervalIntervalTestCase extends 
DateTimeIntervalBasicTestCase {
+            private final LocalTime initTime;
+
+            private SqlTimeIntervalIntervalTestCase(ChronoUnit unit, Integer 
amount) {
+                super(SqlTypeName.TIME, unit, amount);
+
+                initTime = testLocalDate.toLocalTime();
+            }
+
+            @Override
+            String sqlDateLiteral() {
+                return dateString.substring(11);
+            }
+
+            @Override
+            public LocalTime expected() {
+                if (initTime.isSupported(unit)) {
+                    return initTime.plus(amount, unit);
+                }
+
+                return initTime;
+            }
+        }
+    }
 }
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
index 3ea29eb6bc..842405b367 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
@@ -166,13 +166,12 @@ public class ItMetadataTest extends 
BaseSqlIntegrationTest {
                 // ANSI`99 syntax "WITH TIME ZONE" is not supported,
                 // a "WITH LOCAL TIME ZONE" syntax MUST be used instead.
                 + "DATE_C DATE, " + "TIME_C TIME, " + "TIME_C2 TIME(9), "
-                // TODO: IGNITE-19274 Ignite doesn't support the client's time 
zone yet.
+                // TODO: IGNITE-21555 Ignite doesn't support 
TIME_WITH_LOCAL_TIME_ZONE data type.
                 // + "TIME_LTZ_C TIME WITH LOCAL TIME ZONE, "
                 // + "TIME_LTZ_C2 TIME(9) WITH LOCAL TIME ZONE, "
                 + "DATETIME_C TIMESTAMP, " + "DATETIME_C2 TIMESTAMP(9), "
-                // TODO: IGNITE-19274 Ignite doesn't support the client's time 
zone yet.
-                // + "TIMESTAMP_C TIMESTAMP WITH LOCAL TIME ZONE, "
-                // + "TIMESTAMP_C2 TIMESTAMP(9) WITH LOCAL TIME ZONE, "
+                 + "TIMESTAMP_C TIMESTAMP WITH LOCAL TIME ZONE, "
+                 + "TIMESTAMP_C2 TIMESTAMP(9) WITH LOCAL TIME ZONE, "
 
                 // Interval types
                 // TODO: IGNITE-17373: Ignite doesn't support interval types 
yet.
@@ -231,14 +230,13 @@ public class ItMetadataTest extends 
BaseSqlIntegrationTest {
                         new 
MetadataMatcher().name("DATE_C").type(ColumnType.DATE).precision(0).scale(UNDEFINED_SCALE),
                         new 
MetadataMatcher().name("TIME_C").type(ColumnType.TIME).precision(0).scale(UNDEFINED_SCALE),
                         new 
MetadataMatcher().name("TIME_C2").type(ColumnType.TIME).precision(9).scale(UNDEFINED_SCALE),
-                        // TODO: IGNITE-19274 Ignite doesn't support the 
client's time zone yet.
+                        // TODO: IGNITE-21555 Ignite doesn't support 
TIME_WITH_LOCAL_TIME_ZONE data type.
                         // new 
MetadataMatcher().name("TIME_LTZ_C").type(ColumnType.TIME).precision(0).scale(UNDEFINED_SCALE),
                         // new 
MetadataMatcher().name("TIME_LTZ_C2").type(ColumnType.TIME).precision(9).scale(UNDEFINED_SCALE),
                         new 
MetadataMatcher().name("DATETIME_C").type(ColumnType.DATETIME).precision(6).scale(UNDEFINED_SCALE),
                         new 
MetadataMatcher().name("DATETIME_C2").type(ColumnType.DATETIME).precision(9).scale(UNDEFINED_SCALE),
-                        // TODO: IGNITE-19274 Ignite doesn't support the 
client's time zone yet.
-                        // new 
MetadataMatcher().name("TIMESTAMP_C").type(ColumnType.TIMESTAMP).precision(6).scale(UNDEFINED_SCALE),
-                        // new 
MetadataMatcher().name("TIMESTAMP_C2").type(ColumnType.TIMESTAMP).precision(9).scale(UNDEFINED_SCALE),
+                        new 
MetadataMatcher().name("TIMESTAMP_C").type(ColumnType.TIMESTAMP).precision(6).scale(UNDEFINED_SCALE),
+                        new 
MetadataMatcher().name("TIMESTAMP_C2").type(ColumnType.TIMESTAMP).precision(9).scale(UNDEFINED_SCALE),
 
                         // Interval types
                         // TODO: IGNITE-17373: Ignite doesn't support interval 
types yet.
diff --git a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl 
b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
index 51933242f0..3302091770 100644
--- a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
@@ -615,12 +615,13 @@ void AlterZoneOption(List<SqlNode> list) :
 /**
 * Parse datetime types: date, time, timestamp.
 *
-* TODO Method doesn't recognize '*_WITH_LOCAL_TIME_ZONE' types and should be 
removed after IGNITE-19274.
+* TODO Method doesn't recognize 'TIME_WITH_LOCAL_TIME_ZONE' type and should be 
removed after IGNITE-21555.
 */
 SqlTypeNameSpec IgniteDateTimeTypeName() :
 {
     int precision = -1;
     SqlTypeName typeName;
+    boolean withLocalTimeZone = false;
     final Span s;
 }
 {
@@ -638,8 +639,13 @@ SqlTypeNameSpec IgniteDateTimeTypeName() :
 |
     <TIMESTAMP> { s = span(); }
     precision = PrecisionOpt()
+    withLocalTimeZone = TimeZoneOpt()
     {
-        typeName = SqlTypeName.TIMESTAMP;
+        if (withLocalTimeZone) {
+            typeName = SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE;
+        } else {
+            typeName = SqlTypeName.TIMESTAMP;
+        }
         return new SqlBasicTypeNameSpec(typeName, precision, s.end(this));
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionBuilderImpl.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionBuilderImpl.java
index 6d5ec67356..c4ad326745 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionBuilderImpl.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionBuilderImpl.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.sql.api;
 
+import java.time.ZoneId;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentMap;
@@ -63,6 +64,8 @@ public class SessionBuilderImpl implements SessionBuilder {
 
     private int pageSize = AbstractSession.DEFAULT_PAGE_SIZE;
 
+    private ZoneId timeZoneId;
+
     /**
      * Session builder constructor.
      *
@@ -167,6 +170,13 @@ public class SessionBuilderImpl implements SessionBuilder {
         return this;
     }
 
+    @Override
+    public SessionBuilder timeZoneId(ZoneId timeZoneId) {
+        this.timeZoneId = timeZoneId;
+
+        return this;
+    }
+
     /** {@inheritDoc} */
     @Override
     public @Nullable Object property(String name) {
@@ -187,6 +197,7 @@ public class SessionBuilderImpl implements SessionBuilder {
         SqlProperties propsHolder = SqlPropertiesHelper.newBuilder()
                 .set(QueryProperty.QUERY_TIMEOUT, queryTimeout)
                 .set(QueryProperty.DEFAULT_SCHEMA, schema)
+                .set(QueryProperty.TIME_ZONE_ID, timeZoneId == null ? 
ZoneId.systemDefault() : timeZoneId)
                 .build();
 
         SessionId sessionId = new SessionId(UUID.randomUUID());
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java
index 069524d8b5..c7b2453aa9 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/SessionImpl.java
@@ -24,6 +24,7 @@ import static 
org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.SESSION_CLOSED_ERR;
 
 import it.unimi.dsi.fastutil.longs.LongArrayList;
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
@@ -172,6 +173,12 @@ public class SessionImpl implements AbstractSession {
         return pageSize;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public ZoneId timeZoneId() {
+        return props.get(QueryProperty.TIME_ZONE_ID);
+    }
+
     /** {@inheritDoc} */
     @Override
     public @Nullable Object property(String name) {
@@ -213,7 +220,7 @@ public class SessionImpl implements AbstractSession {
         CompletableFuture<AsyncResultSet<SqlRow>> result;
 
         try {
-            SqlProperties properties = SqlPropertiesHelper.newBuilder()
+            SqlProperties properties = 
SqlPropertiesHelper.builderFromProperties(props)
                     .set(QueryProperty.ALLOWED_QUERY_TYPES, 
SqlQueryType.SINGLE_STMT_TYPES)
                     .build();
 
@@ -298,7 +305,7 @@ public class SessionImpl implements AbstractSession {
         }
 
         try {
-            SqlProperties properties = SqlPropertiesHelper.newBuilder()
+            SqlProperties properties = 
SqlPropertiesHelper.builderFromProperties(props)
                     .set(QueryProperty.ALLOWED_QUERY_TYPES, 
EnumSet.of(SqlQueryType.DML))
                     .build();
 
@@ -405,10 +412,12 @@ public class SessionImpl implements AbstractSession {
             return CompletableFuture.failedFuture(sessionIsClosedException());
         }
 
+        SqlProperties properties = 
SqlPropertiesHelper.builderFromProperties(props)
+                .set(QueryProperty.ALLOWED_QUERY_TYPES, SqlQueryType.ALL)
+                .build();
+
         CompletableFuture<Void> resFut = new CompletableFuture<>();
         try {
-            SqlProperties properties = SqlPropertiesHelper.emptyProperties();
-
             CompletableFuture<AsyncSqlCursor<InternalSqlRow>> f =
                     qryProc.queryScriptAsync(properties, transactions, null, 
query, arguments);
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProperty.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProperty.java
index 53d2d085bb..e04b2d9267 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProperty.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProperty.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.sql.engine;
 
 import static 
org.apache.ignite.internal.sql.engine.property.SqlPropertiesHelper.createPropsByNameMap;
 
+import java.time.ZoneId;
 import java.util.Map;
 import java.util.Set;
 import org.apache.ignite.internal.sql.engine.property.Property;
@@ -32,6 +33,7 @@ public final class QueryProperty {
     public static final Property<Set<SqlQueryType>> ALLOWED_QUERY_TYPES =
             new Property<>("allowed_query_types", cast(Set.class));
     public static final Property<String> DEFAULT_SCHEMA = new 
Property<>("default_schema", String.class);
+    public static final Property<ZoneId> TIME_ZONE_ID = new 
Property<>("time_zone_id", ZoneId.class);
 
     private static final Map<String, Property<?>> propsByName = 
createPropsByNameMap(QueryProperty.class);
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
index 7d9965647b..55c93cf0d1 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
@@ -32,6 +32,7 @@ import static 
org.apache.ignite.lang.ErrorGroups.Replicator.REPLICA_UNAVAILABLE_
 import static org.apache.ignite.lang.ErrorGroups.Sql.EXECUTION_CANCELLED_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
 
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -135,6 +136,9 @@ import org.jetbrains.annotations.TestOnly;
  *  Main implementation of {@link QueryProcessor}.
  */
 public class SqlQueryProcessor implements QueryProcessor {
+    /** Default time zone ID. */
+    public static final ZoneId DEFAULT_TIME_ZONE_ID = ZoneId.of("UTC");
+
     /** The logger. */
     private static final IgniteLogger LOG = 
Loggers.forClass(SqlQueryProcessor.class);
 
@@ -152,6 +156,7 @@ public class SqlQueryProcessor implements QueryProcessor {
     private static final SqlProperties DEFAULT_PROPERTIES = 
SqlPropertiesHelper.newBuilder()
             .set(QueryProperty.DEFAULT_SCHEMA, DEFAULT_SCHEMA_NAME)
             .set(QueryProperty.ALLOWED_QUERY_TYPES, SqlQueryType.ALL)
+            .set(QueryProperty.TIME_ZONE_ID, DEFAULT_TIME_ZONE_ID)
             .build();
 
     private static final CacheFactory CACHE_FACTORY = 
CaffeineCacheFactory.INSTANCE;
@@ -541,6 +546,7 @@ public class SqlQueryProcessor implements QueryProcessor {
     ) {
         SqlProperties properties0 = SqlPropertiesHelper.chain(properties, 
DEFAULT_PROPERTIES);
         String schemaName = properties0.get(QueryProperty.DEFAULT_SCHEMA);
+        ZoneId timeZoneId = properties0.get(QueryProperty.TIME_ZONE_ID);
 
         QueryCancel queryCancel = new QueryCancel();
 
@@ -554,7 +560,7 @@ public class SqlQueryProcessor implements QueryProcessor {
 
             QueryTransactionWrapper txWrapper = 
txCtx.getOrStartImplicit(result.queryType());
 
-            return executeParsedStatement(schemaName, result, txWrapper, 
queryCancel, params, null);
+            return executeParsedStatement(schemaName, result, txWrapper, 
queryCancel, timeZoneId, params, null);
         });
 
         // TODO IGNITE-20078 Improve (or remove) CancellationException 
handling.
@@ -577,6 +583,7 @@ public class SqlQueryProcessor implements QueryProcessor {
     ) {
         SqlProperties properties0 = SqlPropertiesHelper.chain(properties, 
DEFAULT_PROPERTIES);
         String schemaName = properties0.get(QueryProperty.DEFAULT_SCHEMA);
+        ZoneId timeZoneId = properties0.get(QueryProperty.TIME_ZONE_ID);
 
         CompletableFuture<?> start = new CompletableFuture<>();
 
@@ -584,7 +591,7 @@ public class SqlQueryProcessor implements QueryProcessor {
                 .thenApply(ignored -> parserService.parseScript(sql))
                 .thenCompose(parsedResults -> {
                     MultiStatementHandler handler = new MultiStatementHandler(
-                            schemaName, txCtx, parsedResults, params);
+                            schemaName, txCtx, parsedResults, params, 
timeZoneId);
 
                     return handler.processNext();
                 });
@@ -618,6 +625,7 @@ public class SqlQueryProcessor implements QueryProcessor {
             ParsedResult parsedResult,
             QueryTransactionWrapper txWrapper,
             QueryCancel queryCancel,
+            ZoneId timeZoneId,
             Object[] params,
             @Nullable CompletableFuture<AsyncSqlCursor<InternalSqlRow>> 
nextStatement
     ) {
@@ -631,6 +639,7 @@ public class SqlQueryProcessor implements QueryProcessor {
                             .cancel(queryCancel)
                             .prefetchCallback(callback)
                             .parameters(params)
+                            .timeZoneId(timeZoneId)
                             .build();
 
                     return prepareSvc.prepareAsync(parsedResult, ctx)
@@ -766,6 +775,7 @@ public class SqlQueryProcessor implements QueryProcessor {
     }
 
     private class MultiStatementHandler {
+        private final ZoneId timeZoneId;
         private final String schemaName;
         private final Queue<ScriptStatement> statements;
         private final ScriptTransactionContext txCtx;
@@ -785,8 +795,10 @@ public class SqlQueryProcessor implements QueryProcessor {
                 String schemaName,
                 QueryTransactionContext txCtx,
                 List<ParsedResult> parsedResults,
-                Object[] params
+                Object[] params,
+                ZoneId timeZoneId
         ) {
+            this.timeZoneId = timeZoneId;
             this.schemaName = schemaName;
             this.statements = prepareStatementsQueue(parsedResults, params);
             this.txCtx = new ScriptTransactionContext(txCtx);
@@ -867,7 +879,7 @@ public class SqlQueryProcessor implements QueryProcessor {
 
                     txCtx.registerCursorFuture(parsedResult.queryType(), 
cursorFuture);
 
-                    fut = executeParsedStatement(schemaName, parsedResult, 
txWrapper, new QueryCancel(), params, nextCurFut);
+                    fut = executeParsedStatement(schemaName, parsedResult, 
txWrapper, new QueryCancel(), timeZoneId, params, nextCurFut);
                 }
 
                 fut.whenComplete((cursor, ex) -> {
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
index 8e69810b07..bf4a9e1b44 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
@@ -21,6 +21,8 @@ import static 
org.apache.ignite.internal.sql.engine.util.Commons.FRAMEWORK_CONFI
 import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
 
 import java.lang.reflect.Type;
+import java.time.Instant;
+import java.time.ZoneId;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -52,8 +54,6 @@ import org.jetbrains.annotations.Nullable;
 public class ExecutionContext<RowT> implements DataContext {
     private static final IgniteLogger LOG = 
Loggers.forClass(ExecutionContext.class);
 
-    private static final TimeZone TIME_ZONE = TimeZone.getDefault(); // TODO 
DistributedSqlConfiguration#timeZone
-
     /**
      * TODO: https://issues.apache.org/jira/browse/IGNITE-15276 Support other 
locales.
      */
@@ -85,6 +85,8 @@ public class ExecutionContext<RowT> implements DataContext {
 
     private final TxAttributes txAttributes;
 
+    private final ZoneId timeZoneId;
+
     private SharedState sharedState = new SharedState();
 
     /**
@@ -92,9 +94,13 @@ public class ExecutionContext<RowT> implements DataContext {
      *
      * @param executor Task executor.
      * @param qryId Query ID.
+     * @param localNode Local node.
+     * @param originatingNodeName Name of the node that initiated the query.
      * @param description Partitions information.
      * @param handler Row handler.
      * @param params Parameters.
+     * @param txAttributes Transaction attributes.
+     * @param timeZoneId Session time zone ID.
      */
     @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
     public ExecutionContext(
@@ -105,7 +111,8 @@ public class ExecutionContext<RowT> implements DataContext {
             FragmentDescription description,
             RowHandler<RowT> handler,
             Map<String, Object> params,
-            TxAttributes txAttributes
+            TxAttributes txAttributes,
+            ZoneId timeZoneId
     ) {
         this.executor = executor;
         this.qryId = qryId;
@@ -115,14 +122,15 @@ public class ExecutionContext<RowT> implements 
DataContext {
         this.localNode = localNode;
         this.originatingNodeName = originatingNodeName;
         this.txAttributes = txAttributes;
+        this.timeZoneId = timeZoneId;
 
         expressionFactory = new ExpressionFactoryImpl<>(
                 this,
                 FRAMEWORK_CONFIG.getParserConfig().conformance()
         );
 
-        long ts = System.currentTimeMillis();
-        startTs = ts + TIME_ZONE.getOffset(ts);
+        Instant nowUtc = Instant.now();
+        startTs = 
nowUtc.plusSeconds(this.timeZoneId.getRules().getOffset(nowUtc).getTotalSeconds()).toEpochMilli();
 
         if (LOG.isTraceEnabled()) {
             LOG.trace("Context created [qryId={}, fragmentId={}]", qryId, 
fragmentId());
@@ -238,6 +246,10 @@ public class ExecutionContext<RowT> implements DataContext 
{
             return LOCALE;
         }
 
+        if (Variable.TIME_ZONE.camelName.equals(name)) {
+            return TimeZone.getTimeZone(timeZoneId);
+        }
+
         if (name.startsWith("?")) {
             Object val = params.get(name);
             return val != null ? TypeUtils.toInternal(val, val.getClass()) : 
null;
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
index 05e0aa081b..b145ce405b 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
@@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
 import java.lang.management.ManagementFactory;
 import java.lang.management.ThreadInfo;
 import java.lang.management.ThreadMXBean;
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -271,7 +272,7 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
         return queryManager.execute(tx, plan);
     }
 
-    private BaseQueryContext createQueryContext(UUID queryId, int 
schemaVersion, Object[] params) {
+    private BaseQueryContext createQueryContext(UUID queryId, int 
schemaVersion, ZoneId timeZoneId, Object[] params) {
         return BaseQueryContext.builder()
                 .queryId(queryId)
                 .parameters(params)
@@ -280,6 +281,7 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
                                 
.defaultSchema(sqlSchemaManager.schema(schemaVersion))
                                 .build()
                 )
+                .timeZoneId(timeZoneId)
                 .build();
     }
 
@@ -343,7 +345,8 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
                 DUMMY_DESCRIPTION,
                 handler,
                 Commons.parametersMap(ctx.parameters()),
-                TxAttributes.fromTx(tx)
+                TxAttributes.fromTx(tx),
+                ctx.timeZoneId()
         );
 
         return plan.execute(ectx, tx, tableRegistry, callback);
@@ -510,7 +513,7 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
 
     private DistributedQueryManager getOrCreateQueryManager(String 
coordinatorNodeName, QueryStartRequest msg) {
         return queryManagerMap.computeIfAbsent(msg.queryId(), key -> {
-            BaseQueryContext ctx = createQueryContext(key, 
msg.schemaVersion(), msg.parameters());
+            BaseQueryContext ctx = createQueryContext(key, 
msg.schemaVersion(), ZoneId.of(msg.timeZoneId()), msg.parameters());
 
             return new DistributedQueryManager(coordinatorNodeName, ctx);
         });
@@ -712,6 +715,7 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
                     .parameters(ctx.parameters())
                     .txAttributes(txAttributes)
                     .schemaVersion(ctx.schemaVersion())
+                    .timeZoneId(ctx.timeZoneId().getId())
                     .build();
 
             return messageService.send(targetNodeName, request);
@@ -798,7 +802,8 @@ public class ExecutionServiceImpl<RowT> implements 
ExecutionService, TopologyEve
                     desc,
                     handler,
                     Commons.parametersMap(ctx.parameters()),
-                    txAttributes
+                    txAttributes,
+                    ctx.timeZoneId()
             );
         }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
index 9c29d4e89c..30c81c1705 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/ExpressionFactoryImpl.java
@@ -32,12 +32,14 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.TimeZone;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.DataContext;
+import org.apache.calcite.DataContext.Variable;
 import org.apache.calcite.adapter.enumerable.EnumUtils;
 import org.apache.calcite.linq4j.function.Function1;
 import org.apache.calcite.linq4j.tree.BlockBuilder;
@@ -316,6 +318,11 @@ public class ExpressionFactoryImpl<RowT> implements 
ExpressionFactory<RowT> {
                 RexLiteral literal = values.get(i);
                 Object val = literal.getValueAs(types.get(field));
 
+                // Literal was parsed as UTC timestamp, now we need to adjust 
it to the client's time zone.
+                if (val != null && literal.getTypeName() == 
SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
+                    val = IgniteSqlFunctions.subtractTimeZoneOffset((long) 
val, (TimeZone) ctx.get(Variable.TIME_ZONE.camelName));
+                }
+
                 rowBuilder.addField(val);
             }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
index 8ae478c785..1b0d6fd210 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
@@ -26,12 +26,12 @@ import static 
org.apache.ignite.lang.ErrorGroups.Sql.RUNTIME_ERR;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.RoundingMode;
-import java.time.DateTimeException;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.time.format.ResolverStyle;
+import java.util.TimeZone;
 import java.util.UUID;
 import org.apache.calcite.DataContext;
 import org.apache.calcite.avatica.util.ByteString;
@@ -97,14 +97,6 @@ public class IgniteSqlFunctions {
      * Otherwise need to fix {@code DateTimeUtils#unixTimestampToString} usage 
additionally.
      */
     public static long timestampStringToNumeric(String dtStr) {
-        try {
-            return timestampStringToNumeric0(dtStr);
-        } catch (DateTimeException e) {
-            throw new SqlException(RUNTIME_ERR, e.getMessage());
-        }
-    }
-
-    private static long timestampStringToNumeric0(String dtStr) {
         dtStr = dtStr.trim();
         //"YYYY-MM-dd HH:mm:ss.ninenanos"
         if (dtStr.length() > 29) {
@@ -564,6 +556,14 @@ public class IgniteSqlFunctions {
         return args1;
     }
 
+    /** Returns the timestamp value minus the offset of the specified 
timezone. */
+    public static Long subtractTimeZoneOffset(long timestamp, TimeZone 
timeZone) {
+        // A second offset calculation is required to handle DST transition 
period correctly.
+        int offset = timeZone.getOffset(timestamp - 
timeZone.getOffset(timestamp));
+
+        return timestamp - offset;
+    }
+
     private static @Nullable Object leastOrGreatest(boolean least, Object 
arg0, Object arg1) {
         if (arg0 == null || arg1 == null) {
             return null;
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
index a9ffec1e4f..7f8c472218 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
@@ -420,6 +420,7 @@ import 
org.apache.calcite.sql.fun.SqlJsonArrayAggAggFunction;
 import org.apache.calcite.sql.fun.SqlJsonObjectAggAggFunction;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.fun.SqlTrimFunction;
+import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.calcite.sql.validate.SqlUserDefinedAggFunction;
@@ -3542,6 +3543,7 @@ public class RexImpTable {
       case DATE:
         switch (typeName) {
         case TIMESTAMP:
+        case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
           trop0 =
               Expressions.convert_(
                   IgniteExpressions.multiplyExact(trop0,
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
index 59e7c2bb95..1f8b00138e 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
@@ -355,6 +355,9 @@ public class RexToLixTranslator implements 
RexVisitor<RexToLixTranslator.Result>
                 switch (sourceType.getSqlTypeName()) {
                     case CHAR:
                     case VARCHAR:
+                        // By default Calcite for this type requires that the 
time zone be explicitly specified.
+                        // Since this type implies a local timezone, its 
explicit indication seems redundant,
+                        // so we prohibit the user from explicitly setting a 
timezone.
                         convert =
                                 
Expressions.call(IgniteMethod.STRING_TO_TIMESTAMP.method(), operand);
                         break;
@@ -400,9 +403,7 @@ public class RexToLixTranslator implements 
RexVisitor<RexToLixTranslator.Result>
                     case CHAR:
                     case VARCHAR:
                         convert =
-                                Expressions.call(
-                                        
BuiltInMethod.STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
-                                        operand);
+                                
Expressions.call(IgniteMethod.STRING_TO_TIMESTAMP.method(), operand);
                         break;
                     case DATE:
                         convert =
@@ -845,6 +846,15 @@ public class RexToLixTranslator implements 
RexVisitor<RexToLixTranslator.Result>
                         Expressions.constant(type.getPrecision()),
                         Expressions.constant(type.getScale())
                 );
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                Object val = literal.getValueAs(Long.class);
+
+                // Literal was parsed as UTC timestamp, now we need to adjust 
it to the client's time zone.
+                return Expressions.call(
+                        IgniteMethod.SUBTRACT_TIMEZONE_OFFSET.method(),
+                        Expressions.constant(val, long.class),
+                        Expressions.call(BuiltInMethod.TIME_ZONE.method, 
DataContext.ROOT)
+                );
             case DATE:
             case TIME:
             case TIME_WITH_LOCAL_TIME_ZONE:
@@ -855,7 +865,6 @@ public class RexToLixTranslator implements 
RexVisitor<RexToLixTranslator.Result>
                 javaClass = int.class;
                 break;
             case TIMESTAMP:
-            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
             case INTERVAL_DAY:
             case INTERVAL_DAY_HOUR:
             case INTERVAL_DAY_MINUTE:
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java
index a543f7c452..eb847bdf10 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/message/QueryStartRequest.java
@@ -54,4 +54,9 @@ public interface QueryStartRequest extends 
ExecutionContextAwareMessage {
      * Return last schema version, just a stub, need to be removed after 
IGNITE-20633.
      */
     int schemaVersion();
+
+    /**
+     * Session time zone ID.
+     */
+    String timeZoneId();
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/BaseQueryContext.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/BaseQueryContext.java
index e27f2a0c86..3337a58dbd 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/BaseQueryContext.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/BaseQueryContext.java
@@ -23,6 +23,7 @@ import static 
org.apache.ignite.internal.sql.engine.util.Commons.FRAMEWORK_CONFI
 
 import com.google.common.collect.Multimap;
 import java.lang.reflect.Method;
+import java.time.ZoneId;
 import java.util.List;
 import java.util.Objects;
 import java.util.Properties;
@@ -142,6 +143,8 @@ public final class BaseQueryContext implements Context {
 
     private final QueryPrefetchCallback prefetchCallback;
 
+    private final ZoneId timeZoneId;
+
     private CalciteCatalogReader catalogReader;
 
     /**
@@ -152,7 +155,8 @@ public final class BaseQueryContext implements Context {
             FrameworkConfig cfg,
             QueryCancel cancel,
             Object[] parameters,
-            QueryPrefetchCallback prefetchCallback
+            QueryPrefetchCallback prefetchCallback,
+            ZoneId timeZoneId
     ) {
         this.parentCtx = Contexts.chain(cfg.getContext());
 
@@ -163,6 +167,7 @@ public final class BaseQueryContext implements Context {
         this.cancel = cancel;
         this.parameters = parameters;
         this.prefetchCallback = prefetchCallback;
+        this.timeZoneId = timeZoneId;
     }
 
     public static Builder builder() {
@@ -227,6 +232,10 @@ public final class BaseQueryContext implements Context {
         return cancel;
     }
 
+    public ZoneId timeZoneId() {
+        return timeZoneId;
+    }
+
     /**
      * Query context builder.
      */
@@ -246,6 +255,8 @@ public final class BaseQueryContext implements Context {
 
         private Object[] parameters = ArrayUtils.OBJECT_EMPTY_ARRAY;
 
+        private ZoneId timeZoneId;
+
         private QueryPrefetchCallback prefetchCallback;
 
         public Builder frameworkConfig(FrameworkConfig frameworkCfg) {
@@ -273,8 +284,21 @@ public final class BaseQueryContext implements Context {
             return this;
         }
 
+        public Builder timeZoneId(ZoneId timeZoneId) {
+            this.timeZoneId = timeZoneId;
+            return this;
+        }
+
+        /** Creates new context. */
         public BaseQueryContext build() {
-            return new BaseQueryContext(Objects.requireNonNull(queryId, 
"queryId"), frameworkCfg, cancel, parameters, prefetchCallback);
+            return new BaseQueryContext(
+                    Objects.requireNonNull(queryId, "queryId"),
+                    frameworkCfg,
+                    cancel,
+                    parameters,
+                    prefetchCallback,
+                    timeZoneId
+            );
         }
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteCustomAssignmentsRules.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteCustomAssignmentsRules.java
index e46cebed09..833c897fde 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteCustomAssignmentsRules.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteCustomAssignmentsRules.java
@@ -129,6 +129,7 @@ public class IgniteCustomAssignmentsRules implements 
SqlTypeMappingRule {
         rule.add(SqlTypeName.DATE);
         rule.add(SqlTypeName.TIME);
         rule.add(SqlTypeName.TIMESTAMP);
+        rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
 
         rules.add(SqlTypeName.CHAR, rule);
         rules.add(SqlTypeName.VARCHAR, rule);
@@ -141,6 +142,7 @@ public class IgniteCustomAssignmentsRules implements 
SqlTypeMappingRule {
         rule.add(SqlTypeName.DATE);
         rule.addAll(CHAR_TYPES);
         rule.add(SqlTypeName.TIMESTAMP);
+        rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
         rules.add(SqlTypeName.DATE, rule);
 
         // TIME is assignable from...
@@ -148,6 +150,7 @@ public class IgniteCustomAssignmentsRules implements 
SqlTypeMappingRule {
         rule.add(SqlTypeName.TIME);
         rule.addAll(CHAR_TYPES);
         rule.add(SqlTypeName.TIMESTAMP);
+        rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
         rules.add(SqlTypeName.TIME, rule);
 
         // TIME WITH LOCAL TIME ZONE is assignable from...
@@ -157,14 +160,20 @@ public class IgniteCustomAssignmentsRules implements 
SqlTypeMappingRule {
         // TIMESTAMP is assignable from ...
         rule.clear();
         rule.add(SqlTypeName.TIMESTAMP);
+        rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
         rule.addAll(CHAR_TYPES);
         rule.add(SqlTypeName.TIME);
         rule.add(SqlTypeName.DATE);
         rules.add(SqlTypeName.TIMESTAMP, rule);
 
         // TIMESTAMP WITH LOCAL TIME ZONE is assignable from...
-        rules.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
-                EnumSet.of(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE));
+        rule.clear();
+        rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+        rule.add(SqlTypeName.TIMESTAMP);
+        rule.addAll(CHAR_TYPES);
+        rule.add(SqlTypeName.TIME);
+        rule.add(SqlTypeName.DATE);
+        rules.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, rule);
 
         // GEOMETRY is assignable from ...
         rule.clear();
@@ -233,6 +242,7 @@ public class IgniteCustomAssignmentsRules implements 
SqlTypeMappingRule {
         rule.add(SqlTypeName.TIME);
         rule.add(SqlTypeName.DATE);
         rule.add(SqlTypeName.TIMESTAMP);
+        rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
         rules.add(SqlTypeName.ANY, rule);
 
         INSTANCE = new IgniteCustomAssignmentsRules(rules.map);
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
index 1cf0b3705e..5d419abbf7 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
@@ -23,6 +23,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.util.Arrays;
 import java.util.Objects;
+import java.util.TimeZone;
 import java.util.UUID;
 import org.apache.calcite.DataContext;
 import org.apache.calcite.avatica.util.ByteString;
@@ -72,6 +73,9 @@ public enum IgniteMethod {
 
     STRING_TO_TIMESTAMP(IgniteSqlFunctions.class, "timestampStringToNumeric", 
String.class),
 
+    /** See {@link IgniteSqlFunctions#subtractTimeZoneOffset(long, TimeZone)}. 
**/
+    SUBTRACT_TIMEZONE_OFFSET(IgniteSqlFunctions.class, 
"subtractTimeZoneOffset", long.class, TimeZone.class),
+
     /** See {@link SqlParserUtil#intervalToMonths(String, 
SqlIntervalQualifier)}. */
     PARSE_INTERVAL_YEAR_MONTH(SqlParserUtil.class, "intervalToMonths", 
String.class, SqlIntervalQualifier.class),
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
index 087fff236f..0a3ed82b8a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
@@ -285,8 +285,7 @@ public class TypeUtils {
         } else if (storageType == LocalTime.class && val instanceof Integer) {
             return 
LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(Long.valueOf((Integer) 
val)));
         } else if (storageType == LocalDateTime.class && (val instanceof 
Long)) {
-            return 
LocalDateTime.ofEpochSecond(TimeUnit.MILLISECONDS.toSeconds((Long) val),
-                    (int) TimeUnit.MILLISECONDS.toNanos((Long) val % 1000), 
ZoneOffset.UTC);
+            return LocalDateTime.ofInstant(Instant.ofEpochMilli((long) val), 
ZoneOffset.UTC);
         } else if (storageType == Instant.class && val instanceof Long) {
             return Instant.ofEpochMilli((long) val);
         } else if (storageType == Duration.class && val instanceof Long) {
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SessionImplTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SessionImplTest.java
index 4709eedd18..be8659ce98 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SessionImplTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SessionImplTest.java
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.time.ZoneId;
 import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
@@ -61,6 +62,7 @@ import 
org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
 import org.apache.ignite.internal.util.AsyncCursor.BatchedResult;
 import org.apache.ignite.internal.util.IgniteSpinBusyLock;
 import org.apache.ignite.sql.BatchedArguments;
+import org.apache.ignite.sql.Session;
 import org.apache.ignite.sql.Session.SessionBuilder;
 import org.apache.ignite.sql.SqlException;
 import org.apache.ignite.sql.async.AsyncResultSet;
@@ -480,8 +482,35 @@ class SessionImplTest extends BaseIgniteAbstractTest {
         assertThat(session.openedCursors(), empty());
     }
 
+    @Test
+    public void localTimeZoneUsedByDefault() {
+        SessionBuilder sessionBuilder = newSessionBuilder();
+
+        // Check default value.
+        {
+            Session session = sessionBuilder.build();
+
+            assertEquals(ZoneId.systemDefault(), session.timeZoneId());
+        }
+
+        // Check that time zone can be changed.
+        {
+            ZoneId utcTz = ZoneId.of("UTC");
+
+            Session session = sessionBuilder.timeZoneId(utcTz).build();
+
+            assertEquals(utcTz, session.timeZoneId());
+        }
+    }
+
     private SessionImpl newSession(long idleTimeout) {
-        SessionBuilder builder = new SessionBuilderImpl(
+        return (SessionImpl) newSessionBuilder()
+                .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
+                .build();
+    }
+
+    private SessionBuilder newSessionBuilder() {
+        return new SessionBuilderImpl(
                 new IgniteSpinBusyLock(),
                 sessions,
                 queryProcessor,
@@ -489,9 +518,5 @@ class SessionImplTest extends BaseIgniteAbstractTest {
                 clock::get,
                 new HashMap<>()
         );
-
-        return (SessionImpl) builder
-                .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
-                .build();
     }
 }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
index 67b4c73fa2..6cd336f9c3 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
@@ -63,7 +63,6 @@ import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.calcite.jdbc.CalciteSchema;
@@ -81,6 +80,7 @@ import 
org.apache.ignite.internal.sql.engine.NodeLeftException;
 import org.apache.ignite.internal.sql.engine.QueryCancel;
 import org.apache.ignite.internal.sql.engine.QueryCancelledException;
 import org.apache.ignite.internal.sql.engine.QueryPrefetchCallback;
+import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import 
org.apache.ignite.internal.sql.engine.exec.ExecutionServiceImplTest.TestCluster.TestNode;
 import org.apache.ignite.internal.sql.engine.exec.ddl.DdlCommandHandler;
 import org.apache.ignite.internal.sql.engine.exec.mapping.ExecutionTarget;
@@ -643,17 +643,6 @@ public class ExecutionServiceImplTest extends 
BaseIgniteAbstractTest {
         AtomicReference<AssertionError> errHolder = new AtomicReference<>();
         ExecutionService execService = executionServices.get(0);
 
-        Function<QueryPrefetchCallback, BaseQueryContext> createCtx = 
(callback) -> BaseQueryContext.builder()
-                .queryId(UUID.randomUUID())
-                .cancel(new QueryCancel())
-                .prefetchCallback(callback)
-                .frameworkConfig(
-                        Frameworks.newConfigBuilder(FRAMEWORK_CONFIG)
-                                .defaultSchema(wrap(schema))
-                                .build()
-                )
-                .build();
-
         QueryPrefetchCallback prefetchListener = new QueryPrefetchCallback() {
             @Override
             public void onPrefetchComplete(@Nullable Throwable err) {
@@ -664,7 +653,7 @@ public class ExecutionServiceImplTest extends 
BaseIgniteAbstractTest {
 
                     assertThat(sql, notNullValue());
 
-                    BaseQueryContext ctx = 
createCtx.apply(queriesQueue.isEmpty() ? null : this);
+                    BaseQueryContext ctx = 
createContext(queriesQueue.isEmpty() ? null : this);
                     InternalTransaction tx = new 
NoOpTransaction(nodeNames.get(0));
                     QueryPlan plan = prepare(sql, ctx);
 
@@ -701,17 +690,7 @@ public class ExecutionServiceImplTest extends 
BaseIgniteAbstractTest {
         ExecutionService execService = executionServices.get(0);
         CompletableFuture<Void> prefetchFut = new CompletableFuture<>();
         IgniteInternalException expectedException = new 
IgniteInternalException(Common.INTERNAL_ERR, "Expected exception");
-
-        BaseQueryContext ctx = BaseQueryContext.builder()
-                .queryId(UUID.randomUUID())
-                .cancel(new QueryCancel())
-                .prefetchCallback(prefetchFut::completeExceptionally)
-                .frameworkConfig(
-                        Frameworks.newConfigBuilder(FRAMEWORK_CONFIG)
-                                .defaultSchema(wrap(schema))
-                                .build()
-                )
-                .build();
+        BaseQueryContext ctx = 
createContext(prefetchFut::completeExceptionally);
 
         testCluster.node(nodeNames.get(2)).interceptor((nodeName, msg, 
original) -> {
             if (msg instanceof QueryStartRequest) {
@@ -908,14 +887,20 @@ public class ExecutionServiceImplTest extends 
BaseIgniteAbstractTest {
     }
 
     private BaseQueryContext createContext() {
+        return createContext(null);
+    }
+
+    private BaseQueryContext createContext(@Nullable QueryPrefetchCallback 
prefetchCallback) {
         return BaseQueryContext.builder()
                 .queryId(UUID.randomUUID())
                 .cancel(new QueryCancel())
+                .prefetchCallback(prefetchCallback)
                 .frameworkConfig(
                         Frameworks.newConfigBuilder(FRAMEWORK_CONFIG)
                                 .defaultSchema(wrap(schema))
                                 .build()
                 )
+                .timeZoneId(SqlQueryProcessor.DEFAULT_TIME_ZONE_ID)
                 .build();
     }
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
index 7ba54ecf29..d9ad4dc699 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/RuntimeSortedIndexTest.java
@@ -35,6 +35,7 @@ import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Pair;
 import org.apache.ignite.internal.network.ClusterNodeImpl;
+import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import org.apache.ignite.internal.sql.engine.framework.ArrayRowHandler;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -120,7 +121,8 @@ public class RuntimeSortedIndexTest extends 
IgniteAbstractTest {
                         null,
                         ArrayRowHandler.INSTANCE,
                         Map.of(),
-                        null
+                        null,
+                        SqlQueryProcessor.DEFAULT_TIME_ZONE_ID
                 ),
                 RelCollations.of(ImmutableIntList.copyOf(idxCols)),
                 (o1, o2) -> {
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java
index 596b510f0d..72d18a2cfd 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctionsTest.java
@@ -25,6 +25,10 @@ import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.TimeZone;
 import java.util.function.Supplier;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeSystem;
@@ -32,6 +36,7 @@ import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Sql functions test.
@@ -481,4 +486,33 @@ public class IgniteSqlFunctionsTest {
     public void testTruncate2LongType(long input, int scale, long result) {
         assertEquals(result, IgniteSqlFunctions.struncate(input, scale));
     }
+
+    @ParameterizedTest
+    @ValueSource(strings = {
+            "2023-10-29 02:01:01",
+            "2023-10-29 03:01:01",
+            "2023-10-29 04:01:01",
+            "2023-10-29 05:01:01",
+            "2024-03-31 02:01:01",
+            "2024-03-31 03:01:01",
+            "2024-03-31 04:01:01",
+            "2024-03-31 05:01:01",
+    })
+    public void testSubtractTimeZoneOffset(String input) throws ParseException 
{
+        TimeZone cyprusTz = TimeZone.getTimeZone("Asia/Nicosia");
+        TimeZone utcTz = TimeZone.getTimeZone("UTC");
+
+        SimpleDateFormat dateFormatTz = new SimpleDateFormat("yyyy-MM-dd 
HH:mm:ss");
+        dateFormatTz.setTimeZone(cyprusTz);
+
+        SimpleDateFormat dateFormatUtc = new SimpleDateFormat("yyyy-MM-dd 
HH:mm:ss");
+        dateFormatUtc.setTimeZone(utcTz);
+
+        long expMillis = dateFormatTz.parse(input).getTime();
+        long utcMillis = dateFormatUtc.parse(input).getTime();
+
+        long actualTs = IgniteSqlFunctions.subtractTimeZoneOffset(utcMillis, 
cyprusTz);
+
+        assertEquals(Instant.ofEpochMilli(expMillis), 
Instant.ofEpochMilli(actualTs));
+    }
 }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
index 7ea0504463..2fae8e9bfb 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractExecutionTest.java
@@ -44,6 +44,7 @@ import org.apache.ignite.internal.network.ClusterNodeImpl;
 import org.apache.ignite.internal.schema.BinaryRowConverter;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.schema.BinaryTupleSchema;
+import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
 import org.apache.ignite.internal.sql.engine.exec.QueryTaskExecutorImpl;
 import org.apache.ignite.internal.sql.engine.exec.RowHandler;
@@ -116,7 +117,8 @@ public abstract class AbstractExecutionTest<T> extends 
IgniteAbstractTest {
                 fragmentDesc,
                 rowHandler(),
                 Map.of(),
-                TxAttributes.fromTx(new NoOpTransaction("fake-test-node"))
+                TxAttributes.fromTx(new NoOpTransaction("fake-test-node")),
+                SqlQueryProcessor.DEFAULT_TIME_ZONE_ID
         );
     }
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
index fe45def647..38d5e52a0a 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/MergeJoinExecutionTest.java
@@ -40,6 +40,7 @@ import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
 import org.apache.ignite.internal.sql.engine.exec.RowHandler;
 import org.apache.ignite.internal.sql.engine.exec.exp.ExpressionFactoryImpl;
@@ -484,8 +485,8 @@ public class MergeJoinExecutionTest extends 
AbstractExecutionTest<Object[]> {
         ScanNode<Object[]> rightNode = new ScanNode<>(ctx, 
Arrays.asList(right));
 
         ExecutionContext<Object[]> ectx =
-                new ExecutionContext<>(null, null, null,
-                        null, null, ArrayRowHandler.INSTANCE, null, null);
+                new ExecutionContext<>(null, null, null, null, null,
+                        ArrayRowHandler.INSTANCE, null, null, 
SqlQueryProcessor.DEFAULT_TIME_ZONE_ID);
 
         ExpressionFactoryImpl<Object[]> expFactory = new 
ExpressionFactoryImpl<>(ectx, SqlConformanceEnum.DEFAULT);
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
index bb5cf902a8..040f70f24e 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
@@ -64,6 +64,7 @@ import 
org.apache.ignite.internal.cluster.management.topology.api.LogicalNode;
 import 
org.apache.ignite.internal.cluster.management.topology.api.LogicalTopologySnapshot;
 import org.apache.ignite.internal.hlc.HybridClockImpl;
 import org.apache.ignite.internal.metrics.MetricManager;
+import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import org.apache.ignite.internal.sql.engine.exec.ExecutableTable;
 import org.apache.ignite.internal.sql.engine.exec.ExecutableTableRegistry;
 import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
@@ -507,7 +508,8 @@ public class TestBuilders {
                     description,
                     ArrayRowHandler.INSTANCE,
                     Map.of(),
-                    TxAttributes.fromTx(new NoOpTransaction(node.name()))
+                    TxAttributes.fromTx(new NoOpTransaction(node.name())),
+                    SqlQueryProcessor.DEFAULT_TIME_ZONE_ID
             );
         }
     }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
index 44d6e0be4d..0aca997180 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestNode.java
@@ -32,6 +32,7 @@ import org.apache.ignite.internal.network.ClusterService;
 import org.apache.ignite.internal.network.MessagingService;
 import org.apache.ignite.internal.sql.engine.InternalSqlRow;
 import org.apache.ignite.internal.sql.engine.QueryCancel;
+import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.exec.ExchangeService;
 import org.apache.ignite.internal.sql.engine.exec.ExchangeServiceImpl;
@@ -250,6 +251,7 @@ public class TestNode implements LifecycleAware {
                                 
.defaultSchema(schemaManager.schema(Long.MAX_VALUE).getSubSchema(DEFAULT_SCHEMA_NAME))
                                 .build()
                 )
+                .timeZoneId(SqlQueryProcessor.DEFAULT_TIME_ZONE_ID)
                 .build();
     }
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/CastResolutionTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/CastResolutionTest.java
index 0b3aca2358..72195e1788 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/CastResolutionTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/CastResolutionTest.java
@@ -120,6 +120,7 @@ public class CastResolutionTest extends AbstractPlannerTest 
{
 
         CHAR_AND_TS.addAll(CHAR_NAMES);
         CHAR_AND_TS.add(SqlTypeName.TIMESTAMP.getName());
+        CHAR_AND_TS.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE.getName());
 
         CHAR_AND_DT.addAll(DT_NAMES);
         CHAR_AND_DT.addAll(CHAR_NAMES);
@@ -150,30 +151,28 @@ public class CastResolutionTest extends 
AbstractPlannerTest {
             String from = makeUsableIntervalFromType(fromInitial);
 
             for (String toType : toTypes) {
-                toType = makeUsableIntervalToType(toType);
-
-                // TODO: https://issues.apache.org/jira/browse/IGNITE-19274
-                if (toType.contains("LOCAL TIME")) {
+                // TODO: https://issues.apache.org/jira/browse/IGNITE-21555
+                if 
(toType.equals(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE.getName())) {
                     continue;
                 }
 
+                toType = makeSpaceName(toType);
+
                 testItems.add(checkStatement().sql(format(template, from, 
toType)).ok(false));
             }
 
-            testItems.add(checkStatement().sql(format(template, from, 
makeUsableIntervalToType(fromInitial))).ok());
+            testItems.add(checkStatement().sql(format(template, from, 
makeSpaceName(fromInitial))).ok());
 
-            testItems.add(checkStatement().sql(format(INTERVAL_TEMPLATE, 
"NULL", makeUsableIntervalToType(fromInitial))).ok());
+            testItems.add(checkStatement().sql(format(INTERVAL_TEMPLATE, 
"NULL", makeSpaceName(fromInitial))).ok());
 
             String finalFrom = from;
             Set<String> deprecatedCastTypes = allTypes.stream().filter(t -> 
!toTypes.contains(t) && !t.equals(finalFrom))
                     .collect(Collectors.toSet());
 
             for (String toType : deprecatedCastTypes) {
-                boolean isInterval = toType.toLowerCase().contains("interval");
-
-                toType = isInterval ? makeUsableIntervalToType(toType) : 
toType;
-
-                testItems.add(checkStatement().sql(format(template, from, 
toType)).fails(CAST_ERROR_MESSAGE));
+                testItems.add(
+                        checkStatement().sql(format(template, from, 
makeSpaceName(toType))).fails(CAST_ERROR_MESSAGE)
+                );
             }
         }
 
@@ -299,12 +298,16 @@ public class CastResolutionTest extends 
AbstractPlannerTest {
         }
     }
 
-    private static String makeUsableIntervalToType(String typeName) {
+    private static String makeSpaceName(String typeName) {
         return typeName.replace("_", " ");
     }
 
     private static String makeUsableIntervalFromType(String typeName) {
-        return typeName.replace("_", " 1 ");
+        if (typeName.toLowerCase().contains("interval")) {
+            return typeName.replace("_", " 1 ");
+        }
+
+        return makeSpaceName(typeName);
     }
 
     private enum CastMatrix {
@@ -342,15 +345,17 @@ public class CastResolutionTest extends 
AbstractPlannerTest {
 
         TIME(SqlTypeName.TIME.getName(), CHAR_AND_TS),
 
+        // TODO: https://issues.apache.org/jira/browse/IGNITE-21555
+        //TIME_TZ(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE.getName(), 
CHAR_AND_DT);
+
         TIMESTAMP(SqlTypeName.TIMESTAMP.getName(), CHAR_AND_DT),
 
+        TIMESTAMP_TZ(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE.getName(), 
CHAR_AND_DT),
+
         INTERVAL_YEAR(SqlTypeName.INTERVAL_YEAR.getName(), 
CHAR_EXACT_AND_YM_INTERVAL),
 
         INTERVAL_HOUR(SqlTypeName.INTERVAL_HOUR.getName(), 
CHAR_EXACT_AND_DAY_INTERVAL);
 
-        // TODO: https://issues.apache.org/jira/browse/IGNITE-19274
-        //TIMESTAMP_TS(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE.getName(), 
charAndDt);
-
         private String from;
         private Set<String> toTypes;
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
index 8c07f714ab..54cdbc94de 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
@@ -31,7 +31,6 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.apache.calcite.schema.ColumnStrategy;
 import org.apache.calcite.sql.SqlBasicCall;
@@ -631,18 +630,14 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
     /**
      * Ensures that the user cannot use the TIME_WITH_LOCAL_TIME_ZONE and 
TIMESTAMP_WITH_LOCAL_TIME_ZONE types for table columns.
      */
-    // TODO: Remove after https://issues.apache.org/jira/browse/IGNITE-19274 
is implemented.
+    // TODO: Remove after https://issues.apache.org/jira/browse/IGNITE-21555 
is implemented.
     @Test
-    public void timestampWithLocalTimeZoneIsNotSupported() {
-        Consumer<String> checker = (query) -> {
-            assertThrowsSqlException(
-                    Sql.STMT_PARSE_ERR,
-                    "Encountered \"WITH\"",
-                    () -> parse(query));
-        };
-
-        checker.accept("CREATE TABLE test (ts TIMESTAMP WITH LOCAL TIME 
ZONE)");
-        checker.accept("CREATE TABLE test (ts TIME WITH LOCAL TIME ZONE)");
+    public void timeWithLocalTimeZoneIsNotSupported() {
+        assertThrowsSqlException(
+                Sql.STMT_PARSE_ERR,
+                "Encountered \"WITH\"",
+                () -> parse("CREATE TABLE test (ts TIME WITH LOCAL TIME ZONE)")
+        );
     }
 
     private IgniteSqlCreateTable parseCreateTable(String stmt) {
diff --git 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
index 01908e1cec..195710df0e 100644
--- 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
+++ 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.fail;
 
 import java.lang.reflect.Array;
 import java.lang.reflect.Type;
+import java.time.ZoneId;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
@@ -297,6 +298,8 @@ public interface QueryChecker {
 
     QueryChecker withParam(Object param);
 
+    QueryChecker withTimeZoneId(ZoneId timeZoneId);
+
     QueryChecker disableRules(String... rules);
 
     QueryChecker returns(Object... res);
diff --git 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
index 5afdfae3ce..b226145aa0 100644
--- 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
+++ 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java
@@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.lang.reflect.Type;
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -47,6 +48,7 @@ import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.InternalSqlRow;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.sql.engine.QueryProperty;
+import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.hint.IgniteHint;
 import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata;
@@ -67,10 +69,6 @@ import org.jetbrains.annotations.Nullable;
 abstract class QueryCheckerImpl implements QueryChecker {
     private static final IgniteLogger LOG = 
Loggers.forClass(QueryCheckerImpl.class);
 
-    private static final SqlProperties PROPERTIES = 
SqlPropertiesHelper.newBuilder()
-            .set(QueryProperty.ALLOWED_QUERY_TYPES, 
SqlQueryType.SINGLE_STMT_TYPES)
-            .build();
-
     private final QueryTemplate queryTemplate;
 
     private final ArrayList<Matcher<String>> planMatchers = new ArrayList<>();
@@ -85,6 +83,8 @@ abstract class QueryCheckerImpl implements QueryChecker {
 
     private Object[] params = OBJECT_EMPTY_ARRAY;
 
+    private ZoneId timeZoneId = SqlQueryProcessor.DEFAULT_TIME_ZONE_ID;
+
     private final @Nullable InternalTransaction tx;
 
     /**
@@ -138,6 +138,19 @@ abstract class QueryCheckerImpl implements QueryChecker {
         return this.withParams(param);
     }
 
+    /**
+     * Set client time zone for query.
+     *
+     * @param zoneId Zone ID.
+     * @return This.
+     */
+    @Override
+    public QueryChecker withTimeZoneId(ZoneId zoneId) {
+        this.timeZoneId = zoneId;
+
+        return this;
+    }
+
     /**
      * Disables rules.
      *
@@ -283,14 +296,18 @@ abstract class QueryCheckerImpl implements QueryChecker {
         // Check plan.
         QueryProcessor qryProc = getEngine();
 
+        SqlProperties properties = SqlPropertiesHelper.newBuilder()
+                .set(QueryProperty.ALLOWED_QUERY_TYPES, 
SqlQueryType.SINGLE_STMT_TYPES)
+                .set(QueryProperty.TIME_ZONE_ID, timeZoneId)
+                .build();
+
         String qry = queryTemplate.createQuery();
 
         LOG.info("Executing query: {}", qry);
 
         if (!CollectionUtils.nullOrEmpty(planMatchers)) {
-
             CompletableFuture<AsyncSqlCursor<InternalSqlRow>> explainCursors = 
qryProc.querySingleAsync(
-                    PROPERTIES, transactions(), tx, "EXPLAIN PLAN FOR " + qry, 
params);
+                    properties, transactions(), tx, "EXPLAIN PLAN FOR " + qry, 
params);
             AsyncSqlCursor<InternalSqlRow> explainCursor = 
await(explainCursors);
             List<InternalSqlRow> explainRes = getAllFromCursor(explainCursor);
 
@@ -305,7 +322,7 @@ abstract class QueryCheckerImpl implements QueryChecker {
 
         // Check column metadata only.
         if (resultChecker == null && metadataMatchers != null) {
-            QueryMetadata queryMetadata = 
await(qryProc.prepareSingleAsync(PROPERTIES, tx, qry, params));
+            QueryMetadata queryMetadata = 
await(qryProc.prepareSingleAsync(properties, tx, qry, params));
 
             assertNotNull(queryMetadata);
 
@@ -316,7 +333,7 @@ abstract class QueryCheckerImpl implements QueryChecker {
 
         // Check result.
         CompletableFuture<AsyncSqlCursor<InternalSqlRow>> cursors =
-                qryProc.querySingleAsync(PROPERTIES, transactions(), tx, qry, 
params);
+                qryProc.querySingleAsync(properties, transactions(), tx, qry, 
params);
 
         AsyncSqlCursor<InternalSqlRow> cur = await(cursors);
 

Reply via email to