You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by su...@apache.org on 2019/10/16 06:07:13 UTC

[incubator-iotdb] branch time_expr created (now cec1ea5)

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

suyue pushed a change to branch time_expr
in repository https://gitbox.apache.org/repos/asf/incubator-iotdb.git.


      at cec1ea5  time expression

This branch includes the following new commits:

     new cec1ea5  time expression

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-iotdb] 01/01: time expression

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

suyue pushed a commit to branch time_expr
in repository https://gitbox.apache.org/repos/asf/incubator-iotdb.git

commit cec1ea5e1d86dec30597ab4a69fa16566e4a34d7
Author: suyue <23...@qq.com>
AuthorDate: Wed Oct 16 14:06:38 2019 +0800

    time expression
---
 .../1-Key Concepts and Terminology.md              |  42 ++++++-
 .../1-IoTDB Query Statement.md                     |   2 +
 .../1-Key Concepts and Terminology.md              |  43 +++++++-
 .../1-IoTDB Query Statement.md                     |   2 +
 pom.xml                                            |   3 +-
 .../antlr3/org/apache/iotdb/db/sql/parse/TSLexer.g |   6 +-
 .../org/apache/iotdb/db/sql/parse/TSParser.g       |  31 ++++--
 .../apache/iotdb/db/qp/constant/DatetimeUtils.java |  60 ++++++++++
 .../apache/iotdb/db/qp/constant/SQLConstant.java   |   2 +
 .../iotdb/db/qp/strategy/LogicalGenerator.java     |  66 +++++++----
 .../apache/iotdb/db/qp/plan/PhysicalPlanTest.java  |  13 +++
 .../org/apache/iotdb/db/sql/SQLParserTest.java     | 122 ++++++++++++++-------
 12 files changed, 317 insertions(+), 75 deletions(-)

diff --git a/docs/Documentation-CHN/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md b/docs/Documentation-CHN/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md
index 6ae2489..7fb5bcd 100644
--- a/docs/Documentation-CHN/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md	
+++ b/docs/Documentation-CHN/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md	
@@ -92,7 +92,11 @@ LayerName: Identifier | STAR
 
 * 时间戳
 
-时间戳是一个数据到来的时间点。IoTDB时间戳分为两种类型,一种为LONG类型,一种为DATETIME类型(包含DATETIME-INPUT, DATETIME-DISPLAY两个小类)。
+时间戳是一个数据到来的时间点,其中包括绝对时间戳和相对时间戳。
+
+* 绝对时间戳
+
+IOTDB中绝对时间戳分为二种,一种为LONG类型,一种为DATETIME类型(包含DATETIME-INPUT, DATETIME-DISPLAY两个小类)。
 
 在用户在输入时间戳时,可以使用LONG类型的时间戳或DATETIME-INPUT类型的时间戳,其中DATETIME-INPUT类型的时间戳支持格式如表所示:
 
@@ -166,6 +170,42 @@ IoTDB在显示时间戳时可以支持LONG类型以及DATETIME-DISPLAY类型,
 
 </center>
 
