Repository: cassandra
Updated Branches:
  refs/heads/trunk 0564c8b42 -> 0409abc26


Add support for + and - operations on dates

patch by Benjamin Lerer; reviewed by Alex Petrov for CASSANDRA-11936


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/0409abc2
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/0409abc2
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/0409abc2

Branch: refs/heads/trunk
Commit: 0409abc26a9bd0dba59bccb37c668f6608dd6ab9
Parents: 0564c8b
Author: Benjamin Lerer <[email protected]>
Authored: Tue Feb 7 11:24:22 2017 +0100
Committer: Benjamin Lerer <[email protected]>
Committed: Tue Feb 7 11:24:22 2017 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 NEWS.txt                                        |   6 +
 doc/source/cql/operators.rst                    |  29 ++-
 .../org/apache/cassandra/cql3/Duration.java     |  72 ++++++++
 .../cassandra/cql3/functions/CastFcts.java      |  10 +-
 .../cassandra/cql3/functions/OperationFcts.java | 144 +++++++++++----
 .../cassandra/cql3/functions/TimeFcts.java      | 177 +++++++------------
 .../cassandra/db/marshal/SimpleDateType.java    |  16 +-
 .../cassandra/db/marshal/TemporalType.java      | 103 +++++++++++
 .../apache/cassandra/db/marshal/TimeType.java   |   2 +-
 .../cassandra/db/marshal/TimeUUIDType.java      |  28 ++-
 .../cassandra/db/marshal/TimestampType.java     |  21 ++-
 .../org/apache/cassandra/cql3/DurationTest.java |  78 +++++++-
 .../cql3/functions/OperationFctsTest.java       |  98 ++++++++++
 .../cassandra/cql3/functions/TimeFctsTest.java  |  19 +-
 15 files changed, 636 insertions(+), 168 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index e1708b7..905a750 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0
+ * Add support for + and - operations on dates (CASSANDRA-11936)
  * Fix consistency of incrementally repaired data (CASSANDRA-9143)
  * Increase commitlog version (CASSANDRA-13161)
  * Make TableMetadata immutable, optimize Schema (CASSANDRA-9425)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/NEWS.txt
----------------------------------------------------------------------
diff --git a/NEWS.txt b/NEWS.txt
index ee1fd6d..9c183f6 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -18,6 +18,12 @@ using the provided 'sstableupgrade' tool.
 
 New features
 ------------
+   - Support for arithmetic operations between `timestamp`/`date` and 
`duration` has been added.
+     See CASSANDRA-11936
+   - Support for arithmetic operations on number has been added. See 
CASSANDRA-11935
+
+3.11
+====
 
 Upgrading
 ---------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/doc/source/cql/operators.rst
----------------------------------------------------------------------
diff --git a/doc/source/cql/operators.rst b/doc/source/cql/operators.rst
index 05f1c61..45f52d9 100644
--- a/doc/source/cql/operators.rst
+++ b/doc/source/cql/operators.rst
@@ -17,6 +17,8 @@
 .. highlight:: cql
 
 .. _arithmetic_operators:
+.. _number-arithmetic:
+.. _datetime--arithmetic:
 
 Arithmetic Operators
 --------------------
@@ -26,15 +28,20 @@ CQL supports the following operators:
 =============== 
=======================================================================================================
  Operator        Description
 =============== 
=======================================================================================================
- \- (unary)       Negates operand
- \+               Addition
- \-               Substraction
- \*               Multiplication
+ \- (unary)      Negates operand
+ \+              Addition
+ \-              Substraction
+ \*              Multiplication
  /               Division
  %               Returns the remainder of a division
 =============== 
=======================================================================================================
 
-Arithmetic operations are only supported on numeric types or counters.
+.. _number-arithmetic:
+
+Number Arithmetic
+^^^^^^^^^^^^^^^^^
+
+All arithmetic operations are supported on numeric types or counters.
 
 The return type of the operation will be based on the operand types:
 
@@ -55,3 +62,15 @@ The return type of the operation will be based on the 
operand types:
 ``*``, ``/`` and ``%`` operators have a higher precedence level than ``+`` and 
``-`` operator. By consequence,
 they will be evaluated before. If two operator in an expression have the same 
precedence level, they will be evaluated
 left to right based on their position in the expression.
+
+.. _datetime--arithmetic:
+
+Datetime Arithmetic
+^^^^^^^^^^^^^^^^^^^
+
+A ``duration`` can be added (+) or substracted (-) from a ``timestamp`` or a 
``date`` to create a new
+``timestamp`` or ``date``. So for instance::
+
+    SELECT * FROM myTable WHERE t = '2017-01-01' - 2d
+
+will select all the records with a value of ``t`` which is in the last 2 days 
of 2016.

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/Duration.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Duration.java 
b/src/java/org/apache/cassandra/cql3/Duration.java
index 48f8850..e6151cb 100644
--- a/src/java/org/apache/cassandra/cql3/Duration.java
+++ b/src/java/org/apache/cassandra/cql3/Duration.java
@@ -17,6 +17,9 @@
  */
 package org.apache.cassandra.cql3;
 
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -26,6 +29,7 @@ import org.apache.cassandra.serializers.MarshalException;
 
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
 import static 
