Files
2026-07-13 12:32:25 +08:00

188 lines
6.9 KiB
Go

// Copyright 2024 Dolthub, Inc.
//
// 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 functions
import (
"strings"
"time"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/postgres/parser/duration"
"github.com/dolthub/doltgresql/postgres/parser/pgdate"
"github.com/dolthub/doltgresql/postgres/parser/timeofday"
"github.com/dolthub/doltgresql/postgres/parser/timetz"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// initTimezone registers the functions to the catalog.
func initTimezone() {
framework.RegisterFunction(timezone_interval_timestamptz)
framework.RegisterFunction(timezone_text_timestamptz)
framework.RegisterFunction(timezone_text_timetz)
framework.RegisterFunction(timezone_interval_timetz)
framework.RegisterFunction(timezone_text_timestamp)
framework.RegisterFunction(timezone_interval_timestamp)
}
// timezone_interval_timestamptz represents the PostgreSQL date/time function, taking {interval, timestamp with time zone}
var timezone_interval_timestamptz = framework.Function2{
Name: "timezone",
Return: pgtypes.Timestamp,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Interval, pgtypes.TimestampTZ},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
dur := val1.(duration.Duration)
t := val2.(time.Time)
return t.UTC().Add(time.Duration(dur.Nanos())), nil
},
}
// timezone_text_timestamptz represents the PostgreSQL date/time function, taking {text, timestamp with time zone}
var timezone_text_timestamptz = framework.Function2{
Name: "timezone",
Return: pgtypes.Timestamp,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.TimestampTZ},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
tz := val1.(string)
timeVal := val2.(time.Time)
_, newOffset, _, err := convertTzToOffsetSecs(timeVal, tz)
if err != nil {
return nil, err
}
return timeVal.UTC().Add(time.Duration(-int64(newOffset) * NanosPerSec)), nil
},
}
// timezone_text_timetz represents the PostgreSQL date/time function, taking {text, time with time zone}
var timezone_text_timetz = framework.Function2{
Name: "timezone",
Return: pgtypes.TimeTZ,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.TimeTZ},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
tz := val1.(string)
timeVal := val2.(timetz.TimeTZ).ToTime()
_, newOffset, _, err := convertTzToOffsetSecs(timeVal, tz)
if err != nil {
return nil, err
}
_, currentOffset := timeVal.Zone()
t := timeVal.Add(time.Duration((-int64(currentOffset) + int64(newOffset)) * NanosPerSec))
return timetz.MakeTimeTZ(timeofday.FromTime(t), -newOffset), nil
},
}
// timezone_interval_timetz represents the PostgreSQL date/time function, taking {interval, time with time zone}
var timezone_interval_timetz = framework.Function2{
Name: "timezone",
Return: pgtypes.TimeTZ,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Interval, pgtypes.TimeTZ},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
dur := val1.(duration.Duration)
timeVal := val2.(timetz.TimeTZ).ToTime()
newOffset := int32(dur.Nanos() / NanosPerSec)
_, currentOffset := timeVal.Zone()
t := timeVal.Add(time.Duration((-int64(currentOffset) + int64(newOffset)) * NanosPerSec))
return timetz.MakeTimeTZ(timeofday.FromTime(t), -newOffset), nil
},
}
// timezone_text_timestamp represents the PostgreSQL date/time function, taking {text, timestamp without time zone}
var timezone_text_timestamp = framework.Function2{
Name: "timezone",
Return: pgtypes.TimestampTZ,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.Timestamp},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
tz := val1.(string)
timeVal := val2.(time.Time)
_, newOffset, isOffset, err := convertTzToOffsetSecs(timeVal, tz)
if err != nil {
return nil, err
}
t := timeVal.Add(time.Duration(int64(-newOffset) * NanosPerSec))
if !isOffset {
// for named time zone
return t, nil
}
// for time offset
serverLoc, err := GetServerLocation(ctx)
if err != nil {
return nil, err
}
return t.In(serverLoc), nil
},
}
// timezone_interval_timestamp represents the PostgreSQL date/time function, taking {interval, timestamp without time zone}
var timezone_interval_timestamp = framework.Function2{
Name: "timezone",
Return: pgtypes.TimestampTZ,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Interval, pgtypes.Timestamp},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
dur := val1.(duration.Duration)
timeVal := val2.(time.Time)
serverLoc, err := GetServerLocation(ctx)
if err != nil {
return nil, err
}
return timeVal.Add(time.Duration(-dur.Nanos())).In(serverLoc), nil
},
}
// TZ input can be in format of 'UTC' or '-04:45:33'.
// It returns location, offset of the given timezone.
// It also returns true if given timezone is offset; false if named timezone.
func convertTzToOffsetSecs(t time.Time, tz string) (*time.Location, int32, bool, error) {
if strings.ToLower(tz) == "utc" {
tz = "UTC"
}
loc, err := time.LoadLocation(tz)
if err == nil {
_, offsetSecsUnconverted := t.In(loc).Zone()
return loc, int32(offsetSecsUnconverted), false, nil
}
if abbr, tzid := pgdate.GetTimezoneIdentifier(tz); tzid != "" {
loc, err = time.LoadLocation(tzid)
if err == nil {
_, offsetSecsUnconverted := t.In(loc).Zone()
return loc, int32(offsetSecsUnconverted), false, nil
}
} else if abbr != "" {
tz = abbr
}
if t, err = time.Parse("Z07", tz); err == nil {
} else if t, err = time.Parse("Z07:00", tz); err == nil {
} else if t, err = time.Parse("Z07:00:00", tz); err != nil {
return nil, 0, false, err
}
_, offsetSecsUnconverted := t.Zone()
return time.FixedZone("", -offsetSecsUnconverted), int32(-offsetSecsUnconverted), true, nil
}