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 21:55:10 UTC

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

[CALCITE-1947] Add time/timestamp with local time zone types to optimizer


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

Branch: refs/heads/master
Commit: 68abba04a685712225c83e3fdcde8ecf429dea9f
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 14:54:35 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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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/68abba04/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));