org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
+import static org.apache.commons.lang.time.DateUtils.MILLIS_PER_DAY;
 
 /**
  * Represents a duration. A durations store separately months, days, and 
seconds due to the fact that
@@ -265,6 +269,53 @@ public final class Duration
         return nanoseconds;
     }
 
+    /**
+     * Adds this duration to the specified time in milliseconds.
+     * @param timeInMillis the time to which the duration must be added
+     * @return the specified time plus this duration
+     */
+    public long addTo(long timeInMillis)
+    {
+        return add(timeInMillis, months, days, nanoseconds);
+    }
+
+    /**
+     * Substracts this duration from the specified time in milliseconds.
+     * @param timeInMillis the time from which the duration must be substracted
+     * @return the specified time minus this duration
+     */
+    public long substractFrom(long timeInMillis)
+    {
+        return add(timeInMillis, -months, -days, -nanoseconds);
+    }
+
+    /**
+     * Adds the specified months, days and nanoseconds to the specified time 
in milliseconds.
+     *
+     * @param timeInMillis the time to which the months, days and nanoseconds 
must be added
+     * @param months the number of months to add
+     * @param days the number of days to add
+     * @param nanoseconds the number of nanoseconds to add
+     * @return the specified time plus the months, days and nanoseconds
+     */
+    private static long add(long timeInMillis, int months, int days, long 
nanoseconds)
+    {
+        // If the duration does not contains any months we can can ignore 
daylight saving,
+        // as time zones are not supported, and simply look at the milliseconds
+        if (months == 0)
+        {
+            long durationInMillis = (days * MILLIS_PER_DAY) + (nanoseconds / 
NANOS_PER_MILLI);
+            return timeInMillis + durationInMillis;
+        }
+
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), 
Locale.US);
+        calendar.setTimeInMillis(timeInMillis);
+        calendar.add(Calendar.MONTH, months);
+        calendar.add(Calendar.DAY_OF_MONTH, days);
+        calendar.add(Calendar.MILLISECOND, (int) (nanoseconds / 
NANOS_PER_MILLI));
+        return calendar.getTimeInMillis();
+    }
+
     @Override
     public int hashCode()
     {
@@ -309,6 +360,27 @@ public final class Duration
     }
 
     /**
+     * Checks if that duration has a day precision (nothing bellow the day 
level).
+     * @return {@code true} if that duration has a day precision, {@code 
false} otherwise
+     */
+    public boolean hasDayPrecision()
+    {
+        return getNanoseconds() == 0;
+    }
+
+    /**
+     * Checks if that duration has a millisecond precision (nothing bellow the 
millisecond level).
+     * @return {@code true} if that duration has a millisecond precision, 
{@code false} otherwise
+     */
+    public boolean hasMillisecondPrecision()
+    {
+        // Checks that the duration has no data bellow milliseconds. We can do 
that by checking that the last
+        // 6 bits of the number of nanoseconds are all zeros. The compiler 
will replace the call to
+        // numberOfTrailingZeros by a TZCNT instruction.
+        return Long.numberOfTrailingZeros(getNanoseconds()) >= 6;
+    }
+
+    /**
      * Appends the result of the division to the specified builder if the 
dividend is not zero.
      *
      * @param builder the builder to append to

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/functions/CastFcts.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/CastFcts.java 
b/src/java/org/apache/cassandra/cql3/functions/CastFcts.java
index 9e5c729..be1d55b 100644
--- a/src/java/org/apache/cassandra/cql3/functions/CastFcts.java
+++ b/src/java/org/apache/cassandra/cql3/functions/CastFcts.java
@@ -46,6 +46,8 @@ import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.db.marshal.UUIDType;
 import org.apache.cassandra.transport.ProtocolVersion;
 
+import static org.apache.cassandra.cql3.functions.TimeFcts.*;
+
 import org.apache.commons.lang3.text.WordUtils;
 
 /**
@@ -93,14 +95,14 @@ public final class CastFcts
         functions.add(CastAsTextFunction.create(BooleanType.instance, 
AsciiType.instance));
         functions.add(CastAsTextFunction.create(BooleanType.instance, 
UTF8Type.instance));
 
-        functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, 
SimpleDateType.instance, TimeFcts.timeUuidtoDate));
-        functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, 
TimestampType.instance, TimeFcts.timeUuidToTimestamp));
+        functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, 
SimpleDateType.instance, toDate(TimeUUIDType.instance)));
+        functions.add(CassandraFunctionWrapper.create(TimeUUIDType.instance, 
TimestampType.instance, toTimestamp(TimeUUIDType.instance)));
         functions.add(CastAsTextFunction.create(TimeUUIDType.instance, 
AsciiType.instance));
         functions.add(CastAsTextFunction.create(TimeUUIDType.instance, 
UTF8Type.instance));
-        functions.add(CassandraFunctionWrapper.create(TimestampType.instance, 
SimpleDateType.instance, TimeFcts.timestampToDate));
+        functions.add(CassandraFunctionWrapper.create(TimestampType.instance, 
SimpleDateType.instance, toDate(TimestampType.instance)));
         functions.add(CastAsTextFunction.create(TimestampType.instance, 
AsciiType.instance));
         functions.add(CastAsTextFunction.create(TimestampType.instance, 
UTF8Type.instance));
-        functions.add(CassandraFunctionWrapper.create(SimpleDateType.instance, 
TimestampType.instance, TimeFcts.dateToTimestamp));
+        functions.add(CassandraFunctionWrapper.create(SimpleDateType.instance, 
TimestampType.instance, toTimestamp(SimpleDateType.instance)));
         functions.add(CastAsTextFunction.create(SimpleDateType.instance, 
AsciiType.instance));
         functions.add(CastAsTextFunction.create(SimpleDateType.instance, 
UTF8Type.instance));
         functions.add(CastAsTextFunction.create(TimeType.instance, 
AsciiType.instance));

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java 
b/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java
index 0a039c0..9232fc1 100644
--- a/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java
+++ b/src/java/org/apache/cassandra/cql3/functions/OperationFcts.java
@@ -37,18 +37,26 @@ public final class OperationFcts
     {
         ADDITION('+', "_add")
         {
-            protected ByteBuffer execute(NumberType<?> resultType,
-                                         NumberType<?> leftType,
-                                         ByteBuffer left,
-                                         NumberType<?> rightType,
-                                         ByteBuffer right)
+            protected ByteBuffer executeOnNumerics(NumberType<?> resultType,
+                                                   NumberType<?> leftType,
+                                                   ByteBuffer left,
+                                                   NumberType<?> rightType,
+                                                   ByteBuffer right)
             {
                 return resultType.add(leftType, left, rightType, right);
             }
+
+            @Override
+            protected ByteBuffer executeOnTemporals(TemporalType<?> type,
+                                                    ByteBuffer temporal,
+                                                    ByteBuffer duration)
+            {
+                return type.addDuration(temporal, duration);
+            }
         },
         SUBSTRACTION('-', "_substract")
         {
-            protected ByteBuffer execute(NumberType<?> resultType,
+            protected ByteBuffer executeOnNumerics(NumberType<?> resultType,
                                          NumberType<?> leftType,
                                          ByteBuffer left,
                                          NumberType<?> rightType,
@@ -56,10 +64,18 @@ public final class OperationFcts
             {
                 return resultType.substract(leftType, left, rightType, right);
             }
+
+            @Override
+            protected ByteBuffer executeOnTemporals(TemporalType<?> type,
+                                                    ByteBuffer temporal,
+                                                    ByteBuffer duration)
+            {
+                return type.substractDuration(temporal, duration);
+            }
         },
         MULTIPLICATION('*', "_multiply")
         {
-            protected ByteBuffer execute(NumberType<?> resultType,
+            protected ByteBuffer executeOnNumerics(NumberType<?> resultType,
                                          NumberType<?> leftType,
                                          ByteBuffer left,
                                          NumberType<?> rightType,
@@ -70,7 +86,7 @@ public final class OperationFcts
         },
         DIVISION('/', "_divide")
         {
-            protected ByteBuffer execute(NumberType<?> resultType,
+            protected ByteBuffer executeOnNumerics(NumberType<?> resultType,
                                          NumberType<?> leftType,
                                          ByteBuffer left,
                                          NumberType<?> rightType,
@@ -81,7 +97,7 @@ public final class OperationFcts
         },
         MODULO('%', "_modulo")
         {
-            protected ByteBuffer execute(NumberType<?> resultType,
+            protected ByteBuffer executeOnNumerics(NumberType<?> resultType,
                                          NumberType<?> leftType,
                                          ByteBuffer left,
                                          NumberType<?> rightType,
@@ -108,7 +124,7 @@ public final class OperationFcts
         }
 
         /**
-         * Executes the operation between the specified operand.
+         * Executes the operation between the specified numeric operand.
          *
          * @param resultType the result ype of the operation
          * @param leftType the type of the left operand
@@ -117,11 +133,26 @@ public final class OperationFcts
          * @param right the right operand
          * @return the operation result
          */
