Browse Source

feat(sql): implement sql funcs for date and time (#2172)

Signed-off-by: Yaid <sunzhenyucn@gmail.com>
Yaid 1 year ago
parent
commit
21dfe1f84e

+ 8 - 0
docs/directory.json

@@ -593,6 +593,10 @@
 							"path": "sqls/functions/json_functions"
 						},
 						{
+							"title": "时间日期函数",
+							"path": "sqls/functions/datetime_functions"
+						},
+						{
 							"title": "其他函数",
 							"path": "sqls/functions/other_functions"
 						},
@@ -1316,6 +1320,10 @@
 							"path": "sqls/functions/json_functions"
 						},
 						{
+							"title": "Date and Time Functions",
+							"path": "sqls/functions/datetime_functions"
+						},
+						{
 							"title": "Other Functions",
 							"path": "sqls/functions/other_functions"
 						},

+ 229 - 0
docs/en_US/sqls/functions/datetime_functions.md

@@ -0,0 +1,229 @@
+# Date and Time Functions
+
+Date and time functions are used to perform operations on date and time type data.
+
+## NOW
+
+```text
+now(fsp)
+```
+
+Returns the current time in the `YYYY-MM-DD HH:mm:ss` format. If the `fsp` parameter is provided to specify fractional seconds precision from 0 to 6, the returned value includes the corresponding fraction of seconds.
+
+## CURRENT_TIMESTAMP
+
+```text
+current_time(fsp)
+```
+
+Synonym for the `NOW` function.
+
+## LOCAL_TIME
+
+```text
+local_time(fsp)
+```
+
+Synonym for the `NOW` function.
+
+## LOCAL_TIMESTAMP
+
+```text
+local_timestamp(fsp)
+```
+
+Synonym for the `NOW` function.
+
+## CUR_DATE
+
+```text
+cur_date()
+```
+
+Returns the current date in the `YYYY-MM-DD` format.
+
+## CURRENT_DATE
+
+```text
+current_date()
+```
+
+Synonym for the `CUR_DATE` function.
+
+## CUR_TIME
+
+```text
+cur_time()
+```
+
+Returns the current time in the `HH:mm:ss` format.
+
+## CURRENT_TIME
+  
+```text
+current_time()
+```
+
+Synonym for the `CUR_TIME` function.
+
+## FORMAT_TIME
+
+```text
+format_time(time, format)
+```
+
+Formats the `time` according to the specified `format` and returns the formatted string.
+
+## DATE_CALC
+
+```text
+date_calc(date, duration)
+```
+
+Calculates the date based on the `date` and `duration` and returns the calculated date.
+
+The `duration` represents a time interval and can be represented as a string using the following formats:
+
+- Nanoseconds (`ns`): Suffixed with "ns".
+- Microseconds (`us` or `µs`): Suffixed with "us" or "µs" (using U+00B5 micro symbol).
+- Milliseconds (`ms`): Suffixed with "ms".
+- Seconds (`s`): Suffixed with "s".
+- Minutes (`m`): Suffixed with "m".
+- Hours (`h`): Suffixed with "h".
+
+It also supports combining these representations for more complex time intervals, for example, `1h30m` represents 1 hour 30 minutes. Multiple time units can be combined without spaces.
+
+To subtract a time interval, you can prepend a `-` sign before the `duration`.
+
+For example, `-1h30m` represents subtracting 1 hour 30 minutes.
+
+Here are some examples for the `duration`:
+
+```text
+date_calc('2019-01-01', '1h')
+date_calc('2019-01-01', '1h30m')
+date_calc('2019-01-01', '1h30m10s')
+date_calc('2019-01-01', '1h30m10s100ms')
+date_calc('2019-01-01', '1h30m10s100ms200us')
+date_calc('2019-01-01', '1h30m10s100ms200us300ns')
+```
+
+## DATE_DIFF
+
+```text
+date_diff(date1, date2)
+```
+
+Calculates the difference in days between `date1` and `date2` and returns the calculated difference.
+
+## DAY_NAME
+
+```text
+day_name(date)
+```
+
+Returns the name of the day of the week for the given `date`, such as `Monday`, `Tuesday`, etc.
+
+## DAY_OF_MONTH
+
+```text
+day_of_month(date)
+```
+
+Returns the day of the month for the given `date`.
+
+## DAY
+  
+```text
+day(date)
+```
+
+Synonym for `DAY_OF_MONTH`.
+
+## DAY_OF_WEEK
+
+```text
+day_of_week(date)
+```
+
+Returns the day of the week for the given `date`, where Sunday is 1, Monday is 2, and so on.
+
+## DAY_OF_YEAR
+
+```text
+day_of_year(date)
+```
+
+Returns the day of the year for the given `date`.
+
+## FROM_DAYS
+
+```text
+from_days(days)
+```
+
+Converts the `days` value to a date and returns the converted date.
+
+## FROM_UNIX_TIME
+
+```text
+from_unix_time(unix_timestamp)
+```
+
+Converts the `unix_timestamp` value to a date and returns the converted date.
+
+## HOUR
+
+```text
+hour(date)
+```
+
+Returns the hour part of the given `date`.
+
+## LAST_DAY
+
+```text
+last_day(date)
+```
+
+Returns the last day of the month for the given `date`.
+
+## MICROSECOND
+
+```text
+microsecond(date)
+```
+
+Returns the microsecond part of the given `date`.
+
+## MINUTE
+
+```text
+minute(date)
+```
+
+Returns the minute part of the given `date`.
+
+## MONTH
+
+```text
+month(date)
+```
+
+Returns the month part of the given `date`.
+
+## MONTH_NAME
+
+```text
+month_name(date)
+```
+
+Returns the name of the month for the given `date`, such as `January`, `February`, etc.
+
+## SECOND
+
+```text
+second(date)
+```
+
+Returns the second part of the given `date`.

