You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jc...@apache.org on 2017/09/05 22:01:11 UTC

[1/2] calcite git commit: [CALCITE-1947] Add time/timestamp with local time zone types to optimizer [Forced Update!]

Repository: calcite
Updated Branches:
  refs/heads/master 68abba04a -> 939c9a62b (forced update)


http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/test/java/org/apache/calcite/test/RexProgramTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java
index 7cbcfa6..ccecc98 100644
--- a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java
@@ -16,9 +16,11 @@
  */
 package org.apache.calcite.test;
 
+import org.apache.calcite.DataContext;
 import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
+import org.apache.calcite.linq4j.QueryProvider;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.Strong;
 import org.apache.calcite.rel.type.RelDataType;
@@ -27,6 +29,8 @@ import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexExecutor;
+import org.apache.calcite.rex.RexExecutorImpl;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexLocalRef;
@@ -35,6 +39,7 @@ import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.rex.RexProgramBuilder;
 import org.apache.calcite.rex.RexSimplify;
 import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlSpecialOperator;
@@ -48,6 +53,7 @@ import org.apache.calcite.util.NlsString;
 import org.apache.calcite.util.TestUtil;
 import org.apache.calcite.util.TimeString;
 import org.apache.calcite.util.TimestampString;
+import org.apache.calcite.util.TimestampWithTimeZoneString;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
@@ -65,6 +71,7 @@ import java.util.Arrays;
 import java.util.Calendar;
 import java.util.List;
 import java.util.Map;