-        protected abstract ByteBuffer execute(NumberType<?> resultType,
-                                              NumberType<?> leftType,
-                                              ByteBuffer left,
-                                              NumberType<?> rightType,
-                                              ByteBuffer right);
+        protected abstract ByteBuffer executeOnNumerics(NumberType<?> 
resultType,
+                                                        NumberType<?> leftType,
+                                                        ByteBuffer left,
+                                                        NumberType<?> 
rightType,
+                                                        ByteBuffer right);
+
+        /**
+         * Executes the operation on the specified temporal operand.
+         *
+         * @param type the temporal type
+         * @param temporal the temporal value
+         * @param duration the duration
+         * @return the operation result
+         */
+        protected ByteBuffer executeOnTemporals(TemporalType<?> type,
+                                                ByteBuffer temporal,
+                                                ByteBuffer duration)
+        {
+            throw new UnsupportedOperationException();
+        }
 
         /**
          * Returns the {@code OPERATOR} associated to the specified function.
@@ -178,14 +209,18 @@ public final class OperationFcts
             for (NumberType<?> right : numericTypes)
             {
                 NumberType<?> returnType = returnType(left, right);
-                functions.add(new OperationFunction(returnType, left, 
OPERATION.ADDITION, right));
-                functions.add(new OperationFunction(returnType, left, 
OPERATION.SUBSTRACTION, right));
-                functions.add(new OperationFunction(returnType, left, 
OPERATION.MULTIPLICATION, right));
-                functions.add(new OperationFunction(returnType, left, 
OPERATION.DIVISION, right));
-                functions.add(new OperationFunction(returnType, left, 
OPERATION.MODULO, right));
+                for (OPERATION operation : OPERATION.values())
+                    functions.add(new NumericOperationFunction(returnType, 
left, operation, right));
             }
-            functions.add(new NegationFunction(left));
+            functions.add(new NumericNegationFunction(left));
+        }
+
+        for (OPERATION operation : new OPERATION[] {OPERATION.ADDITION, 
OPERATION.SUBSTRACTION})
+        {
+            functions.add(new 
TemporalOperationFunction(TimestampType.instance, operation));
+            functions.add(new 
TemporalOperationFunction(SimpleDateType.instance, operation));
         }
+
         return functions;
     }
 
@@ -298,16 +333,16 @@ public final class OperationFcts
     }
 
     /**
-     * Function that execute operations.
+     * Base class for functions that execute operations.
      */
-    private static class OperationFunction extends NativeScalarFunction
+    private static abstract class OperationFunction extends 
NativeScalarFunction
     {
         private final OPERATION operation;
 
-        public OperationFunction(NumberType<?> returnType,
-                                 NumberType<?> left,
+        public OperationFunction(AbstractType<?> returnType,
+                                 AbstractType<?> left,
                                  OPERATION operation,
-                                 NumberType<?> right)
+                                 AbstractType<?> right)
         {
             super(operation.functionName, returnType, left, right);
             this.operation = operation;
@@ -326,13 +361,9 @@ public final class OperationFcts
             if (left == null || !left.hasRemaining() || right == null || 
!right.hasRemaining())
                 return null;
 
-            NumberType<?> leftType = (NumberType<?>) argTypes().get(0);
-            NumberType<?> rightType = (NumberType<?>) argTypes().get(1);
-            NumberType<?> resultType = (NumberType<?>) returnType();
-
             try
             {
-                return operation.execute(resultType, leftType, left, 
rightType, right);
+                return doExecute(left, operation, right);
             }
             catch (Exception e)
             {
@@ -340,22 +371,67 @@ public final class OperationFcts
             }
         }
 
+        protected abstract ByteBuffer doExecute(ByteBuffer left, OPERATION 
operation, ByteBuffer right);
+
         /**
          * Returns the operator symbol.
          * @return the operator symbol
          */
-        private char getOperator()
+        private final char getOperator()
         {
             return operation.symbol;
         }
     }
 
     /**
+     * Function that execute operations on numbers.
+     */
+    private static class NumericOperationFunction extends OperationFunction
+    {
+        public NumericOperationFunction(NumberType<?> returnType,
+                                        NumberType<?> left,
+                                        OPERATION operation,
+                                        NumberType<?> right)
+        {
+            super(returnType, left, operation, right);
+        }
+
+        @Override
+        protected ByteBuffer doExecute(ByteBuffer left, OPERATION operation, 
ByteBuffer right)
+        {
+            NumberType<?> leftType = (NumberType<?>) argTypes().get(0);
+            NumberType<?> rightType = (NumberType<?>) argTypes().get(1);
+            NumberType<?> resultType = (NumberType<?>) returnType();
+
+            return operation.executeOnNumerics(resultType, leftType, left, 
rightType, right);
+        }
+    }
+
+    /**
+     * Function that execute operations on temporals (timestamp, date, ...).
+     */
+    private static class TemporalOperationFunction extends OperationFunction
+    {
+        public TemporalOperationFunction(TemporalType<?> type,
+                                         OPERATION operation)
+        {
+            super(type, type, operation, DurationType.instance);
+        }
+
+        @Override
+        protected ByteBuffer doExecute(ByteBuffer left, OPERATION operation, 
ByteBuffer right)
+        {
+            TemporalType<?> resultType = (TemporalType<?>) returnType();
+            return operation.executeOnTemporals(resultType, left, right);
+        }
+    }
+
+    /**
      * Function that negate a number.
      */
