diff --git a/lib/column/date.go b/lib/column/date.go index 16d46e17ed..7453afba23 100644 --- a/lib/column/date.go +++ b/lib/column/date.go @@ -250,9 +250,7 @@ func parseDate(value string, minDate time.Time, maxDate time.Time, location *tim return tv, nil } if tv, err = time.Parse(defaultDateFormatNoZone, value); err == nil { - return time.Date( - tv.Year(), tv.Month(), tv.Day(), tv.Hour(), tv.Minute(), tv.Second(), tv.Nanosecond(), location, - ), nil + return getTimeWithDifferentLocation(tv, location), nil } return time.Time{}, err } @@ -272,10 +270,10 @@ func (col *Date) Encode(buffer *proto.Buffer) { func (col *Date) row(i int) time.Time { t := col.col.Row(i) - if col.location != nil { + if col.location != nil && col.location != time.UTC { // proto.Date is normalized as time.Time with UTC timezone. // We make sure Date return from ClickHouse matches server timezone or user defined location. - t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), col.location) + t = getTimeWithDifferentLocation(t, col.location) } return t } diff --git a/lib/column/date32.go b/lib/column/date32.go index 174b3761d3..515042939d 100644 --- a/lib/column/date32.go +++ b/lib/column/date32.go @@ -247,10 +247,10 @@ func (col *Date32) Encode(buffer *proto.Buffer) { func (col *Date32) row(i int) time.Time { t := col.col.Row(i) - if col.location != nil { + if col.location != nil && col.location != time.UTC { // proto.Date is normalized as time.Time with UTC timezone. // We make sure Date return from ClickHouse matches server timezone or user defined location. - t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), col.location) + t = getTimeWithDifferentLocation(t, col.location) } return t } diff --git a/lib/column/datetime.go b/lib/column/datetime.go index 027931a89b..04f24f86a5 100644 --- a/lib/column/datetime.go +++ b/lib/column/datetime.go @@ -302,9 +302,7 @@ func (col *DateTime) parseDateTime(value string) (tv time.Time, err error) { return tv, nil } if tv, err = time.Parse(defaultDateTimeFormatNoZone, value); err == nil { - return time.Date( - tv.Year(), tv.Month(), tv.Day(), tv.Hour(), tv.Minute(), tv.Second(), tv.Nanosecond(), time.Local, - ), nil + return getTimeWithDifferentLocation(tv, time.Local), nil } return time.Time{}, err } diff --git a/lib/column/datetime64.go b/lib/column/datetime64.go index 518176e2fc..3e1f438b9f 100644 --- a/lib/column/datetime64.go +++ b/lib/column/datetime64.go @@ -307,9 +307,7 @@ func (col *DateTime64) parseDateTime(value string) (tv time.Time, err error) { return tv, nil } if tv, err = time.Parse(defaultDateTime64FormatNoZone, value); err == nil { - return time.Date( - tv.Year(), tv.Month(), tv.Day(), tv.Hour(), tv.Minute(), tv.Second(), tv.Nanosecond(), time.Local, - ), nil + return getTimeWithDifferentLocation(tv, time.Local), nil } return time.Time{}, err } diff --git a/lib/column/time_helper.go b/lib/column/time_helper.go new file mode 100644 index 0000000000..f71b8f92e5 --- /dev/null +++ b/lib/column/time_helper.go @@ -0,0 +1,29 @@ +// Licensed to ClickHouse, Inc. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. ClickHouse, Inc. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package column + +import "time" + +// getTimeWithDifferentLocation returns the same time but with different location, e.g. +// "2024-08-15 13:22:34 -03:00" will become "2024-08-15 13:22:34 +04:00". +func getTimeWithDifferentLocation(t time.Time, loc *time.Location) time.Time { + year, month, day := t.Date() + hour, minute, sec := t.Clock() + + return time.Date(year, month, day, hour, minute, sec, t.Nanosecond(), loc) +} diff --git a/lib/column/time_helper_test.go b/lib/column/time_helper_test.go new file mode 100644 index 0000000000..b4e0ce512b --- /dev/null +++ b/lib/column/time_helper_test.go @@ -0,0 +1,76 @@ +// Licensed to ClickHouse, Inc. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. ClickHouse, Inc. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package column + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGetTimeWithDifferentLocation(t *testing.T) { + tests := []struct { + in string + loc *time.Location + want string + }{ + { + in: "2023-02-15 14:02:12.321 +00:00", + loc: time.FixedZone("", 60*60), + want: "2023-02-15 14:02:12.321 +01:00", + }, + { + in: "2023-02-15 14:02:12.321 +03:00", + loc: time.FixedZone("", -4*60*60), + want: "2023-02-15 14:02:12.321 -04:00", + }, + { + in: "2024-02-29 02:01:12 -06:00", + loc: time.FixedZone("", -4*60*60), + want: "2024-02-29 02:01:12 -04:00", + }, + { + in: "2023-02-15 04:02:12.321 +02:00", + loc: time.UTC, + want: "2023-02-15 04:02:12.321 +00:00", + }, + { + in: "2023-02-15 04:02:12.321 +00:00", + loc: time.UTC, + want: "2023-02-15 04:02:12.321 +00:00", + }, + } + for _, tt := range tests { + in, _ := time.Parse(defaultDateTime64FormatWithZone, tt.in) + got := getTimeWithDifferentLocation(in, tt.loc) + assert.Equal(t, tt.want, got.Format(defaultDateTime64FormatWithZone)) + } +} + +var benchmarkResultTime time.Time + +func BenchmarkGetTimeWithDifferentLocation(b *testing.B) { + t := time.Date(2023, time.April, 12, 1, 12, 33, 0, time.UTC) + loc := time.FixedZone("", 4*60*60) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchmarkResultTime = getTimeWithDifferentLocation(t, loc) + } +}