+import java.util.TimeZone;
 import java.util.TreeMap;
 
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -100,7 +107,9 @@ public class RexProgramTest {
   public void setUp() {
     typeFactory = new JavaTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
     rexBuilder = new RexBuilder(typeFactory);
-    simplify = new RexSimplify(rexBuilder, false, RexUtil.EXECUTOR);
+    RexExecutor executor =
+        new RexExecutorImpl(new DummyTestDataContext());
+    simplify = new RexSimplify(rexBuilder, false, executor);
     trueLiteral = rexBuilder.makeLiteral(true);
     falseLiteral = rexBuilder.makeLiteral(false);
     final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
@@ -108,6 +117,34 @@ public class RexProgramTest {
     unknownLiteral = rexBuilder.makeNullLiteral(trueLiteral.getType());
   }
 
+  /** Dummy data context for test. */
+  private static class DummyTestDataContext implements DataContext {
+    private final ImmutableMap<String, Object> map;
+
+    DummyTestDataContext() {
+      this.map =
+          ImmutableMap.<String, Object>of(
+              Variable.TIME_ZONE.camelName, TimeZone.getTimeZone("America/Los_Angeles"),
+              Variable.CURRENT_TIMESTAMP.camelName, new Long(1311120000000L));
+    }
+
+    public SchemaPlus getRootSchema() {
+      return null;
+    }
+
+    public JavaTypeFactory getTypeFactory() {
+      return null;
+    }
+
+    public QueryProvider getQueryProvider() {
+      return null;
+    }
+
+    public Object get(String name) {
+      return map.get(name);
+    }
+  }
+
   private void checkCnf(RexNode node, String expected) {
     assertThat(RexUtil.toCnf(rexBuilder, node).toString(), equalTo(expected));
   }
@@ -1491,6 +1528,95 @@ public class RexProgramTest {
         "1970-01-01 00:00:00"); // different from Hive
   }
 
+  @Test public void testSimplifyCastLiteral3() {
+    // Default TimeZone is "America/Los_Angeles" (DummyDataContext)
+    final RexLiteral literalDate = rexBuilder.makeDateLiteral(new DateString("2011-07-20"));
+    final RexLiteral literalTime = rexBuilder.makeTimeLiteral(new TimeString("12:34:56"), 0);
+    final RexLiteral literalTimestamp = rexBuilder.makeTimestampLiteral(
+        new TimestampString("2011-07-20 12:34:56"), 0);
+    final RexLiteral literalTimeLTZ =
+        rexBuilder.makeTimeWithLocalTimeZoneLiteral(
+            new TimeString(1, 23, 45), 0);
+    final RexLiteral timeLTZChar1 = rexBuilder.makeLiteral("12:34:45 America/Los_Angeles");
+    final RexLiteral timeLTZChar2 = rexBuilder.makeLiteral("12:34:45 UTC");
+    final RexLiteral timeLTZChar3 = rexBuilder.makeLiteral("12:34:45 GMT+01");
+    final RexLiteral timestampLTZChar1 = rexBuilder.makeLiteral("2011-07-20 12:34:56 Asia/Tokyo");
+    final RexLiteral timestampLTZChar2 = rexBuilder.makeLiteral("2011-07-20 12:34:56 GMT+01");
+    final RexLiteral timestampLTZChar3 = rexBuilder.makeLiteral("2011-07-20 12:34:56 UTC");
+    final RexLiteral literalTimestampLTZ =
+        rexBuilder.makeTimestampWithLocalTimeZoneLiteral(
+            new TimestampString(2011, 7, 20, 8, 23, 45), 0);
+
+    final RelDataType dateType =
+        typeFactory.createSqlType(SqlTypeName.DATE);
+    final RelDataType timeType =
+        typeFactory.createSqlType(SqlTypeName.TIME);
+    final RelDataType timestampType =
+        typeFactory.createSqlType(SqlTypeName.TIMESTAMP);
+    final RelDataType timeLTZType =
+        typeFactory.createSqlType(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
+    final RelDataType timestampLTZType =
+        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+    final RelDataType varCharType =
+        typeFactory.createSqlType(SqlTypeName.VARCHAR, 40);
+
+    checkSimplify(cast(timeLTZChar1, timeLTZType), "20:34:45");
+    checkSimplify(cast(timeLTZChar2, timeLTZType), "12:34:45");
+    checkSimplify(cast(timeLTZChar3, timeLTZType), "11:34:45");
+    checkSimplify(cast(literalTimeLTZ, timeLTZType), "01:23:45");
+    checkSimplify(cast(timestampLTZChar1, timestampLTZType),
+        "2011-07-20 03:34:56");
+    checkSimplify(cast(timestampLTZChar2, timestampLTZType),
+        "2011-07-20 11:34:56");
+    checkSimplify(cast(timestampLTZChar3, timestampLTZType),
+        "2011-07-20 12:34:56");
+    checkSimplify(cast(literalTimestampLTZ, timestampLTZType),
+        "2011-07-20 08:23:45");
+    checkSimplify(cast(literalDate, timestampLTZType),
+        "2011-07-20 07:00:00");
+    checkSimplify(cast(literalTime, timestampLTZType),
+        "2011-07-20 19:34:56");
+    checkSimplify(cast(literalTimestamp, timestampLTZType),
+        "2011-07-20 19:34:56");
+    checkSimplify(cast(literalTimestamp, dateType),
+        "2011-07-20");
+    checkSimplify(cast(literalTimestampLTZ, dateType),
+        "2011-07-20");
+    checkSimplify(cast(literalTimestampLTZ, timeType),
+        "01:23:45");
+    checkSimplify(cast(literalTimestampLTZ, timestampType),
+        "2011-07-20 01:23:45");
+    checkSimplify(cast(literalTimeLTZ, timeType),
+        "17:23:45");
+    checkSimplify(cast(literalTime, timeLTZType),
+        "20:34:56");
+    checkSimplify(cast(literalTimestampLTZ, timeLTZType),
+        "08:23:45");
+    checkSimplify(cast(literalTimeLTZ, varCharType),
+        "'17:23:45 America/Los_Angeles'");
+    checkSimplify(cast(literalTimestampLTZ, varCharType),
+        "'2011-07-20 01:23:45 America/Los_Angeles'");
+    checkSimplify(cast(literalTimeLTZ, timestampType),
+        "2011-07-19 18:23:45");
+    checkSimplify(cast(literalTimeLTZ, timestampLTZType),
+        "2011-07-20 01:23:45");
+  }
+
+  @Test public void testCompareTimestampWithTimeZone() {
+    final TimestampWithTimeZoneString timestampLTZChar1 =
+        new TimestampWithTimeZoneString("2011-07-20 10:34:56 America/Los_Angeles");
+    final TimestampWithTimeZoneString timestampLTZChar2 =
+        new TimestampWithTimeZoneString("2011-07-20 19:34:56 Europe/Rome");
+    final TimestampWithTimeZoneString timestampLTZChar3 =
+        new TimestampWithTimeZoneString("2011-07-20 01:34:56 Asia/Tokyo");
+    final TimestampWithTimeZoneString timestampLTZChar4 =
+        new TimestampWithTimeZoneString("2011-07-20 10:34:56 America/Los_Angeles");
+
+    assertThat(timestampLTZChar1.equals(timestampLTZChar2), is(false));
+    assertThat(timestampLTZChar1.equals(timestampLTZChar3), is(false));
+    assertThat(timestampLTZChar1.equals(timestampLTZChar4), is(true));
+  }
+
   @Test public void testSimplifyLiterals() {
     final RexLiteral literalAbc = rexBuilder.makeLiteral("abc");
     final RexLiteral literalDef = rexBuilder.makeLiteral("def");

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/main/java/org/apache/calcite/adapter/druid/DruidConnectionImpl.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidConnectionImpl.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidConnectionImpl.java
index 3be8d20..1951396 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidConnectionImpl.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidConnectionImpl.java
@@ -512,7 +512,7 @@ class DruidConnectionImpl implements DruidConnection {
               JsonSegmentMetadata.class);
       final List<JsonSegmentMetadata> list = mapper.readValue(in, listType);
       in.close();
-      fieldBuilder.put(timestampColumnName, SqlTypeName.TIMESTAMP);
+      fieldBuilder.put(timestampColumnName, SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
       for (JsonSegmentMetadata o : list) {
         for (Map.Entry<String, JsonColumn> entry : o.columns.entrySet()) {
           if (entry.getKey().equals(DruidTable.DEFAULT_TIMESTAMP_COLUMN)) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
index 0328882..fb69353 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.adapter.druid;
 
+import org.apache.calcite.avatica.util.DateTimeUtils;
 import org.apache.calcite.avatica.util.TimeUnitRange;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexCall;
@@ -26,6 +27,7 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.DateString;
 import org.apache.calcite.util.TimestampString;
+import org.apache.calcite.util.TimestampWithTimeZoneString;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.trace.CalciteTrace;
 
@@ -40,6 +42,7 @@ import org.slf4j.Logger;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.TimeZone;
 
 /**
  * Utilities for generating intervals from RexNode.
@@ -57,9 +60,9 @@ public class DruidDateTimeUtils {
    * expression. Assumes that all the predicates in the input
    * reference a single column: the timestamp column.
    */
-  public static List<LocalInterval> createInterval(RelDataType type,
-      RexNode e) {
-    final List<Range<TimestampString>> ranges = extractRanges(e, false);
+  public static List<LocalInterval> createInterval(RexNode e, String timeZone) {
+    final List<Range<TimestampString>> ranges =
+        extractRanges(e, TimeZone.getTimeZone(timeZone), false);
     if (ranges == null) {
       // We did not succeed, bail out
       return null;
@@ -71,10 +74,12 @@ public class DruidDateTimeUtils {
     if (LOGGER.isDebugEnabled()) {
       LOGGER.debug("Inferred ranges on interval : " + condensedRanges);
     }
-    return toInterval(ImmutableList.<Range>copyOf(condensedRanges.asRanges()));
+    return toInterval(
+        ImmutableList.<Range>copyOf(condensedRanges.asRanges()));
   }
 
-  protected static List<LocalInterval> toInterval(List<Range<TimestampString>> ranges) {
+  protected static List<LocalInterval> toInterval(
+      List<Range<TimestampString>> ranges) {
     List<LocalInterval> intervals = Lists.transform(ranges,
         new Function<Range<TimestampString>, LocalInterval>() {
           public LocalInterval apply(Range<TimestampString> range) {
@@ -105,7 +110,7 @@ public class DruidDateTimeUtils {
   }
 
   protected static List<Range<TimestampString>> extractRanges(RexNode node,
-      boolean withNot) {
+      TimeZone timeZone, boolean withNot) {
     switch (node.getKind()) {
     case EQUALS:
     case LESS_THAN:
@@ -114,16 +119,17 @@ public class DruidDateTimeUtils {
     case GREATER_THAN_OR_EQUAL:
     case BETWEEN:
     case IN:
-      return leafToRanges((RexCall) node, withNot);
+      return leafToRanges((RexCall) node, timeZone, withNot);
 
     case NOT:
-      return extractRanges(((RexCall) node).getOperands().get(0), !withNot);
+      return extractRanges(((RexCall) node).getOperands().get(0), timeZone, !withNot);
 
     case OR: {
       RexCall call = (RexCall) node;
       List<Range<TimestampString>> intervals = Lists.newArrayList();
       for (RexNode child : call.getOperands()) {
-        List<Range<TimestampString>> extracted = extractRanges(child, withNot);
+        List<Range<TimestampString>> extracted =
+            extractRanges(child, timeZone, withNot);
         if (extracted != null) {
           intervals.addAll(extracted);
         }
@@ -135,7 +141,8 @@ public class DruidDateTimeUtils {
       RexCall call = (RexCall) node;
       List<Range<TimestampString>> ranges = new ArrayList<>();
       for (RexNode child : call.getOperands()) {
-        List<Range<TimestampString>> extractedRanges = extractRanges(child, false);
+        List<Range<TimestampString>> extractedRanges =
+            extractRanges(child, timeZone, false);
         if (extractedRanges == null || extractedRanges.isEmpty()) {
           // We could not extract, we bail out
           return null;
@@ -163,7 +170,7 @@ public class DruidDateTimeUtils {
   }
 
   protected static List<Range<TimestampString>> leafToRanges(RexCall call,
-      boolean withNot) {
+      TimeZone timeZone, boolean withNot) {
     switch (call.getKind()) {
     case EQUALS:
     case LESS_THAN:
@@ -173,11 +180,11 @@ public class DruidDateTimeUtils {
     {
       final TimestampString value;
       if (call.getOperands().get(0) instanceof RexInputRef
-          && literalValue(call.getOperands().get(1)) != null) {
-        value = literalValue(call.getOperands().get(1));
+          && literalValue(call.getOperands().get(1), timeZone) != null) {
+        value = literalValue(call.getOperands().get(1), timeZone);
       } else if (call.getOperands().get(1) instanceof RexInputRef
-          && literalValue(call.getOperands().get(0)) != null) {
-        value = literalValue(call.getOperands().get(0));
+          && literalValue(call.getOperands().get(0), timeZone) != null) {
+        value = literalValue(call.getOperands().get(0), timeZone);
       } else {
         return null;
       }
@@ -201,10 +208,10 @@ public class DruidDateTimeUtils {
     {
       final TimestampString value1;
       final TimestampString value2;
-      if (literalValue(call.getOperands().get(2)) != null
-          && literalValue(call.getOperands().get(3)) != null) {
-        value1 = literalValue(call.getOperands().get(2));
-        value2 = literalValue(call.getOperands().get(3));
+      if (literalValue(call.getOperands().get(2), timeZone) != null
+          && literalValue(call.getOperands().get(3), timeZone) != null) {
+        value1 = literalValue(call.getOperands().get(2), timeZone);
+        value2 = literalValue(call.getOperands().get(3), timeZone);
       } else {
         return null;
       }
@@ -219,9 +226,10 @@ public class DruidDateTimeUtils {
     }
     case IN:
     {
-      ImmutableList.Builder<Range<TimestampString>> ranges = ImmutableList.builder();
+      ImmutableList.Builder<Range<TimestampString>> ranges =
+          ImmutableList.builder();
       for (RexNode operand : Util.skip(call.operands)) {
-        final TimestampString element = literalValue(operand);
+        final TimestampString element = literalValue(operand, timeZone);
         if (element == null) {
           return null;
         }
@@ -239,16 +247,24 @@ public class DruidDateTimeUtils {
     }
   }
 
-  private static TimestampString literalValue(RexNode node) {
+  private static TimestampString literalValue(RexNode node, TimeZone timeZone) {
     switch (node.getKind()) {
     case LITERAL:
       switch (((RexLiteral) node).getTypeName()) {
-      case TIMESTAMP:
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
         return ((RexLiteral) node).getValueAs(TimestampString.class);
+      case TIMESTAMP:
+        // Cast timestamp to timestamp with local time zone
+        final TimestampString t = ((RexLiteral) node).getValueAs(TimestampString.class);
+        return new TimestampWithTimeZoneString(t.toString() + " " + timeZone.getID())
+            .withTimeZone(DateTimeUtils.UTC_ZONE).getLocalTimestampString();
       case DATE:
-        // For uniformity, treat dates as timestamps
+        // Cast date to timestamp with local time zone
         final DateString d = ((RexLiteral) node).getValueAs(DateString.class);
-        return TimestampString.fromMillisSinceEpoch(d.getMillisSinceEpoch());
+        return new TimestampWithTimeZoneString(
+            TimestampString.fromMillisSinceEpoch(
+                d.getMillisSinceEpoch()).toString() + " " + timeZone.getID())
+            .withTimeZone(DateTimeUtils.UTC_ZONE).getLocalTimestampString();
       }
       break;
     case CAST:
@@ -262,11 +278,13 @@ public class DruidDateTimeUtils {
       final RelDataType callType = call.getType();
       final RelDataType operandType = operand.getType();
       if (operand.getKind() == SqlKind.LITERAL
-          && callType.getSqlTypeName() == SqlTypeName.TIMESTAMP
+          && callType.getSqlTypeName() == operandType.getSqlTypeName()
+          && (callType.getSqlTypeName() == SqlTypeName.DATE
+              || callType.getSqlTypeName() == SqlTypeName.TIMESTAMP
+              || callType.getSqlTypeName() == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)
           && callType.isNullable()
-          && operandType.getSqlTypeName() == SqlTypeName.TIMESTAMP
           && !operandType.isNullable()) {
-        return literalValue(operand);
+        return literalValue(operand, timeZone);
       }
     }
     return null;

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
index b12ad9a..b4a069b 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
@@ -512,7 +512,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
       ImmutableBitSet numericCollationIndexes, Integer fetch, Project postProject) {
     final CalciteConnectionConfig config = getConnectionConfig();
     QueryType queryType = QueryType.SELECT;
-    final Translator translator = new Translator(druidTable, rowType);
+    final Translator translator = new Translator(druidTable, rowType, config.timeZone());
     List<String> fieldNames = rowType.getFieldNames();
     Set<String> usedFieldNames = Sets.newHashSet(fieldNames);
 
@@ -564,7 +564,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
               String extractColumnName = SqlValidatorUtil.uniquify(EXTRACT_COLUMN_NAME_PREFIX,
                   usedFieldNames, SqlValidatorUtil.EXPR_SUGGESTER);
               timeExtractionDimensionSpec = TimeExtractionDimensionSpec.makeFullTimeExtract(
-                  extractColumnName);
+                  extractColumnName, config.timeZone());
               dimensions.add(timeExtractionDimensionSpec);
               builder.add(extractColumnName);
               assert timePositionIdx == -1;
@@ -587,7 +587,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
                         + "_" + funcGranularity.value, usedFieldNames,
                     SqlValidatorUtil.EXPR_SUGGESTER);
                 timeExtractionDimensionSpec = TimeExtractionDimensionSpec.makeTimeExtract(
-                    funcGranularity, extractColumnName);
+                    funcGranularity, extractColumnName, config.timeZone());
                 dimensions.add(timeExtractionDimensionSpec);
                 builder.add(extractColumnName);
                 break;
@@ -600,7 +600,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
                       SqlValidatorUtil.EXPR_SUGGESTER);
                   dimensions.add(
                       TimeExtractionDimensionSpec.makeTimeFloor(funcGranularity,
-                          extractColumnName));
+                          extractColumnName, config.timeZone()));
                   finalGranularity = Granularity.ALL;
                   builder.add(extractColumnName);
                 } else {
@@ -632,7 +632,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
             String extractColumnName = SqlValidatorUtil.uniquify(EXTRACT_COLUMN_NAME_PREFIX,
                 usedFieldNames, SqlValidatorUtil.EXPR_SUGGESTER);
             timeExtractionDimensionSpec = TimeExtractionDimensionSpec.makeFullTimeExtract(
-                extractColumnName);
+                extractColumnName, config.timeZone());
             dimensions.add(timeExtractionDimensionSpec);
             builder.add(extractColumnName);
             assert timePositionIdx == -1;
@@ -1083,8 +1083,9 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
     final List<String> metrics = new ArrayList<>();
     final DruidTable druidTable;
     final RelDataType rowType;
+    final String timeZone;
 
-    Translator(DruidTable druidTable, RelDataType rowType) {
+    Translator(DruidTable druidTable, RelDataType rowType, String timeZone) {
       this.druidTable = druidTable;
       this.rowType = rowType;
       for (RelDataTypeField f : rowType.getFieldList()) {
@@ -1096,6 +1097,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
           dimensions.add(fieldName);
         }
       }
+      this.timeZone = timeZone;
     }
 
     protected void clearFieldNameLists() {
@@ -1169,7 +1171,8 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
         // in case no extraction the field will be omitted from the serialization
         ExtractionFunction extractionFunction = null;
         if (granularity != null) {
-          extractionFunction = TimeExtractionFunction.createExtractFromGranularity(granularity);
+          extractionFunction =
+              TimeExtractionFunction.createExtractFromGranularity(granularity, timeZone);
         }
         String dimName = tr(e, posRef);
         if (dimName.equals(DruidConnectionImpl.DEFAULT_RESPONSE_TIMESTAMP_COLUMN)) {
@@ -1279,7 +1282,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
 
     private ColumnMetaData.Rep getPrimitive(RelDataTypeField field) {
       switch (field.getType().getSqlTypeName()) {
-      case TIMESTAMP:
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
         return ColumnMetaData.Rep.JAVA_SQL_TIMESTAMP;
       case BIGINT:
         return ColumnMetaData.Rep.LONG;

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
index 343f03e..562e568 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
@@ -230,8 +230,8 @@ public class DruidRules {
       List<LocalInterval> intervals = null;
       if (!triple.getLeft().isEmpty()) {
         intervals = DruidDateTimeUtils.createInterval(
-            query.getRowType().getFieldList().get(timestampFieldIdx).getType(),
-            RexUtil.composeConjunction(rexBuilder, triple.getLeft(), false));
+            RexUtil.composeConjunction(rexBuilder, triple.getLeft(), false),
+            cluster.getPlanner().getContext().unwrap(CalciteConnectionConfig.class).timeZone());
         if (intervals == null || intervals.isEmpty()) {
           // Case we have an filter with extract that can not be written as interval push down
           triple.getMiddle().addAll(triple.getLeft());
@@ -579,7 +579,7 @@ public class DruidRules {
         case MINUS:
         case DIVIDE:
         case TIMES:
-        case CAST:
+        //case CAST:
           return true;
         default:
           return false;

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTableFactory.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTableFactory.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTableFactory.java
index d636ce8..d34e000 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTableFactory.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTableFactory.java
@@ -59,7 +59,7 @@ public class DruidTableFactory implements TableFactory {
     } else {
       timestampColumnName = DruidTable.DEFAULT_TIMESTAMP_COLUMN;
     }
-    fieldBuilder.put(timestampColumnName, SqlTypeName.TIMESTAMP);
+    fieldBuilder.put(timestampColumnName, SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
     final Object dimensionsRaw = operand.get("dimensions");
     if (dimensionsRaw instanceof List) {
       // noinspection unchecked

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
index 656ee77..7ef19a6 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionDimensionSpec.java
@@ -34,9 +34,10 @@ public class TimeExtractionDimensionSpec extends ExtractionDimensionSpec {
    *
    * @return the time extraction DimensionSpec instance
    */
-  public static TimeExtractionDimensionSpec makeFullTimeExtract(String outputName) {
+  public static TimeExtractionDimensionSpec makeFullTimeExtract(
+      String outputName, String timeZone) {
     return new TimeExtractionDimensionSpec(
-        TimeExtractionFunction.createDefault(), outputName);
+        TimeExtractionFunction.createDefault(timeZone), outputName);
   }
 
   /**
@@ -51,9 +52,9 @@ public class TimeExtractionDimensionSpec extends ExtractionDimensionSpec {
    * is not supported
    */
   public static TimeExtractionDimensionSpec makeTimeExtract(
-      Granularity granularity, String outputName) {
+      Granularity granularity, String outputName, String timeZone) {
     return new TimeExtractionDimensionSpec(
-        TimeExtractionFunction.createExtractFromGranularity(granularity), outputName);
+        TimeExtractionFunction.createExtractFromGranularity(granularity, timeZone), outputName);
   }
 
   /**
@@ -64,8 +65,9 @@ public class TimeExtractionDimensionSpec extends ExtractionDimensionSpec {
    * @return floor time extraction DimensionSpec instance.
    */
   public static TimeExtractionDimensionSpec makeTimeFloor(Granularity granularity,
-      String outputName) {
-    ExtractionFunction fn = TimeExtractionFunction.createFloorFromGranularity(granularity);
+      String outputName, String timeZone) {
+    ExtractionFunction fn =
+        TimeExtractionFunction.createFloorFromGranularity(granularity, timeZone);
     return new TimeExtractionDimensionSpec(fn, outputName);
   }
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
index 22733be..b1f8870 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/TimeExtractionFunction.java
@@ -75,8 +75,8 @@ public class TimeExtractionFunction implements ExtractionFunction {
    *
    * @return the time extraction function
    */
-  public static TimeExtractionFunction createDefault() {
-    return new TimeExtractionFunction(ISO_TIME_FORMAT, null, "UTC", null);
+  public static TimeExtractionFunction createDefault(String timeZone) {
+    return new TimeExtractionFunction(ISO_TIME_FORMAT, null, timeZone, null);
   }
 
   /**
@@ -87,16 +87,18 @@ public class TimeExtractionFunction implements ExtractionFunction {
    * @return the time extraction function corresponding to the granularity input unit
    * {@link TimeExtractionFunction#VALID_TIME_EXTRACT} for supported granularity
    */
-  public static TimeExtractionFunction createExtractFromGranularity(Granularity granularity) {
+  public static TimeExtractionFunction createExtractFromGranularity(
+      Granularity granularity, String timeZone) {
     switch (granularity) {
     case DAY:
-      return new TimeExtractionFunction("d", null, "UTC", Locale.getDefault().toLanguageTag());
+      return new TimeExtractionFunction("d", null, timeZone, Locale.getDefault().toLanguageTag());
     case MONTH:
-      return new TimeExtractionFunction("M", null, "UTC", Locale.getDefault().toLanguageTag());
+      return new TimeExtractionFunction("M", null, timeZone, Locale.getDefault().toLanguageTag());
     case YEAR:
-      return new TimeExtractionFunction("yyyy", null, "UTC", Locale.getDefault().toLanguageTag());
+      return new TimeExtractionFunction("yyyy", null, timeZone,
+          Locale.getDefault().toLanguageTag());
     case WEEK:
-      return new TimeExtractionFunction("w", null, "UTC", Locale.getDefault().toLanguageTag());
+      return new TimeExtractionFunction("w", null, timeZone, Locale.getDefault().toLanguageTag());
     default:
       throw new IllegalArgumentException("Granularity [" + granularity + "] is not supported");
     }
@@ -108,8 +110,9 @@ public class TimeExtractionFunction implements ExtractionFunction {
    * @param granularity granularity to apply to the column
    * @return the time extraction function or null if granularity is not supported
    */
-  public static TimeExtractionFunction createFloorFromGranularity(Granularity granularity) {
-    return new TimeExtractionFunction(ISO_TIME_FORMAT, granularity.value, "UTC", Locale
+  public static TimeExtractionFunction createFloorFromGranularity(
+      Granularity granularity, String timeZone) {
+    return new TimeExtractionFunction(ISO_TIME_FORMAT, granularity.value, timeZone, Locale
         .getDefault().toLanguageTag());
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/test/java/org/apache/calcite/adapter/druid/DruidQueryFilterTest.java
----------------------------------------------------------------------
diff --git a/druid/src/test/java/org/apache/calcite/adapter/druid/DruidQueryFilterTest.java b/druid/src/test/java/org/apache/calcite/adapter/druid/DruidQueryFilterTest.java
index b2e8635..e8e42be 100644
--- a/druid/src/test/java/org/apache/calcite/adapter/druid/DruidQueryFilterTest.java
+++ b/druid/src/test/java/org/apache/calcite/adapter/druid/DruidQueryFilterTest.java
@@ -121,7 +121,7 @@ public class DruidQueryFilterTest {
         .add("dimensionName", varcharType)
         .build();
     final DruidQuery.Translator translatorStringKind =
-        new DruidQuery.Translator(druidTable, varcharRowType);
+        new DruidQuery.Translator(druidTable, varcharRowType, "UTC");
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
----------------------------------------------------------------------
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
index e88aaed..1a0d3d3 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
@@ -235,12 +235,11 @@ public class DruidAdapterIT {
   @Test public void testSelectTimestampColumnNoTables2() {
     // Since columns are not explicitly declared, we use the default time
     // column in the query.
-    final String sql = "select \"__time\"\n"
+    final String sql = "select cast(\"__time\" as timestamp) as \"__time\"\n"
         + "from \"wikiticker\"\n"
         + "limit 1\n";
-    final String explain = "PLAN="
-        + "EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[wiki, wikiticker]], intervals=[[1900-01-01T00:00:00.000/3000-01-01T00:00:00.000]], projects=[[$0]], fetch=[1])\n";
+    final String explain =
+        "DruidQuery(table=[[wiki, wikiticker]], intervals=[[1900-01-01T00:00:00.000/3000-01-01T00:00:00.000]], projects=[[$0]], fetch=[1])\n";
     final String druidQuery = "{'queryType':'select',"
         + "'dataSource':'wikiticker','descending':false,"
         + "'intervals':['1900-01-01T00:00:00.000/3000-01-01T00:00:00.000'],"
@@ -255,12 +254,12 @@ public class DruidAdapterIT {
   @Test public void testSelectTimestampColumnNoTables3() {
     // Since columns are not explicitly declared, we use the default time
     // column in the query.
-    final String sql = "select floor(\"__time\" to DAY) as \"day\", sum(\"added\")\n"
+    final String sql =
+        "select cast(floor(\"__time\" to DAY) as timestamp) as \"day\", sum(\"added\")\n"
         + "from \"wikiticker\"\n"
         + "group by floor(\"__time\" to DAY)";
-    final String explain = "PLAN="
-        + "EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[wiki, wikiticker]], intervals=[[1900-01-01T00:00:00.000/3000-01-01T00:00:00.000]], projects=[[FLOOR($0, FLAG(DAY)), $1]], groups=[{0}], aggs=[[SUM($1)]])\n";
+    final String explain =
+        "DruidQuery(table=[[wiki, wikiticker]], intervals=[[1900-01-01T00:00:00.000/3000-01-01T00:00:00.000]], projects=[[FLOOR($0, FLAG(DAY)), $1]], groups=[{0}], aggs=[[SUM($1)]])\n";
     final String druidQuery = "{'queryType':'timeseries',"
         + "'dataSource':'wikiticker','descending':false,'granularity':'day',"
         + "'aggregations':[{'type':'longSum','name':'EXPR$1','fieldName':'added'}],"
@@ -276,12 +275,12 @@ public class DruidAdapterIT {
     // Since columns are not explicitly declared, we use the default time
     // column in the query.
     final String sql = "select sum(\"added\") as \"s\", \"page\", "
-        + "floor(\"__time\" to DAY) as \"day\"\n"
+        + "cast(floor(\"__time\" to DAY) as timestamp) as \"day\"\n"
         + "from \"wikiticker\"\n"
         + "group by \"page\", floor(\"__time\" to DAY)\n"
         + "order by \"s\" desc";
     final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  BindableProject(s=[$2], page=[$0], day=[$1])\n"
+        + "  BindableProject(s=[$2], page=[$0], day=[CAST($1):TIMESTAMP(0)])\n"
         + "    DruidQuery(table=[[wiki, wikiticker]], "
         + "intervals=[[1900-01-01T00:00:00.000/3000-01-01T00:00:00.000]], projects=[[$17, FLOOR"
         + "($0, FLAG(DAY)), $1]], groups=[{0, 1}], aggs=[[SUM($2)]], sort0=[2], dir0=[DESC])";
@@ -296,7 +295,8 @@ public class DruidAdapterIT {
   }
 
   @Test public void testSkipEmptyBuckets() {
-    final String sql = "select floor(\"__time\" to SECOND) as \"second\", sum(\"added\")\n"
+    final String sql =
+        "select cast(floor(\"__time\" to SECOND) as timestamp) as \"second\", sum(\"added\")\n"
         + "from \"wikiticker\"\n"
         + "where \"page\" = 'Jeremy Corbyn'\n"
         + "group by floor(\"__time\" to SECOND)";
@@ -334,12 +334,10 @@ public class DruidAdapterIT {
    * Druid adapter: Send timestamp literals to Druid as local time, not
    * UTC</a>. */
   @Test public void testFilterTime() {
-    final String sql = "select \"__time\"\n"
+    final String sql = "select cast(\"__time\" as timestamp) as \"__time\"\n"
         + "from \"wikiticker\"\n"
-        + "where \"__time\" < '2015-10-12 00:00:00'";
-    final String explain = "PLAN="
-        + "EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[wiki, wikiticker]], "
+        + "where \"__time\" < '2015-10-12 00:00:00 UTC'";
+    final String explain = "\n    DruidQuery(table=[[wiki, wikiticker]], "
         + "intervals=[[1900-01-01T00:00:00.000/2015-10-12T00:00:00.000]], "
         + "projects=[[$0]])\n";
     final String druidQuery = "{'queryType':'select',"
@@ -357,12 +355,14 @@ public class DruidAdapterIT {
   }
 
   @Test public void testFilterTimeDistinct() {
-    final String sql = "select distinct \"__time\"\n"
+    final String sql = "select CAST(\"c1\" AS timestamp) as \"__time\" from\n"
+        + "(select distinct \"__time\" as \"c1\"\n"
         + "from \"wikiticker\"\n"
-        + "where \"__time\" < '2015-10-12 00:00:00'";
+        + "where \"__time\" < '2015-10-12 00:00:00 UTC')";
     final String explain = "PLAN="
         + "EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[wiki, wikiticker]], "
+        + "  BindableProject(__time=[CAST($0):TIMESTAMP(0)])\n"
+        + "    DruidQuery(table=[[wiki, wikiticker]], "
         + "intervals=[[1900-01-01T00:00:00.000/2015-10-12T00:00:00.000]], "
         + "groups=[{0}], aggs=[[]])\n";
     final String subDruidQuery = "{'queryType':'groupBy','dataSource':'wikiticker',"
@@ -371,10 +371,10 @@ public class DruidAdapterIT {
         + "'extractionFn':{'type':'timeFormat'";
     sql(sql, WIKI_AUTO2)
         .limit(2)
-        .returnsUnordered("__time=2015-09-12 00:46:58",
-            "__time=2015-09-12 00:47:00")
         .explainContains(explain)
-        .queryContains(druidChecker(subDruidQuery));
+        .queryContains(druidChecker(subDruidQuery))
+        .returnsUnordered("__time=2015-09-12 00:46:58",
+            "__time=2015-09-12 00:47:00");
   }
 
   @Test public void testMetadataColumns() throws Exception {
@@ -390,10 +390,11 @@ public class DruidAdapterIT {
                   while (r.next()) {
                     map.put(r.getString("TYPE_NAME"), true);
                   }
+                  System.out.println(map);
                   // 1 timestamp, 2 float measure, 1 int measure, 88 dimensions
                   assertThat(map.keySet().size(), is(4));
                   assertThat(map.values().size(), is(92));
-                  assertThat(map.get("TIMESTAMP(0)").size(), is(1));
+                  assertThat(map.get("TIMESTAMP_WITH_LOCAL_TIME_ZONE(0)").size(), is(1));
                   assertThat(map.get("DOUBLE").size(), is(2));
                   assertThat(map.get("BIGINT").size(), is(1));
                   assertThat(map.get(VARCHAR_TYPE).size(), is(88));
@@ -689,18 +690,14 @@ public class DruidAdapterIT {
    * <p>Before CALCITE-1578 was fixed, this would use a "topN" query but return
    * the wrong results. */
   @Test public void testGroupByDaySortDescLimit() {
-    final String sql = "select \"brand_name\", floor(\"timestamp\" to DAY) as d,"
+    final String sql = "select \"brand_name\","
+        + " cast(floor(\"timestamp\" to DAY) as timestamp) as d,"
         + " sum(\"unit_sales\") as s\n"
         + "from \"foodmart\"\n"
         + "group by \"brand_name\", floor(\"timestamp\" to DAY)\n"
         + "order by s desc limit 30";
-    final String druidQuery = "{'queryType':'groupBy','dataSource':'foodmart',"
-        + "'granularity':'day','dimensions':[{'type':'default','dimension':'brand_name'}],"
-        + "'limitSpec':{'type':'default'},"
-        + "'aggregations':[{'type':'longSum','name':'S','fieldName':'unit_sales'}],"
-        + "'intervals':['1900-01-09T00:00:00.000/2992-01-10T00:00:00.000']}";
-    final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[foodmart, foodmart]], "
+    final String explain =
+        "    DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[$2, FLOOR"
         + "($0, FLAG(DAY)), $89]], groups=[{0, 1}], aggs=[[SUM($2)]], sort0=[2], dir0=[DESC], "
         + "fetch=[30])";
@@ -725,7 +722,8 @@ public class DruidAdapterIT {
    * wrongly try to use a {@code limitSpec} to sort and filter. (A "topN" query
    * was not possible because the sort was {@code ASC}.) */
   @Test public void testGroupByDaySortLimit() {
-    final String sql = "select \"brand_name\", floor(\"timestamp\" to DAY) as d,"
+    final String sql = "select \"brand_name\","
+        + " cast(floor(\"timestamp\" to DAY) as timestamp) as d,"
         + " sum(\"unit_sales\") as s\n"
         + "from \"foodmart\"\n"
         + "group by \"brand_name\", floor(\"timestamp\" to DAY)\n"
@@ -739,8 +737,7 @@ public class DruidAdapterIT {
         + "'dimensionOrder':'numeric'}]},'aggregations':[{'type':'longSum',"
         + "'name':'S','fieldName':'unit_sales'}],"
         + "'intervals':['1900-01-09T00:00:00.000/2992-01-10T00:00:00.000']}";
-    final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[foodmart, foodmart]], "
+    final String explain = "DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[$2, FLOOR"
         + "($0, FLAG(DAY)), $89]], groups=[{0, 1}], aggs=[[SUM($2)]], sort0=[2], dir0=[DESC], "
         + "fetch=[30])";
@@ -757,7 +754,8 @@ public class DruidAdapterIT {
    * <a href="https://issues.apache.org/jira/browse/CALCITE-1580">[CALCITE-1580]
    * Druid adapter: Wrong semantics for ordering within groupBy queries</a>. */
   @Test public void testGroupByDaySortDimension() {
-    final String sql = "select \"brand_name\", floor(\"timestamp\" to DAY) as d,"
+    final String sql =
+        "select \"brand_name\", cast(floor(\"timestamp\" to DAY) as timestamp) as d,"
         + " sum(\"unit_sales\") as s\n"
         + "from \"foodmart\"\n"
         + "group by \"brand_name\", floor(\"timestamp\" to DAY)\n"
@@ -766,8 +764,7 @@ public class DruidAdapterIT {
         + "'granularity':'all','dimensions':[{'type':'default',"
         + "'dimension':'brand_name'},{'type':'extraction','dimension':'__time',"
         + "'outputName':'floor_day','extractionFn':{'type':'timeFormat'";
-    final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[foodmart, foodmart]], "
+    final String explain = "    DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[$2, FLOOR"
         + "($0, FLAG(DAY)), $89]], groups=[{0, 1}], aggs=[[SUM($2)]], sort0=[0], dir0=[ASC])";
     sql(sql)
@@ -790,8 +787,7 @@ public class DruidAdapterIT {
         + "'filter':{'type':'and','fields':["
         + "{'type':'bound','dimension':'product_id','lower':'1500','lowerStrict':false,'ordering':'lexicographic'},"
         + "{'type':'bound','dimension':'product_id','upper':'1502','upperStrict':false,'ordering':'lexicographic'}]},"
-        + "'dimensions':['product_name','state_province','product_id'],"
-        + "'metrics':[],'granularity':'all',"
+        + "'dimensions':['product_name','state_province','product_id'],'metrics':[],'granularity':'all',"
         + "'pagingSpec':{'threshold':16384,'fromNext':true},'context':{'druid.query.fetch':false}}";
     sql(sql)
         .limit(4)
@@ -819,13 +815,13 @@ public class DruidAdapterIT {
     final String sql = "select \"product_name\" from \"foodmart\"\n"
         + "where \"product_id\" BETWEEN 1500 AND 1502\n"
         + "order by \"state_province\" desc, \"product_id\"";
-    final String druidQuery = "{'queryType':'select','dataSource':'foodmart','descending':false,"
-            + "'intervals':['1900-01-09T00:00:00.000/2992-01-10T00:00:00.000'],'filter':{'type':"
-            + "'and','fields':[{'type':'bound','dimension':'product_id','lower':'1500',"
-            + "'lowerStrict':false,'ordering':'numeric'},{'type':'bound','dimension':'product_id',"
-            + "'upper':'1502','upperStrict':false,'ordering':'numeric'}]},'dimensions':"
-            + "['product_name','state_province','product_id'],'metrics':[],'granularity':'all','pagingSpec':"
-            + "{'threshold':16384,'fromNext':true},'context':{'druid.query.fetch':false}}";
+    final String druidQuery = "{'queryType':'select','dataSource':'foodmart',"
+        + "'descending':false,'intervals':['1900-01-09T00:00:00.000/2992-01-10T00:00:00.000'],"
+        + "'filter':{'type':'and','fields':["
+        + "{'type':'bound','dimension':'product_id','lower':'1500','lowerStrict':false,'ordering':'numeric'},"
+        + "{'type':'bound','dimension':'product_id','upper':'1502','upperStrict':false,'ordering':'numeric'}]},"
+        + "'dimensions':['product_name','state_province','product_id'],'metrics':[],'granularity':'all',"
+        + "'pagingSpec':{'threshold':16384,'fromNext':true},'context':{'druid.query.fetch':false}}";
     sql(sql)
         .limit(4)
         .returns(
@@ -854,8 +850,7 @@ public class DruidAdapterIT {
     final String druidQuery = "{'queryType':'select','dataSource':'foodmart',"
         + "'descending':false,'intervals':['1900-01-09T00:00:00.000/2992-01-10T00:00:00.000'],"
         + "'filter':{'type':'selector','dimension':'product_id','value':'-1'},"
-        + "'dimensions':['product_name'],"
-        + "'metrics':[],'granularity':'all',"
+        + "'dimensions':['product_name'],'metrics':[],'granularity':'all',"
         + "'pagingSpec':{'threshold':16384,'fromNext':true},'context':{'druid.query.fetch':false}}";
     sql(sql)
         .limit(4)
@@ -871,8 +866,7 @@ public class DruidAdapterIT {
         + "order by \"state_province\" desc, \"product_id\"";
     final String druidQuery = "{'queryType':'select','dataSource':'foodmart',"
         + "'descending':false,'intervals':['1900-01-09T00:00:00.000/2992-01-10T00:00:00.000'],"
-        + "'dimensions':['product_id','product_name','state_province'],"
-        + "'metrics':[],'granularity':'all',"
+        + "'dimensions':['product_id','product_name','state_province'],'metrics':[],'granularity':'all',"
         + "'pagingSpec':{'threshold':16384,'fromNext':true},'context':{'druid.query.fetch':false}}";
     sql(sql)
         .limit(4)
@@ -959,7 +953,8 @@ public class DruidAdapterIT {
    * "topN" because we have a global limit, and that requires
    * {@code granularity: all}. */
   @Test public void testGroupByTimeAndOneColumnNotProjectedWithLimit() {
-    final String sql = "select count(*) as \"c\", floor(\"timestamp\" to MONTH) as \"month\"\n"
+    final String sql = "select count(*) as \"c\","
+        + " cast(floor(\"timestamp\" to MONTH) as timestamp) as \"month\"\n"
         + "from \"foodmart\"\n"
         + "group by floor(\"timestamp\" to MONTH), \"state_province\"\n"
         + "order by \"c\" desc limit 3";
@@ -972,7 +967,7 @@ public class DruidAdapterIT {
 
   @Test public void testGroupByTimeAndOneMetricNotProjected() {
     final String sql =
-            "select count(*) as \"c\", floor(\"timestamp\" to MONTH) as \"month\", floor"
+            "select count(*) as \"c\", cast(floor(\"timestamp\" to MONTH) as timestamp) as \"month\", floor"
                     + "(\"store_sales\") as sales\n"
                     + "from \"foodmart\"\n"
                     + "group by floor(\"timestamp\" to MONTH), \"state_province\", floor"
@@ -986,7 +981,7 @@ public class DruidAdapterIT {
 
   @Test public void testGroupByTimeAndOneColumnNotProjected() {
     final String sql = "select count(*) as \"c\",\n"
-        + "  floor(\"timestamp\" to MONTH) as \"month\"\n"
+        + "  cast(floor(\"timestamp\" to MONTH) as timestamp) as \"month\"\n"
         + "from \"foodmart\"\n"
         + "group by floor(\"timestamp\" to MONTH), \"state_province\"\n"
         + "having count(*) > 3500";
@@ -1066,46 +1061,45 @@ public class DruidAdapterIT {
    * <a href="https://issues.apache.org/jira/browse/CALCITE-1577">[CALCITE-1577]
    * Druid adapter: Incorrect result - limit on timestamp disappears</a>. */
   @Test public void testGroupByMonthGranularitySort() {
-    final String sql = "select floor(\"timestamp\" to MONTH) as m,\n"
-        + " sum(\"unit_sales\") as s,\n"
+    final String sql = "select sum(\"unit_sales\") as s,\n"
         + " count(\"store_sqft\") as c\n"
         + "from \"foodmart\"\n"
         + "group by floor(\"timestamp\" to MONTH)\n"
         + "order by floor(\"timestamp\" to MONTH) ASC";
     final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  BindableSort(sort0=[$0], dir0=[ASC])\n"
-        + "    BindableAggregate(group=[{0}], S=[SUM($1)], C=[COUNT($2)])\n"
-        + "      BindableProject(M=[FLOOR($0, FLAG(MONTH))], unit_sales=[$2], store_sqft=[$1])\n"
-        + "        DruidQuery(table=[[foodmart, foodmart]], "
+        + "  BindableSort(sort0=[$2], dir0=[ASC])\n"
+        + "    BindableProject(S=[$1], C=[$2], EXPR$2=[$0])\n"
+        + "      BindableAggregate(group=[{0}], S=[SUM($1)], C=[COUNT($2)])\n"
+        + "        BindableProject($f0=[FLOOR($0, FLAG(MONTH))], unit_sales=[$2], store_sqft=[$1])\n"
+        + "          DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[$0, $71, $89]])";
     sql(sql)
-        .returnsOrdered("M=1997-01-01 00:00:00; S=21628; C=5957",
-            "M=1997-02-01 00:00:00; S=20957; C=5842",
-            "M=1997-03-01 00:00:00; S=23706; C=6528",
-            "M=1997-04-01 00:00:00; S=20179; C=5523",
-            "M=1997-05-01 00:00:00; S=21081; C=5793",
-            "M=1997-06-01 00:00:00; S=21350; C=5863",
-            "M=1997-07-01 00:00:00; S=23763; C=6762",
-            "M=1997-08-01 00:00:00; S=21697; C=5915",
-            "M=1997-09-01 00:00:00; S=20388; C=5591",
-            "M=1997-10-01 00:00:00; S=19958; C=5606",
-            "M=1997-11-01 00:00:00; S=25270; C=7026",
-            "M=1997-12-01 00:00:00; S=26796; C=7338")
-        .explainContains(explain);
+        .explainContains(explain)
+        .returnsOrdered("S=21628; C=5957",
+                "S=20957; C=5842",
+                "S=23706; C=6528",
+                "S=20179; C=5523",
+                "S=21081; C=5793",
+                "S=21350; C=5863",
+                "S=23763; C=6762",
+                "S=21697; C=5915",
+                "S=20388; C=5591",
+                "S=19958; C=5606",
+                "S=25270; C=7026",
+                "S=26796; C=7338");
   }
 
   @Test public void testGroupByMonthGranularitySortLimit() {
-    final String sql = "select floor(\"timestamp\" to MONTH) as m,\n"
+    final String sql = "select cast(floor(\"timestamp\" to MONTH) as timestamp) as m,\n"
         + " sum(\"unit_sales\") as s,\n"
         + " count(\"store_sqft\") as c\n"
         + "from \"foodmart\"\n"
         + "group by floor(\"timestamp\" to MONTH)\n"
         + "order by floor(\"timestamp\" to MONTH) limit 3";
-    final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  BindableSort(sort0=[$0], dir0=[ASC], fetch=[3])\n"
-        + "    BindableAggregate(group=[{0}], S=[SUM($1)], C=[COUNT($2)])\n"
-        + "      BindableProject(M=[FLOOR($0, FLAG(MONTH))], unit_sales=[$2], store_sqft=[$1])\n"
-        + "        DruidQuery(table=[[foodmart, foodmart]], "
+    final String explain = "BindableSort(sort0=[$0], dir0=[ASC], fetch=[3])\n"
+        + "      BindableAggregate(group=[{0}], S=[SUM($1)], C=[COUNT($2)])\n"
+        + "        BindableProject($f0=[FLOOR($0, FLAG(MONTH))], unit_sales=[$2], store_sqft=[$1])\n"
+        + "          DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[$0, $71, $89]])";
     sql(sql)
         .returnsOrdered("M=1997-01-01 00:00:00; S=21628; C=5957",
@@ -1122,16 +1116,16 @@ public class DruidAdapterIT {
     String druidQuery = "{'queryType':'select','dataSource':'foodmart'";
     sql(sql)
         .limit(3)
-        .returnsUnordered("S=1244; C=391", "S=550; C=112", "S=580; C=171")
-        .queryContains(druidChecker(druidQuery));
+        .queryContains(druidChecker(druidQuery))
+        .returnsUnordered("S=1244; C=391", "S=550; C=112", "S=580; C=171");
   }
 
   @Test public void testGroupByMonthGranularityFiltered() {
     final String sql = "select sum(\"unit_sales\") as s,\n"
         + " count(\"store_sqft\") as c\n"
         + "from \"foodmart\"\n"
-        + "where \"timestamp\" >= '1996-01-01 00:00:00' and "
-        + " \"timestamp\" < '1998-01-01 00:00:00'\n"
+        + "where \"timestamp\" >= '1996-01-01 00:00:00 UTC' and "
+        + " \"timestamp\" < '1998-01-01 00:00:00 UTC'\n"
         + "group by floor(\"timestamp\" to MONTH)";
     String druidQuery = "{'queryType':'select','dataSource':'foodmart'";
     sql(sql)
@@ -1179,8 +1173,8 @@ public class DruidAdapterIT {
         + "max(\"unit_sales\") as m,\n"
         + "\"state_province\" as p\n"
         + "from \"foodmart\"\n"
-        + "where \"timestamp\" >= '1997-01-01 00:00:00' and "
-        + " \"timestamp\" < '1997-09-01 00:00:00'\n"
+        + "where \"timestamp\" >= '1997-01-01 00:00:00 UTC' and "
+        + " \"timestamp\" < '1997-09-01 00:00:00 UTC'\n"
         + "group by \"state_province\", floor(\"timestamp\" to DAY)\n"
         + "order by s desc limit 6";
     final String explain = "PLAN=EnumerableInterpreter\n"
@@ -1383,12 +1377,11 @@ public class DruidAdapterIT {
         + "from \"foodmart\"\n"
         + "where extract(year from \"timestamp\") = 1997\n"
         + "and extract(month from \"timestamp\") in (4, 6)\n";
-    final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[foodmart, foodmart]], "
+    final String explain = "DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], filter=[AND(="
-        + "(EXTRACT_DATE(FLAG(YEAR), /INT(Reinterpret($0), 86400000)), 1997), OR(=(EXTRACT_DATE"
-        + "(FLAG(MONTH), /INT(Reinterpret($0), 86400000)), 4), =(EXTRACT_DATE(FLAG(MONTH), /INT"
-        + "(Reinterpret($0), 86400000)), 6)))], groups=[{}], aggs=[[COUNT()]])";
+        + "(EXTRACT_DATE(FLAG(YEAR), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), 1997), OR(=(EXTRACT_DATE"
+        + "(FLAG(MONTH), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), 4), =(EXTRACT_DATE(FLAG(MONTH), "
+        + "/INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), 6)))], groups=[{}], aggs=[[COUNT()]])";
     sql(sql)
         .explainContains(explain)
         .returnsUnordered("C=13500");
@@ -1430,17 +1423,17 @@ public class DruidAdapterIT {
   @Test public void testFieldBasedCostColumnPruning() {
     // A query where filter cannot be pushed to Druid but
     // the project can still be pushed in order to prune extra columns.
-    String sql = "select \"countryName\", floor(\"time\" to DAY),\n"
+    String sql = "select \"countryName\", floor(CAST(\"time\" AS TIMESTAMP) to DAY),\n"
         + "  cast(count(*) as integer) as c\n"
         + "from \"wiki\"\n"
-        + "where floor(\"time\" to DAY) >= '1997-01-01 00:00:00'\n"
-        + "and floor(\"time\" to DAY) < '1997-09-01 00:00:00'\n"
-        + "group by \"countryName\", floor(\"time\" TO DAY)\n"
+        + "where floor(\"time\" to DAY) >= '1997-01-01 00:00:00 UTC'\n"
+        + "and floor(\"time\" to DAY) < '1997-09-01 00:00:00 UTC'\n"
+        + "group by \"countryName\", floor(CAST(\"time\" AS TIMESTAMP) TO DAY)\n"
         + "order by c limit 5";
     String plan = "BindableProject(countryName=[$0], EXPR$1=[$1], C=[CAST($2):INTEGER NOT NULL])\n"
         + "    BindableSort(sort0=[$2], dir0=[ASC], fetch=[5])\n"
         + "      BindableAggregate(group=[{0, 1}], agg#0=[COUNT()])\n"
-        + "        BindableProject(countryName=[$1], EXPR$1=[FLOOR($0, FLAG(DAY))])\n"
+        + "        BindableProject(countryName=[$1], EXPR$1=[FLOOR(CAST($0):TIMESTAMP(0), FLAG(DAY))])\n"
         + "          BindableFilter(condition=[AND(>=(FLOOR($0, FLAG(DAY)), 1997-01-01 00:00:00), <(FLOOR($0, FLAG(DAY)), 1997-09-01 00:00:00))])\n"
         + "            DruidQuery(table=[[wiki, wiki]], intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[$0, $5]])";
     // NOTE: Druid query only has countryName as the dimension
@@ -1459,10 +1452,11 @@ public class DruidAdapterIT {
   }
 
   @Test public void testGroupByMetricAndExtractTime() {
-    final String sql = "SELECT count(*), floor(\"timestamp\" to DAY), \"store_sales\" "
-            + "FROM \"foodmart\"\n"
-            + "GROUP BY \"store_sales\", floor(\"timestamp\" to DAY)\n ORDER BY \"store_sales\" DESC\n"
-            + "LIMIT 10\n";
+    final String sql =
+        "SELECT count(*), cast(floor(\"timestamp\" to DAY) as timestamp), \"store_sales\" "
+        + "FROM \"foodmart\"\n"
+        + "GROUP BY \"store_sales\", floor(\"timestamp\" to DAY)\n ORDER BY \"store_sales\" DESC\n"
+        + "LIMIT 10\n";
     sql(sql).queryContains(druidChecker("{\"queryType\":\"select\""));
   }
 
@@ -1475,10 +1469,11 @@ public class DruidAdapterIT {
   }
 
   @Test public void testPushAggregateOnTime() {
-    String sql = "select \"product_id\", \"timestamp\" as \"time\" from \"foodmart\" "
+    String sql = "select \"product_id\", cast(\"timestamp\" as timestamp) as \"time\" "
+        + "from \"foodmart\" "
         + "where \"product_id\" = 1016 "
-        + "and \"timestamp\" < cast('1997-01-03' as timestamp) "
-        + "and \"timestamp\" > cast('1990-01-01' as timestamp) "
+        + "and \"timestamp\" < '1997-01-03 00:00:00 UTC' "
+        + "and \"timestamp\" > '1990-01-01 00:00:00 UTC' "
         + "group by \"timestamp\", \"product_id\" ";
     String druidQuery = "{'queryType':'groupBy','dataSource':'foodmart',"
         + "'granularity':'all','dimensions':[{'type':'extraction',"
@@ -1592,9 +1587,9 @@ public class DruidAdapterIT {
         .explainContains("PLAN=EnumerableInterpreter\n"
             + "  DruidQuery(table=[[foodmart, foodmart]], "
             + "intervals=[[1997-01-01T00:00:00.001/1997-01-20T00:00:00.000]], filter=[=($1, 1016)"
-            + "], projects=[[EXTRACT_DATE(FLAG(DAY), /INT(Reinterpret($0), 86400000)), "
-            + "EXTRACT_DATE(FLAG(MONTH), /INT(Reinterpret($0), 86400000)), EXTRACT_DATE(FLAG"
-            + "(YEAR), /INT(Reinterpret($0), 86400000)), $1]], groups=[{0, 1, 2, 3}], aggs=[[]])\n")
+            + "], projects=[[EXTRACT_DATE(FLAG(DAY), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), "
+            + "EXTRACT_DATE(FLAG(MONTH), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), EXTRACT_DATE(FLAG"
+            + "(YEAR), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), $1]], groups=[{0, 1, 2, 3}], aggs=[[]])\n")
         .returnsUnordered("day=2; month=1; year=1997; product_id=1016",
             "day=10; month=1; year=1997; product_id=1016",
             "day=13; month=1; year=1997; product_id=1016",
@@ -1626,9 +1621,9 @@ public class DruidAdapterIT {
         .explainContains("PLAN=EnumerableInterpreter\n"
             + "  DruidQuery(table=[[foodmart, foodmart]], "
             + "intervals=[[1997-01-01T00:00:00.001/1997-01-20T00:00:00.000]], filter=[=($1, 1016)"
-            + "], projects=[[EXTRACT_DATE(FLAG(DAY), /INT(Reinterpret($0), 86400000)), "
-            + "EXTRACT_DATE(FLAG(MONTH), /INT(Reinterpret($0), 86400000)), EXTRACT_DATE(FLAG"
-            + "(YEAR), /INT(Reinterpret($0), 86400000)), $1]], groups=[{0, 1, 2, 3}], aggs=[[]])\n")
+            + "], projects=[[EXTRACT_DATE(FLAG(DAY), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), "
+            + "EXTRACT_DATE(FLAG(MONTH), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), EXTRACT_DATE(FLAG"
+            + "(YEAR), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), $1]], groups=[{0, 1, 2, 3}], aggs=[[]])\n")
         .returnsUnordered("EXPR$0=2; EXPR$1=1; EXPR$2=1997; product_id=1016",
             "EXPR$0=10; EXPR$1=1; EXPR$2=1997; product_id=1016",
             "EXPR$0=13; EXPR$1=1; EXPR$2=1997; product_id=1016",
@@ -1653,7 +1648,7 @@ public class DruidAdapterIT {
         .explainContains("PLAN=EnumerableInterpreter\n"
             + "  DruidQuery(table=[[foodmart, foodmart]], "
             + "intervals=[[1997-01-01T00:00:00.001/1997-01-20T00:00:00.000]], filter=[=($1, 1016)], "
-            + "projects=[[EXTRACT_DATE(FLAG(DAY), /INT(Reinterpret($0), 86400000)), $1]], "
+            + "projects=[[EXTRACT_DATE(FLAG(DAY), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), $1]], "
             + "groups=[{0, 1}], aggs=[[]])\n")
         .returnsUnordered("EXPR$0=2; dayOfMonth=1016", "EXPR$0=10; dayOfMonth=1016",
             "EXPR$0=13; dayOfMonth=1016", "EXPR$0=16; dayOfMonth=1016");
@@ -1682,7 +1677,7 @@ public class DruidAdapterIT {
             + "  DruidQuery(table=[[foodmart, foodmart]], "
             + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], filter=[AND(>=(CAST"
             + "($11):BIGINT, 8), <=(CAST($11):BIGINT, 10), <(CAST($10):BIGINT, 15), =(EXTRACT_DATE"
-            + "(FLAG(YEAR), /INT(Reinterpret($0), 86400000)), 1997))], groups=[{}], "
+            + "(FLAG(YEAR), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), 1997))], groups=[{}], "
             + "aggs=[[SUM($90)]])")
         .queryContains(druidChecker(druidQuery))
         .returnsUnordered("EXPR$0=75364.09998679161");
@@ -1830,33 +1825,33 @@ public class DruidAdapterIT {
         .explainContains("PLAN=EnumerableInterpreter\n"
             + "  DruidQuery(table=[[foodmart, foodmart]], "
             + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], filter=[>=(CAST($1)"
-            + ":BIGINT, 1558)], projects=[[EXTRACT_DATE(FLAG(MONTH), /INT(Reinterpret($0), "
+            + ":BIGINT, 1558)], projects=[[EXTRACT_DATE(FLAG(MONTH), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), "
             + "86400000)), $1, $89]], groups=[{0, 1}], aggs=[[SUM($2)]], sort0=[0], sort1=[2], "
             + "sort2=[1], dir0=[ASC], dir1=[ASC], dir2=[ASC])");
   }
 
 
   @Test public void testGroupByFloorTimeWithoutLimit() {
-    final String sql = "select  floor(\"timestamp\" to MONTH) as \"month\"\n"
+    final String sql = "select cast(floor(\"timestamp\" to MONTH) as timestamp) as \"month\"\n"
         + "from \"foodmart\"\n"
         + "group by floor(\"timestamp\" to MONTH)\n"
         + "order by \"month\" DESC";
     sql(sql)
-        .explainContains("PLAN=EnumerableInterpreter\n"
-        + "  DruidQuery(table=[[foodmart, foodmart]], "
+        .explainContains("DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[FLOOR($0, "
         + "FLAG(MONTH))]], groups=[{0}], aggs=[[]], sort0=[0], dir0=[DESC])")
         .queryContains(druidChecker("'queryType':'timeseries'", "'descending':true"));
   }
 
   @Test public void testGroupByFloorTimeWithLimit() {
-    final String sql = "select  floor(\"timestamp\" to MONTH) as \"floor_month\"\n"
+    final String sql =
+        "select cast(floor(\"timestamp\" to MONTH) as timestamp) as \"floor_month\"\n"
         + "from \"foodmart\"\n"
         + "group by floor(\"timestamp\" to MONTH)\n"
         + "order by \"floor_month\" DESC LIMIT 3";
-    final String explain = "PLAN=EnumerableInterpreter\n"
-        + "  BindableSort(sort0=[$0], dir0=[DESC], fetch=[3])\n"
-        + "    DruidQuery(table=[[foodmart, foodmart]], "
+    final String explain =
+        "    BindableSort(sort0=[$0], dir0=[DESC], fetch=[3])\n"
+        + "      DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], "
         + "projects=[[FLOOR($0, FLAG(MONTH))]], groups=[{0}], aggs=[[]], "
         + "sort0=[0], dir0=[DESC])";
@@ -1876,8 +1871,8 @@ public class DruidAdapterIT {
     final String expectedPlan = "PLAN=EnumerableInterpreter\n"
         + "  DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], filter=[>=(CAST($1)"
-        + ":BIGINT, 1558)], projects=[[EXTRACT_DATE(FLAG(YEAR), /INT(Reinterpret($0), 86400000)),"
-        + " EXTRACT_DATE(FLAG(MONTH), /INT(Reinterpret($0), 86400000)), $1, $89]], groups=[{0, 1,"
+        + ":BIGINT, 1558)], projects=[[EXTRACT_DATE(FLAG(YEAR), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)),"
+        + " EXTRACT_DATE(FLAG(MONTH), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), $1, $89]], groups=[{0, 1,"
         + " 2}], aggs=[[SUM($3)]], sort0=[0], sort1=[1], sort2=[3], sort3=[2], dir0=[DESC], "
         + "dir1=[ASC], dir2=[DESC], dir3=[ASC], fetch=[3])";
     final String expectedDruidQuery = "{'queryType':'groupBy','dataSource':'foodmart',"
@@ -1912,8 +1907,8 @@ public class DruidAdapterIT {
     final String expectedPlan = "PLAN=EnumerableInterpreter\n"
         + "  DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], filter=[>=(CAST($1)"
-        + ":BIGINT, 1558)], projects=[[EXTRACT_DATE(FLAG(YEAR), /INT(Reinterpret($0), 86400000)),"
-        + " EXTRACT_DATE(FLAG(MONTH), /INT(Reinterpret($0), 86400000)), $1, $89]], groups=[{0, 1,"
+        + ":BIGINT, 1558)], projects=[[EXTRACT_DATE(FLAG(YEAR), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)),"
+        + " EXTRACT_DATE(FLAG(MONTH), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000)), $1, $89]], groups=[{0, 1,"
         + " 2}], aggs=[[SUM($3)]], sort0=[3], sort1=[1], sort2=[2], dir0=[DESC], dir1=[DESC], "
         + "dir2=[ASC], fetch=[3])";
     final String expectedDruidQuery = "{'queryType':'groupBy','dataSource':'foodmart',"
@@ -1939,12 +1934,13 @@ public class DruidAdapterIT {
   }
 
   @Test public void testGroupByTimeSortOverMetrics() {
-    final String sqlQuery = "SELECT count(*) as c , SUM(\"unit_sales\") as s, floor(\"timestamp\""
-        + " to month) FROM \"foodmart\" group by floor(\"timestamp\" to month) order by s DESC";
+    final String sqlQuery = "SELECT count(*) as c , SUM(\"unit_sales\") as s,"
+        + " cast(floor(\"timestamp\" to month) as timestamp)"
+        + " FROM \"foodmart\" group by floor(\"timestamp\" to month) order by s DESC";
     sql(sqlQuery)
         .explainContains("PLAN=EnumerableInterpreter\n"
         + "  BindableSort(sort0=[$1], dir0=[DESC])\n"
-        + "    BindableProject(C=[$1], S=[$2], EXPR$2=[$0])\n"
+        + "    BindableProject(C=[$1], S=[$2], EXPR$2=[CAST($0):TIMESTAMP(0)])\n"
         + "      DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], projects=[[FLOOR($0, "
         + "FLAG(MONTH)), $89]], groups=[{0}], aggs=[[COUNT(), SUM($1)]])")
@@ -1964,8 +1960,8 @@ public class DruidAdapterIT {
   }
 
   @Test public void testNumericOrderingOfOrderByOperatorFullTime() {
-    final String sqlQuery = "SELECT \"timestamp\", count(*) as c, SUM(\"unit_sales\")  "
-        + "as s FROM "
+    final String sqlQuery = "SELECT cast(\"timestamp\" as timestamp) as \"timestamp\","
+        + " count(*) as c, SUM(\"unit_sales\") as s FROM "
         + "\"foodmart\" group by \"timestamp\" order by \"timestamp\" DESC, c DESC, s LIMIT 5";
     final String druidSubQuery = "'limitSpec':{'type':'default','limit':5,"
         + "'columns':[{'dimension':'extract','direction':'descending',"
@@ -2044,7 +2040,7 @@ public class DruidAdapterIT {
         + "\"product_id\" = 1558 group by extract(CENTURY from \"timestamp\")";
     final String plan = "PLAN=EnumerableInterpreter\n"
         + "  BindableAggregate(group=[{0}])\n"
-        + "    BindableProject(EXPR$0=[EXTRACT_DATE(FLAG(CENTURY), /INT(Reinterpret($0), 86400000))])\n"
+        + "    BindableProject(EXPR$0=[EXTRACT_DATE(FLAG(CENTURY), /INT(CAST(Reinterpret($0)):TIMESTAMP(0), 86400000))])\n"
         + "      DruidQuery(table=[[foodmart, foodmart]], "
         + "intervals=[[1900-01-09T00:00:00.000/2992-01-10T00:00:00.000]], filter=[=($1, 1558)], "
         + "projects=[[$0]])";
@@ -2491,7 +2487,7 @@ public class DruidAdapterIT {
   @Test public void testOrderByOnMetricsInSelectDruidQuery() {
     final String sqlQuery = "select \"store_sales\" as a, \"store_cost\" as b, \"store_sales\" - "
             + "\"store_cost\" as c from \"foodmart\" where \"timestamp\" "
-            + ">= '1997-01-01 00:00:00' and \"timestamp\" < '1997-09-01 00:00:00' order by c "
+            + ">= '1997-01-01 00:00:00 UTC' and \"timestamp\" < '1997-09-01 00:00:00 UTC' order by c "
             + "limit 5";
     String postAggString = "'queryType':'select'";
     final String plan = "PLAN=EnumerableInterpreter\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/druid/src/test/java/org/apache/calcite/test/DruidDateRangeRulesTest.java
----------------------------------------------------------------------
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidDateRangeRulesTest.java b/druid/src/test/java/org/apache/calcite/test/DruidDateRangeRulesTest.java
index 74ce10c..0e1948d 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidDateRangeRulesTest.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidDateRangeRulesTest.java
@@ -158,7 +158,7 @@ public class DruidDateRangeRulesTest {
               operandRanges));
     }
     final List<LocalInterval> intervals =
-        DruidDateTimeUtils.createInterval(f.timeStampDataType, e);
+        DruidDateTimeUtils.createInterval(e, "UTC");
     assertThat(intervals, notNullValue());
     assertThat(intervals.toString(), intervalMatcher);
   }
@@ -178,7 +178,7 @@ public class DruidDateRangeRulesTest {
     }
     final RexNode e2 = f.simplify.simplify(e);
     List<LocalInterval> intervals =
-        DruidDateTimeUtils.createInterval(f.timeStampDataType, e2);
+        DruidDateTimeUtils.createInterval(e2, "UTC");
     if (intervals == null) {
       throw new AssertionError("null interval");
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 209d39a..2bc70fd 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -978,6 +978,7 @@ name will have been converted to upper case also.
 | DATE        | Date                      | Example: DATE '1969-07-20'
 | TIME        | Time of day               | Example: TIME '20:17:40'
 | TIMESTAMP [ WITHOUT TIME ZONE ] | Date and time | Example: TIMESTAMP '1969-07-20 20:17:40'
+| TIMESTAMP WITH LOCAL TIME ZONE | Date and time with local time zone | Example: TIMESTAMP '1969-07-20 20:17:40 America/Los Angeles'
 | TIMESTAMP WITH TIME ZONE | Date and time with time zone | Example: TIMESTAMP '1969-07-20 20:17:40 America/Los Angeles'
 | INTERVAL timeUnit [ TO timeUnit ] | Date time interval | Examples: INTERVAL '1-5' YEAR TO MONTH, INTERVAL '45' DAY, INTERVAL '1 2:34:56.789' DAY TO SECOND
 | GEOMETRY | Geometry | Examples: ST_GeomFromText('POINT (30 10)')
@@ -991,9 +992,11 @@ timeUnit:
 
 Note:
 
-* DATE, TIME and TIMESTAMP have no time zone. There is not even an implicit
-  time zone, such as UTC (as in Java) or the local time zone. It is left to
-  the user or application to supply a time zone.
+* DATE, TIME and TIMESTAMP have no time zone. For those types, there is not
+  even an implicit time zone, such as UTC (as in Java) or the local time zone.
+  It is left to the user or application to supply a time zone. In turn,
+  TIMESTAMP WITH LOCAL TIME ZONE does not store the time zone internally, but
+  it will rely on the supplied time zone to provide correct semantics.
 * GEOMETRY is allowed only in certain
   [conformance levels]({{ site.apiRoot }}/org/apache/calcite/sql/validate/SqlConformance.html#allowGeometry--).
 


[2/2] calcite git commit: [CALCITE-1947] Add time/timestamp with local time zone types to optimizer

Posted by jc...@apache.org.
[CALCITE-1947] Add time/timestamp with local time zone types to optimizer

Close apache/calcite#519


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/939c9a62
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/939c9a62
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/939c9a62

Branch: refs/heads/master
Commit: 939c9a62b4905d2cfffcb4adaefd76a809aa3520
Parents: f10950b
Author: Jesus Camacho Rodriguez <jc...@apache.org>
Authored: Mon Aug 14 17:46:42 2017 -0700
Committer: Jesus Camacho Rodriguez <jc...@apache.org>
Committed: Tue Sep 5 15:00:36 2017 -0700

----------------------------------------------------------------------
 .../calcite/adapter/enumerable/RexImpTable.java |  15 +-
 .../adapter/enumerable/RexToLixTranslator.java  | 153 +++++++++++
 .../calcite/jdbc/JavaTypeFactoryImpl.java       |   2 +
 .../apache/calcite/rel/metadata/RelMdSize.java  |   4 +
 .../rel/rules/SortProjectTransposeRule.java     |   3 +-
 .../calcite/rel/type/RelDataTypeSystemImpl.java |   8 +
 .../java/org/apache/calcite/rex/RexBuilder.java |  57 +++++
 .../java/org/apache/calcite/rex/RexLiteral.java |  26 ++
 .../org/apache/calcite/rex/RexSimplify.java     |   1 +
 .../apache/calcite/runtime/SqlFunctions.java    |  98 ++++++++
 .../java/org/apache/calcite/schema/Schemas.java |   4 +-
 .../apache/calcite/sql/SqlJdbcDataTypeName.java |   2 +
 .../sql/type/SqlTypeAssignmentRules.java        |  34 +++
 .../apache/calcite/sql/type/SqlTypeFamily.java  |   6 +-
 .../apache/calcite/sql/type/SqlTypeName.java    |  11 +-
 .../sql2rel/StandardConvertletTable.java        |  26 ++
 .../org/apache/calcite/util/BuiltInMethod.java  |  28 ++-
 .../org/apache/calcite/util/DateString.java     |   2 +-
 .../calcite/util/DateTimeStringUtils.java       |  88 +++++++
 .../org/apache/calcite/util/TimeString.java     |   6 +-
 .../calcite/util/TimeWithTimeZoneString.java    | 188 ++++++++++++++
 .../apache/calcite/util/TimestampString.java    |  52 +---
 .../util/TimestampWithTimeZoneString.java       | 194 ++++++++++++++
 .../calcite/jdbc/CalciteRemoteDriverTest.java   |   2 +-
 .../org/apache/calcite/rex/RexBuilderTest.java  |  73 ++++++
 .../org/apache/calcite/test/CalciteAssert.java  |   3 +
 .../org/apache/calcite/test/RexProgramTest.java | 128 +++++++++-
 .../adapter/druid/DruidConnectionImpl.java      |   2 +-
 .../adapter/druid/DruidDateTimeUtils.java       |  74 +++---
 .../calcite/adapter/druid/DruidQuery.java       |  19 +-
 .../calcite/adapter/druid/DruidRules.java       |   6 +-
 .../adapter/druid/DruidTableFactory.java        |   2 +-
 .../druid/TimeExtractionDimensionSpec.java      |  14 +-
 .../adapter/druid/TimeExtractionFunction.java   |  21 +-
 .../adapter/druid/DruidQueryFilterTest.java     |   2 +-
 .../org/apache/calcite/test/DruidAdapterIT.java | 252 +++++++++----------
 .../calcite/test/DruidDateRangeRulesTest.java   |   4 +-
 site/_docs/reference.md                         |   9 +-
 38 files changed, 1361 insertions(+), 258 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index 0c1f542..0b3aa84 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -1655,7 +1655,14 @@ public class RexImpTable {
       case 2:
         final Type type;
         final Method floorMethod;
+        Expression operand = translatedOperands.get(0);
         switch (call.getType().getSqlTypeName()) {
+        case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+          operand = Expressions.call(
+              BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+              operand,
+              Expressions.call(BuiltInMethod.TIME_ZONE.method, translator.getRoot()));
+          // fall through
         case TIMESTAMP:
           type = long.class;
           floorMethod = timestampMethod;
@@ -1671,19 +1678,19 @@ public class RexImpTable {
         case YEAR:
         case MONTH:
           return Expressions.call(floorMethod, tur,
-              call(translatedOperands, type, TimeUnit.DAY));
+              call(operand, type, TimeUnit.DAY));
         default:
-          return call(translatedOperands, type, timeUnitRange.startUnit);
+          return call(operand, type, timeUnitRange.startUnit);
         }
       default:
         throw new AssertionError();
       }
     }
 
-    private Expression call(List<Expression> translatedOperands, Type type,
+    private Expression call(Expression operand, Type type,
         TimeUnit timeUnit) {
       return Expressions.call(SqlFunctions.class, methodName,
-          Types.castIfNecessary(type, translatedOperands.get(0)),
+          Types.castIfNecessary(type, operand),
           Types.castIfNecessary(type,
               Expressions.constant(timeUnit.multiplier)));
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index 10e969f..270db82 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -245,6 +245,14 @@ public class RexToLixTranslator {
             Expressions.call(BuiltInMethod.FLOOR_DIV.method,
                 operand, Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
             int.class);
+        break;
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_DATE.method,
+                operand,
+                Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
       }
       break;
     case TIME:
@@ -254,6 +262,14 @@ public class RexToLixTranslator {
         convert =
             Expressions.call(BuiltInMethod.STRING_TO_TIME.method, operand);
         break;
+      case TIME_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIME.method,
+                operand,
+                Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+        break;
       case TIMESTAMP:
         convert = Expressions.convert_(
             Expressions.call(
@@ -261,6 +277,49 @@ public class RexToLixTranslator {
                 operand,
                 Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
             int.class);
+        break;
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME.method,
+                operand,
+                Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+      }
+      break;
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      switch (sourceType.getSqlTypeName()) {
+      case CHAR:
+      case VARCHAR:
+        convert =
+            Expressions.call(BuiltInMethod.STRING_TO_TIME_WITH_LOCAL_TIME_ZONE.method, operand);
+        break;
+      case TIME:
+        convert = Expressions.call(
+            BuiltInMethod.TIME_STRING_TO_TIME_WITH_LOCAL_TIME_ZONE.method,
+            RexImpTable.optimize2(
+                operand,
+                Expressions.call(
+                    BuiltInMethod.UNIX_TIME_TO_STRING.method,
+                    operand)),
+            Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+        break;
+      case TIMESTAMP:
+        convert = Expressions.call(
+            BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+            RexImpTable.optimize2(
+                operand,
+                Expressions.call(
+                    BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+                    operand)),
+            Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+        break;
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME_WITH_LOCAL_TIME_ZONE.method,
+                operand));
       }
       break;
     case TIMESTAMP:
@@ -285,6 +344,82 @@ public class RexToLixTranslator {
                     Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
                 Expressions.convert_(operand, long.class));
         break;
+      case TIME_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+                Expressions.call(
+                    BuiltInMethod.UNIX_DATE_TO_STRING.method,
+                    Expressions.call(BuiltInMethod.CURRENT_DATE.method, root)),
+                operand,
+                Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+        break;
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+                operand,
+                Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+      }
+      break;
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      switch (sourceType.getSqlTypeName()) {
+      case CHAR:
+      case VARCHAR:
+        convert =
+            Expressions.call(
+                BuiltInMethod.STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+                operand);
+        break;
+      case DATE:
+        convert = Expressions.call(
+            BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+            RexImpTable.optimize2(
+                operand,
+                Expressions.call(
+                    BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+                    Expressions.multiply(
+                        Expressions.convert_(operand, long.class),
+                        Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)))),
+            Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+        break;
+      case TIME:
+        convert = Expressions.call(
+            BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+            RexImpTable.optimize2(
+                operand,
+                Expressions.call(
+                    BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+                    Expressions.add(
+                        Expressions.multiply(
+                            Expressions.convert_(
+                                Expressions.call(BuiltInMethod.CURRENT_DATE.method, root),
+                                long.class),
+                            Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
+                        Expressions.convert_(operand, long.class)))),
+            Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+        break;
+      case TIME_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+                Expressions.call(
+                    BuiltInMethod.UNIX_DATE_TO_STRING.method,
+                    Expressions.call(BuiltInMethod.CURRENT_DATE.method, root)),
+                operand));
+        break;
+      case TIMESTAMP:
+        convert = Expressions.call(
+            BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+            RexImpTable.optimize2(
+                operand,
+                Expressions.call(
+                    BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+                    operand)),
+            Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
       }
       break;
     case BOOLEAN:
@@ -315,6 +450,14 @@ public class RexToLixTranslator {
                 BuiltInMethod.UNIX_TIME_TO_STRING.method,
                 operand));
         break;
+      case TIME_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_STRING.method,
+                operand,
+                Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+        break;
       case TIMESTAMP:
         convert = RexImpTable.optimize2(
             operand,
@@ -322,6 +465,14 @@ public class RexToLixTranslator {
                 BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
                 operand));
         break;
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        convert = RexImpTable.optimize2(
+            operand,
+            Expressions.call(
+                BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_STRING.method,
+                operand,
+                Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+        break;
       case INTERVAL_YEAR:
       case INTERVAL_YEAR_MONTH:
       case INTERVAL_MONTH:
@@ -621,6 +772,7 @@ public class RexToLixTranslator {
           Expressions.constant(bd.toString()));
     case DATE:
     case TIME:
+    case TIME_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
     case INTERVAL_MONTH:
@@ -628,6 +780,7 @@ public class RexToLixTranslator {
       javaClass = int.class;
       break;
     case TIMESTAMP:
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_DAY:
     case INTERVAL_DAY_HOUR:
     case INTERVAL_DAY_MINUTE:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
index 2e0ded2..8999f44 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
@@ -176,12 +176,14 @@ public class JavaTypeFactoryImpl
         return String.class;
       case DATE:
       case TIME:
+      case TIME_WITH_LOCAL_TIME_ZONE:
       case INTEGER:
       case INTERVAL_YEAR:
       case INTERVAL_YEAR_MONTH:
       case INTERVAL_MONTH:
         return type.isNullable() ? Integer.class : int.class;
       case TIMESTAMP:
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
       case BIGINT:
       case INTERVAL_DAY:
       case INTERVAL_DAY_HOUR:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
index bd12347..085fd7c 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
@@ -281,6 +281,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> {
     case DECIMAL:
     case DATE:
     case TIME:
+    case TIME_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
     case INTERVAL_MONTH:
@@ -289,6 +290,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> {
     case DOUBLE:
     case FLOAT: // sic
     case TIMESTAMP:
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_DAY:
     case INTERVAL_DAY_HOUR:
     case INTERVAL_DAY_MINUTE:
@@ -339,6 +341,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> {
     case REAL:
     case DATE:
     case TIME:
+    case TIME_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
     case INTERVAL_MONTH:
@@ -346,6 +349,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> {
     case BIGINT:
     case DOUBLE:
     case TIMESTAMP:
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
     case INTERVAL_DAY:
     case INTERVAL_DAY_HOUR:
     case INTERVAL_DAY_MINUTE:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java
index e7b4467..127e95e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java
@@ -23,6 +23,7 @@ import org.apache.calcite.plan.RelOptRuleOperand;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollationTraitDef;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Project;
@@ -102,7 +103,7 @@ public class SortProjectTransposeRule extends RelOptRule {
         final RexCall cast = (RexCall) node;
         final RexCallBinding binding =
             RexCallBinding.create(cluster.getTypeFactory(), cast,
-                ImmutableList.of(RexUtil.apply(map, sort.getCollation())));
+                ImmutableList.of(RelCollations.of(RexUtil.apply(map, fc))));
         if (cast.getOperator().getMonotonicity(binding) == SqlMonotonicity.NOT_MONOTONIC) {
           return;
         }

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
index 3e0eebd..b7b8839 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
@@ -97,9 +97,11 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
     case DOUBLE:
       return 15;
     case TIME:
+    case TIME_WITH_LOCAL_TIME_ZONE:
     case DATE:
       return 0; // SQL99 part 2 section 6.1 syntax rule 30
     case TIMESTAMP:
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
       // farrago supports only 0 (see
       // SqlTypeName.getDefaultPrecision), but it should be 6
       // (microseconds) per SQL99 part 2 section 6.1 syntax rule 30.
@@ -120,7 +122,9 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
     case BINARY:
       return 65536;
     case TIME:
+    case TIME_WITH_LOCAL_TIME_ZONE:
     case TIMESTAMP:
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
       return SqlTypeName.MAX_DATETIME_PRECISION;
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
@@ -159,6 +163,8 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
       return isPrefix ? "x'" : "'";
     case TIMESTAMP:
       return isPrefix ? "TIMESTAMP '" : "'";
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      return isPrefix ? "TIMESTAMP WITH LOCAL TIME ZONE '" : "'";
     case INTERVAL_DAY:
     case INTERVAL_DAY_HOUR:
     case INTERVAL_DAY_MINUTE:
@@ -176,6 +182,8 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
       return isPrefix ? "INTERVAL '" : "' YEAR TO MONTH";
     case TIME:
       return isPrefix ? "TIME '" : "'";
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      return isPrefix ? "TIME WITH LOCAL TIME ZONE '" : "'";
     case DATE:
       return isPrefix ? "DATE '" : "'";
     case ARRAY:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index adf7fee..2144ab8 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -863,6 +863,14 @@ public class RexBuilder {
       }
       o = ((TimeString) o).round(p);
       break;
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      assert o instanceof TimeString;
+      p = type.getPrecision();
+      if (p == RelDataType.PRECISION_NOT_SPECIFIED) {
+        p = 0;
+      }
+      o = ((TimeString) o).round(p);
+      break;
     case TIMESTAMP:
       assert o instanceof TimestampString;
       p = type.getPrecision();
@@ -871,6 +879,13 @@ public class RexBuilder {
       }
       o = ((TimestampString) o).round(p);
       break;
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      assert o instanceof TimestampString;
+      p = type.getPrecision();
+      if (p == RelDataType.PRECISION_NOT_SPECIFIED) {
+        p = 0;
+      }
+      o = ((TimestampString) o).round(p);
     }
     return new RexLiteral(o, type, typeName);
   }
@@ -1084,6 +1099,17 @@ public class RexBuilder {
         SqlTypeName.TIME);
   }
 
+  /**
+   * Creates a Time with local time-zone literal.
+   */
+  public RexLiteral makeTimeWithLocalTimeZoneLiteral(
+      TimeString time,
+      int precision) {
+    return makeLiteral(Preconditions.checkNotNull(time),
+        typeFactory.createSqlType(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE, precision),
+        SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
+  }
+
   /** @deprecated Use {@link #makeTimestampLiteral(TimestampString, int)}. */
   @Deprecated // to be removed before 2.0
   public RexLiteral makeTimestampLiteral(Calendar calendar, int precision) {
@@ -1102,6 +1128,17 @@ public class RexBuilder {
   }
 
   /**
+   * Creates a Timestamp with local time-zone literal.
+   */
+  public RexLiteral makeTimestampWithLocalTimeZoneLiteral(
+      TimestampString timestamp,
+      int precision) {
+    return makeLiteral(Preconditions.checkNotNull(timestamp),
+        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, precision),
+        SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+  }
+
+  /**
    * Creates a literal representing an interval type, for example
    * {@code YEAR TO MONTH} or {@code DOW}.
    */
@@ -1225,6 +1262,10 @@ public class RexBuilder {
     case DATE:
     case TIMESTAMP:
       return DateTimeUtils.ZERO_CALENDAR;
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      return new TimeString(0, 0, 0);
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      return new TimestampString(0, 0, 0, 0, 0, 0);
     default:
       throw Util.unexpected(type.getSqlTypeName());
     }
@@ -1288,10 +1329,14 @@ public class RexBuilder {
       return (Boolean) value ? booleanTrue : booleanFalse;
     case TIME:
       return makeTimeLiteral((TimeString) value, type.getPrecision());
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      return makeTimeWithLocalTimeZoneLiteral((TimeString) value, type.getPrecision());
     case DATE:
       return makeDateLiteral((DateString) value);
     case TIMESTAMP:
       return makeTimestampLiteral((TimestampString) value, type.getPrecision());
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      return makeTimestampWithLocalTimeZoneLiteral((TimestampString) value, type.getPrecision());
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
     case INTERVAL_MONTH:
@@ -1423,6 +1468,12 @@ public class RexBuilder {
       } else {
         return TimeString.fromMillisOfDay((Integer) o);
       }
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      if (o instanceof TimeString) {
+        return o;
+      } else {
+        return TimeString.fromMillisOfDay((Integer) o);
+      }
     case DATE:
       if (o instanceof DateString) {
         return o;
@@ -1445,6 +1496,12 @@ public class RexBuilder {
       } else {
         return TimestampString.fromMillisSinceEpoch((Long) o);
       }
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      if (o instanceof TimestampString) {
+        return o;
+      } else {
+        return TimestampString.fromMillisSinceEpoch((Long) o);
+      }
     default:
       return o;
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index 8c4c732..a5ed918 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -255,8 +255,12 @@ public class RexLiteral extends RexNode {
       return value instanceof DateString;
     case TIME:
       return value instanceof TimeString;
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      return value instanceof TimeString;
     case TIMESTAMP:
       return value instanceof TimestampString;
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      return value instanceof TimestampString;
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
     case INTERVAL_MONTH:
@@ -498,10 +502,18 @@ public class RexLiteral extends RexNode {
       assert value instanceof TimeString;
       pw.print(value);
       break;
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      assert value instanceof TimeString;
+      pw.print(value);
+      break;
     case TIMESTAMP:
       assert value instanceof TimestampString;
       pw.print(value);
       break;
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      assert value instanceof TimestampString;
+      pw.print(value);
+      break;
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
     case INTERVAL_MONTH:
@@ -724,9 +736,11 @@ public class RexLiteral extends RexNode {
       return getValueAs(String.class);
     case DECIMAL:
     case TIMESTAMP:
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
       return getValueAs(Long.class);
     case DATE:
     case TIME:
+    case TIME_WITH_LOCAL_TIME_ZONE:
       return getValueAs(Integer.class);
     default:
       return value;
@@ -835,6 +849,12 @@ public class RexLiteral extends RexNode {
         return clazz.cast(((TimeString) value).toCalendar());
       }
       break;
+    case TIME_WITH_LOCAL_TIME_ZONE:
+      if (clazz == Integer.class) {
+        // Milliseconds since 1970-01-01 00:00:00
+        return clazz.cast(((TimeString) value).getMillisOfDay());
+      }
+      break;
     case TIMESTAMP:
       if (clazz == Long.class) {
         // Milliseconds since 1970-01-01 00:00:00
@@ -844,6 +864,12 @@ public class RexLiteral extends RexNode {
         return clazz.cast(((TimestampString) value).toCalendar());
       }
       break;
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+      if (clazz == Long.class) {
+        // Milliseconds since 1970-01-01 00:00:00
+        return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
+      }
+      break;
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:
     case INTERVAL_MONTH:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
index 67ac1b8..52c4795 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -811,6 +811,7 @@ public class RexSimplify {
         case TIMESTAMP:
           return e;
         }
+        break;
       }
       final List<RexNode> reducedValues = new ArrayList<>();
       executor.reduce(rexBuilder, ImmutableList.<RexNode>of(e), reducedValues);

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 6832ee4..736034c 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -33,6 +33,8 @@ import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.runtime.FlatLists.ComparableList;
 import org.apache.calcite.util.Bug;
 import org.apache.calcite.util.NumberUtil;
+import org.apache.calcite.util.TimeWithTimeZoneString;
+import org.apache.calcite.util.TimestampWithTimeZoneString;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -1695,6 +1697,50 @@ public class SqlFunctions {
     return v == null ? null : internalToTime(v.intValue());
   }
 
+  public static Integer toTimeWithLocalTimeZone(String v) {
+    return v == null ? null : new TimeWithTimeZoneString(v)
+        .withTimeZone(DateTimeUtils.UTC_ZONE)
+        .getLocalTimeString()
+        .getMillisOfDay();
+  }
+
+  public static Integer toTimeWithLocalTimeZone(String v, TimeZone timeZone) {
+    return v == null ? null : new TimeWithTimeZoneString(v + " " + timeZone.getID())
+        .withTimeZone(DateTimeUtils.UTC_ZONE)
+        .getLocalTimeString()
+        .getMillisOfDay();
+  }
+
+  public static int timeWithLocalTimeZoneToTime(int v, TimeZone timeZone) {
+    return TimeWithTimeZoneString.fromMillisOfDay(v)
+        .withTimeZone(timeZone)
+        .getLocalTimeString()
+        .getMillisOfDay();
+  }
+
+  public static long timeWithLocalTimeZoneToTimestamp(String date, int v, TimeZone timeZone) {
+    final TimeWithTimeZoneString tTZ = TimeWithTimeZoneString.fromMillisOfDay(v)
+        .withTimeZone(DateTimeUtils.UTC_ZONE);
+    return new TimestampWithTimeZoneString(date + " " + tTZ.toString())
+        .withTimeZone(timeZone)
+        .getLocalTimestampString()
+        .getMillisSinceEpoch();
+  }
+
+  public static long timeWithLocalTimeZoneToTimestampWithLocalTimeZone(String date, int v) {
+    final TimeWithTimeZoneString tTZ = TimeWithTimeZoneString.fromMillisOfDay(v)
+        .withTimeZone(DateTimeUtils.UTC_ZONE);
+    return new TimestampWithTimeZoneString(date + " " + tTZ.toString())
+        .getLocalTimestampString()
+        .getMillisSinceEpoch();
+  }
+
+  public static String timeWithLocalTimeZoneToString(int v, TimeZone timeZone) {
+    return TimeWithTimeZoneString.fromMillisOfDay(v)
+        .withTimeZone(timeZone)
+        .toString();
+  }
+
   /** Converts the internal representation of a SQL TIMESTAMP (long) to the Java
    * type used for UDF parameters ({@link java.sql.Timestamp}). */
   public static java.sql.Timestamp internalToTimestamp(long v) {
@@ -1705,6 +1751,53 @@ public class SqlFunctions {
     return v == null ? null : internalToTimestamp(v.longValue());
   }
 
+  public static int timestampWithLocalTimeZoneToDate(long v, TimeZone timeZone) {
+    return TimestampWithTimeZoneString.fromMillisSinceEpoch(v)
+        .withTimeZone(timeZone)
+        .getLocalDateString()
+        .getDaysSinceEpoch();
+  }
+
+  public static int timestampWithLocalTimeZoneToTime(long v, TimeZone timeZone) {
+    return TimestampWithTimeZoneString.fromMillisSinceEpoch(v)
+        .withTimeZone(timeZone)
+        .getLocalTimeString()
+        .getMillisOfDay();
+  }
+
+  public static long timestampWithLocalTimeZoneToTimestamp(long v, TimeZone timeZone) {
+    return TimestampWithTimeZoneString.fromMillisSinceEpoch(v)
+        .withTimeZone(timeZone)
+        .getLocalTimestampString()
+        .getMillisSinceEpoch();
+  }
+
+  public static String timestampWithLocalTimeZoneToString(long v, TimeZone timeZone) {
+    return TimestampWithTimeZoneString.fromMillisSinceEpoch(v)
+        .withTimeZone(timeZone)
+        .toString();
+  }
+
+  public static int timestampWithLocalTimeZoneToTimeWithLocalTimeZone(long v) {
+    return TimestampWithTimeZoneString.fromMillisSinceEpoch(v)
+        .getLocalTimeString()
+        .getMillisOfDay();
+  }
+
+  public static Long toTimestampWithLocalTimeZone(String v) {
+    return v == null ? null : new TimestampWithTimeZoneString(v)
+        .withTimeZone(DateTimeUtils.UTC_ZONE)
+        .getLocalTimestampString()
+        .getMillisSinceEpoch();
+  }
+
+  public static Long toTimestampWithLocalTimeZone(String v, TimeZone timeZone) {
+    return v == null ? null : new TimestampWithTimeZoneString(v + " " + timeZone.getID())
+        .withTimeZone(DateTimeUtils.UTC_ZONE)
+        .getLocalTimestampString()
+        .getMillisSinceEpoch();
+  }
+
   // Don't need shortValueOf etc. - Short.valueOf is sufficient.
 
   /** Helper for CAST(... AS VARCHAR(maxLength)). */
@@ -1867,6 +1960,11 @@ public class SqlFunctions {
     return (int) (localTimestamp(root) % DateTimeUtils.MILLIS_PER_DAY);
   }
 
+  @NonDeterministic
+  public static TimeZone timeZone(DataContext root) {
+    return (TimeZone) DataContext.Variable.TIME_ZONE.get(root);
+  }
+
   /** SQL {@code TRANSLATE(string, search_chars, replacement_chars)}
    * function. */
   public static String translate3(String s, String search, String replacement) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/schema/Schemas.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/schema/Schemas.java b/core/src/main/java/org/apache/calcite/schema/Schemas.java
index edb833a..6466d31 100644
--- a/core/src/main/java/org/apache/calcite/schema/Schemas.java
+++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java
@@ -54,7 +54,6 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.TimeZone;
 
 /**
  * Utility functions for schemas.
@@ -564,8 +563,7 @@ public final class Schemas {
     DummyDataContext(CalciteConnection connection, SchemaPlus rootSchema) {
       this.connection = connection;
       this.rootSchema = rootSchema;
-      this.map =
-          ImmutableMap.<String, Object>of("timeZone", TimeZone.getDefault());
+      this.map = ImmutableMap.<String, Object>of();
     }
 
     public SchemaPlus getRootSchema() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java b/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java
index 90b0597..2f9d974 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java
@@ -34,7 +34,9 @@ public enum SqlJdbcDataTypeName {
   SQL_VARCHAR(SqlTypeName.VARCHAR),
   SQL_DATE(SqlTypeName.DATE),
   SQL_TIME(SqlTypeName.TIME),
+  SQL_TIME_WITH_LOCAL_TIME_ZONE(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE),
   SQL_TIMESTAMP(SqlTypeName.TIMESTAMP),
+  SQL_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE),
   SQL_DECIMAL(SqlTypeName.DECIMAL),
   SQL_NUMERIC(SqlTypeName.DECIMAL),
   SQL_BOOLEAN(SqlTypeName.BOOLEAN),

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
index f77d639..cd46c39 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java
@@ -158,9 +158,17 @@ public class SqlTypeAssignmentRules {
     rule.add(SqlTypeName.TIMESTAMP);
     rules.put(SqlTypeName.TIME, rule);
 
+    // Time with local time-zone is assignable from ...
+    rules.put(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE,
+        EnumSet.of(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE));
+
     // Timestamp is assignable from ...
     rules.put(SqlTypeName.TIMESTAMP, EnumSet.of(SqlTypeName.TIMESTAMP));
 
+    // Timestamp with local time-zone is assignable from ...
+    rules.put(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
+        EnumSet.of(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE));
+
     // Geometry is assignable from ...
     rules.put(SqlTypeName.GEOMETRY, EnumSet.of(SqlTypeName.GEOMETRY));
 
@@ -275,6 +283,7 @@ public class SqlTypeAssignmentRules {
     rule = new HashSet<>();
     rule.add(SqlTypeName.DATE);
     rule.add(SqlTypeName.TIMESTAMP);
+    rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
     rule.add(SqlTypeName.CHAR);
     rule.add(SqlTypeName.VARCHAR);
     coerceRules.put(SqlTypeName.DATE, rule);
@@ -282,19 +291,44 @@ public class SqlTypeAssignmentRules {
     // Time is castable from ...
     rule = new HashSet<>();
     rule.add(SqlTypeName.TIME);
+    rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
     rule.add(SqlTypeName.TIMESTAMP);
+    rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
     rule.add(SqlTypeName.CHAR);
     rule.add(SqlTypeName.VARCHAR);
     coerceRules.put(SqlTypeName.TIME, rule);
 
+    // Time with local time-zone is castable from ...
+    rule = new HashSet<>();
+    rule.add(SqlTypeName.TIME);
+    rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
+    rule.add(SqlTypeName.TIMESTAMP);
+    rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+    rule.add(SqlTypeName.CHAR);
+    rule.add(SqlTypeName.VARCHAR);
+    coerceRules.put(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE, rule);
+
     // Timestamp is castable from ...
     rule = new HashSet<>();
     rule.add(SqlTypeName.TIMESTAMP);
+    rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
     rule.add(SqlTypeName.DATE);
     rule.add(SqlTypeName.TIME);
+    rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
     rule.add(SqlTypeName.CHAR);
     rule.add(SqlTypeName.VARCHAR);
     coerceRules.put(SqlTypeName.TIMESTAMP, rule);
+
+    // Timestamp with local time-zone is castable from ...
+    rule = new HashSet<>();
+    rule.add(SqlTypeName.TIMESTAMP);
+    rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+    rule.add(SqlTypeName.DATE);
+    rule.add(SqlTypeName.TIME);
+    rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
+    rule.add(SqlTypeName.CHAR);
+    rule.add(SqlTypeName.VARCHAR);
+    coerceRules.put(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, rule);
   }
 
   //~ Methods ----------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
index e88f96a..af1733b 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
@@ -99,7 +99,9 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
 
           .put(Types.DATE, DATE)
           .put(Types.TIME, TIME)
+          .put(Types.TIME_WITH_TIMEZONE, TIME)
           .put(Types.TIMESTAMP, TIMESTAMP)
+          .put(Types.TIMESTAMP_WITH_TIMEZONE, TIMESTAMP)
           .put(Types.BOOLEAN, BOOLEAN)
 
           .put(ExtraSqlTypes.REF_CURSOR, CURSOR)
@@ -130,9 +132,9 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
     case DATE:
       return ImmutableList.of(SqlTypeName.DATE);
     case TIME:
-      return ImmutableList.of(SqlTypeName.TIME);
+      return ImmutableList.of(SqlTypeName.TIME, SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE);
     case TIMESTAMP:
-      return ImmutableList.of(SqlTypeName.TIMESTAMP);
+      return ImmutableList.of(SqlTypeName.TIMESTAMP, SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
     case BOOLEAN:
       return SqlTypeName.BOOLEAN_TYPES;
     case INTERVAL_YEAR_MONTH:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
index fcae1ec..e56b9cf 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
@@ -63,8 +63,12 @@ public enum SqlTypeName {
   DATE(PrecScale.NO_NO, false, Types.DATE, SqlTypeFamily.DATE),
   TIME(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TIME,
       SqlTypeFamily.TIME),
+  TIME_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.OTHER,
+      SqlTypeFamily.TIME),
   TIMESTAMP(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TIMESTAMP,
       SqlTypeFamily.TIMESTAMP),
+  TIMESTAMP_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.OTHER,
+      SqlTypeFamily.TIMESTAMP),
   INTERVAL_YEAR(PrecScale.NO_NO, false, Types.OTHER,
       SqlTypeFamily.INTERVAL_YEAR_MONTH),
   INTERVAL_YEAR_MONTH(PrecScale.NO_NO, false, Types.OTHER,
@@ -147,7 +151,7 @@ public enum SqlTypeName {
           INTERVAL_DAY, INTERVAL_DAY_HOUR, INTERVAL_DAY_MINUTE,
           INTERVAL_DAY_SECOND, INTERVAL_HOUR, INTERVAL_HOUR_MINUTE,
           INTERVAL_HOUR_SECOND, INTERVAL_MINUTE, INTERVAL_MINUTE_SECOND,
-          INTERVAL_SECOND,
+          INTERVAL_SECOND, TIME_WITH_LOCAL_TIME_ZONE, TIMESTAMP_WITH_LOCAL_TIME_ZONE,
           FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST);
 
   public static final List<SqlTypeName> BOOLEAN_TYPES =
@@ -178,7 +182,8 @@ public enum SqlTypeName {
       combine(CHAR_TYPES, BINARY_TYPES);
 
   public static final List<SqlTypeName> DATETIME_TYPES =
-      ImmutableList.of(DATE, TIME, TIMESTAMP);
+      ImmutableList.of(DATE, TIME, TIME_WITH_LOCAL_TIME_ZONE,
+          TIMESTAMP, TIMESTAMP_WITH_LOCAL_TIME_ZONE);
 
   public static final Set<SqlTypeName> YEAR_INTERVAL_TYPES =
       Sets.immutableEnumSet(SqlTypeName.INTERVAL_YEAR,
@@ -740,7 +745,9 @@ public enum SqlTypeName {
     case VARBINARY:
     case BINARY:
     case TIME:
+    case TIME_WITH_LOCAL_TIME_ZONE:
     case TIMESTAMP:
+    case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
       return 1;
     case INTERVAL_YEAR:
     case INTERVAL_YEAR_MONTH:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index 8940629..14bff94 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -678,6 +678,14 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
       case INTERVAL_MINUTE_SECOND:
       case INTERVAL_SECOND:
         break;
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        RelDataType type =
+            cx.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP);
+        type = cx.getTypeFactory().createTypeWithNullability(
+            type,
+            exprs.get(1).getType().isNullable());
+        res = rexBuilder.makeCast(type, res);
+        // fall through
       case TIMESTAMP:
         res = divide(rexBuilder, res, TimeUnit.DAY.multiplier);
         // fall through
@@ -690,6 +698,14 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
       break;
     case DECADE:
       switch (sqlTypeName) {
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        RelDataType type =
+            cx.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP);
+        type = cx.getTypeFactory().createTypeWithNullability(
+            type,
+            exprs.get(1).getType().isNullable());
+        res = rexBuilder.makeCast(type, res);
+        // fall through
       case TIMESTAMP:
         res = divide(rexBuilder, res, TimeUnit.DAY.multiplier);
         // fall through
@@ -709,6 +725,16 @@ public class StandardConvertletTable extends ReflectiveConvertletTable {
       case TIMESTAMP:
         // convert to seconds
         return divide(rexBuilder, res, TimeUnit.SECOND.multiplier);
+      case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+        RelDataType type =
+            cx.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP);
+        type = cx.getTypeFactory().createTypeWithNullability(
+            type,
+            exprs.get(1).getType().isNullable());
+        return divide(
+            rexBuilder,
+            rexBuilder.makeCast(type, res),
+            TimeUnit.SECOND.multiplier);
       case INTERVAL_YEAR:
       case INTERVAL_YEAR_MONTH:
       case INTERVAL_MONTH:

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 3265db5..648f1b0 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -297,8 +297,33 @@ public enum BuiltInMethod {
   INTERNAL_TO_TIMESTAMP(SqlFunctions.class, "internalToTimestamp", long.class),
   STRING_TO_DATE(DateTimeUtils.class, "dateStringToUnixDate", String.class),
   STRING_TO_TIME(DateTimeUtils.class, "timeStringToUnixDate", String.class),
-  STRING_TO_TIMESTAMP(DateTimeUtils.class, "timestampStringToUnixDate",
+  STRING_TO_TIMESTAMP(DateTimeUtils.class, "timestampStringToUnixDate", String.class),
+  STRING_TO_TIME_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, "toTimeWithLocalTimeZone",
       String.class),
+  TIME_STRING_TO_TIME_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, "toTimeWithLocalTimeZone",
+      String.class, TimeZone.class),
+  STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, "toTimestampWithLocalTimeZone",
+      String.class),
+  TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class,
+      "toTimestampWithLocalTimeZone", String.class, TimeZone.class),
+  TIME_WITH_LOCAL_TIME_ZONE_TO_TIME(SqlFunctions.class, "timeWithLocalTimeZoneToTime",
+      int.class, TimeZone.class),
+  TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP(SqlFunctions.class, "timeWithLocalTimeZoneToTimestamp",
+      String.class, int.class, TimeZone.class),
+  TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class,
+      "timeWithLocalTimeZoneToTimestampWithLocalTimeZone", String.class, int.class),
+  TIME_WITH_LOCAL_TIME_ZONE_TO_STRING(SqlFunctions.class, "timeWithLocalTimeZoneToString",
+      int.class, TimeZone.class),
+  TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_DATE(SqlFunctions.class, "timestampWithLocalTimeZoneToDate",
+      long.class, TimeZone.class),
+  TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME(SqlFunctions.class, "timestampWithLocalTimeZoneToTime",
+      long.class, TimeZone.class),
+  TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME_WITH_LOCAL_TIME_ZONE(SqlFunctions.class,
+      "timestampWithLocalTimeZoneToTimeWithLocalTimeZone", long.class),
+  TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP(SqlFunctions.class,
+      "timestampWithLocalTimeZoneToTimestamp", long.class, TimeZone.class),
+  TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_STRING(SqlFunctions.class,
+      "timestampWithLocalTimeZoneToString", long.class, TimeZone.class),
   UNIX_DATE_TO_STRING(DateTimeUtils.class, "unixDateToString", int.class),
   UNIX_TIME_TO_STRING(DateTimeUtils.class, "unixTimeToString", int.class),
   UNIX_TIMESTAMP_TO_STRING(DateTimeUtils.class, "unixTimestampToString",
@@ -322,6 +347,7 @@ public enum BuiltInMethod {
   CURRENT_DATE(SqlFunctions.class, "currentDate", DataContext.class),
   LOCAL_TIMESTAMP(SqlFunctions.class, "localTimestamp", DataContext.class),
   LOCAL_TIME(SqlFunctions.class, "localTime", DataContext.class),
+  TIME_ZONE(SqlFunctions.class, "timeZone", DataContext.class),
   BOOLEAN_TO_STRING(SqlFunctions.class, "toString", boolean.class),
   JDBC_ARRAY_TO_LIST(SqlFunctions.class, "arrayToList", java.sql.Array.class),
   OBJECT_TO_STRING(Object.class, "toString"),

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/util/DateString.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/DateString.java b/core/src/main/java/org/apache/calcite/util/DateString.java
index cea9df3..5aaa1b8 100644
--- a/core/src/main/java/org/apache/calcite/util/DateString.java
+++ b/core/src/main/java/org/apache/calcite/util/DateString.java
@@ -42,7 +42,7 @@ public class DateString implements Comparable<DateString> {
 
   /** Creates a DateString for year, month, day values. */
   public DateString(int year, int month, int day) {
-    this(TimestampString.ymd(new StringBuilder(), year, month, day).toString());
+    this(DateTimeStringUtils.ymd(new StringBuilder(), year, month, day).toString());
   }
 
   @Override public String toString() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java b/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java
new file mode 100644
index 0000000..c0d38df
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.util;
+
+import java.util.TimeZone;
+
+/**
+ * Utility methods to manipulate String representation of DateTime values.
+ */
+public class DateTimeStringUtils {
+
+  private DateTimeStringUtils() {}
+
+  static String pad(int length, long v) {
+    StringBuilder s = new StringBuilder(Long.toString(v));
+    while (s.length() < length) {
+      s.insert(0, "0");
+    }
+    return s.toString();
+  }
+
+  static StringBuilder hms(StringBuilder b, int h, int m, int s) {
+    int2(b, h);
+    b.append(':');
+    int2(b, m);
+    b.append(':');
+    int2(b, s);
+    return b;
+  }
+
+  static StringBuilder ymdhms(StringBuilder b, int year, int month, int day,
+      int h, int m, int s) {
+    ymd(b, year, month, day);
+    b.append(' ');
+    hms(b, h, m, s);
+    return b;
+  }
+
+  static StringBuilder ymd(StringBuilder b, int year, int month, int day) {
+    int4(b, year);
+    b.append('-');
+    int2(b, month);
+    b.append('-');
+    int2(b, day);
+    return b;
+  }
+
+  private static void int4(StringBuilder buf, int i) {
+    buf.append((char) ('0' + (i / 1000) % 10));
+    buf.append((char) ('0' + (i / 100) % 10));
+    buf.append((char) ('0' + (i / 10) % 10));
+    buf.append((char) ('0' + i % 10));
+  }
+
+  private static void int2(StringBuilder buf, int i) {
+    buf.append((char) ('0' + (i / 10) % 10));
+    buf.append((char) ('0' + i % 10));
+  }
+
+  static boolean isValidTimeZone(final String timeZone) {
+    if (timeZone.equals("GMT")) {
+      return true;
+    } else {
+      String id = TimeZone.getTimeZone(timeZone).getID();
+      if (!id.equals("GMT")) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+}
+
+// End DateTimeStringUtils.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/util/TimeString.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/TimeString.java b/core/src/main/java/org/apache/calcite/util/TimeString.java
index 75aa96b..ce03d87 100644
--- a/core/src/main/java/org/apache/calcite/util/TimeString.java
+++ b/core/src/main/java/org/apache/calcite/util/TimeString.java
@@ -44,7 +44,7 @@ public class TimeString implements Comparable<TimeString> {
 
   /** Creates a TimeString for hour, minute, second and millisecond values. */
   public TimeString(int h, int m, int s) {
-    this(TimestampString.hms(new StringBuilder(), h, m, s).toString());
+    this(DateTimeStringUtils.hms(new StringBuilder(), h, m, s).toString());
   }
 
   /** Sets the fraction field of a {@code TimeString} to a given number
@@ -55,7 +55,7 @@ public class TimeString implements Comparable<TimeString> {
    * yields {@code TIME '1970-01-01 02:03:04.056'}. */
   public TimeString withMillis(int millis) {
     Preconditions.checkArgument(millis >= 0 && millis < 1000);
-    return withFraction(TimestampString.pad(3, millis));
+    return withFraction(DateTimeStringUtils.pad(3, millis));
   }
 
   /** Sets the fraction field of a {@code TimeString} to a given number
@@ -66,7 +66,7 @@ public class TimeString implements Comparable<TimeString> {
    * yields {@code TIME '1970-01-01 02:03:04.000056789'}. */
   public TimeString withNanos(int nanos) {
     Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000);
-    return withFraction(TimestampString.pad(9, nanos));
+    return withFraction(DateTimeStringUtils.pad(9, nanos));
   }
 
   /** Sets the fraction field of a {@code TimeString}.

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java b/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java
new file mode 100644
index 0000000..6547f06
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.util;
+
+import org.apache.calcite.avatica.util.DateTimeUtils;
+
+import com.google.common.base.Preconditions;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Time with time-zone literal.
+ *
+ * <p>Immutable, internally represented as a string (in ISO format),
+ * and can support unlimited precision (milliseconds, nanoseconds).
+ */
+public class TimeWithTimeZoneString implements Comparable<TimeWithTimeZoneString> {
+
+  final TimeString localTime;
+  final TimeZone timeZone;
+  final String v;
+
+  /** Creates a TimeWithTimeZoneString. */
+  public TimeWithTimeZoneString(TimeString localTime, TimeZone timeZone) {
+    this.localTime = localTime;
+    this.timeZone = timeZone;
+    this.v = localTime.toString() + " " + timeZone.getID();
+  }
+
+  /** Creates a TimeWithTimeZoneString. */
+  public TimeWithTimeZoneString(String v) {
+    this.localTime = new TimeString(v.substring(0, 8));
+    String timeZoneString = v.substring(9);
+    Preconditions.checkArgument(DateTimeStringUtils.isValidTimeZone(timeZoneString));
+    this.timeZone = TimeZone.getTimeZone(timeZoneString);
+    this.v = v;
+  }
+
+  /** Creates a TimeWithTimeZoneString for hour, minute, second and millisecond values
+   * in the given time-zone. */
+  public TimeWithTimeZoneString(int h, int m, int s, String timeZone) {
+    this(DateTimeStringUtils.hms(new StringBuilder(), h, m, s).toString() + " " + timeZone);
+  }
+
+  /** Sets the fraction field of a {@code TimeWithTimeZoneString} to a given number
+   * of milliseconds. Nukes the value set via {@link #withNanos}.
+   *
+   * <p>For example,
+   * {@code new TimeWithTimeZoneString(1970, 1, 1, 2, 3, 4, "UTC").withMillis(56)}
+   * yields {@code TIME WITH LOCAL TIME ZONE '1970-01-01 02:03:04.056 UTC'}. */
+  public TimeWithTimeZoneString withMillis(int millis) {
+    Preconditions.checkArgument(millis >= 0 && millis < 1000);
+    return withFraction(DateTimeStringUtils.pad(3, millis));
+  }
+
+  /** Sets the fraction field of a {@code TimeString} to a given number
+   * of nanoseconds. Nukes the value set via {@link #withMillis(int)}.
+   *
+   * <p>For example,
+   * {@code new TimeWithTimeZoneString(1970, 1, 1, 2, 3, 4, "UTC").withNanos(56789)}
+   * yields {@code TIME WITH LOCAL TIME ZONE '1970-01-01 02:03:04.000056789 UTC'}. */
+  public TimeWithTimeZoneString withNanos(int nanos) {
+    Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000);
+    return withFraction(DateTimeStringUtils.pad(9, nanos));
+  }
+
+  /** Sets the fraction field of a {@code TimeWithTimeZoneString}.
+   * The precision is determined by the number of leading zeros.
+   * Trailing zeros are stripped.
+   *
+   * <p>For example,
+   * {@code new TimeWithTimeZoneString(1970, 1, 1, 2, 3, 4, "UTC").withFraction("00506000")}
+   * yields {@code TIME WITH LOCAL TIME ZONE '1970-01-01 02:03:04.00506 UTC'}. */
+  public TimeWithTimeZoneString withFraction(String fraction) {
+    String v = this.v;
+    int i = v.indexOf('.');
+    if (i >= 0) {
+      v = v.substring(0, i);
+    } else {
+      v = v.substring(0, 8);
+    }
+    while (fraction.endsWith("0")) {
+      fraction = fraction.substring(0, fraction.length() - 1);
+    }
+    if (fraction.length() > 0) {
+      v = v + "." + fraction;
+    }
+    v = v + this.v.substring(8); // time-zone
+    return new TimeWithTimeZoneString(v);
+  }
+
+  public TimeWithTimeZoneString withTimeZone(TimeZone timeZone) {
+    if (this.timeZone.equals(timeZone)) {
+      return this;
+    }
+    String localTimeString = localTime.toString();
+    String v;
+    String fraction;
+    int i = localTimeString.indexOf('.');
+    if (i >= 0) {
+      v = localTimeString.substring(0, i);
+      fraction = localTimeString.substring(i + 1);
+    } else {
+      v = localTimeString;
+      fraction = null;
+    }
+    final DateTimeUtils.PrecisionTime pt =
+        DateTimeUtils.parsePrecisionDateTimeLiteral(v,
+            new SimpleDateFormat(DateTimeUtils.TIME_FORMAT_STRING, Locale.ROOT),
+            this.timeZone, -1);
+    pt.getCalendar().setTimeZone(timeZone);
+    if (fraction != null) {
+      return new TimeWithTimeZoneString(
+          pt.getCalendar().get(Calendar.HOUR_OF_DAY),
+          pt.getCalendar().get(Calendar.MINUTE),
+          pt.getCalendar().get(Calendar.SECOND),
+          timeZone.getID())
+              .withFraction(fraction);
+    }
+    return new TimeWithTimeZoneString(
+        pt.getCalendar().get(Calendar.HOUR_OF_DAY),
+        pt.getCalendar().get(Calendar.MINUTE),
+        pt.getCalendar().get(Calendar.SECOND),
+        timeZone.getID());
+  }
+
+  @Override public String toString() {
+    return v;
+  }
+
+  @Override public boolean equals(Object o) {
+    // The value is in canonical form (no trailing zeros).
+    return o == this
+        || o instanceof TimeWithTimeZoneString
+        && ((TimeWithTimeZoneString) o).v.equals(v);
+  }
+
+  @Override public int hashCode() {
+    return v.hashCode();
+  }
+
+  @Override public int compareTo(TimeWithTimeZoneString o) {
+    return v.compareTo(o.v);
+  }
+
+  public TimeWithTimeZoneString round(int precision) {
+    Preconditions.checkArgument(precision >= 0);
+    return new TimeWithTimeZoneString(
+        localTime.round(precision), timeZone);
+  }
+
+  public static TimeWithTimeZoneString fromMillisOfDay(int i) {
+    return new TimeWithTimeZoneString(
+        DateTimeUtils.unixTimeToString(i) + " " + DateTimeUtils.UTC_ZONE.getID())
+            .withMillis((int) DateTimeUtils.floorMod(i, 1000));
+  }
+
+  /** Converts this TimeWithTimeZoneString to a string, truncated or padded with
+   * zeroes to a given precision. */
+  public String toString(int precision) {
+    Preconditions.checkArgument(precision >= 0);
+    return localTime.toString(precision) + " " + timeZone.getID();
+  }
+
+  public TimeString getLocalTimeString() {
+    return localTime;
+  }
+
+}
+
+// End TimeWithTimeZoneString.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/util/TimestampString.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/TimestampString.java b/core/src/main/java/org/apache/calcite/util/TimestampString.java
index 4e392f0..604d5a4 100644
--- a/core/src/main/java/org/apache/calcite/util/TimestampString.java
+++ b/core/src/main/java/org/apache/calcite/util/TimestampString.java
@@ -47,7 +47,7 @@ public class TimestampString implements Comparable<TimestampString> {
   /** Creates a TimestampString for year, month, day, hour, minute, second,
    *  millisecond values. */
   public TimestampString(int year, int month, int day, int h, int m, int s) {
-    this(ymdhms(new StringBuilder(), year, month, day, h, m, s).toString());
+    this(DateTimeStringUtils.ymdhms(new StringBuilder(), year, month, day, h, m, s).toString());
   }
 
   /** Sets the fraction field of a {@code TimestampString} to a given number
@@ -58,7 +58,7 @@ public class TimestampString implements Comparable<TimestampString> {
    * yields {@code TIMESTAMP '1970-01-01 02:03:04.056'}. */
   public TimestampString withMillis(int millis) {
     Preconditions.checkArgument(millis >= 0 && millis < 1000);
-    return withFraction(pad(3, millis));
+    return withFraction(DateTimeStringUtils.pad(3, millis));
   }
 
   /** Sets the fraction field of a {@code TimestampString} to a given number
@@ -69,7 +69,7 @@ public class TimestampString implements Comparable<TimestampString> {
    * yields {@code TIMESTAMP '1970-01-01 02:03:04.000056789'}. */
   public TimestampString withNanos(int nanos) {
     Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000);
-    return withFraction(pad(9, nanos));
+    return withFraction(DateTimeStringUtils.pad(9, nanos));
   }
 
   /** Sets the fraction field of a {@code TimestampString}.
@@ -113,44 +113,6 @@ public class TimestampString implements Comparable<TimestampString> {
     return v.compareTo(o.v);
   }
 
-  static StringBuilder hms(StringBuilder b, int h, int m, int s) {
-    int2(b, h);
-    b.append(':');
-    int2(b, m);
-    b.append(':');
-    int2(b, s);
-    return b;
-  }
-
-  static StringBuilder ymdhms(StringBuilder b, int year, int month, int day,
-      int h, int m, int s) {
-    ymd(b, year, month, day);
-    b.append(' ');
-    hms(b, h, m, s);
-    return b;
-  }
-
-  static StringBuilder ymd(StringBuilder b, int year, int month, int day) {
-    int4(b, year);
-    b.append('-');
-    int2(b, month);
-    b.append('-');
-    int2(b, day);
-    return b;
-  }
-
-  private static void int4(StringBuilder buf, int i) {
-    buf.append((char) ('0' + (i / 1000) % 10));
-    buf.append((char) ('0' + (i / 100) % 10));
-    buf.append((char) ('0' + (i / 10) % 10));
-    buf.append((char) ('0' + i % 10));
-  }
-
-  private static void int2(StringBuilder buf, int i) {
-    buf.append((char) ('0' + (i / 10) % 10));
-    buf.append((char) ('0' + i % 10));
-  }
-
   /** Creates a TimestampString from a Calendar. */
   public static TimestampString fromCalendarFields(Calendar calendar) {
     return new TimestampString(
@@ -214,14 +176,6 @@ public class TimestampString implements Comparable<TimestampString> {
         .withMillis((int) DateTimeUtils.floorMod(millis, 1000));
   }
 
-  static String pad(int length, long v) {
-    StringBuilder s = new StringBuilder(Long.toString(v));
-    while (s.length() < length) {
-      s.insert(0, "0");
-    }
-    return s.toString();
-  }
-
   public Calendar toCalendar() {
     return Util.calendar(getMillisSinceEpoch());
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java b/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java
new file mode 100644
index 0000000..3f32762
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.util;
+
+import org.apache.calcite.avatica.util.DateTimeUtils;
+
+import com.google.common.base.Preconditions;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Timestamp with time-zone literal.
+ *
+ * <p>Immutable, internally represented as a string (in ISO format),
+ * and can support unlimited precision (milliseconds, nanoseconds).
+ */
+public class TimestampWithTimeZoneString
+    implements Comparable<TimestampWithTimeZoneString> {
+
+  final TimestampString localDateTime;
+  final TimeZone timeZone;
+  final String v;
+
+  /** Creates a TimestampWithTimeZoneString. */
+  public TimestampWithTimeZoneString(TimestampString localDateTime, TimeZone timeZone) {
+    this.localDateTime = localDateTime;
+    this.timeZone = timeZone;
+    this.v = localDateTime.toString() + " " + timeZone.getID();
+  }
+
+  /** Creates a TimestampWithTimeZoneString. */
+  public TimestampWithTimeZoneString(String v) {
+    this.localDateTime = new TimestampString(v.substring(0, v.indexOf(' ', 11)));
+    String timeZoneString = v.substring(v.indexOf(' ', 11) + 1);
+    Preconditions.checkArgument(DateTimeStringUtils.isValidTimeZone(timeZoneString));
+    this.timeZone = TimeZone.getTimeZone(timeZoneString);
+    this.v = v;
+  }
+
+  /** Creates a TimestampWithTimeZoneString for year, month, day, hour, minute, second,
+   *  millisecond values in the given time-zone. */
+  public TimestampWithTimeZoneString(int year, int month, int day, int h, int m, int s,
+      String timeZone) {
+    this(DateTimeStringUtils.ymdhms(new StringBuilder(), year, month, day, h, m, s).toString()
+        + " " + timeZone);
+  }
+
+  /** Sets the fraction field of a {@code TimestampWithTimeZoneString} to a given number
+   * of milliseconds. Nukes the value set via {@link #withNanos}.
+   *
+   * <p>For example,
+   * {@code new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withMillis(56)}
+   * yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.056 GMT'}. */
+  public TimestampWithTimeZoneString withMillis(int millis) {
+    Preconditions.checkArgument(millis >= 0 && millis < 1000);
+    return withFraction(DateTimeStringUtils.pad(3, millis));
+  }
+
+  /** Sets the fraction field of a {@code TimestampWithTimeZoneString} to a given number
+   * of nanoseconds. Nukes the value set via {@link #withMillis(int)}.
+   *
+   * <p>For example,
+   * {@code new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withNanos(56789)}
+   * yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.000056789 GMT'}. */
+  public TimestampWithTimeZoneString withNanos(int nanos) {
+    Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000);
+    return withFraction(DateTimeStringUtils.pad(9, nanos));
+  }
+
+  /** Sets the fraction field of a {@code TimestampString}.
+   * The precision is determined by the number of leading zeros.
+   * Trailing zeros are stripped.
+   *
+   * <p>For example, {@code
+   * new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withFraction("00506000")}
+   * yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.00506 GMT'}. */
+  public TimestampWithTimeZoneString withFraction(String fraction) {
+    return new TimestampWithTimeZoneString(
+        localDateTime.withFraction(fraction), timeZone);
+  }
+
+  public TimestampWithTimeZoneString withTimeZone(TimeZone timeZone) {
+    if (this.timeZone.equals(timeZone)) {
+      return this;
+    }
+    String localDateTimeString = localDateTime.toString();
+    String v;
+    String fraction;
+    int i = localDateTimeString.indexOf('.');
+    if (i >= 0) {
+      v = localDateTimeString.substring(0, i);
+      fraction = localDateTimeString.substring(i + 1);
+    } else {
+      v = localDateTimeString;
+      fraction = null;
+    }
+    final DateTimeUtils.PrecisionTime pt =
+        DateTimeUtils.parsePrecisionDateTimeLiteral(v,
+            new SimpleDateFormat(DateTimeUtils.TIMESTAMP_FORMAT_STRING, Locale.ROOT),
+            this.timeZone, -1);
+    pt.getCalendar().setTimeZone(timeZone);
+    if (fraction != null) {
+      return new TimestampWithTimeZoneString(
+          pt.getCalendar().get(Calendar.YEAR),
+          pt.getCalendar().get(Calendar.MONTH) + 1,
+          pt.getCalendar().get(Calendar.DAY_OF_MONTH),
+          pt.getCalendar().get(Calendar.HOUR_OF_DAY),
+          pt.getCalendar().get(Calendar.MINUTE),
+          pt.getCalendar().get(Calendar.SECOND),
+          timeZone.getID())
+              .withFraction(fraction);
+    }
+    return new TimestampWithTimeZoneString(
+        pt.getCalendar().get(Calendar.YEAR),
+        pt.getCalendar().get(Calendar.MONTH) + 1,
+        pt.getCalendar().get(Calendar.DAY_OF_MONTH),
+        pt.getCalendar().get(Calendar.HOUR_OF_DAY),
+        pt.getCalendar().get(Calendar.MINUTE),
+        pt.getCalendar().get(Calendar.SECOND),
+        timeZone.getID());
+  }
+
+  @Override public String toString() {
+    return v;
+  }
+
+  @Override public boolean equals(Object o) {
+    // The value is in canonical form (no trailing zeros).
+    return o == this
+        || o instanceof TimestampWithTimeZoneString
+        && ((TimestampWithTimeZoneString) o).v.equals(v);
+  }
+
+  @Override public int hashCode() {
+    return v.hashCode();
+  }
+
+  @Override public int compareTo(TimestampWithTimeZoneString o) {
+    return v.compareTo(o.v);
+  }
+
+  public TimestampWithTimeZoneString round(int precision) {
+    Preconditions.checkArgument(precision >= 0);
+    return new TimestampWithTimeZoneString(
+        localDateTime.round(precision), timeZone);
+  }
+
+  /** Creates a TimestampWithTimeZoneString that is a given number of milliseconds since
+   * the epoch UTC. */
+  public static TimestampWithTimeZoneString fromMillisSinceEpoch(long millis) {
+    return new TimestampWithTimeZoneString(
+        DateTimeUtils.unixTimestampToString(millis) + " " + DateTimeUtils.UTC_ZONE.getID())
+            .withMillis((int) DateTimeUtils.floorMod(millis, 1000));
+  }
+
+  /** Converts this TimestampWithTimeZoneString to a string, truncated or padded with
+   * zeroes to a given precision. */
+  public String toString(int precision) {
+    Preconditions.checkArgument(precision >= 0);
+    return localDateTime.toString(precision) + " " + timeZone.getID();
+  }
+
+  public DateString getLocalDateString() {
+    return new DateString(localDateTime.toString().substring(0, 10));
+  }
+
+  public TimeString getLocalTimeString() {
+    return new TimeString(localDateTime.toString().substring(11));
+  }
+
+  public TimestampString getLocalTimestampString() {
+    return localDateTime;
+  }
+
+}
+
+// End TimestampWithTimeZoneString.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
index 9dd3019..1e6f1a3 100644
--- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
+++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
@@ -274,7 +274,7 @@ public class CalciteRemoteDriverTest {
   @Test public void testRemoteTypeInfo() throws Exception {
     CalciteAssert.hr().with(REMOTE_CONNECTION_FACTORY)
         .metaData(GET_TYPEINFO)
-        .returns(CalciteAssert.checkResultCount(is(43)));
+        .returns(CalciteAssert.checkResultCount(is(45)));
   }
 
   @Test public void testRemoteTableTypes() throws Exception {

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
index 0fd99f8..f9a61fc 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
@@ -24,11 +24,13 @@ import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.DateString;
 import org.apache.calcite.util.TimeString;
 import org.apache.calcite.util.TimestampString;
+import org.apache.calcite.util.TimestampWithTimeZoneString;
 import org.apache.calcite.util.Util;
 
 import org.junit.Test;
 
 import java.util.Calendar;
+import java.util.TimeZone;
 
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.core.Is.is;
@@ -176,6 +178,77 @@ public class RexBuilderTest {
     assertThat(literal.getValueAs(TimestampString.class), notNullValue());
   }
 
+  /** Tests
+   * {@link RexBuilder#makeTimestampWithLocalTimeZoneLiteral(TimestampWithTimeZoneString, int)}. */
+  @Test public void testTimestampWithLocalTimeZoneLiteral() {
+    final RelDataTypeFactory typeFactory =
+        new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RelDataType timestampType =
+        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+    final RelDataType timestampType3 =
+        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 3);
+    final RelDataType timestampType9 =
+        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 9);
+    final RelDataType timestampType18 =
+        typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 18);
+    final RexBuilder builder = new RexBuilder(typeFactory);
+
+    // The new way
+    final TimestampWithTimeZoneString ts = new TimestampWithTimeZoneString(
+        1969, 7, 21, 2, 56, 15, TimeZone.getTimeZone("PST").getID());
+    checkTimestampWithLocalTimeZone(
+        builder.makeLiteral(ts.getLocalTimestampString(), timestampType, false));
+
+    // Now with milliseconds
+    final TimestampWithTimeZoneString ts2 = ts.withMillis(56);
+    assertThat(ts2.toString(), is("1969-07-21 02:56:15.056 PST"));
+    final RexNode literal2 = builder.makeLiteral(
+        ts2.getLocalTimestampString(), timestampType3, false);
+    assertThat(((RexLiteral) literal2).getValue().toString(), is("1969-07-21 02:56:15.056"));
+
+    // Now with nanoseconds
+    final TimestampWithTimeZoneString ts3 = ts.withNanos(56);
+    final RexNode literal3 = builder.makeLiteral(
+        ts3.getLocalTimestampString(), timestampType9, false);
+    assertThat(((RexLiteral) literal3).getValueAs(TimestampString.class)
+            .toString(), is("1969-07-21 02:56:15"));
+    final TimestampWithTimeZoneString ts3b = ts.withNanos(2345678);
+    final RexNode literal3b = builder.makeLiteral(
+        ts3b.getLocalTimestampString(), timestampType9, false);
+    assertThat(((RexLiteral) literal3b).getValueAs(TimestampString.class)
+            .toString(), is("1969-07-21 02:56:15.002"));
+
+    // Now with a very long fraction
+    final TimestampWithTimeZoneString ts4 = ts.withFraction("102030405060708090102");
+    final RexNode literal4 = builder.makeLiteral(
+        ts4.getLocalTimestampString(), timestampType18, false);
+    assertThat(((RexLiteral) literal4).getValueAs(TimestampString.class)
+            .toString(), is("1969-07-21 02:56:15.102"));
+
+    // toString
+    assertThat(ts2.round(1).toString(), is("1969-07-21 02:56:15 PST"));
+    assertThat(ts2.round(2).toString(), is("1969-07-21 02:56:15.05 PST"));
+    assertThat(ts2.round(3).toString(), is("1969-07-21 02:56:15.056 PST"));
+    assertThat(ts2.round(4).toString(), is("1969-07-21 02:56:15.056 PST"));
+
+    assertThat(ts2.toString(6), is("1969-07-21 02:56:15.056000 PST"));
+    assertThat(ts2.toString(1), is("1969-07-21 02:56:15.0 PST"));
+    assertThat(ts2.toString(0), is("1969-07-21 02:56:15 PST"));
+
+    assertThat(ts2.round(0).toString(), is("1969-07-21 02:56:15 PST"));
+    assertThat(ts2.round(0).toString(0), is("1969-07-21 02:56:15 PST"));
+    assertThat(ts2.round(0).toString(1), is("1969-07-21 02:56:15.0 PST"));
+    assertThat(ts2.round(0).toString(2), is("1969-07-21 02:56:15.00 PST"));
+  }
+
+  private void checkTimestampWithLocalTimeZone(RexNode node) {
+    assertThat(node.toString(), is("1969-07-21 02:56:15"));
+    RexLiteral literal = (RexLiteral) node;
+    assertThat(literal.getValue() instanceof TimestampString, is(true));
+    assertThat(literal.getValue2() instanceof Long, is(true));
+    assertThat(literal.getValue3() instanceof Long, is(true));
+  }
+
   /** Tests {@link RexBuilder#makeTimeLiteral(TimeString, int)}. */
   @Test public void testTimeLiteral() {
     final RelDataTypeFactory typeFactory =

http://git-wip-us.apache.org/repos/asf/calcite/blob/939c9a62/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index 6db9435..3e6b414 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -548,6 +548,9 @@ public class CalciteAssert {
         calciteConnection.getProperties().setProperty(
             CalciteConnectionProperty.CREATE_MATERIALIZATIONS.camelName(),
             Boolean.toString(materializationsEnabled));
+        calciteConnection.getProperties().setProperty(
+            CalciteConnectionProperty.TIME_ZONE.camelName(),
+            DateTimeUtils.UTC_ZONE.getID());
       }
       for (Pair<Hook, Function> hook : hooks) {
         closer.add(hook.left.addThread(hook.right));