-    private static class NegationFunction extends NativeScalarFunction
+    private static class NumericNegationFunction extends NativeScalarFunction
     {
-        public NegationFunction(NumberType<?> inputType)
+        public NumericNegationFunction(NumberType<?> inputType)
         {
             super(NEGATION_FUNCTION_NAME, inputType, inputType);
         }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java 
b/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java
index e682dcd..944f183 100644
--- a/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java
+++ b/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java
@@ -19,7 +19,6 @@ package org.apache.cassandra.cql3.functions;
 
 import java.nio.ByteBuffer;
 import java.util.Collection;
-import java.util.Date;
 import java.util.List;
 
 import com.google.common.collect.ImmutableList;
@@ -37,26 +36,30 @@ public abstract class TimeFcts
 
     public static Collection<Function> all()
     {
-        return ImmutableList.of(nowFct,
+        return ImmutableList.of(now(TimeUUIDType.instance),
                                 minTimeuuidFct,
                                 maxTimeuuidFct,
                                 dateOfFct,
                                 unixTimestampOfFct,
-                                timeUuidtoDate,
-                                timeUuidToTimestamp,
-                                timeUuidToUnixTimestamp,
-                                timestampToUnixTimestamp,
-                                timestampToDate,
-                                dateToUnixTimestamp,
-                                dateToTimestamp);
+                                toDate(TimeUUIDType.instance),
+                                toTimestamp(TimeUUIDType.instance),
+                                toUnixTimestamp(TimeUUIDType.instance),
+                                toUnixTimestamp(TimestampType.instance),
+                                toDate(SimpleDateType.instance),
+                                toUnixTimestamp(SimpleDateType.instance),
+                                toTimestamp(SimpleDateType.instance));
     }
 
-    public static final Function nowFct = new NativeScalarFunction("now", 
TimeUUIDType.instance)
+    public static final Function now(final TemporalType<?> type)
     {
-        public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
+        return new NativeScalarFunction("now", type)
         {
-            return ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
-        }
+            @Override
+            public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
+            {
+                return type.now();
+            }
+        };
     };
 
     public static final Function minTimeuuidFct = new 
NativeScalarFunction("mintimeuuid", TimeUUIDType.instance, 
TimestampType.instance)
@@ -134,114 +137,66 @@ public abstract class TimeFcts
         }
     };
 
-    /**
-     * Function that convert a value of <code>TIMEUUID</code> into a value of 
type <code>DATE</code>.
-     */
-    public static final NativeScalarFunction timeUuidtoDate = new 
NativeScalarFunction("todate", SimpleDateType.instance, TimeUUIDType.instance)
-    {
-        public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
-        {
-            ByteBuffer bb = parameters.get(0);
-            if (bb == null)
-                return null;
-
-            long timeInMillis = UUIDGen.unixTimestamp(UUIDGen.getUUID(bb));
-            return SimpleDateType.instance.fromTimeInMillis(timeInMillis);
-        }
-    };
-
-    /**
-     * Function that convert a value of type <code>TIMEUUID</code> into a 
value of type <code>TIMESTAMP</code>.
-     */
-    public static final NativeScalarFunction timeUuidToTimestamp = new 
NativeScalarFunction("totimestamp", TimestampType.instance, 
TimeUUIDType.instance)
-    {
-        public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
-        {
-            ByteBuffer bb = parameters.get(0);
-            if (bb == null)
-                return null;
-
-            long timeInMillis = UUIDGen.unixTimestamp(UUIDGen.getUUID(bb));
-            return TimestampType.instance.fromTimeInMillis(timeInMillis);
-        }
-    };
-
-    /**
-     * Function that convert a value of type <code>TIMEUUID</code> into an 
UNIX timestamp.
-     */
-    public static final NativeScalarFunction timeUuidToUnixTimestamp = new 
NativeScalarFunction("tounixtimestamp", LongType.instance, 
TimeUUIDType.instance)
-    {
-        public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
-        {
-            ByteBuffer bb = parameters.get(0);
-            if (bb == null)
-                return null;
-
-            return 
ByteBufferUtil.bytes(UUIDGen.unixTimestamp(UUIDGen.getUUID(bb)));
-        }
-    };
-
-    /**
-     * Function that convert a value of type <code>TIMESTAMP</code> into an 
UNIX timestamp.
-     */
-    public static final NativeScalarFunction timestampToUnixTimestamp = new 
NativeScalarFunction("tounixtimestamp", LongType.instance, 
TimestampType.instance)
-    {
-        public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
-        {
-            ByteBuffer bb = parameters.get(0);
-            if (bb == null)
-                return null;
-
-            Date date = TimestampType.instance.compose(bb);
-            return date == null ? null : ByteBufferUtil.bytes(date.getTime());
-        }
-    };
-
    /**
-    * Function that convert a value of type <code>TIMESTAMP</code> into a 
<code>DATE</code>.
+    * Creates a function that convert a value of the specified type into a 
<code>DATE</code>.
+    * @param type the temporal type
+    * @return a function that convert a value of the specified type into a 
<code>DATE</code>.
     */
-   public static final NativeScalarFunction timestampToDate = new 
NativeScalarFunction("todate", SimpleDateType.instance, TimestampType.instance)
+   public static final NativeScalarFunction toDate(final TemporalType<?> type)
    {
-       public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
+       return new NativeScalarFunction("todate", SimpleDateType.instance, type)
        {
-           ByteBuffer bb = parameters.get(0);
-           if (bb == null)
-               return null;
-
-           Date date = TimestampType.instance.compose(bb);
-           return date == null ? null : 
SimpleDateType.instance.fromTimeInMillis(date.getTime());
-       }
-   };
+           public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
+           {
+               ByteBuffer bb = parameters.get(0);
+               if (bb == null || !bb.hasRemaining())
+                   return null;
+
+               long millis = type.toTimeInMillis(bb);
+               return SimpleDateType.instance.fromTimeInMillis(millis);
+           }
+       };
+   }
 
    /**
-    * Function that convert a value of type <code>TIMESTAMP</code> into a 
<code>DATE</code>.
+    * Creates a function that convert a value of the specified type into a 
<code>TIMESTAMP</code>.
+    * @param type the temporal type
+    * @return a function that convert a value of the specified type into a 
<code>TIMESTAMP</code>.
     */