+* 相对时间戳
+  
+  相对时间是指与服务器时间```now()```和```DATETIME```类型时间相差一定时间间隔的时间。
+  形式化定义为:
+  ```
+  Duration = (Digit+ ('Y'|'MO'|'W'|'D'|'H'|'M'|'S'|'MS'|'US'|'NS'))+
+  RelativeTime = (now() | DATETIME) ((+|-) Duration)+
+        
+  ```
+  
+  <center>**The syntax of the duration unit**
+  
+  |Symbol|Meaning|Presentation|Examples|
+  |:---:|:---:|:---:|:---:|
+  |y|year|1y=365 days|1y|
+  |mo|month|1mo=30 days|1mo|
+  |w|week|1w=7 days|1w|
+  |d|day|1d=1 day|1d|
+  |||||
+  |h|hour|1h=3600 seconds|1h|
+  |m|minute|1m=60 seconds|1m|
+  |s|second|1s=1 second|1s|
+  |||||
+  |ms|millisecond|1ms=1000_000 nanoseconds|1ms|
+  |us|microsecond|1us=1000 nanoseconds|1us|
+  |ns|nanosecond|1ns=1 nanosecond|1ns|  
+  
+  </center>
+  
+  例子:
+  ```
+  now() - 1d2h //比服务器时间早1天2小时的时间
+  now() - 1w //比服务器时间早1周的时间
+  ```
+  > 注意:'+'和'—'的左右两边必须有空格 
+
 * 值
 
 一个时间序列的值是由实际中的传感器向IoTDB发送的数值。这个值可以按照数据类型被IoTDB存储,同时用户也可以针对这个值的数据类型选择压缩方式,以及对应的编码方式。数据类型与对应编码的详细信息请参见本文[数据类型](/#/Documents/0.8.0/chap2/sec2)与[编码方式](/#/Documents/latest/chap2/sec3)节。
diff --git a/docs/Documentation-CHN/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md b/docs/Documentation-CHN/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md
index 44ddc82..6b63808 100644
--- a/docs/Documentation-CHN/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md	
+++ b/docs/Documentation-CHN/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md	
@@ -141,6 +141,7 @@ TimeExpr : TIME PrecedenceEqualOperator <TimeValue>
 SensorExpr : (<Timeseries> | <Path>) PrecedenceEqualOperator <PointValue>
 Eg: IoTDB > SELECT status, temperature FROM root.ln.wf01.wt01 WHERE temperature < 24 and time > 2017-11-1 0:13:00
 Eg. IoTDB > SELECT * FROM root
+Eg. IoTDB > SELECT * FROM root where time > now() - 5m
 Eg. IoTDB > SELECT COUNT(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25
 Eg. IoTDB > SELECT MIN_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25
 Eg. IoTDB > SELECT MAX_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature > 24
@@ -149,6 +150,7 @@ Eg. IoTDB > SELECT MAX_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.w
 Note: the statement needs to satisfy this constraint: <Path>(SelectClause) + <PrefixPath>(FromClause) = <Timeseries>
 Note: If the <SensorExpr>(WhereClause) is started with <Path> and not with ROOT, the statement needs to satisfy this constraint: <PrefixPath>(FromClause) + <Path>(SensorExpr) = <Timeseries>
 Note: In Version 0.7.0, if <WhereClause> includes `OR`, time filter can not be used.
+Note: There must be a space on both sides of the plus and minus operator appearing in the time expression 
 ```
 
 * Group By语句
diff --git a/docs/Documentation/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md b/docs/Documentation/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md
index 89f56df..daa4e11 100644
--- a/docs/Documentation/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md	
+++ b/docs/Documentation/UserGuide/2-Concept Key Concepts and Terminology/1-Key Concepts and Terminology.md	
@@ -95,7 +95,11 @@ When `*` appears in the middle of the path, it represents `*` itself, i.e., a la
 
 * Timestamp
 
-The timestamp is the time point at which data is produced. IoTDB timestamps are divided into two types: LONG and DATETIME (including DATETIME-INPUT and DATETIME-DISPLAY). When a user inputs a timestamp, he can use a LONG type timestamp or a DATETIME-INPUT type timestamp, and the supported formats of the DATETIME-INPUT type timestamp are shown in the table below:
+The timestamp is the time point at which data is produced. It includes absolute timestamps and relative timestamps
+
+* Absolute timestamp
+
+Absolute timestamps in IoTDB are divided into two types: LONG and DATETIME (including DATETIME-INPUT and DATETIME-DISPLAY). When a user inputs a timestamp, he can use a LONG type timestamp or a DATETIME-INPUT type timestamp, and the supported formats of the DATETIME-INPUT type timestamp are shown in the table below:
 
 <center>**Supported formats of DATETIME-INPUT type timestamp**
 
@@ -167,6 +171,43 @@ IoTDB can support LONG types and DATETIME-DISPLAY types when displaying timestam
 
 </center>
 
+* Relative timestamp
+
+Relative time refers to the time relative to the server time ```now()``` and ```DATETIME``` time.
+
+ Syntax:
+ ```
+  Duration = (Digit+ ('Y'|'MO'|'W'|'D'|'H'|'M'|'S'|'MS'|'US'|'NS'))+
+  RelativeTime = (now() | DATETIME) ((+|-) Duration)+
+        
+  ```
+  
+  <center>**The syntax of the duration unit**
+  
+  |Symbol|Meaning|Presentation|Examples|
+  |:---:|:---:|:---:|:---:|
+  |y|year|1y=365 days|1y|
+  |mo|month|1mo=30 days|1mo|
+  |w|week|1w=7 days|1w|
+  |d|day|1d=1 day|1d|
+  |||||
+  |h|hour|1h=3600 seconds|1h|
+  |m|minute|1m=60 seconds|1m|
+  |s|second|1s=1 second|1s|
+  |||||
+  |ms|millisecond|1ms=1000_000 nanoseconds|1ms|
+  |us|microsecond|1us=1000 nanoseconds|1us|
+  |ns|nanosecond|1ns=1 nanosecond|1ns|  
+  
+  </center>
+  
+  eg:
+  ```
+  now() - 1d2h //1 day and 2 hours earlier than the current server time
+  now() - 1w //1 week earlier than the current server time
+  ```
+  > Note:There must be spaces on the left and right of '+' and '-'.
+
 * Value
 
 The value of a time series is actually the value sent by a sensor to IoTDB. This value can be stored by IoTDB according to the data type. At the same time, users can select the compression mode and the corresponding encoding mode according to the data type of this value. See [Data Type](/#/Documents/0.8.0/chap2/sec2) and [Encoding](/#/Documents/0.8.0/chap2/sec3) of this document for details on data type and corresponding encoding.
diff --git a/docs/Documentation/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md b/docs/Documentation/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md
index 9b34bb4..32455bc 100644
--- a/docs/Documentation/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md	
+++ b/docs/Documentation/UserGuide/5-IoTDB SQL Documentation/1-IoTDB Query Statement.md	
@@ -150,6 +150,7 @@ TimeExpr : TIME PrecedenceEqualOperator <TimeValue>
 SensorExpr : (<Timeseries> | <Path>) PrecedenceEqualOperator <PointValue>
 Eg: IoTDB > SELECT status, temperature FROM root.ln.wf01.wt01 WHERE temperature < 24 and time > 2017-11-1 0:13:00
 Eg. IoTDB > SELECT * FROM root
+Eg. IoTDB > SELECT * FROM root where time > now() - 5m
 Eg. IoTDB > SELECT COUNT(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25
 Eg. IoTDB > SELECT MIN_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25
 Eg. IoTDB > SELECT MAX_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature > 24
@@ -158,6 +159,7 @@ Eg. IoTDB > SELECT MAX_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.w
 Note: the statement needs to satisfy this constraint: <Path>(SelectClause) + <PrefixPath>(FromClause) = <Timeseries>
 Note: If the <SensorExpr>(WhereClause) is started with <Path> and not with ROOT, the statement needs to satisfy this constraint: <PrefixPath>(FromClause) + <Path>(SensorExpr) = <Timeseries>
 Note: In Version 0.7.0, if <WhereClause> includes `OR`, time filter can not be used.
+Note: There must be a space on both sides of the plus and minus operator appearing in the time expression 
 ```
 
 * Group By Statement
diff --git a/pom.xml b/pom.xml
index 164cc6c..d1e4331 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
         <module>spark-iotdb-connector</module>
         <module>distribution</module>
     </modules>
-<!-- Properties Management -->
+    <!-- Properties Management -->
     <properties>
         <maven.compiler.source>1.8</maven.compiler.source>
         <maven.compiler.target>1.8</maven.compiler.target>
@@ -404,7 +404,6 @@
                         <generateBackupPoms>false</generateBackupPoms>
                     </configuration>
                 </plugin>
-
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-javadoc-plugin</artifactId>
diff --git a/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSLexer.g b/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSLexer.g
index 1d65efa..d87df26 100644
--- a/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSLexer.g
+++ b/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSLexer.g
@@ -25,7 +25,6 @@ package org.apache.iotdb.db.sql.parse;
 }
 
 
-
 KW_PRIVILEGES : 'PRIVILEGES';
 KW_TIMESERIES : 'TIMESERIES';
 KW_TIME : 'TIME';
@@ -218,4 +217,9 @@ Identifier
 WS
     :
     (' '|'\r'|'\t'|'\n') { $channel=HIDDEN; }
+    ;
+
+Duration
+    :
+    (Digit+ ('Y'|'MO'|'W'|'D'|'H'|'M'|'S'|'MS'|'US'|'NS'))+
     ;
\ No newline at end of file
diff --git a/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSParser.g b/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSParser.g
index bca8cdd..cbbe7b9 100644
--- a/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSParser.g
+++ b/server/src/main/antlr3/org/apache/iotdb/db/sql/parse/TSParser.g
@@ -82,6 +82,9 @@ TOK_FLOAT_COMB;
 TOK_GRANT_WATERMARK_EMBEDDING;
 TOK_REVOKE_WATERMARK_EMBEDDING;
 
+TOK_DATE_EXPR;
+TOK_DURATION;
+
 /*
   BELOW IS THE METADATA TOKEN
 */
@@ -367,7 +370,8 @@ execStatement
 
 
 dateFormat
-    : datetime=DATETIME -> ^(TOK_DATETIME $datetime)
+    : duration=Duration -> ^(TOK_DURATION $duration)
+    | datetime=DATETIME -> ^(TOK_DATETIME $datetime)
     | func=Identifier LPAREN RPAREN -> ^(TOK_DATETIME $func)
     ;
 
@@ -376,6 +380,14 @@ dateFormatWithNumber
     | integer -> integer
     ;
 
+dateExpression
+    : dateExpr=dateSubExpression -> ^(TOK_DATE_EXPR $dateExpr)
+    ;
+
+dateSubExpression
+    : dateFormatWithNumber ((PLUS^ | MINUS^) dateFormatWithNumber)*
+    ;
+
 
 /*
 ****
@@ -772,8 +784,8 @@ whereClause
 
 groupbyClause
     :
-    KW_GROUP KW_BY LPAREN value=integer unit=Identifier (COMMA timeOrigin=dateFormatWithNumber)? COMMA timeInterval (COMMA timeInterval)* RPAREN
-    -> ^(TOK_GROUPBY ^(TOK_TIMEUNIT $value $unit) ^(TOK_TIMEORIGIN $timeOrigin)? ^(TOK_TIMEINTERVAL timeInterval+))
+    KW_GROUP KW_BY LPAREN Duration (COMMA timeOrigin=dateFormatWithNumber)? COMMA timeInterval (COMMA timeInterval)* RPAREN
+    -> ^(TOK_GROUPBY ^(TOK_TIMEUNIT Duration) ^(TOK_TIMEORIGIN $timeOrigin)? ^(TOK_TIMEINTERVAL timeInterval+))
     ;
 
 fillClause
@@ -812,15 +824,13 @@ typeClause
 
 interTypeClause
     :
-    KW_LINEAR (COMMA value1=integer unit1=Identifier COMMA value2=integer unit2=Identifier)?
-    -> ^(TOK_LINEAR (^(TOK_TIMEUNIT $value1 $unit1) ^(TOK_TIMEUNIT $value2 $unit2))?)
+    KW_LINEAR (COMMA Duration COMMA Duration)?
+    -> ^(TOK_LINEAR (^(TOK_TIMEUNIT Duration) ^(TOK_TIMEUNIT Duration))?)
     |
-    KW_PREVIOUS (COMMA value1=integer unit1=Identifier)?
-    -> ^(TOK_PREVIOUS ^(TOK_TIMEUNIT $value1 $unit1)?)
+    KW_PREVIOUS (COMMA Duration)?
+    -> ^(TOK_PREVIOUS ^(TOK_TIMEUNIT Duration)?)
     ;
 
-
-
 timeInterval
     :
     LSQUARE startTime=dateFormatWithNumber COMMA endTime=dateFormatWithNumber RSQUARE
@@ -876,7 +886,6 @@ nullCondition
     | KW_NOT KW_NULL -> ^(TOK_ISNOTNULL)
     ;
 
-
 atomExpressionWithNumberPath
     :
     (KW_NULL) => KW_NULL -> TOK_NULL
@@ -901,5 +910,5 @@ atomExpression
 constant
     : number
     | StringLiteral
-    | dateFormat
+    | dateExpression
     ;
\ No newline at end of file
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/constant/DatetimeUtils.java b/server/src/main/java/org/apache/iotdb/db/qp/constant/DatetimeUtils.java
index 35e9d68..74591c3 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/constant/DatetimeUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/constant/DatetimeUtils.java
@@ -475,6 +475,66 @@ public class DatetimeUtils {
     return getInstantWithPrecision(str, timestampPrecision);
   }
 
+  /**
+   * convert duration string to millisecond, microsecond or nanosecond.
+   */
+  public static long convertDurationStrToLong(long value, String unit, String timestampPrecision) {
+
+    long res = value;
+    switch (unit) {
+      case "y":
+        res *= 365 * 86400_000;
+        break;
+      case "mo":
+        res *= 30 * 86400_000;
+        break;
+      case "w":
+        res *= 7 * 86400_000;
+        break;
+      case "d":
+        res *= 86400_000;
+        break;
+      case "h":
+        res *= 3600_000;
+        break;
+      case "m":
+        res *= 60_000;
+        break;
+      case "s":
+        res *= 1_000;
+        break;
+      default:
+        break;
+    }
+
+    if (timestampPrecision.equals("us")) {
+      if (unit.equals("ns")) {
+        return value / 1000;
+      } else if (unit.equals("us")) {
+        return value;
+      } else {
+        return res * 1000;
+      }
+    } else if (timestampPrecision.equals("ns")) {
+      if (unit.equals("ns")) {
+        return value;
+      } else if (unit.equals("us")) {
+        return value * 1000;
+      } else {
+        return res * 1000_000;
+      }
+    } else {
+      if (unit.equals("ns")) {
+        return value / 1000_0000;
+      } else if (unit.equals("us")) {
+        return value / 1000;
+      } else {
+        return res;
+      }
+    }
+
+  }
+
   public static ZoneOffset toZoneOffset(ZoneId zoneId) {
     return zoneId.getRules().getOffset(Instant.now());
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
index 797a87f..1eae4fc 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
@@ -92,6 +92,8 @@ public class SQLConstant {
   public static final int TOK_PROPERTY_LINK = 57;
   public static final int TOK_PROPERTY_UNLINK = 58;
   public static final int TOK_LIST = 59;
+  public static final int TOK_DURATION = 60;
+  public static final int TOK_DATE_EXPR = 61;
 
   public static final Map<Integer, String> tokenSymbol = new HashMap<>();
   public static final Map<Integer, String> tokenNames = new HashMap<>();
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
index f888f6d..ec37ccf 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
@@ -28,6 +28,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.antlr.runtime.Token;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.exception.ArgsErrorException;
 import org.apache.iotdb.db.exception.MetadataErrorException;
 import org.apache.iotdb.db.exception.qp.IllegalASTFormatException;
@@ -807,25 +808,10 @@ public class LogicalGenerator {
   }
 
   private long parseTimeUnit(AstNode node) throws LogicalOperatorException {
-    long timeInterval = Long.parseLong(node.getChild(0).getText());
+    long timeInterval = parseTokenDuration(node);
     if (timeInterval <= 0) {
       throw new LogicalOperatorException("Interval must more than 0.");
     }
-    String granu = node.getChild(1).getText();
-    switch (granu) {
-      case "w":
-        timeInterval *= 7;
-      case "d":
-        timeInterval *= 24;
-      case "h":
-        timeInterval *= 60;
-      case "m":
-        timeInterval *= 60;
-      case "s":
-        timeInterval *= 1000;
-      default:
-        break;
-    }
     return timeInterval;
   }
 
@@ -848,17 +834,59 @@ public class LogicalGenerator {
       if (!seriesPath.equals(SQLConstant.RESERVED_TIME)) {
         throw new LogicalOperatorException("Date can only be used to time");
       }
-      seriesValue = parseTokenTime(rightKey);
+      seriesValue = parseTokenTime(rightKey) + "";
     } else if (rightKey.getType() == TSParser.TOK_FLOAT_COMB) {
       seriesValue = parseTokens(rightKey);
+    } else if (rightKey.getType() == TSParser.TOK_DATE_EXPR) {
+      seriesValue = parseTokenDataExpresssion(rightKey.getChild(0)) + "";
     } else {
       seriesValue = rightKey.getText();
     }
     return new Pair<>(seriesPath, seriesValue);
   }
 
-  private String parseTokenTime(AstNode astNode) throws LogicalOperatorException {
-    return parseTimeFormat(parseTokens(astNode)) + "";
+  private Long parseTokenDataExpresssion(AstNode astNode) throws LogicalOperatorException {
+    if (astNode.getType() == TSParser.PLUS) {
+      return parseTokenDataExpresssion(astNode.getChild(0)) + parseTokenDataExpresssion(
+          astNode.getChild(1));
+    } else if (astNode.getType() == TSParser.MINUS) {
+      return parseTokenDataExpresssion(astNode.getChild(0)) - parseTokenDataExpresssion(
+          astNode.getChild(1));
+    } else {
+      return parseTokenTime(astNode);
+    }
+  }
+
+  private Long parseTokenTime(AstNode astNode) throws LogicalOperatorException {
+    if (astNode.getType() == TSParser.TOK_DURATION) {
+      return parseTokenDuration(astNode);
+    }
+    return parseTimeFormat(parseTokens(astNode));
+  }
+
+  private Long parseTokenDuration(AstNode astNode) throws LogicalOperatorException {
+    String durationStr = parseTokens(astNode);
+    String timestampPrecision = IoTDBDescriptor.getInstance().getConfig().getTimestampPrecision();
+
+    long total = 0;
+    long tmp = 0;
+    for (int i = 0; i < durationStr.length(); i++) {
+      char ch = durationStr.charAt(i);
+      if (Character.isDigit(ch)) {
+        tmp *= 10;
+        tmp += (ch - '0');
+      } else {
+        String unit = durationStr.charAt(i) + "";
+        if (i + 1 < durationStr.length() && !Character.isDigit(durationStr.charAt(i + 1))) {
+          i++;
+          unit += durationStr.charAt(i);
+        }
+        total += DatetimeUtils
+            .convertDurationStrToLong(tmp, unit.toLowerCase(), timestampPrecision);
+        tmp = 0;
+      }
+    }
+    return total;
   }
 
   private String parseTokens(AstNode astNode) throws LogicalOperatorException {
diff --git a/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java b/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java
index 3cdfb1e..5b3a7b9 100644
--- a/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java
@@ -154,6 +154,7 @@ public class PhysicalPlanTest {
       fail();
     }
     GroupByPlan mergePlan = (GroupByPlan) plan;
+    assertEquals(10 * 60 * 1000L, mergePlan.getUnit());
     assertEquals(44, mergePlan.getOrigin());
   }
 
@@ -302,6 +303,18 @@ public class PhysicalPlanTest {
   }
 
   @Test
+  public void testQuery7()
+      throws QueryProcessorException, ArgsErrorException, MetadataErrorException {
+    String sqlStr = "SELECT s1 FROM root.vehicle.d1 WHERE time > 1571194740000ms - 1d5h or time < 10";
+    PhysicalPlan plan = processor.parseSQLToPhysicalPlan(sqlStr);
+    IExpression queryFilter = ((QueryPlan) plan).getExpression();
+    IExpression expect = new GlobalTimeExpression(
+        FilterFactory.or(TimeFilter.gt(1571090340000L), TimeFilter.lt(10L)));
+    assertEquals(expect.toString(), queryFilter.toString());
+
+  }
+
+  @Test
   public void testQueryFloat1()
       throws QueryProcessorException, ArgsErrorException, MetadataErrorException {
     String sqlStr = "SELECT s1 FROM root.vehicle.d1 WHERE s1 > 20.5e3";
diff --git a/server/src/test/java/org/apache/iotdb/db/sql/SQLParserTest.java b/server/src/test/java/org/apache/iotdb/db/sql/SQLParserTest.java
index 0e51686..f06c9e6 100644
--- a/server/src/test/java/org/apache/iotdb/db/sql/SQLParserTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/sql/SQLParserTest.java
@@ -285,9 +285,9 @@ public class SQLParserTest {
   @Test
   public void delete() throws ParseException {
     // template for test case
-    ArrayList<String> ans = new ArrayList<>(
-        Arrays.asList("TOK_DELETE", "TOK_PATH", "TOK_ROOT", "d1", "s1",
-            "TOK_WHERE", "<", "TOK_PATH", "time", "TOK_DATETIME", "2016-11-16 16:22:33+08:00"));
+    ArrayList<String> ans = new ArrayList<>(Arrays
+        .asList("TOK_DELETE", "TOK_PATH", "TOK_ROOT", "d1", "s1", "TOK_WHERE", "<", "TOK_PATH",
+            "time", "TOK_DATE_EXPR", "TOK_DATETIME", "2016-11-16 16:22:33+08:00"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST("delete from root.d1.s1 where time < 2016-11-16 16:22:33+08:00");
@@ -304,9 +304,9 @@ public class SQLParserTest {
   @Test
   public void delete2() throws ParseException {
     // template for test case
-    ArrayList<String> ans = new ArrayList<>(
-        Arrays.asList("TOK_DELETE", "TOK_PATH", "TOK_ROOT", "d1", "s1",
-            "TOK_WHERE", "<", "TOK_PATH", "time", "TOK_DATETIME", "now"));
+    ArrayList<String> ans = new ArrayList<>(Arrays
+        .asList("TOK_DELETE", "TOK_PATH", "TOK_ROOT", "d1", "s1", "TOK_WHERE", "<", "TOK_PATH",
+            "time", "TOK_DATE_EXPR", "TOK_DATETIME", "now"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST("delete from root.d1.s1 where time < now();");
     astTree = ParseUtils.findRootNonNullToken(astTree);
@@ -341,10 +341,9 @@ public class SQLParserTest {
   @Test
   public void delete4() throws ParseException {
     // template for test case
-    ArrayList<String> ans = new ArrayList<>(
-        Arrays.asList("TOK_DELETE", "TOK_PATH", "TOK_ROOT", "d1", "s1",
-            "TOK_PATH", "TOK_ROOT", "d2", "s3", "TOK_WHERE", "<", "TOK_PATH", "time",
-            "TOK_DATETIME", "now"));
+    ArrayList<String> ans = new ArrayList<>(Arrays
+        .asList("TOK_DELETE", "TOK_PATH", "TOK_ROOT", "d1", "s1", "TOK_PATH", "TOK_ROOT", "d2",
+            "s3", "TOK_WHERE", "<", "TOK_PATH", "time", "TOK_DATE_EXPR", "TOK_DATETIME", "now"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST("delete from root.d1.s1,root.d2.s3 where time < now();");
@@ -827,7 +826,7 @@ public class SQLParserTest {
             "TOK_FROM", "TOK_PATH", "TOK_ROOT", "vehicle",
             "TOK_WHERE", "&&",
             "<", "TOK_PATH", "TOK_ROOT", "laptop", "device_1", "sensor_1", "-2.2E10",
-            ">", "TOK_PATH", "time", "TOK_DATETIME", "now"));
+            ">", "TOK_PATH", "time", "TOK_DATE_EXPR", "TOK_DATETIME", "now"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST(
         "SELECT device_1.sensor_1,device_2.sensor_2 FROM root.vehicle "
@@ -852,7 +851,7 @@ public class SQLParserTest {
             "TOK_FROM", "TOK_PATH", "TOK_ROOT", "vehicle",
             "TOK_WHERE", "&",
             "<", "TOK_PATH", "time", "1234567",
-            ">", "TOK_PATH", "time", "TOK_DATETIME", "2017-6-2T12:00:12+07:00"));
+            ">", "TOK_PATH", "time", "TOK_DATE_EXPR", "TOK_DATETIME", "2017-6-2T12:00:12+07:00"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST(
         "SELECT device_1.sensor_1,device_2.sensor_2 FROM root.vehicle "
@@ -876,7 +875,7 @@ public class SQLParserTest {
             "TOK_FROM", "TOK_PATH", "TOK_ROOT", "vehicle",
             "TOK_WHERE", "||",
             "<", "TOK_PATH", "time", "1234567",
-            ">", "TOK_PATH", "time", "TOK_DATETIME", "2017.6.2 12:00:12+07:00"));
+            ">", "TOK_PATH", "time", "TOK_DATE_EXPR", "TOK_DATETIME", "2017.6.2 12:00:12+07:00"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST(
@@ -948,7 +947,7 @@ public class SQLParserTest {
             "TOK_WHERE", "and",
             "<", "TOK_PATH", "TOK_ROOT", "vehicle", "d1", "s1", "TOK_FLOAT_COMB", "0", ".32e6",
             "<=", "TOK_PATH", "time",
-            "TOK_DATETIME", "now"));
+            "TOK_DATE_EXPR", "TOK_DATETIME", "now"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST(
         "select count(s1),max_time(s2) from root.vehicle.d1 where root.vehicle.d1.s1 < 0.32e6 and time <= now()");
@@ -1015,9 +1014,9 @@ public class SQLParserTest {
             "TOK_PATH", "TOK_ROOT", "vehicle", "d1",
             "TOK_WHERE", "and",
             "<", "TOK_PATH", "TOK_ROOT", "vehicle", "d1", "s1", "TOK_FLOAT_COMB", "0", ".32e6",
-            "<=", "TOK_PATH", "time", "TOK_DATETIME", "now", "TOK_GROUPBY",
+            "<=", "TOK_PATH", "time", "TOK_DATE_EXPR", "TOK_DATETIME", "now", "TOK_GROUPBY",
             "TOK_TIMEUNIT",
-            "10", "w", "TOK_TIMEORIGIN", "44", "TOK_TIMEINTERVAL", "TOK_TIMEINTERVALPAIR", "1", "3",
+            "10w", "TOK_TIMEORIGIN", "44", "TOK_TIMEINTERVAL", "TOK_TIMEINTERVALPAIR", "1", "3",
             "TOK_TIMEINTERVALPAIR", "4", "5"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
@@ -1042,8 +1041,8 @@ public class SQLParserTest {
             "TOK_PATH", "s2", "sum", "TOK_FROM", "TOK_PATH", "TOK_ROOT", "vehicle", "d1",
             "TOK_WHERE", "or", "<",
             "TOK_PATH", "s1", "2000", ">=", "TOK_PATH", "time", "1234567", "TOK_GROUPBY",
-            "TOK_TIMEUNIT", "111",
-            "ms", "TOK_TIMEINTERVAL", "TOK_TIMEINTERVALPAIR", "123", "TOK_DATETIME",
+            "TOK_TIMEUNIT", "111ms",
+            "TOK_TIMEINTERVAL", "TOK_TIMEINTERVALPAIR", "123", "TOK_DATETIME",
             "2017-6-2T12:00:12+07:00",
             "TOK_TIMEINTERVALPAIR", "55555", "TOK_DATETIME", "now"));
     ArrayList<String> rec = new ArrayList<>();
@@ -1052,6 +1051,7 @@ public class SQLParserTest {
             "select sum(s2) " + "FROM root.vehicle.d1 " + "WHERE s1 < 2000 or time >= 1234567 "
                 + "group by(111ms, [123,2017-6-2T12:00:12+07:00], [55555, now()]);");
     astTree = ParseUtils.findRootNonNullToken(astTree);
+
     recursivePrintSon(astTree, rec);
 
     int i = 0;
@@ -1070,7 +1070,7 @@ public class SQLParserTest {
             "d1",
             "TOK_WHERE", "|", "<", "TOK_PATH", "TOK_ROOT", "vehicle", "d1", "s1", "2000", ">=",
             "TOK_PATH", "time",
-            "1234567", "TOK_GROUPBY", "TOK_TIMEUNIT", "111", "w", "TOK_TIMEORIGIN", "TOK_DATETIME",
+            "1234567", "TOK_GROUPBY", "TOK_TIMEUNIT", "111w", "TOK_TIMEORIGIN", "TOK_DATETIME",
             "2017-6-2T02:00:12+07:00", "TOK_TIMEINTERVAL", "TOK_TIMEINTERVALPAIR", "TOK_DATETIME",
             "2017-6-2T12:00:12+07:00", "TOK_DATETIME", "now"));
     ArrayList<String> rec = new ArrayList<>();
@@ -1094,7 +1094,7 @@ public class SQLParserTest {
         Arrays.asList("TOK_QUERY", "TOK_SELECT", "TOK_PATH", "s1", "TOK_FROM",
             "TOK_PATH", "TOK_ROOT", "vehicle", "d1", "TOK_WHERE", "=", "TOK_PATH", "time",
             "1234567", "TOK_FILL",
-            "TOK_TYPE", "float", "TOK_PREVIOUS", "TOK_TIMEUNIT", "11", "h"));
+            "TOK_TYPE", "float", "TOK_PREVIOUS", "TOK_TIMEUNIT", "11h"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST(
         "select s1 " + "FROM root.vehicle.d1 " + "WHERE time = 1234567 "
@@ -1137,8 +1137,7 @@ public class SQLParserTest {
         Arrays.asList("TOK_QUERY", "TOK_SELECT", "TOK_PATH", "s1", "TOK_FROM",
             "TOK_PATH", "TOK_ROOT", "vehicle", "d1", "TOK_WHERE", "=", "TOK_PATH", "time",
             "1234567", "TOK_FILL",
-            "TOK_TYPE", "boolean", "TOK_LINEAR", "TOK_TIMEUNIT", "31", "s", "TOK_TIMEUNIT", "123",
-            "d"));
+            "TOK_TYPE", "boolean", "TOK_LINEAR", "TOK_TIMEUNIT", "31s", "TOK_TIMEUNIT", "123d"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST(
         "select s1 " + "FROM root.vehicle.d1 " + "WHERE time = 1234567 "
@@ -1181,11 +1180,10 @@ public class SQLParserTest {
         Arrays.asList("TOK_QUERY", "TOK_SELECT", "TOK_PATH", "s1", "TOK_PATH",
             "s2", "TOK_FROM", "TOK_PATH", "TOK_ROOT", "vehicle", "d1", "TOK_WHERE", "=", "TOK_PATH",
             "time",
-            "1234567", "TOK_FILL", "TOK_TYPE", "int", "TOK_LINEAR", "TOK_TIMEUNIT", "5", "ms",
-            "TOK_TIMEUNIT", "7",
-            "d", "TOK_TYPE", "float", "TOK_PREVIOUS", "TOK_TYPE", "boolean", "TOK_LINEAR",
-            "TOK_TYPE", "text",
-            "TOK_PREVIOUS", "TOK_TIMEUNIT", "66", "w"));
+            "1234567", "TOK_FILL", "TOK_TYPE", "int", "TOK_LINEAR", "TOK_TIMEUNIT", "5ms",
+            "TOK_TIMEUNIT", "7d", "TOK_TYPE", "float", "TOK_PREVIOUS", "TOK_TYPE", "boolean",
+            "TOK_LINEAR", "TOK_TYPE", "text",
+            "TOK_PREVIOUS", "TOK_TIMEUNIT", "66w"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST("select s1,s2 " + "FROM root.vehicle.d1 " + "WHERE time = 1234567 "
@@ -1289,7 +1287,7 @@ public class SQLParserTest {
             "TOK_WHERE",
             "&&", "<", "TOK_PATH", "TOK_ROOT", "laptop", "device_1", "sensor_1", "-2.2E10", ">",
             "TOK_PATH", "time",
-            "TOK_DATETIME", "now", "TOK_SLIMIT", "10"));
+            "TOK_DATE_EXPR", "TOK_DATETIME", "now", "TOK_SLIMIT", "10"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST("SELECT device_1.sensor_1,device_2.* FROM root.vehicle "
@@ -1322,7 +1320,7 @@ public class SQLParserTest {
             "TOK_WHERE",
             "&&", "<", "TOK_PATH", "TOK_ROOT", "laptop", "device_1", "sensor_1", "-2.2E10", ">",
             "TOK_PATH", "time",
-            "TOK_DATETIME", "now", "TOK_SLIMIT", "10", "TOK_SOFFSET", "3"));
+            "TOK_DATE_EXPR", "TOK_DATETIME", "now", "TOK_SLIMIT", "10", "TOK_SOFFSET", "3"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST("SELECT device_1.sensor_1,device_2.sensor_2 FROM root.* "
@@ -1354,8 +1352,8 @@ public class SQLParserTest {
             "TOK_PATH", "device_2", "*", "TOK_FROM", "TOK_PATH", "TOK_ROOT", "vehicle", "TOK_WHERE",
             "&&", "<",
             "TOK_PATH", "TOK_ROOT", "laptop", "device_1", "sensor_1", "-2.2E10", ">", "TOK_PATH",
-            "time",
-            "TOK_DATETIME", "now", "TOK_LIMIT", "100", "TOK_SLIMIT", "10", "TOK_SOFFSET", "3"));
+            "time", "TOK_DATE_EXPR", "TOK_DATETIME", "now", "TOK_LIMIT", "100", "TOK_SLIMIT", "10",
+            "TOK_SOFFSET", "3"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST("SELECT device_1.*,device_2.* FROM root.vehicle "
         + "WHERE root.laptop.device_1.sensor_1 < -2.2E10 && time > now() LIMIT 100 OFFSET 1 SLIMIT 10 SOFFSET 3");
@@ -1385,9 +1383,9 @@ public class SQLParserTest {
             "TOK_FROM",
             "TOK_PATH", "TOK_ROOT", "vehicle", "*", "TOK_WHERE", "and",
             "<", "TOK_PATH", "TOK_ROOT", "vehicle", "d1", "s1", "TOK_FLOAT_COMB", "0", ".32e6",
-            "<=", "TOK_PATH", "time", "TOK_DATETIME", "now", "TOK_GROUPBY",
+            "<=", "TOK_PATH", "time", "TOK_DATE_EXPR", "TOK_DATETIME", "now", "TOK_GROUPBY",
             "TOK_TIMEUNIT",
-            "10", "w", "TOK_TIMEORIGIN", "44", "TOK_TIMEINTERVAL", "TOK_TIMEINTERVALPAIR", "1", "3",
+            "10w", "TOK_TIMEORIGIN", "44", "TOK_TIMEINTERVAL", "TOK_TIMEINTERVALPAIR", "1", "3",
             "TOK_TIMEINTERVALPAIR", "4", "5", "TOK_SLIMIT", "1", "TOK_SOFFSET", "1", "TOK_LIMIT",
             "11"));
     ArrayList<String> rec = new ArrayList<>();
@@ -1395,7 +1393,7 @@ public class SQLParserTest {
         .generateAST("select count(s1),max_time(s2) " + "from root.vehicle.* "
             + "where root.vehicle.d1.s1 < 0.32e6 and time <= now() "
             + "group by(10w, 44, [1,3], [4,5])"
-            + "slimit 1" + "soffset 1" + "limit 11" + "offset 3");
+            + "slimit 1 " + "soffset 1" + "limit 11" + "offset 3");
     astTree = ParseUtils.findRootNonNullToken(astTree);
     recursivePrintSon(astTree, rec);
 
@@ -1421,12 +1419,12 @@ public class SQLParserTest {
                 "vehicle",
                 "d1", "TOK_WHERE", "=", "TOK_PATH", "time", "1234567", "TOK_FILL", "TOK_TYPE",
                 "float",
-                "TOK_PREVIOUS", "TOK_TIMEUNIT", "11", "h", "TOK_SLIMIT", "15", "TOK_SOFFSET",
+                "TOK_PREVIOUS", "TOK_TIMEUNIT", "11h", "TOK_SLIMIT", "15", "TOK_SOFFSET",
                 "50"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST("select * " + "FROM root.vehicle.d1 " + "WHERE time = 1234567 "
-            + "fill(float[previous, 11h])" + "slimit 15" + "soffset 50");
+            + "fill(float[previous, 11h])" + "slimit 15" + " soffset 50");
     astTree = ParseUtils.findRootNonNullToken(astTree);
     recursivePrintSon(astTree, rec);
 
@@ -1794,7 +1792,7 @@ public class SQLParserTest {
         Arrays.asList("TOK_CREATE", "TOK_INDEX", "TOK_PATH", "TOK_ROOT", "a",
             "b", "c", "TOK_FUNC", "kv-match2", "TOK_WITH", "TOK_INDEX_KV", "xxx", "50",
             "TOK_INDEX_KV", "xxx",
-            "123", "TOK_WHERE", ">", "TOK_PATH", "time", "TOK_DATETIME", "now"));
+            "123", "TOK_WHERE", ">", "TOK_PATH", "time", "TOK_DATE_EXPR", "TOK_DATETIME", "now"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator
         .generateAST(
@@ -1845,9 +1843,9 @@ public class SQLParserTest {
     // 3, 0.0, 1.0, 0.0) from root.vehicle.d0.s0");
     AstNode astTree = ParseGenerator.generateAST(
         "select index subsequence_matching(root.a.b.c, root.a.b.c, 123, 132 , 123.1, 0.123, 0.5) from root.a.b;");
+    System.out.println(astTree.toStringTree());
     astTree = ParseUtils.findRootNonNullToken(astTree);
     recursivePrintSon(astTree, rec);
-
     int i = 0;
     while (i <= rec.size() - 1) {
       assertEquals(rec.get(i), ans.get(i));
@@ -1898,7 +1896,8 @@ public class SQLParserTest {
 
   @Test
   public void grantWatermarkEmbedding() throws ParseException {
-    ArrayList<String> ans = new ArrayList<>(Arrays.asList("TOK_GRANT_WATERMARK_EMBEDDING", "a", "b"));
+    ArrayList<String> ans = new ArrayList<>(
+        Arrays.asList("TOK_GRANT_WATERMARK_EMBEDDING", "a", "b"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST("GRANT watermark_embedding to a,b");
     astTree = ParseUtils.findRootNonNullToken(astTree);
@@ -1913,7 +1912,8 @@ public class SQLParserTest {
 
   @Test
   public void revokeWatermarkEmbedding() throws ParseException {
-    ArrayList<String> ans = new ArrayList<>(Arrays.asList("TOK_REVOKE_WATERMARK_EMBEDDING", "a", "b"));
+    ArrayList<String> ans = new ArrayList<>(
+        Arrays.asList("TOK_REVOKE_WATERMARK_EMBEDDING", "a", "b"));
     ArrayList<String> rec = new ArrayList<>();
     AstNode astTree = ParseGenerator.generateAST("revoke watermark_embedding from a,b");
     astTree = ParseUtils.findRootNonNullToken(astTree);
@@ -1926,6 +1926,48 @@ public class SQLParserTest {
     }
   }
 
+  @Test
+  public void testDuration1() throws ParseException {
+    ArrayList<String> ans = new ArrayList<>(Arrays
+        .asList("TOK_QUERY", "TOK_SELECT", "TOK_PATH", "*", "TOK_FROM", "TOK_PATH", "TOK_ROOT",
+            "vehicle", "d1", "TOK_WHERE", "=", "TOK_PATH", "time", "TOK_DATE_EXPR", "-", "+", "-",
+            "TOK_DATETIME", "now", "TOK_DURATION", "34y8mo8d9h78m", "TOK_DURATION", "890h",
+            "TOK_DURATION", "89s"));
+    ArrayList<String> rec = new ArrayList<>();
+    String sqlStr = "select * from root.vehicle.d1 where time = now() - 34y8mo8d9h78m + 890h - 89s";
+    AstNode astTree = ParseGenerator.generateAST(sqlStr);
+    astTree = ParseUtils.findRootNonNullToken(astTree);
+    recursivePrintSon(astTree, rec);
+
+    int i = 0;
+    while (i <= rec.size() - 1) {
+      assertEquals(rec.get(i), ans.get(i));
+      i++;
+    }
+    assertEquals(rec.size(), ans.size());
+  }
+
+  @Test
+  public void testDuration2() throws ParseException {
+    ArrayList<String> ans = new ArrayList<>(Arrays
+        .asList("TOK_QUERY", "TOK_SELECT", "TOK_PATH", "*", "TOK_FROM", "TOK_PATH", "TOK_ROOT",
+            "vehicle", "d1", "TOK_WHERE", "=", "TOK_PATH", "time", "TOK_DATE_EXPR", "+", "-",
+            "TOK_DATETIME", "2016-11-16 16:22:33+08:00", "TOK_DURATION", "7y", "90"));
+    ArrayList<String> rec = new ArrayList<>();
+    String sqlStr = "select * from root.vehicle.d1 where time = 2016-11-16 16:22:33+08:00 - 7y + 90";
+    AstNode astTree = ParseGenerator.generateAST(sqlStr);
+    System.out.println(astTree.toStringTree());
+    astTree = ParseUtils.findRootNonNullToken(astTree);
+    recursivePrintSon(astTree, rec);
+
+    int i = 0;
+    while (i <= rec.size() - 1) {
+      assertEquals(rec.get(i), ans.get(i));
+      i++;
+    }
+    assertEquals(rec.size(), ans.size());
+  }
+
   public void recursivePrintSon(Node ns, ArrayList<String> rec) {
     rec.add(ns.toString());
     if (ns.getChildren() != null) {