+ 1 - 0
docs/en_US/sqls/functions/overview.md

@@ -10,6 +10,7 @@ eKuiper has many built-in functions for performing calculations on data.
 - [Hashing Functions](./hashing_functions.md)
 - [Transform Functions](./transform_functions.md)
 - [JSON Functions](./json_functions.md)
+- [Date and Time Functions](./datetime_functions.md)
 - [Other Functions](./other_functions.md)
 
 - [Analytic Functions](./analytic_functions.md)

+ 225 - 0
docs/zh_CN/sqls/functions/datetime_functions.md

@@ -0,0 +1,225 @@
+# 日期和时间函数
+
+日期和时间函数用于对日期和时间类型数据做操作。
+
+## NOW
+
+```text
+now(fsp)
+```
+
+按照 `YYYY-MM-DD HH:mm:ss` 返回当前时间,如果提供了 `fsp` 参数来指定从 0 到 6 的小数秒精度,则返回值包括该数字所对应的小数秒部分。
+
+## CURRENT_TIMESTAMP
+
+```text
+current_time(fsp)
+```
+
+`NOW` 函数的同义词。
+
+## LOCAL_TIME
+
+```text
+local_time(fsp)
+```
+
+`NOW` 函数的同义词。
+
+## LOCAL_TIMESTAMP
+
+```text
+local_timestamp(fsp)
+```
+
+`NOW` 函数的同义词。
+
+## CUR_DATE
+
+```text
+cur_date()
+```
+
+按照 `YYYY-MM-DD` 的格式返回当前日期。
+
+## CURRENT_DATE
+
+```text
+current_date()
+```
+
+`CUR_DATE` 的同义词。
+
+## CUR_TIME
+
+```text
+cur_time()
+```
+
+按照 `HH:mm:ss` 的格式返回当前时间。
+
+## CURRENT_TIME
+  
+```text
+current_time()
+```
+
+`CUR_TIME` 的同义词。
+
+## FORMAT_TIME
+
+```text
+format_time(time, format)
+```
+
+按照 `format` 格式化 `time`,返回格式化后的字符串。
+
+## DATE_CALC
+
+```text
+date_calc(date, duration)
+```
+
+按照 `date` 和 `duration` 计算日期,返回计算后的日期。`duration` 表示一段时间间隔,可以通过字符串来表示,支持的字符串表示形式如下:
+
+- 纳秒(nanoseconds):以 "ns" 作为后缀。
+- 微秒(microseconds):以 "us" 或 "µs"(使用 U+00B5,即微符号)作为后缀。
+- 毫秒(milliseconds):以 "ms" 作为后缀。
+- 秒(seconds):以 "s" 作为后缀。
+- 分钟(minutes):以 "m" 作为后缀。
+- 小时(hours):以 "h" 作为后缀。
+
+还支持通过组合来表示更复杂的时间间隔,例如 `1h30m` 表示 1 小时 30 分钟。在表示中,多个时间单位可以连续组合使用,它们之间不需要空格。
+
+如果需要实现减去一段时间间隔,可以在 `duration` 前面加上 `-` 符号,例如 `-1h30m` 表示减去 1 小时 30 分钟。
+
+以下是一些示例:
+
+```text
+date_calc('2019-01-01', '1h')
+date_calc('2019-01-01', '1h30m')
+date_calc('2019-01-01', '1h30m10s')
+date_calc('2019-01-01', '1h30m10s100ms')
+date_calc('2019-01-01', '1h30m10s100ms200us')
+date_calc('2019-01-01', '1h30m10s100ms200us300ns')
+```
+
+## DATE_DIFF
+
+```text
+date_diff(date1, date2)
+```
+
+计算 `date1` 和 `date2` 之间的天数差,返回计算后的天数差。
+
+## DAY_NAME
+
+```text
+day_name(date)
+```
+
+返回 `date` 所在的天的名称,例如 `Monday`、`Tuesday` 等。
+
+## DAY_OF_MONTH
+
+```text
+day_of_month(date)
+```
+
+返回 `date` 所在的月份的第几天。
+
+## DAY
+  
+```text
+day(date)
+```
+
+`DAY_OF_MONTH` 的同义词。
+
+## DAY_OF_WEEK
+
+```text
+day_of_week(date)
+```
+
+返回 `date` 所在的星期的第几天,星期天为 1,星期一为 2,以此类推。
+
+## DAY_OF_YEAR
+
+```text
+day_of_year(date)
+```
+
+返回 `date` 所在的年份的第几天。
+
+## FROM_DAYS
+
+```text
+from_days(days)
+```
+
+将 `days` 转换为日期,返回转换后的日期。
+
+## FROM_UNIX_TIME
+
+```text
+from_unix_time(unix_timestamp)
+```
+
+将 `unix_timestamp` 转换为日期,返回转换后的日期。
+
+## HOUR
+
+```text
+hour(date)
+```
+
+返回 `date` 的小时部分。
+
+## LAST_DAY
+
+```text
+last_day(date)
+```
+
+返回 `date` 所在月份的最后一天。
+
+## MICROSECOND
+
+```text
+microsecond(date)
+```
+
+返回 `date` 的微秒部分。
+
+## MINUTE
+
+```text
+minute(date)
+```
+
+返回 `date` 的分钟部分。
+
+## MONTH
+
+```text
+month(date)
+```
+
+返回 `date` 的月份部分。
+
+## MONTH_NAME
+
+```text
+month_name(date)
+```
+
+返回 `date` 的月份名称,例如 `January`、`February` 等。
+
+## SECOND
+
+```text
+second(date)
+```
+
+返回 `date` 的秒部分。