-   public static final NativeScalarFunction dateToTimestamp = new 
NativeScalarFunction("totimestamp", TimestampType.instance, 
SimpleDateType.instance)
+   public static final NativeScalarFunction toTimestamp(final TemporalType<?> 
type)
    {
-       public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
+       return new NativeScalarFunction("totimestamp", TimestampType.instance, 
type)
        {
-           ByteBuffer bb = parameters.get(0);
-           if (bb == null)
-               return null;
+           public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
+           {
+               ByteBuffer bb = parameters.get(0);
+               if (bb == null || !bb.hasRemaining())
+                   return null;
+
+               long millis = type.toTimeInMillis(bb);
+               return TimestampType.instance.fromTimeInMillis(millis);
+           }
+       };
+   }
 
-           long millis = SimpleDateType.instance.toTimeInMillis(bb);
-           return TimestampType.instance.fromTimeInMillis(millis);
-       }
-   };
-
-   /**
-    * Function that convert a value of type <code>DATE</code> into an UNIX 
timestamp.
-    */
-   public static final NativeScalarFunction dateToUnixTimestamp = new 
NativeScalarFunction("tounixtimestamp", LongType.instance, 
SimpleDateType.instance)
-   {
-       public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
-       {
-           ByteBuffer bb = parameters.get(0);
-           if (bb == null)
-               return null;
+    /**
+     * Creates a function that convert a value of the specified type into an 
UNIX timestamp.
+     * @param type the temporal type
+     * @return a function that convert a value of the specified type into an 
UNIX timestamp.
+     */
+    public static final NativeScalarFunction toUnixTimestamp(final 
TemporalType<?> type)
+    {
+        return new NativeScalarFunction("tounixtimestamp", LongType.instance, 
type)
+        {
+            public ByteBuffer execute(ProtocolVersion protocolVersion, 
List<ByteBuffer> parameters)
+            {
+                ByteBuffer bb = parameters.get(0);
+                if (bb == null || !bb.hasRemaining())
+                    return null;
 
-           return 
ByteBufferUtil.bytes(SimpleDateType.instance.toTimeInMillis(bb));
-       }
-   };
+                return ByteBufferUtil.bytes(type.toTimeInMillis(bb));
+            }
+        };
+    }
 }
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java 
b/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java
index 9db5e36..f883ccd 100644
--- a/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java
+++ b/src/java/org/apache/cassandra/db/marshal/SimpleDateType.java
@@ -21,14 +21,18 @@ import java.nio.ByteBuffer;
 
 import org.apache.cassandra.cql3.CQL3Type;
 import org.apache.cassandra.cql3.Constants;
+import org.apache.cassandra.cql3.Duration;
 import org.apache.cassandra.cql3.Term;
+import org.apache.cassandra.cql3.statements.RequestValidations;
 import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.serializers.SimpleDateSerializer;
 import org.apache.cassandra.serializers.TypeSerializer;
 import org.apache.cassandra.transport.ProtocolVersion;
 import org.apache.cassandra.utils.ByteBufferUtil;
 
-public class SimpleDateType extends AbstractType<Integer>
+import static 
org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
+
+public class SimpleDateType extends TemporalType<Integer>
 {
     public static final SimpleDateType instance = new SimpleDateType();
 
@@ -39,11 +43,13 @@ public class SimpleDateType extends AbstractType<Integer>
         return 
ByteBufferUtil.bytes(SimpleDateSerializer.dateStringToDays(source));
     }
 
+    @Override
     public ByteBuffer fromTimeInMillis(long millis) throws MarshalException
     {
         return 
ByteBufferUtil.bytes(SimpleDateSerializer.timeInMillisToDay(millis));
     }
 
