Browse Source

fix: time function should consider timezone (#2183)

Signed-off-by: yisaer <disxiaofei@163.com>
Signed-off-by: Song Gao <disxiaofei@163.com>
Song Gao 1 year atrás
parent
commit
1e08d95c67

+ 2 - 2
etc/kuiper.yaml

@@ -17,8 +17,8 @@ basic:
   restIp: 0.0.0.0
   # REST service port
   restPort: 9081
-  # The global time zone from the IANA time zone database, or UTC if not set.
-  timezone: UTC
+  # The global time zone from the IANA time zone database, or Local if not set.
+  timezone: Local
   # true|false, when true, will check the RSA jwt token for rest api
   authentication: false
   #  restTls:

+ 11 - 6
internal/binder/function/funcs_datetime.go

@@ -19,6 +19,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/pkg/api"
 	"github.com/lf-edge/ekuiper/pkg/ast"
 	"github.com/lf-edge/ekuiper/pkg/cast"
@@ -276,8 +277,7 @@ func registerDateTimeFunc() {
 			if seconds == 0 {
 				return nil, true
 			}
-
-			t := time.Unix(int64(seconds), 0)
+			t := time.Unix(int64(seconds), 0).In(cast.GetConfiguredTimeZone())
 			result, err := cast.FormatTime(t, "yyyy-MM-dd HH:mm:ss")
 			if err != nil {
 				return err, false
@@ -456,6 +456,10 @@ func execGetCurrentDate() funcExe {
 // 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 nil
+		}
+
 		if len(args) > 1 {
 			return errTooManyArguments
 		}
@@ -471,10 +475,12 @@ func validFspArgs() funcVal {
 func execGetCurrentDateTime(timeOnly bool) funcExe {
 	return func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		fsp := 0
-		if len(args) == 1 {
+		switch len(args) {
+		case 0:
+			fsp = 0
+		default:
 			fsp = args[0].(int)
 		}
-
 		formatted, err := getCurrentWithFsp(fsp, timeOnly)
 		if err != nil {
 			return err, false
@@ -486,8 +492,7 @@ func execGetCurrentDateTime(timeOnly bool) funcExe {
 // 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()
-
+	now := conf.GetNow().In(cast.GetConfiguredTimeZone())
 	switch fsp {
 	case 1:
 		format += ".S"

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

@@ -21,6 +21,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/benbjohnson/clock"
+	"github.com/stretchr/testify/require"
+
 	"github.com/lf-edge/ekuiper/internal/conf"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
@@ -863,3 +866,55 @@ func TestDateTimeFunctions(t *testing.T) {
 		}
 	}
 }
+
+const layout = "2006-01-02 15:04:05"
+
+func TestTimeFunctionWithTZ(t *testing.T) {
+	l1, err := time.LoadLocation("UTC")
+	require.NoError(t, err)
+	l2, err := time.LoadLocation("Asia/Shanghai")
+	require.NoError(t, err)
+	err = cast.SetTimeZone("UTC")
+	require.NoError(t, err)
+	now := time.Now().In(l1)
+	m := conf.Clock.(*clock.Mock)
+	m.Set(now)
+	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)
+	f, ok := builtins["now"]
+	require.True(t, ok)
+	result, ok := f.exec(fctx, []interface{}{})
+	require.True(t, ok)
+	require.Equal(t, result.(string), now.Format(layout))
+	err = cast.SetTimeZone("Asia/Shanghai")
+	require.NoError(t, err)
+	result, ok = f.exec(fctx, []interface{}{})
+	require.True(t, ok)
+	require.Equal(t, result.(string), now.In(l2).Format(layout))
+
+	err = cast.SetTimeZone("UTC")
+	require.NoError(t, err)
+	f, ok = builtins["from_unix_time"]
+	require.True(t, ok)
+	result, ok = f.exec(fctx, []interface{}{1691995105})
+	require.True(t, ok)
+	require.Equal(t, result.(string), "2023-08-14 06:38:25")
+
+	err = cast.SetTimeZone("Asia/Shanghai")
+	require.NoError(t, err)
+	result, ok = f.exec(fctx, []interface{}{1691995105})
+	require.True(t, ok)
+	require.Equal(t, result.(string), "2023-08-14 14:38:25")
+}
+
+func TestValidateFsp(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)
+	f := validFspArgs()
+	err := f(fctx, []ast.Expr{})
+	require.NoError(t, err)
+}

+ 7 - 1
internal/conf/conf.go

@@ -249,7 +249,9 @@ func InitConf() {
 		Config.Basic.RestIp = "0.0.0.0"
 	}
 
-	Config.Basic.RulePatrolInterval = "10s"
+	if len(Config.Basic.RulePatrolInterval) < 1 {
+		Config.Basic.RulePatrolInterval = "10s"
+	}
 
 	if Config.Basic.Debug {
 		SetDebugLevel(true)
@@ -263,6 +265,10 @@ func InitConf() {
 		if err := cast.SetTimeZone(Config.Basic.TimeZone); err != nil {
 			Log.Fatal(err)
 		}
+	} else {
+		if err := cast.SetTimeZone("Local"); err != nil {
+			Log.Fatal(err)
+		}
 	}
 
 	if Config.Store.Type == "redis" && Config.Store.Redis.ConnectionSelector != "" {

+ 6 - 1
internal/topo/operator/preprocessor_test.go

@@ -27,6 +27,7 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/converter"
@@ -572,6 +573,8 @@ func TestPreprocessor_Apply(t *testing.T) {
 }
 
 func TestPreprocessorTime_Apply(t *testing.T) {
+	err := cast.SetTimeZone("Local")
+	require.NoError(t, err)
 	tests := []struct {
 		stmt   *ast.StreamStmt
 		data   []byte
@@ -707,7 +710,7 @@ func TestPreprocessorTime_Apply(t *testing.T) {
 			// workaround make sure all the timezone are the same for time vars or the DeepEqual will be false.
 			if rt, ok := result.(*xsql.Tuple); ok {
 				if rtt, ok := rt.Message["abc"].(time.Time); ok {
-					rt.Message["abc"] = rtt.UTC()
+					rt.Message["abc"] = rtt.Local()
 				}
 			}
 			if !reflect.DeepEqual(tt.result, result) {
@@ -730,6 +733,8 @@ func convertFields(o ast.StreamFields) []interface{} {
 }
 
 func TestPreprocessorEventtime_Apply(t *testing.T) {
+	err := cast.SetTimeZone("UTC")
+	require.NoError(t, err)
 	tests := []struct {
 		stmt   *ast.StreamStmt
 		data   []byte

+ 5 - 1
pkg/cast/time.go

@@ -70,7 +70,11 @@ func init() {
 	now.TimeFormats = append(now.TimeFormats, JSISO, ISO8601)
 }
 
-var localTimeZone = time.UTC
+func GetConfiguredTimeZone() *time.Location {
+	return localTimeZone
+}
+
+var localTimeZone = time.Local
 
 func SetTimeZone(name string) error {
 	loc, err := time.LoadLocation(name)

+ 39 - 30
pkg/cast/time_test.go

@@ -19,17 +19,20 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestTimeToAndFromMilli(t *testing.T) {
+	err := SetTimeZone("Asia/Shanghai")
+	require.NoError(t, err)
 	tests := []struct {
 		m int64
 		t time.Time
 	}{
-		{int64(1579140864913), time.Date(2020, time.January, 16, 2, 14, 24, 913000000, time.UTC)},
-		{int64(4913), time.Date(1970, time.January, 1, 0, 0, 4, 913000000, time.UTC)},
-		{int64(2579140864913), time.Date(2051, time.September, 24, 4, 1, 4, 913000000, time.UTC)},
-		{int64(-1579140864913), time.Date(1919, time.December, 17, 21, 45, 35, 87000000, time.UTC)},
+		{int64(1579140864913), time.Date(2020, time.January, 16, 10, 14, 24, 913000000, GetConfiguredTimeZone())},
+		{int64(4913), time.Date(1970, time.January, 1, 8, 0, 4, 913000000, GetConfiguredTimeZone())},
+		{int64(2579140864913), time.Date(2051, time.September, 24, 12, 1, 4, 913000000, GetConfiguredTimeZone())},
+		{int64(-1579140864913), time.Date(1919, time.December, 18, 5, 45, 35, 87000000, GetConfiguredTimeZone())},
 	}
 	for i, tt := range tests {
 		time := TimeFromUnixMilli(tt.m)
@@ -75,6 +78,8 @@ func TestFormatTime(t *testing.T) {
 }
 
 func TestParseTime(t *testing.T) {
+	err := SetTimeZone("Asia/Shanghai")
+	require.NoError(t, err)
 	tests := []struct {
 		d       time.Time
 		t       string
@@ -82,19 +87,19 @@ func TestParseTime(t *testing.T) {
 		wantErr bool
 	}{
 		{
-			time.Date(2020, time.January, 16, 2, 14, 24, 913000000, time.UTC),
+			time.Date(2020, time.January, 16, 2, 14, 24, 913000000, GetConfiguredTimeZone()),
 			"2020-01-16 02:14:24.913",
 			"YYYY-MM-dd HH:mm:ssSSS",
 			false,
 		},
 		{
-			time.Date(2020, time.January, 16, 2, 14, 24, 0, time.UTC),
+			time.Date(2020, time.January, 16, 2, 14, 24, 0, GetConfiguredTimeZone()),
 			"2020-01-16 02:14:24",
 			"YYYY-MM-dd HH:mm:ss",
 			false,
 		},
 		{
-			time.Date(2020, time.January, 16, 2, 14, 24, 0, time.UTC),
+			time.Date(2020, time.January, 16, 2, 14, 24, 0, GetConfiguredTimeZone()),
 			"2020-01-16 02:14:24",
 			"",
 			false,
@@ -118,6 +123,8 @@ func TestParseTime(t *testing.T) {
 }
 
 func TestInterfaceToTime(t *testing.T) {
+	err := SetTimeZone("Asia/Shanghai")
+	require.NoError(t, err)
 	tests := []struct {
 		i       interface{}
 		f       string
@@ -127,67 +134,67 @@ func TestInterfaceToTime(t *testing.T) {
 		{
 			"2022-04-13 06:22:32.233",
 			"YYYY-MM-dd HH:mm:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			"2022-04-13 6:22:32.2",
 			"YYYY-MM-dd h:m:sS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 200000000, time.UTC),
+			time.Date(2022, time.April, 13, 6, 22, 32, 200000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			"2022-04-13 6:22:32.23",
 			"YYYY-MM-dd h:m:sSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 230000000, time.UTC),
+			time.Date(2022, time.April, 13, 6, 22, 32, 230000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			"2022-04-13 Wed 06:22:32.233",
 			"YYYY-MM-dd EEE HH:m:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			"2022-04-13 Wednesday 06:22:32.233",
 			"YYYY-MM-dd EEEE HH:m:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			1649830952233,
 			"YYYY-MM-dd HH:mm:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 14, 22, 32, 233000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			int64(1649830952233),
 			"YYYY-MM-dd HH:mm:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 14, 22, 32, 233000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			float64(1649830952233),
 			"YYYY-MM-dd HH:mm:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 14, 22, 32, 233000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 14, 22, 32, 233000000, GetConfiguredTimeZone()),
 			"YYYY-MM-dd HH:mm:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 14, 22, 32, 233000000, GetConfiguredTimeZone()),
 			false,
 		},
 		{
 			"2022-04-13 06:22:32.233",
 			"YYYy-MM-dd HH:mm:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.Local),
 			true,
 		},
 		{
 			struct{}{},
 			"YYYY-MM-dd HH:mm:ssSSS",
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 14, 22, 32, 233000000, time.Local),
 			true,
 		},
 	}
@@ -203,6 +210,8 @@ func TestInterfaceToTime(t *testing.T) {
 }
 
 func TestInterfaceToUnixMilli(t *testing.T) {
+	err := SetTimeZone("Asia/Shanghai")
+	require.NoError(t, err)
 	tests := []struct {
 		i       interface{}
 		f       string
@@ -212,43 +221,43 @@ func TestInterfaceToUnixMilli(t *testing.T) {
 		{
 			"2022-04-13 06:22:32.233",
 			"YYYY-MM-dd HH:mm:ssSSS",
-			1649830952233,
+			1649802152233,
 			false,
 		},
 		{
-			1649830952233,
+			1649802152233,
 			"YYYY-MM-dd HH:mm:ssSSS",
-			1649830952233,
+			1649802152233,
 			false,
 		},
 		{
-			int64(1649830952233),
+			int64(1649802152233),
 			"YYYY-MM-dd HH:mm:ssSSS",
-			1649830952233,
+			1649802152233,
 			false,
 		},
 		{
-			float64(1649830952233),
+			float64(1649802152233),
 			"YYYY-MM-dd HH:mm:ssSSS",
-			1649830952233,
+			1649802152233,
 			false,
 		},
 		{
-			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, time.UTC),
+			time.Date(2022, time.April, 13, 6, 22, 32, 233000000, GetConfiguredTimeZone()),
 			"YYYY-MM-dd HH:mm:ssSSS",
-			1649830952233,
+			1649802152233,
 			false,
 		},
 		{
 			"2022-04-13 06:22:32.233",
 			"YYYy-MM-dd HH:mm:ssSSS",
-			1649830952233,
+			1649802152233,
 			true,
 		},
 		{
 			struct{}{},
 			"YYYY-MM-dd HH:mm:ssSSS",
-			1649830952233,
+			1649802152233,
 			true,
 		},
 	}