+ 1 - 0
docs/zh_CN/sqls/functions/overview.md

@@ -10,6 +10,7 @@ eKuiper 具有许多内置函数,可以对数据执行计算。
 - [哈希函数](./hashing_functions.md)
 - [转换函数](./transform_functions.md)
 - [JSON 函数](./json_functions.md)
+- [时间日期函数](./datetime_functions.md)
 - [其他函数](./other_functions.md)
 
 - [分析函数](./analytic_functions.md)

+ 517 - 0
internal/binder/function/funcs_datetime.go

@@ -0,0 +1,517 @@
+// Copyright 2022-2023 EMQ Technologies Co., Ltd.
+//
+// Licensed 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 function
+
+import (
+	"errors"
+	"strings"
+	"time"
+
+	"github.com/lf-edge/ekuiper/pkg/api"
+	"github.com/lf-edge/ekuiper/pkg/ast"
+	"github.com/lf-edge/ekuiper/pkg/cast"
+)
+
+var errTooManyArguments = errors.New("too many arguments")
+
+type IntervalUnit string
+
+// registerDateTimeFunc registers the date and time functions.
+func registerDateTimeFunc() {
+	builtins["now"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec:  execGetCurrentDateTime(false),
+		val:   validFspArgs(),
+	}
+	builtins["current_timestamp"] = builtins["now"]
+	builtins["local_time"] = builtins["now"]
+	builtins["local_timestamp"] = builtins["now"]
+
+	builtins["cur_date"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec:  execGetCurrentDate(),
+		val:   ValidateNoArg,
+	}
+	builtins["current_date"] = builtins["cur_date"]
+
+	builtins["cur_time"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec:  execGetCurrentDateTime(true),
+		val:   validFspArgs(),
+	}
+	builtins["current_time"] = builtins["cur_time"]
+
+	builtins["format_time"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+			arg1 := cast.ToStringAlways(args[1])
+			if s, err := cast.FormatTime(arg0, arg1); err == nil {
+				return s, true
+			} else {
+				return err, false
+			}
+		},
+		val: func(_ api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(2, len(args)); err != nil {
+				return err
+			}
+
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			if ast.IsNumericArg(args[1]) || ast.IsTimeArg(args[1]) || ast.IsBooleanArg(args[1]) {
+				return ProduceErrInfo(1, "string")
+			}
+			return nil
+		},
+		check: returnNilIfHasAnyNil,
+	}
+	builtins["date_calc"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			arg1 := cast.ToStringAlways(args[1])
+
+			unitSign := 1
+			if len(arg1) > 0 && arg1[0] == '-' {
+				unitSign = -1
+				arg1 = arg1[1:]
+			}
+
+			unit, err := cast.InterfaceToDuration(cast.ToStringAlways(arg1))
+			if err != nil {
+				return err, false
+			}
+
+			t, err := cast.FormatTime(arg0.Add(unit*time.Duration(unitSign)), "yyyy-MM-dd HH:mm:ss")
+			if err != nil {
+				return err, false
+			}
+
+			return t, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(2, len(args)); err != nil {
+				return err
+			}
+
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+
+			if ast.IsStringArg(args[1]) {
+				return ProduceErrInfo(1, "string")
+			}
+			return nil
+		},
+	}
+	builtins["date_diff"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+			arg1, err := cast.InterfaceToTime(args[1], "")
+			if err != nil {
+				return err, false
+			}
+			return arg1.Sub(arg0), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(2, len(args)); err != nil {
+				return err
+			}
+
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+
+			if ast.IsNumericArg(args[1]) || ast.IsStringArg(args[1]) || ast.IsBooleanArg(args[1]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["day_name"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+			return arg0.Weekday().String(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["day_of_month"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+			return arg0.Day(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["day"] = builtins["day_of_month"]
+
+	builtins["day_of_week"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+			return arg0.Weekday(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["day_of_year"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+			return arg0.YearDay(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["from_days"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			days, err := cast.ToInt(args[0], cast.STRICT)
+			if err != nil {
+				return err, false
+			}
+
+			if days == 0 {
+				return nil, true
+			}
+
+			t := time.Unix(0, 0).Add(time.Duration(days-1) * 24 * time.Hour)
+			result, err := cast.FormatTime(t, "yyyy-MM-dd")
+			if err != nil {
+				return err, false
+			}
+			return result, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "int")
+			}
+			return nil
+		},
+		check: returnNilIfHasAnyNil,
+	}
+	builtins["from_unix_time"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			seconds, err := cast.ToInt(args[0], cast.STRICT)
+			if err != nil {
+				return err, false
+			}
+
+			if seconds == 0 {
+				return nil, true
+			}
+
+			t := time.Unix(int64(seconds), 0)
+			result, err := cast.FormatTime(t, "yyyy-MM-dd HH:mm:ss")
+			if err != nil {
+				return err, false
+			}
+			return result, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "int")
+			}
+			return nil
+		},
+		check: returnNilIfHasAnyNil,
+	}
+	builtins["hour"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			return arg0.Hour(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["last_day"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			year, month, _ := arg0.Date()
+			lastDay := time.Date(year, month+1, 0, 0, 0, 0, 0, time.UTC)
+			result, err := cast.FormatTime(lastDay, "yyyy-MM-dd")
+			if err != nil {
+				return err, false
+			}
+			return result, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["microsecond"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			return arg0.Nanosecond() / 1000, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["minute"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			return arg0.Minute(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["month"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			return int(arg0.Month()), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["month_name"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			return arg0.Month().String(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(1, "datetime")
+			}
+			return nil
+		},
+	}
+	builtins["second"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg0, err := cast.InterfaceToTime(args[0], "")
+			if err != nil {
+				return err, false
+			}
+
+			return arg0.Second(), true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(1, len(args)); err != nil {
+				return err
+			}
+			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
+				return ProduceErrInfo(0, "datetime")
+			}
+			return nil
+		},
+	}
+}
+
+func execGetCurrentDate() funcExe {
+	return func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+		formatted, err := cast.FormatTime(time.Now(), "yyyy-MM-dd")
+		if err != nil {
+			return err, false
+		}
+		return formatted, true
+	}
+}
+
+// validFspArgs returns a function that validates the 'fsp' arg.
+func validFspArgs() funcVal {
+	return func(ctx api.FunctionContext, args []ast.Expr) error {
+		if len(args) > 1 {
+			return errTooManyArguments
+		}
+
+		if !ast.IsIntegerArg(args[0]) {
+			return ProduceErrInfo(0, "int")
+		}
+
+		return nil
+	}
+}
+
+func execGetCurrentDateTime(timeOnly bool) funcExe {
+	return func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+		fsp := 0
+		if len(args) == 1 {
+			fsp = args[0].(int)
+		}
+
+		formatted, err := getCurrentWithFsp(fsp, timeOnly)
+		if err != nil {
+			return err, false
+		}
+		return formatted, true
+	}
+}
+
+// getCurrentWithFsp returns the current date/time with the specified number of fractional seconds precision.
+func getCurrentWithFsp(fsp int, timeOnly bool) (string, error) {
+	format := "yyyy-MM-dd HH:mm:ss"
+	now := time.Now()
+
+	switch fsp {
+	case 1:
+		format += ".S"
+	case 2:
+		format += ".SS"
+	case 3:
+		format += ".SSS"
+	case 4:
+		format += ".SSSS"
+	case 5:
+		format += ".SSSSS"
+	case 6:
+		format += ".SSSSSS"
+	default:
+	}
+
+	formatted, err := cast.FormatTime(now, format)
+	if err != nil {
+		return "", err
+	}
+
+	if timeOnly {
+		return strings.SplitN(formatted, " ", 2)[1], nil
+	}
+
+	return formatted, nil
+}

+ 865 - 0
internal/binder/function/funcs_datetime_test.go

@@ -0,0 +1,865 @@
+// Copyright 2023 EMQ Technologies Co., Ltd.
+//
+// Licensed 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 function
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/lf-edge/ekuiper/internal/conf"
+	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
+	"github.com/lf-edge/ekuiper/internal/topo/state"
+	"github.com/lf-edge/ekuiper/pkg/api"
+	"github.com/lf-edge/ekuiper/pkg/ast"
+	"github.com/lf-edge/ekuiper/pkg/cast"
+)
+
+// TestDateTimeFunctions test the date and time functions.
+func TestDateTimeFunctions(t *testing.T) {
+	contextLogger := conf.Log.WithField("rule", "testExec")
+	ctx := kctx.WithValue(kctx.Background(), kctx.LoggerKey, contextLogger)
+	tempStore, _ := state.CreateStore("mockRule0", api.AtMostOnce)
+	fctx := kctx.NewDefaultFuncContext(ctx.WithMeta("mockRule0", "test", tempStore), 2)
+
+	tests := []struct {
+		// testCaseName represent the name of the test case
+		testCaseName string
+		// funcName represent the SQL function name to be tested
+		funcName string
+		// execArgs represent the arguments to be passed to the function
+		execArgs []interface{}
+		// valFunc represent the function to validate the result
+		valFunc func(t interface{}) error
+		// execTest represent whether to test the exec function
+		execTest bool
+		// valArgs represent the arguments to be passed to the builtinFunc.val
+		valArgs []ast.Expr
+	}{
+		{
+			testCaseName: "test now() with no args",
+			funcName:     "now",
+			execArgs:     []interface{}{},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test now() with fsp set to 1",
+			funcName:     "now",
+			execArgs:     []interface{}{1},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss.S")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test now() with fsp set to 2",
+			funcName:     "now",
+			execArgs:     []interface{}{2},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss.SS")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test now() with fsp set to 3",
+			funcName:     "now",
+			execArgs:     []interface{}{3},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss.SSS")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test now() with fsp set to 4",
+			funcName:     "now",
+			execArgs:     []interface{}{4},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss.SSSS")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test now() with fsp set to 5",
+			funcName:     "now",
+			execArgs:     []interface{}{5},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss.SSSSS")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test now() with fsp set to 6",
+			funcName:     "now",
+			execArgs:     []interface{}{6},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss.SSSSSS")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test now() with too many args",
+			funcName:     "now",
+			valFunc: func(t interface{}) error {
+				if !reflect.DeepEqual(t, errTooManyArguments) {
+					return errors.New("mismatch error")
+				}
+				return nil
+			},
+			execTest: false,
+			valArgs:  []ast.Expr{&ast.IntegerLiteral{Val: 1}, &ast.IntegerLiteral{Val: 2}},
+		},
+		{
+			testCaseName: "test cur_date() with no args",
+			funcName:     "cur_date",
+			execArgs:     []interface{}{},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd")
+				return err
+			},
+			execTest: true,
+		},
+		{
+			testCaseName: "test cur_date() with too many args",
+			funcName:     "cur_date",
+			execTest:     false,
+			valFunc: func(t interface{}) error {
+				if !reflect.DeepEqual(t, errors.New("Expect 0 arguments but found 1.")) {
+					return errors.New("mismatch error")
+				}
+				return nil
+			},
+			valArgs: []ast.Expr{&ast.IntegerLiteral{Val: 1}},
+		},
+		{
+			testCaseName: "test cur_time() with no args",
+			funcName:     "cur_time",
+			execTest:     true,
+			execArgs:     []interface{}{},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "HH:mm:ss")
+				return err
+			},
+		},
+		{
+			testCaseName: "test cur_time() with fsp set to 1",
+			funcName:     "cur_time",
+			execTest:     true,
+			execArgs:     []interface{}{1},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "HH:mm:ss.S")
+				return err
+			},
+		},
+		{
+			testCaseName: "test format_time() with 'yyyy-MM-dd' format",
+			funcName:     "format_time",
+			execTest:     true,
+			execArgs:     []interface{}{time.Now(), "yyyy-MM-dd"},
+			valFunc: func(t interface{}) error {
+				_, err := cast.ParseTime(t.(string), "yyyy-MM-dd")
+				return err
+			},
+		},
+		{
+			testCaseName: "test format_time() with 1 arg",
+			funcName:     "format_time",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				if !reflect.DeepEqual(t, errors.New("Expect 2 arguments but found 1.")) {
+					return errors.New("mismatch error")
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test format_time() with invalid date time arg",
+			funcName:     "format_time",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+				&ast.StringLiteral{Val: "yyyy-MM-dd"},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test date_calc() for add 1 day (24h)",
+			funcName:     "date_calc",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 00:00:00", "24h"},
+			valFunc: func(t interface{}) error {
+				parsed, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss")
+				if err != nil {
+					return err
+				}
+				if parsed.Day() != 2 {
+					return fmt.Errorf("mismatch days, expect %d, got %d", 2, parsed.Day())
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test date_calc() with invalid date time arg",
+			funcName:     "date_calc",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+				&ast.StringLiteral{Val: "24h"},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test date_calc() with no args",
+			funcName:     "date_calc",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 2 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test date_calc() for sub 1 day (24h)",
+			funcName:     "date_calc",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 00:00:00", "-24h"},
+			valFunc: func(t interface{}) error {
+				parsed, err := cast.ParseTime(t.(string), "yyyy-MM-dd HH:mm:ss")
+				if err != nil {
+					return err
+				}
+				if parsed.Day() != 31 {
+					return fmt.Errorf("mismatch days, expect %d, got %d", 31, parsed.Day())
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test date_diff with 2 args",
+			funcName:     "date_diff",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 00:00:00", "2019-01-02 00:00:00"},
+			valFunc: func(t interface{}) error {
+				result := t.(time.Duration)
+				if result.Milliseconds() != 24*3600*1000 {
+					return fmt.Errorf("mismatch result, expect %d, got %d", 26*3600*1000, result.Milliseconds())
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test date_diff with no arg",
+			funcName:     "date_diff",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 2 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test date_diff with invalid date time arg",
+			funcName:     "date_diff",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+				&ast.IntegerLiteral{Val: 2},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_name with 1 arg",
+			funcName:     "day_name",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 00:00:00"},
+			valFunc: func(t interface{}) error {
+				if t.(string) != "Tuesday" {
+					return fmt.Errorf("mismatch day name, expect %s, got %s", "Tuesday", t.(string))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_name with no arg",
+			funcName:     "day_name",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_name with invalid date time arg",
+			funcName:     "day_name",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_month with 1 arg",
+			funcName:     "day_of_month",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 00:00:00"},
+			valFunc: func(t interface{}) error {
+				if t.(int) != 1 {
+					return fmt.Errorf("mismatch day of month, expect %d, got %d", 1, t.(int))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_month with no arg",
+			funcName:     "day_of_month",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_month with invalid date time arg",
+			funcName:     "day_of_month",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_week with 1 arg",
+			funcName:     "day_of_week",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 00:00:00"},
+			valFunc: func(t interface{}) error {
+				if t.(time.Weekday) != time.Tuesday {
+					return fmt.Errorf("mismatch day of week, expect %d, got %d", time.Tuesday, t.(time.Weekday))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_week with no arg",
+			funcName:     "day_of_week",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_week with invalid date time arg",
+			funcName:     "day_of_week",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_year with 1 arg",
+			funcName:     "day_of_year",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 00:00:00"},
+			valFunc: func(t interface{}) error {
+				if t.(int) != 1 {
+					return fmt.Errorf("mismatch day of year, expect %d, got %d", 1, t.(int))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_year with no arg",
+			funcName:     "day_of_year",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test day_of_year with invalid date time arg",
+			funcName:     "day_of_year",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test from_days with no arg",
+			funcName:     "from_days",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test from_days with invalid date time arg",
+			funcName:     "from_days",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect int type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test from_days with 100",
+			funcName:     "from_days",
+			execTest:     true,
+			execArgs:     []interface{}{100},
+			valFunc: func(t interface{}) error {
+				if t.(string) != "1970-04-10" {
+					return fmt.Errorf("mismatch date, expect %s, got %s", "2019-01-01", t.(string))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test from_unix_time with no arg",
+			funcName:     "from_unix_time",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %s", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test from_unix_time with invalid arg",
+			funcName:     "from_unix_time",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.NumberLiteral{Val: 0.1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect int type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test from_unix_time with 100",
+			funcName:     "from_unix_time",
+			execTest:     true,
+			execArgs:     []interface{}{100},
+			valFunc: func(t interface{}) error {
+				expect := time.Unix(100, 0)
+
+				expectStr, err := cast.FormatTime(expect, "yyyy-MM-dd HH:mm:ss")
+				if err != nil {
+					return err
+				}
+
+				if t.(string) != expectStr {
+					return fmt.Errorf("mismatch date, expect %s, got %s", expectStr, t.(string))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test hour with no arg",
+			funcName:     "hour",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test hour with invalid date time arg",
+			funcName:     "hour",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test hour with 1 arg",
+			funcName:     "hour",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 01:00:00"},
+			valFunc: func(t interface{}) error {
+				if t.(int) != 1 {
+					return fmt.Errorf("mismatch hour, expect %d, got %d", 1, t.(int))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test last_day with no arg",
+			funcName:     "last_day",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test last_day with invalid date time arg",
+			funcName:     "last_day",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test last_day with 1 arg",
+			funcName:     "last_day",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 01:00:00"},
+			valFunc: func(t interface{}) error {
+				if t.(string) != "2019-01-31" {
+					return fmt.Errorf("mismatch date, expect %s, got %s", "2019-01-31", t.(string))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test microsecond with no arg",
+			funcName:     "microsecond",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test microsecond with invalid date time arg",
+			funcName:     "microsecond",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test microsecond with 1 arg",
+			funcName:     "microsecond",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 01:00:00.123456"},
+			valFunc: func(t interface{}) error {
+				if t.(int) != 123456 {
+					return fmt.Errorf("mismatch microsecond, expect %d, got %d", 123456, t.(int))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test minute with no arg",
+			funcName:     "minute",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test minute with invalid date time arg",
+			funcName:     "minute",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test minute with 1 arg",
+			funcName:     "minute",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 01:23:45"},
+			valFunc: func(t interface{}) error {
+				if t.(int) != 23 {
+					return fmt.Errorf("mismatch minute, expect %d, got %d", 23, t.(int))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test month with no arg",
+			funcName:     "month",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got: %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test month with invalid date time arg",
+			funcName:     "month",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect: %s, got %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test month with 1 arg",
+			funcName:     "month",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 01:23:45"},
+			valFunc: func(t interface{}) error {
+				if t.(int) != 1 {
+					return fmt.Errorf("mismatch month, expect %d, got %d", 1, t.(int))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test month_name with no arg",
+			funcName:     "month_name",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, expect) {
+					return fmt.Errorf("mismatch error, expect %s, got %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test month_name with invalid date time arg",
+			funcName:     "month_name",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, t) {
+					return fmt.Errorf("mismatch error, expect %s, got %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test month_name with 1 arg",
+			funcName:     "month_name",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 01:23:45"},
+			valFunc: func(t interface{}) error {
+				if t.(string) != "January" {
+					return fmt.Errorf("mismatch month name, expect %s, got %s", "January", t.(string))
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test second with no arg",
+			funcName:     "second",
+			execTest:     false,
+			valArgs:      []ast.Expr{},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect 1 arguments but found 0.")
+				if !reflect.DeepEqual(t, t) {
+					return fmt.Errorf("mismatch error, expect %s, got %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test second with invalid date time arg",
+			funcName:     "second",
+			execTest:     false,
+			valArgs: []ast.Expr{
+				&ast.IntegerLiteral{Val: 1},
+			},
+			valFunc: func(t interface{}) error {
+				expect := errors.New("Expect datetime type for parameter 1")
+				if !reflect.DeepEqual(t, t) {
+					return fmt.Errorf("mismatch error, expect %s, got %v", expect, t)
+				}
+				return nil
+			},
+		},
+		{
+			testCaseName: "test second with 1 arg",
+			funcName:     "second",
+			execTest:     true,
+			execArgs:     []interface{}{"2019-01-01 01:23:45"},
+			valFunc: func(t interface{}) error {
+				if t.(int) != 45 {
+					return fmt.Errorf("mismatch second, expect %d, got %d", 45, t.(int))
+				}
+				return nil
+			},
+		},
+	}
+
+	for _, test := range tests {
+		f, ok := builtins[test.funcName]
+		if !ok {
+			t.Fatalf("builtin '%s' not found", test.funcName)
+		}
+
+		var result interface{}
+		if test.execTest {
+			result, _ = f.exec(fctx, test.execArgs)
+		} else {
+			result = f.val(fctx, test.valArgs)
+		}
+		if err := test.valFunc(result); err != nil {
+			t.Errorf("\n%s: %q", test.testCaseName, err)
+		}
+	}
+}

+ 0 - 29
internal/binder/function/funcs_str.go

@@ -127,35 +127,6 @@ func registerStrFunc() {
 		val:   ValidateOneStrArg,
 		check: return0IfHasAnyNil,
 	}
-	builtins["format_time"] = builtinFunc{
-		fType: ast.FuncTypeScalar,
-		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			arg0, err := cast.InterfaceToTime(args[0], "")
-			if err != nil {
-				return err, false
-			}
-			arg1 := cast.ToStringAlways(args[1])
-			if s, err := cast.FormatTime(arg0, arg1); err == nil {
-				return s, true
-			} else {
-				return err, false
-			}
-		},
-		val: func(_ api.FunctionContext, args []ast.Expr) error {
-			if err := ValidateLen(2, len(args)); err != nil {
-				return err
-			}
-
-			if ast.IsNumericArg(args[0]) || ast.IsStringArg(args[0]) || ast.IsBooleanArg(args[0]) {
-				return ProduceErrInfo(0, "datetime")
-			}
-			if ast.IsNumericArg(args[1]) || ast.IsTimeArg(args[1]) || ast.IsBooleanArg(args[1]) {
-				return ProduceErrInfo(1, "string")
-			}
-			return nil
-		},
-		check: returnNilIfHasAnyNil,
-	}
 	builtins["regexp_matches"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {

+ 1 - 0
internal/binder/function/function.go

@@ -53,6 +53,7 @@ func init() {
 	registerArrayFunc()
 	registerObjectFunc()
 	registerGlobalStateFunc()
+	registerDateTimeFunc()
 	registerGlobalAggFunc()
 	registerWindowFunc()
 }

+ 9 - 0
pkg/cast/time.go

@@ -351,3 +351,12 @@ func convertFormat(f string) (string, error) {
 	}
 	return out, nil
 }
+
+// InterfaceToDuration converts an interface to a time.Duration.
+func InterfaceToDuration(i interface{}) (time.Duration, error) {
+	duration, err := ToString(i, STRICT)
+	if err != nil {
+		return 0, fmt.Errorf("given arguments cannot convert to duration: %q", err)
+	}
+	return time.ParseDuration(duration)
+}