+    @Override
     public long toTimeInMillis(ByteBuffer buffer) throws MarshalException
     {
         return 
SimpleDateSerializer.dayToTimeInMillis(ByteBufferUtil.toInt(buffer));
@@ -85,4 +91,12 @@ public class SimpleDateType extends AbstractType<Integer>
     {
         return SimpleDateSerializer.instance;
     }
+
+    @Override
+    protected void validateDuration(Duration duration)
+    {
+        // Checks that the duration has no data below days.
+        if (!duration.hasDayPrecision())
+            throw invalidRequest("The duration must have a day precision. Was: 
%s", duration);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/TemporalType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/TemporalType.java 
b/src/java/org/apache/cassandra/db/marshal/TemporalType.java
new file mode 100644
index 0000000..4e2ac5a
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/marshal/TemporalType.java
@@ -0,0 +1,103 @@
+/*
+ * 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.cassandra.db.marshal;
+
+import java.nio.ByteBuffer;
+
+import org.apache.cassandra.cql3.Duration;
+
+/**
+ * Base type for temporal types (timestamp, date ...).
+ *
+ */
+public abstract class TemporalType<T> extends AbstractType<T>
+{
+    protected TemporalType(ComparisonType comparisonType)
+    {
+        super(comparisonType);
+    }
+
+    /**
+     * Returns the current temporal value.
+     * @return the current temporal value.
+     */
+    public ByteBuffer now()
+    {
+        return fromTimeInMillis(System.currentTimeMillis());
+    }
+
+    /**
+     * Converts this temporal in UNIX timestamp.
+     * @param value the temporal value.
+     * @return the UNIX timestamp corresponding to this temporal.
+     */
+    public long toTimeInMillis(ByteBuffer value)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns the temporal value corresponding to the specified UNIX 
timestamp.
+     * @param timeInMillis the UNIX timestamp to convert
+     * @return the temporal value corresponding to the specified UNIX timestamp
+     */
+    public ByteBuffer fromTimeInMillis(long timeInMillis)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Adds the duration to the specified value.
+     *
+     * @param temporal the value to add to
+     * @param duration the duration to add
+     * @return the addition result
+     */
+    public ByteBuffer addDuration(ByteBuffer temporal,
+                                  ByteBuffer duration)
+    {
+        long timeInMillis = toTimeInMillis(temporal);
+        Duration d = DurationType.instance.compose(duration);
+        validateDuration(d);
+        return fromTimeInMillis(d.addTo(timeInMillis));
+    }
+
+    /**
+     * Substract the duration from the specified value.
+     *
+     * @param temporal the value to substract from
+     * @param duration the duration to substract
+     * @return the substracion result
+     */
+    public ByteBuffer substractDuration(ByteBuffer temporal,
+                                ByteBuffer duration)
+    {
+        long timeInMillis = toTimeInMillis(temporal);
+        Duration d = DurationType.instance.compose(duration);
+        validateDuration(d);
+        return fromTimeInMillis(d.substractFrom(timeInMillis));
+    }
+
+    /**
+     * Validates that the duration has the correct precision.
+     * @param duration the duration to validate.
+     */
+    protected void validateDuration(Duration duration)
+    {
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/TimeType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/TimeType.java 
b/src/java/org/apache/cassandra/db/marshal/TimeType.java
index 99f4f67..4b9d3a1 100644
--- a/src/java/org/apache/cassandra/db/marshal/TimeType.java
+++ b/src/java/org/apache/cassandra/db/marshal/TimeType.java
@@ -30,7 +30,7 @@ import org.apache.cassandra.transport.ProtocolVersion;
 /**
  * Nanosecond resolution time values
  */
-public class TimeType extends AbstractType<Long>
+public class TimeType extends TemporalType<Long>
 {
     public static final TimeType instance = new TimeType();
     private TimeType() {super(ComparisonType.BYTE_ORDER);} // singleton

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java 
b/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java
index f8e58db..39d1513 100644
--- a/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java
+++ b/src/java/org/apache/cassandra/db/marshal/TimeUUIDType.java
@@ -21,13 +21,15 @@ import java.nio.ByteBuffer;
 import java.util.UUID;
 
 import org.apache.cassandra.cql3.CQL3Type;
+import org.apache.cassandra.cql3.ColumnSpecification;
 import org.apache.cassandra.cql3.Constants;
 import org.apache.cassandra.cql3.Term;
 import org.apache.cassandra.serializers.TypeSerializer;
+import org.apache.cassandra.utils.UUIDGen;
 import org.apache.cassandra.serializers.MarshalException;
 import org.apache.cassandra.serializers.TimeUUIDSerializer;
 
-public class TimeUUIDType extends AbstractType<UUID>
+public class TimeUUIDType extends TemporalType<UUID>
 {
     public static final TimeUUIDType instance = new TimeUUIDType();
 
@@ -134,4 +136,28 @@ public class TimeUUIDType extends AbstractType<UUID>
     {
         return 16;
     }
+
+    @Override
+    public long toTimeInMillis(ByteBuffer value)
+    {
+        return UUIDGen.unixTimestamp(UUIDGen.getUUID(value));
+    }
+
+    @Override
+    public ByteBuffer addDuration(ByteBuffer temporal, ByteBuffer duration)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ByteBuffer substractDuration(ByteBuffer temporal, ByteBuffer 
duration)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ByteBuffer now()
+    {
+        return ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/src/java/org/apache/cassandra/db/marshal/TimestampType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/TimestampType.java 
b/src/java/org/apache/cassandra/db/marshal/TimestampType.java
index ae74e2f..0699050 100644
--- a/src/java/org/apache/cassandra/db/marshal/TimestampType.java
+++ b/src/java/org/apache/cassandra/db/marshal/TimestampType.java
@@ -21,7 +21,10 @@ import java.nio.ByteBuffer;
 import java.util.Date;
 
 import org.apache.cassandra.cql3.Constants;
+import org.apache.cassandra.cql3.Duration;
 import org.apache.cassandra.cql3.Term;
+import org.apache.cassandra.cql3.statements.RequestValidations;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.cassandra.cql3.CQL3Type;
@@ -31,6 +34,8 @@ import org.apache.cassandra.serializers.TimestampSerializer;
 import org.apache.cassandra.transport.ProtocolVersion;
 import org.apache.cassandra.utils.ByteBufferUtil;
 
+import static 
org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
+
 /**
  * Type for date-time values.
  *
@@ -38,7 +43,7 @@ import org.apache.cassandra.utils.ByteBufferUtil;
  * pre-unix-epoch dates, sorting them *after* post-unix-epoch ones (due to it's
  * use of unsigned bytes comparison).
  */
-public class TimestampType extends AbstractType<Date>
+public class TimestampType extends TemporalType<Date>
 {
     private static final Logger logger = 
LoggerFactory.getLogger(TimestampType.class);
 
@@ -65,12 +70,19 @@ public class TimestampType extends AbstractType<Date>
       return 
ByteBufferUtil.bytes(TimestampSerializer.dateStringToTimestamp(source));
     }
 
+    @Override
     public ByteBuffer fromTimeInMillis(long millis) throws MarshalException
     {
         return ByteBufferUtil.bytes(millis);
     }
 
     @Override
+    public long toTimeInMillis(ByteBuffer value)
+    {
+        return ByteBufferUtil.toLong(value);
+    }
+
+    @Override
     public Term fromJSONObject(Object parsed) throws MarshalException
     {
         if (parsed instanceof Long)
@@ -132,4 +144,11 @@ public class TimestampType extends AbstractType<Date>
     {
         return 8;
     }
+
+    @Override
+    protected void validateDuration(Duration duration)
+    {
+        if (!duration.hasMillisecondPrecision())
+            throw invalidRequest("The duration must have a millisecond 
precision. Was: %s", duration);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/test/unit/org/apache/cassandra/cql3/DurationTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/DurationTest.java 
b/test/unit/org/apache/cassandra/cql3/DurationTest.java
index b8f4400..ef031c4 100644
--- a/test/unit/org/apache/cassandra/cql3/DurationTest.java
+++ b/test/unit/org/apache/cassandra/cql3/DurationTest.java
@@ -18,6 +18,14 @@
  */
 package org.apache.cassandra.cql3;
 
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.time.DateUtils;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -99,11 +107,79 @@ public class DurationTest
         assertInvalidDuration("P0002-00-20", "Unable to convert 'P0002-00-20' 
to a duration");
     }
 
+    @Test
+    public void testAddTo()
+    {
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("0m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("10us").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T00:10:00"), 
Duration.from("10m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T01:30:00"), 
Duration.from("90m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T02:10:00"), 
Duration.from("2h10m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-23T00:10:00"), 
Duration.from("2d10m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-24T01:00:00"), 
Duration.from("2d25h").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-10-21T00:00:00"), 
Duration.from("1mo").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2017-11-21T00:00:00"), 
Duration.from("14mo").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2017-02-28T00:00:00"), 
Duration.from("12mo").addTo(toMillis("2016-02-29T00:00:00")));
+    }
+
+    @Test
+    public void testAddToWithNegativeDurations()
+    {
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("-0m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("-10us").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-20T23:50:00"), 
Duration.from("-10m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-20T22:30:00"), 
Duration.from("-90m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-20T21:50:00"), 
Duration.from("-2h10m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-18T23:50:00"), 
Duration.from("-2d10m").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-17T23:00:00"), 
Duration.from("-2d25h").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-08-21T00:00:00"), 
Duration.from("-1mo").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2015-07-21T00:00:00"), 
Duration.from("-14mo").addTo(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2015-02-28T00:00:00"), 
Duration.from("-12mo").addTo(toMillis("2016-02-29T00:00:00")));
+    }
+
+    @Test
+    public void testSubstractFrom()
+    {
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("0m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("10us").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-20T23:50:00"), 
Duration.from("10m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-20T22:30:00"), 
Duration.from("90m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-20T21:50:00"), 
Duration.from("2h10m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-18T23:50:00"), 
Duration.from("2d10m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-17T23:00:00"), 
Duration.from("2d25h").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-08-21T00:00:00"), 
Duration.from("1mo").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2015-07-21T00:00:00"), 
Duration.from("14mo").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2015-02-28T00:00:00"), 
Duration.from("12mo").substractFrom(toMillis("2016-02-29T00:00:00")));
+    }
+
+    @Test
+    public void testSubstractWithNegativeDurations()
+    {
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("-0m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T00:00:00"), 
Duration.from("-10us").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T00:10:00"), 
Duration.from("-10m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T01:30:00"), 
Duration.from("-90m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-21T02:10:00"), 
Duration.from("-2h10m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-23T00:10:00"), 
Duration.from("-2d10m").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-09-24T01:00:00"), 
Duration.from("-2d25h").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2016-10-21T00:00:00"), 
Duration.from("-1mo").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2017-11-21T00:00:00"), 
Duration.from("-14mo").substractFrom(toMillis("2016-09-21T00:00:00")));
+        assertEquals(toMillis("2017-02-28T00:00:00"), 
Duration.from("-12mo").substractFrom(toMillis("2016-02-29T00:00:00")));
+    }
+
+    private long toMillis(String timeAsString)
+    {
+        SimpleDateFormat parser = new 
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+        parser.setTimeZone(TimeZone.getTimeZone("UTC"));
+        Date date = parser.parse(timeAsString, new ParsePosition(0));
+        return DateUtils.truncate(date, Calendar.SECOND).getTime();
+    }
+
     public void assertInvalidDuration(String duration, String 
expectedErrorMessage)
     {
         try
         {
-            System.out.println(Duration.from(duration));
+            Duration.from(duration);
             Assert.fail();
         }
         catch (InvalidRequestException e)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java
----------------------------------------------------------------------
diff --git 
a/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java 
b/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java
index 6de5fdb..49c9e30 100644
--- a/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java
+++ b/test/unit/org/apache/cassandra/cql3/functions/OperationFctsTest.java
@@ -19,12 +19,15 @@ package org.apache.cassandra.cql3.functions;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.Date;
 
 import org.junit.Test;
 
 import org.apache.cassandra.cql3.CQLTester;
 import org.apache.cassandra.cql3.UntypedResultSet;
 import org.apache.cassandra.exceptions.OperationExecutionException;
+import org.apache.cassandra.serializers.SimpleDateSerializer;
+import org.apache.cassandra.serializers.TimestampSerializer;
 
 public class OperationFctsTest extends CQLTester
 {
@@ -741,4 +744,99 @@ public class OperationFctsTest extends CQLTester
         assertRows(execute("SELECT a + (int) ?, b + (tinyint) ?, c + 
(smallint) ? FROM %s", Integer.MAX_VALUE, Byte.MAX_VALUE, Short.MAX_VALUE),
                    row(Integer.MIN_VALUE, Byte.MIN_VALUE, Short.MIN_VALUE));
     }
+
+    @Test
+    public void testOperationsWithDuration() throws Throwable
+    {
+        // Test with timestamp type.
+        createTable("CREATE TABLE %s (pk int, time timestamp, v int, primary 
key (pk, time))");
+
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:10:00 
UTC', 1)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:12:00 
UTC', 2)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:14:00 
UTC', 3)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:15:00 
UTC', 4)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:21:00 
UTC', 5)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27 16:22:00 
UTC', 6)");
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time > ? - 5m", 
toTimestamp("2016-09-27 16:20:00 UTC")),
+                   row(1, toTimestamp("2016-09-27 16:21:00 UTC"), 5),
+                   row(1, toTimestamp("2016-09-27 16:22:00 UTC"), 6));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time >= ? - 
10m", toTimestamp("2016-09-27 16:25:00 UTC")),
+                   row(1, toTimestamp("2016-09-27 16:15:00 UTC"), 4),
+                   row(1, toTimestamp("2016-09-27 16:21:00 UTC"), 5),
+                   row(1, toTimestamp("2016-09-27 16:22:00 UTC"), 6));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time >= ? + 5m", 
toTimestamp("2016-09-27 16:15:00 UTC")),
+                   row(1, toTimestamp("2016-09-27 16:21:00 UTC"), 5),
+                   row(1, toTimestamp("2016-09-27 16:22:00 UTC"), 6));
+
+        assertRows(execute("SELECT time - 10m FROM %s WHERE pk = 1"),
+                   row(toTimestamp("2016-09-27 16:00:00 UTC")),
+                   row(toTimestamp("2016-09-27 16:02:00 UTC")),
+                   row(toTimestamp("2016-09-27 16:04:00 UTC")),
+                   row(toTimestamp("2016-09-27 16:05:00 UTC")),
+                   row(toTimestamp("2016-09-27 16:11:00 UTC")),
+                   row(toTimestamp("2016-09-27 16:12:00 UTC")));
+
+        assertInvalidMessage("the '%' operation is not supported between time 
and 10m",
+                             "SELECT time %% 10m FROM %s WHERE pk = 1");
+        assertInvalidMessage("the '*' operation is not supported between time 
and 10m",
+                             "SELECT time * 10m FROM %s WHERE pk = 1");
+        assertInvalidMessage("the '/' operation is not supported between time 
and 10m",
+                             "SELECT time / 10m FROM %s WHERE pk = 1");
+        assertInvalidMessage("the operation 'timestamp - duration' failed: The 
duration must have a millisecond precision. Was: 10us",
+                             "SELECT * FROM %s WHERE pk = 1 AND time > ? - 
10us", toTimestamp("2016-09-27 16:15:00 UTC"));
+
+        // Test with date type.
+        createTable("CREATE TABLE %s (pk int, time date, v int, primary key 
(pk, time))");
+
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-27', 1)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-28', 2)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-29', 3)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-09-30', 4)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-10-01', 5)");
+        execute("INSERT INTO %s (pk, time, v) VALUES (1, '2016-10-04', 6)");
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time > ? - 5d", 
toDate("2016-10-04")),
+                   row(1, toDate("2016-09-30"), 4),
+                   row(1, toDate("2016-10-01"), 5),
+                   row(1, toDate("2016-10-04"), 6));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time > ? - 6d", 
toDate("2016-10-04")),
+                   row(1, toDate("2016-09-29"), 3),
+                   row(1, toDate("2016-09-30"), 4),
+                   row(1, toDate("2016-10-01"), 5),
+                   row(1, toDate("2016-10-04"), 6));
+
+        assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND time >= ? + 1d", 
 toDate("2016-10-01")),
