From 6cf6057c4c054df9f4a53fcbcbf1314ff70637a5 Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Mon, 17 Apr 2023 12:00:55 +0800 Subject: [PATCH 1/8] pkg/tz(ticdc): fix some bugs when setting timezone --- pkg/sink/mysql/config.go | 21 +++++++++++++-------- pkg/util/tz.go | 12 ++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pkg/sink/mysql/config.go b/pkg/sink/mysql/config.go index 24dc0506f94..8f4e8583849 100644 --- a/pkg/sink/mysql/config.go +++ b/pkg/sink/mysql/config.go @@ -26,10 +26,12 @@ import ( "github.com/pingcap/log" "github.com/pingcap/tiflow/cdc/contextutil" "github.com/pingcap/tiflow/cdc/model" + cmdcontext "github.com/pingcap/tiflow/pkg/cmd/context" "github.com/pingcap/tiflow/pkg/config" cerror "github.com/pingcap/tiflow/pkg/errors" "github.com/pingcap/tiflow/pkg/security" "github.com/pingcap/tiflow/pkg/sink" + "github.com/pingcap/tiflow/pkg/util" "go.uber.org/zap" ) @@ -160,7 +162,7 @@ func (c *Config) Apply( if err = getSafeMode(query, &c.SafeMode); err != nil { return err } - if err = getTimezone(ctx, query, &c.Timezone); err != nil { + if err = getTimezone(query, &c.Timezone); err != nil { return err } if err = getDuration(query, "read-timeout", &c.ReadTimeout); err != nil { @@ -357,9 +359,14 @@ func getSafeMode(values url.Values, safeMode *bool) error { return nil } -func getTimezone(ctx context.Context, values url.Values, timezone *string) error { +func getTimezone(values url.Values, timezone *string) error { if _, ok := values["time-zone"]; !ok { - tz := contextutil.TimezoneFromCtx(ctx) + // If time-zone is not specified, use the timezone of the server. + tz := contextutil.TimezoneFromCtx(cmdcontext.GetDefaultContext()) + log.Warn("Because time-zone is not specified, the timezone of the TiCDC server will be used. "+ + "We recommend that you specify the time-zone explicitly. "+ + "Please make sure that the timezone of the TiCDC server, sink-uri and the downstream database are consistent.", + zap.String("time-zone", tz.String())) *timezone = fmt.Sprintf(`"%s"`, tz.String()) return nil } @@ -367,14 +374,12 @@ func getTimezone(ctx context.Context, values url.Values, timezone *string) error s := values.Get("time-zone") if len(s) == 0 { *timezone = "" + log.Warn("Because time-zone is empty, the timezone of the downstream database will be used. " + + "We recommend that you specify the time-zone explicitly. ") return nil } - value, err := url.QueryUnescape(s) - if err != nil { - return cerror.WrapError(cerror.ErrMySQLInvalidConfig, err) - } - _, err = time.LoadLocation(value) + _, err := util.GetTimezone(s) if err != nil { return cerror.WrapError(cerror.ErrMySQLInvalidConfig, err) } diff --git a/pkg/util/tz.go b/pkg/util/tz.go index fbdc2a1bd0e..daaf53bbd91 100644 --- a/pkg/util/tz.go +++ b/pkg/util/tz.go @@ -18,8 +18,10 @@ import ( "strings" "time" + "github.com/pingcap/log" "github.com/pingcap/tidb/util/timeutil" cerror "github.com/pingcap/tiflow/pkg/errors" + "go.uber.org/zap" ) // GetTimezone returns the timezone specified by the name @@ -28,9 +30,19 @@ func GetTimezone(name string) (tz *time.Location, err error) { case "", "system", "local": tz, err = GetLocalTimezone() err = cerror.WrapError(cerror.ErrLoadTimezone, err) + if err == nil { + log.Info("Use the timezone of the TiCDC server machine", + zap.String("timezoneName", name), + zap.String("timezone", tz.String())) + } default: tz, err = time.LoadLocation(name) err = cerror.WrapError(cerror.ErrLoadTimezone, err) + if err == nil { + log.Info("Load the timezone specified by the user", + zap.String("timezoneName", name), + zap.String("timezone", tz.String())) + } } return } From d90519f54d16478faff6397819aba6b522239677 Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Mon, 17 Apr 2023 14:54:57 +0800 Subject: [PATCH 2/8] pkg/tz(ticdc): better code --- pkg/sink/mysql/config.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/sink/mysql/config.go b/pkg/sink/mysql/config.go index 8f4e8583849..306ada23a9d 100644 --- a/pkg/sink/mysql/config.go +++ b/pkg/sink/mysql/config.go @@ -360,13 +360,16 @@ func getSafeMode(values url.Values, safeMode *bool) error { } func getTimezone(values url.Values, timezone *string) error { + const pleaseSpecifyTimezone = "We recommend that you specify the time-zone explicitly. " + + "Please make sure that the timezone of the TiCDC server, " + + "sink-uri and the downstream database are consistent." if _, ok := values["time-zone"]; !ok { // If time-zone is not specified, use the timezone of the server. tz := contextutil.TimezoneFromCtx(cmdcontext.GetDefaultContext()) - log.Warn("Because time-zone is not specified, the timezone of the TiCDC server will be used. "+ - "We recommend that you specify the time-zone explicitly. "+ - "Please make sure that the timezone of the TiCDC server, sink-uri and the downstream database are consistent.", - zap.String("time-zone", tz.String())) + log.Warn("Because time-zone is not specified, "+ + "the timezone of the TiCDC server will be used. "+ + pleaseSpecifyTimezone, + zap.String("timezone", tz.String())) *timezone = fmt.Sprintf(`"%s"`, tz.String()) return nil } @@ -374,8 +377,9 @@ func getTimezone(values url.Values, timezone *string) error { s := values.Get("time-zone") if len(s) == 0 { *timezone = "" - log.Warn("Because time-zone is empty, the timezone of the downstream database will be used. " + - "We recommend that you specify the time-zone explicitly. ") + log.Warn("Because time-zone is empty, " + + "the timezone of the downstream database will be used. " + + pleaseSpecifyTimezone) return nil } From f72101600c23b7b63742198e17da57b990963786 Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Mon, 17 Apr 2023 15:07:33 +0800 Subject: [PATCH 3/8] pkg/tz(ticdc): bail out if timezone inconsistent --- pkg/sink/mysql/config.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg/sink/mysql/config.go b/pkg/sink/mysql/config.go index 306ada23a9d..8e4541360b7 100644 --- a/pkg/sink/mysql/config.go +++ b/pkg/sink/mysql/config.go @@ -363,14 +363,14 @@ func getTimezone(values url.Values, timezone *string) error { const pleaseSpecifyTimezone = "We recommend that you specify the time-zone explicitly. " + "Please make sure that the timezone of the TiCDC server, " + "sink-uri and the downstream database are consistent." + serverTimezone := contextutil.TimezoneFromCtx(cmdcontext.GetDefaultContext()) if _, ok := values["time-zone"]; !ok { // If time-zone is not specified, use the timezone of the server. - tz := contextutil.TimezoneFromCtx(cmdcontext.GetDefaultContext()) log.Warn("Because time-zone is not specified, "+ "the timezone of the TiCDC server will be used. "+ pleaseSpecifyTimezone, - zap.String("timezone", tz.String())) - *timezone = fmt.Sprintf(`"%s"`, tz.String()) + zap.String("timezone", serverTimezone.String())) + *timezone = fmt.Sprintf(`"%s"`, serverTimezone.String()) return nil } @@ -383,11 +383,22 @@ func getTimezone(values url.Values, timezone *string) error { return nil } - _, err := util.GetTimezone(s) + changefeedTimezone, err := util.GetTimezone(s) if err != nil { return cerror.WrapError(cerror.ErrMySQLInvalidConfig, err) } *timezone = fmt.Sprintf(`"%s"`, s) + // We need to check whether the timezone of the TiCDC server and the sink-uri are consistent. + // If they are inconsistent, it may cause the data to be inconsistent. + if changefeedTimezone.String() != serverTimezone.String() { + return cerror.WrapError(cerror.ErrMySQLInvalidConfig, errors.Errorf( + "the timezone of the TiCDC server and the sink-uri are inconsistent. "+ + "TiCDC server timezone: %s, sink-uri timezone: %s. "+ + "Please make sure that the timezone of the TiCDC server, "+ + "sink-uri and the downstream database are consistent.", + serverTimezone.String(), changefeedTimezone.String())) + } + return nil } From 2e6714c1651197f72887bc0de9364fdd1c7ab0b5 Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Mon, 17 Apr 2023 18:32:37 +0800 Subject: [PATCH 4/8] pkg/tz(ticdc): fix bug and add tests --- cdc/server/server.go | 15 ++++--- cdc/server/server_test.go | 4 +- pkg/sink/mysql/config.go | 9 ++-- pkg/sink/mysql/config_test.go | 80 +++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/cdc/server/server.go b/cdc/server/server.go index 48c57a03a98..7fe4106c098 100644 --- a/cdc/server/server.go +++ b/cdc/server/server.go @@ -30,6 +30,7 @@ import ( "github.com/pingcap/tidb/util/gctuner" "github.com/pingcap/tiflow/cdc" "github.com/pingcap/tiflow/cdc/capture" + "github.com/pingcap/tiflow/cdc/contextutil" "github.com/pingcap/tiflow/cdc/kv" "github.com/pingcap/tiflow/cdc/processor/pipeline/system" "github.com/pingcap/tiflow/cdc/processor/sourcemanager/engine/factory" @@ -281,23 +282,23 @@ func (s *server) startActorSystems(ctx context.Context) error { } // Run runs the server. -func (s *server) Run(ctx context.Context) error { - if err := s.prepare(ctx); err != nil { +func (s *server) Run(serverCtx context.Context) error { + if err := s.prepare(serverCtx); err != nil { return err } - err := s.startStatusHTTP(s.tcpServer.HTTP1Listener()) + err := s.startStatusHTTP(serverCtx, s.tcpServer.HTTP1Listener()) if err != nil { return err } - return s.run(ctx) + return s.run(serverCtx) } // startStatusHTTP starts the HTTP server. // `lis` is a listener that gives us plain-text HTTP requests. // TODO: can we decouple the HTTP server from the capture server? -func (s *server) startStatusHTTP(lis net.Listener) error { +func (s *server) startStatusHTTP(serverCtx context.Context, lis net.Listener) error { // LimitListener returns a Listener that accepts at most n simultaneous // connections from the provided Listener. Connections that exceed the // limit will wait in a queue and no new goroutines will be created until @@ -321,6 +322,10 @@ func (s *server) startStatusHTTP(lis net.Listener) error { Handler: router, ReadTimeout: httpConnectionTimeout, WriteTimeout: httpConnectionTimeout, + BaseContext: func(listener net.Listener) context.Context { + return contextutil.PutTimezoneInCtx(context.Background(), + contextutil.TimezoneFromCtx(serverCtx)) + }, } go func() { diff --git a/cdc/server/server_test.go b/cdc/server/server_test.go index 6cc83a53199..e4366aa84eb 100644 --- a/cdc/server/server_test.go +++ b/cdc/server/server_test.go @@ -190,7 +190,7 @@ func TestServerTLSWithoutCommonName(t *testing.T) { cp.EtcdClient = etcdClient server.capture = cp require.Nil(t, err) - err = server.startStatusHTTP(server.tcpServer.HTTP1Listener()) + err = server.startStatusHTTP(context.TODO(), server.tcpServer.HTTP1Listener()) require.Nil(t, err) defer func() { require.Nil(t, server.statusServer.Close()) @@ -277,7 +277,7 @@ func TestServerTLSWithCommonNameAndRotate(t *testing.T) { cp.EtcdClient = etcdClient server.capture = cp require.Nil(t, err) - err = server.startStatusHTTP(server.tcpServer.HTTP1Listener()) + err = server.startStatusHTTP(context.TODO(), server.tcpServer.HTTP1Listener()) require.Nil(t, err) defer func() { require.Nil(t, server.statusServer.Close()) diff --git a/pkg/sink/mysql/config.go b/pkg/sink/mysql/config.go index 8e4541360b7..805e984f10a 100644 --- a/pkg/sink/mysql/config.go +++ b/pkg/sink/mysql/config.go @@ -26,7 +26,6 @@ import ( "github.com/pingcap/log" "github.com/pingcap/tiflow/cdc/contextutil" "github.com/pingcap/tiflow/cdc/model" - cmdcontext "github.com/pingcap/tiflow/pkg/cmd/context" "github.com/pingcap/tiflow/pkg/config" cerror "github.com/pingcap/tiflow/pkg/errors" "github.com/pingcap/tiflow/pkg/security" @@ -162,7 +161,7 @@ func (c *Config) Apply( if err = getSafeMode(query, &c.SafeMode); err != nil { return err } - if err = getTimezone(query, &c.Timezone); err != nil { + if err = getTimezone(ctx, query, &c.Timezone); err != nil { return err } if err = getDuration(query, "read-timeout", &c.ReadTimeout); err != nil { @@ -359,11 +358,11 @@ func getSafeMode(values url.Values, safeMode *bool) error { return nil } -func getTimezone(values url.Values, timezone *string) error { +func getTimezone(ctxWithTimezone context.Context, values url.Values, timezone *string) error { const pleaseSpecifyTimezone = "We recommend that you specify the time-zone explicitly. " + "Please make sure that the timezone of the TiCDC server, " + "sink-uri and the downstream database are consistent." - serverTimezone := contextutil.TimezoneFromCtx(cmdcontext.GetDefaultContext()) + serverTimezone := contextutil.TimezoneFromCtx(ctxWithTimezone) if _, ok := values["time-zone"]; !ok { // If time-zone is not specified, use the timezone of the server. log.Warn("Because time-zone is not specified, "+ @@ -387,7 +386,7 @@ func getTimezone(values url.Values, timezone *string) error { if err != nil { return cerror.WrapError(cerror.ErrMySQLInvalidConfig, err) } - *timezone = fmt.Sprintf(`"%s"`, s) + *timezone = fmt.Sprintf(`"%s"`, changefeedTimezone.String()) // We need to check whether the timezone of the TiCDC server and the sink-uri are consistent. // If they are inconsistent, it may cause the data to be inconsistent. if changefeedTimezone.String() != serverTimezone.String() { diff --git a/pkg/sink/mysql/config_test.go b/pkg/sink/mysql/config_test.go index 9ba903f841a..b68e86b13d3 100644 --- a/pkg/sink/mysql/config_test.go +++ b/pkg/sink/mysql/config_test.go @@ -19,11 +19,14 @@ import ( "net/url" "strings" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" dmysql "github.com/go-sql-driver/mysql" + "github.com/pingcap/tiflow/cdc/contextutil" "github.com/pingcap/tiflow/cdc/model" "github.com/pingcap/tiflow/pkg/config" + "github.com/pingcap/tiflow/pkg/util" "github.com/stretchr/testify/require" ) @@ -341,3 +344,80 @@ func TestCheckTiDBVariable(t *testing.T) { require.NotNil(t, err) require.Regexp(t, ".*"+sql.ErrConnDone.Error(), err.Error()) } + +func TestApplyTimezone(t *testing.T) { + localTimezone, err := util.GetTimezone("Local") + require.Nil(t, err) + + tests := []struct { + name string + noChangefeedTimezone bool + changefeedTimezone string + serverTimezone *time.Location + expected string + expectedHasErr bool + expectedErr string + }{ + { + name: "no changefeed timezone", + noChangefeedTimezone: true, + serverTimezone: time.UTC, + expected: "\"UTC\"", + expectedHasErr: false, + }, + { + name: "empty changefeed timezone", + noChangefeedTimezone: false, + changefeedTimezone: "", + serverTimezone: time.UTC, + expected: "", + expectedHasErr: false, + }, + { + name: "normal changefeed timezone", + noChangefeedTimezone: false, + changefeedTimezone: "UTC", + serverTimezone: time.UTC, + expected: "\"UTC\"", + expectedHasErr: false, + }, + { + name: "local timezone", + noChangefeedTimezone: false, + changefeedTimezone: "Local", + serverTimezone: localTimezone, + expected: "\"" + localTimezone.String() + "\"", + expectedHasErr: false, + }, + { + name: "sink-uri timezone different from server timezone", + noChangefeedTimezone: false, + changefeedTimezone: "UTC", + serverTimezone: localTimezone, + expectedHasErr: true, + expectedErr: ".*Please make sure that the timezone of the TiCDC server.*", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg := NewConfig() + ctx := contextutil.PutTimezoneInCtx(context.Background(), test.serverTimezone) + sinkURI := "mysql://127.0.0.1:3306" + if !test.noChangefeedTimezone { + sinkURI = sinkURI + "?time-zone=" + test.changefeedTimezone + } + uri, err := url.Parse(sinkURI) + require.Nil(t, err) + err = cfg.Apply(ctx, + model.DefaultChangeFeedID("changefeed-01"), uri, config.GetDefaultReplicaConfig()) + if test.expectedHasErr { + require.NotNil(t, err) + require.Regexp(t, test.expectedErr, err.Error()) + } else { + require.Nil(t, err) + require.Equal(t, test.expected, cfg.Timezone) + } + }) + } +} From 360ee7d54d9768aa810b452663e5646e1ab05ebd Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Tue, 18 Apr 2023 11:11:56 +0800 Subject: [PATCH 5/8] pkg/tz(ticdc): fix tests --- pkg/sink/mysql/config_test.go | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/pkg/sink/mysql/config_test.go b/pkg/sink/mysql/config_test.go index b68e86b13d3..54e49674fe5 100644 --- a/pkg/sink/mysql/config_test.go +++ b/pkg/sink/mysql/config_test.go @@ -204,32 +204,6 @@ func TestApplySinkURIParamsToConfig(t *testing.T) { require.Equal(t, expected, cfg) } -func TestParseSinkURITimezone(t *testing.T) { - t.Parallel() - - uris := []string{ - "mysql://127.0.0.1:3306/?time-zone=Asia/Shanghai&worker-count=32", - "mysql://127.0.0.1:3306/?time-zone=&worker-count=32", - "mysql://127.0.0.1:3306/?worker-count=32", - } - expected := []string{ - "\"Asia/Shanghai\"", - "", - "\"UTC\"", - } - ctx := context.TODO() - for i, uriStr := range uris { - uri, err := url.Parse(uriStr) - require.Nil(t, err) - cfg := NewConfig() - err = cfg.Apply(ctx, - model.DefaultChangeFeedID("cf"), - uri, config.GetDefaultReplicaConfig()) - require.Nil(t, err) - require.Equal(t, expected[i], cfg.Timezone) - } -} - func TestParseSinkURIOverride(t *testing.T) { t.Parallel() @@ -346,6 +320,8 @@ func TestCheckTiDBVariable(t *testing.T) { } func TestApplyTimezone(t *testing.T) { + t.Parallel() + localTimezone, err := util.GetTimezone("Local") require.Nil(t, err) @@ -401,6 +377,8 @@ func TestApplyTimezone(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() + cfg := NewConfig() ctx := contextutil.PutTimezoneInCtx(context.Background(), test.serverTimezone) sinkURI := "mysql://127.0.0.1:3306" From 6a608f0a5f0a6219067ff393d4d7b358a3fd0608 Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Tue, 18 Apr 2023 11:22:16 +0800 Subject: [PATCH 6/8] pkg/tz(ticdc): add more tests --- pkg/sink/mysql/config_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/sink/mysql/config_test.go b/pkg/sink/mysql/config_test.go index 54e49674fe5..e606883cc36 100644 --- a/pkg/sink/mysql/config_test.go +++ b/pkg/sink/mysql/config_test.go @@ -371,7 +371,15 @@ func TestApplyTimezone(t *testing.T) { changefeedTimezone: "UTC", serverTimezone: localTimezone, expectedHasErr: true, - expectedErr: ".*Please make sure that the timezone of the TiCDC server.*", + expectedErr: "Please make sure that the timezone of the TiCDC server", + }, + { + name: "unsupported timezone format", + noChangefeedTimezone: false, + changefeedTimezone: "%2B08%3A00", // +08:00 + serverTimezone: time.UTC, + expectedHasErr: true, + expectedErr: "unknown time zone +08:00", }, } @@ -391,7 +399,7 @@ func TestApplyTimezone(t *testing.T) { model.DefaultChangeFeedID("changefeed-01"), uri, config.GetDefaultReplicaConfig()) if test.expectedHasErr { require.NotNil(t, err) - require.Regexp(t, test.expectedErr, err.Error()) + require.Contains(t, err.Error(), test.expectedErr) } else { require.Nil(t, err) require.Equal(t, test.expected, cfg.Timezone) From 44c4ee30770efa9895c1b03064fa4f6a56db0ca0 Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Tue, 18 Apr 2023 12:49:28 +0800 Subject: [PATCH 7/8] pkg/tz(ticdc): fix lint --- pkg/sink/mysql/config_test.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pkg/sink/mysql/config_test.go b/pkg/sink/mysql/config_test.go index e606883cc36..afbfd127e7e 100644 --- a/pkg/sink/mysql/config_test.go +++ b/pkg/sink/mysql/config_test.go @@ -325,7 +325,7 @@ func TestApplyTimezone(t *testing.T) { localTimezone, err := util.GetTimezone("Local") require.Nil(t, err) - tests := []struct { + for _, test := range []struct { name string noChangefeedTimezone bool changefeedTimezone string @@ -381,28 +381,27 @@ func TestApplyTimezone(t *testing.T) { expectedHasErr: true, expectedErr: "unknown time zone +08:00", }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { + } { + tc := test + t.Run(tc.name, func(t *testing.T) { t.Parallel() cfg := NewConfig() - ctx := contextutil.PutTimezoneInCtx(context.Background(), test.serverTimezone) + ctx := contextutil.PutTimezoneInCtx(context.Background(), tc.serverTimezone) sinkURI := "mysql://127.0.0.1:3306" - if !test.noChangefeedTimezone { - sinkURI = sinkURI + "?time-zone=" + test.changefeedTimezone + if !tc.noChangefeedTimezone { + sinkURI = sinkURI + "?time-zone=" + tc.changefeedTimezone } uri, err := url.Parse(sinkURI) require.Nil(t, err) err = cfg.Apply(ctx, model.DefaultChangeFeedID("changefeed-01"), uri, config.GetDefaultReplicaConfig()) - if test.expectedHasErr { + if tc.expectedHasErr { require.NotNil(t, err) - require.Contains(t, err.Error(), test.expectedErr) + require.Contains(t, err.Error(), tc.expectedErr) } else { require.Nil(t, err) - require.Equal(t, test.expected, cfg.Timezone) + require.Equal(t, tc.expected, cfg.Timezone) } }) } From 736daece108ae2e094f51a3790bdfaa9e994b40f Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Tue, 18 Apr 2023 13:00:21 +0800 Subject: [PATCH 8/8] pkg/tz(ticdc): better log --- pkg/sink/mysql/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/sink/mysql/config.go b/pkg/sink/mysql/config.go index 805e984f10a..de744872894 100644 --- a/pkg/sink/mysql/config.go +++ b/pkg/sink/mysql/config.go @@ -361,7 +361,9 @@ func getSafeMode(values url.Values, safeMode *bool) error { func getTimezone(ctxWithTimezone context.Context, values url.Values, timezone *string) error { const pleaseSpecifyTimezone = "We recommend that you specify the time-zone explicitly. " + "Please make sure that the timezone of the TiCDC server, " + - "sink-uri and the downstream database are consistent." + "sink-uri and the downstream database are consistent. " + + "If the downstream database does not load the timezone information, " + + "you can refer to https://dev.mysql.com/doc/refman/8.0/en/mysql-tzinfo-to-sql.html." serverTimezone := contextutil.TimezoneFromCtx(ctxWithTimezone) if _, ok := values["time-zone"]; !ok { // If time-zone is not specified, use the timezone of the server.