+                   row(1, toDate("2016-10-04"), 6));
+
+        assertRows(execute("SELECT time - 2d FROM %s WHERE pk = 1"),
+                   row(toDate("2016-09-25")),
+                   row(toDate("2016-09-26")),
+                   row(toDate("2016-09-27")),
+                   row(toDate("2016-09-28")),
+                   row(toDate("2016-09-29")),
+                   row(toDate("2016-10-02")));
+
+        assertInvalidMessage("the '%' operation is not supported between time 
and 10m",
+                             "SELECT time %% 10m FROM %s WHERE pk = 1");
+        assertInvalidMessage("the '*' operation is not supported between time 
and 10m",
+                             "SELECT time * 10m FROM %s WHERE pk = 1");
+        assertInvalidMessage("the '/' operation is not supported between time 
and 10m",
+                             "SELECT time / 10m FROM %s WHERE pk = 1");
+        assertInvalidMessage("the operation 'date - duration' failed: The 
duration must have a day precision. Was: 10m",
+                             "SELECT * FROM %s WHERE pk = 1 AND time > ? - 
10m", toDate("2016-10-04"));
+    }
+
+    private Date toTimestamp(String timestampAsString)
+    {
+        return new 
Date(TimestampSerializer.dateStringToTimestamp(timestampAsString));
+    }
+
+    private int toDate(String dateAsString)
+    {
+        return SimpleDateSerializer.dateStringToDays(dateAsString);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/0409abc2/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java 
b/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java
index 5b9737f..b7b99b4 100644
--- a/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java
+++ b/test/unit/org/apache/cassandra/cql3/functions/TimeFctsTest.java
@@ -34,6 +34,7 @@ import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.format.DateTimeFormat;
 
+import static org.apache.cassandra.cql3.functions.TimeFcts.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
@@ -87,7 +88,7 @@ public class TimeFctsTest
 
         long timeInMillis = dateTime.getMillis();
         ByteBuffer input = 
ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(timeInMillis, 0));
-        ByteBuffer output = executeFunction(TimeFcts.timeUuidToTimestamp, 
input);
+        ByteBuffer output = 
executeFunction(toTimestamp(TimeUUIDType.instance), input);
         assertEquals(dateTime.toDate(), 
TimestampType.instance.compose(output));
     }
 
@@ -113,7 +114,7 @@ public class TimeFctsTest
 
         long timeInMillis = dateTime.getMillis();
         ByteBuffer input = 
ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(timeInMillis, 0));
-        ByteBuffer output = executeFunction(TimeFcts.timeUuidToUnixTimestamp, 
input);
+        ByteBuffer output = 
executeFunction(toUnixTimestamp(TimeUUIDType.instance), input);
         assertEquals(timeInMillis, 
LongType.instance.compose(output).longValue());
     }
 
@@ -126,7 +127,7 @@ public class TimeFctsTest
 
         long timeInMillis = dateTime.getMillis();
         ByteBuffer input = 
ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(timeInMillis, 0));
-        ByteBuffer output = executeFunction(TimeFcts.timeUuidtoDate, input);
+        ByteBuffer output = executeFunction(toDate(TimeUUIDType.instance), 
input);
 
         long expectedTime = DateTimeFormat.forPattern("yyyy-MM-dd")
                                           .withZone(DateTimeZone.UTC)
@@ -144,7 +145,7 @@ public class TimeFctsTest
                                           .parseDateTime("2015-05-21");
 
         ByteBuffer input = SimpleDateType.instance.fromString("2015-05-21");
-        ByteBuffer output = executeFunction(TimeFcts.dateToTimestamp, input);
+        ByteBuffer output = 
executeFunction(toTimestamp(SimpleDateType.instance), input);
         assertEquals(dateTime.toDate(), 
TimestampType.instance.compose(output));
     }
 
@@ -156,7 +157,7 @@ public class TimeFctsTest
                                           .parseDateTime("2015-05-21");
 
         ByteBuffer input = SimpleDateType.instance.fromString("2015-05-21");
-        ByteBuffer output = executeFunction(TimeFcts.dateToUnixTimestamp, 
input);
+        ByteBuffer output = 
executeFunction(toUnixTimestamp(SimpleDateType.instance), input);
         assertEquals(dateTime.getMillis(), 
LongType.instance.compose(output).longValue());
     }
 
@@ -168,14 +169,14 @@ public class TimeFctsTest
                                           .parseDateTime("2015-05-21");
 
         ByteBuffer input = TimestampType.instance.fromString("2015-05-21 
11:03:02+00");
-        ByteBuffer output = executeFunction(TimeFcts.timestampToDate, input);
+        ByteBuffer output = executeFunction(toDate(TimestampType.instance), 
input);
         assertEquals(dateTime.getMillis(), 
SimpleDateType.instance.toTimeInMillis(output));
     }
 
     @Test
     public void testTimestampToDateWithEmptyInput()
     {
-        ByteBuffer output = executeFunction(TimeFcts.timestampToDate, 
ByteBufferUtil.EMPTY_BYTE_BUFFER);
+        ByteBuffer output = executeFunction(toDate(TimestampType.instance), 
ByteBufferUtil.EMPTY_BYTE_BUFFER);
         assertNull(output);
     }
 
@@ -187,14 +188,14 @@ public class TimeFctsTest
                                           .parseDateTime("2015-05-21 
11:03:02");
 
         ByteBuffer input = TimestampType.instance.decompose(dateTime.toDate());
-        ByteBuffer output = executeFunction(TimeFcts.timestampToUnixTimestamp, 
input);
+        ByteBuffer output = 
executeFunction(toUnixTimestamp(TimestampType.instance), input);
         assertEquals(dateTime.getMillis(), 
LongType.instance.compose(output).longValue());
     }
 
     @Test
     public void testTimestampToUnixTimestampWithEmptyInput()
     {
-        ByteBuffer output = executeFunction(TimeFcts.timestampToUnixTimestamp, 
ByteBufferUtil.EMPTY_BYTE_BUFFER);
+        ByteBuffer output = 
executeFunction(TimeFcts.toUnixTimestamp(TimestampType.instance), 
ByteBufferUtil.EMPTY_BYTE_BUFFER);
         assertNull(output);
     }
 

Reply via email to