From 08e907c3db3a073e1b542a4c2161364f013ce0e1 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 23 Jul 2026 19:03:50 +0800 Subject: [PATCH] fix(oracle): preserve timezone-less datetime values --- agents/drivers/oracle-go/main.go | 12 +++++++++++ agents/drivers/oracle-go/main_test.go | 30 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/agents/drivers/oracle-go/main.go b/agents/drivers/oracle-go/main.go index c272c584c..7c4eefd61 100644 --- a/agents/drivers/oracle-go/main.go +++ b/agents/drivers/oracle-go/main.go @@ -3628,6 +3628,10 @@ func normalizeValue(value any, columnTypeName string) any { } return string(v) case time.Time: + // Oracle DATE and plain TIMESTAMP are wall-clock values; adding an offset makes clients shift them. + if isOracleTimezoneLessDateTime(columnTypeName) { + return v.Format("2006-01-02T15:04:05.999999999") + } return v.Format(time.RFC3339Nano) case int64, float64, bool, string: return v @@ -3638,6 +3642,14 @@ func normalizeValue(value any, columnTypeName string) any { } } +func isOracleTimezoneLessDateTime(columnTypeName string) bool { + normalized := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(columnTypeName), " ", "")) + if normalized == "DATE" || normalized == "TIMESTAMPDTY" || normalized == "TIMESTAMP" { + return true + } + return strings.HasPrefix(normalized, "TIMESTAMP(") && strings.HasSuffix(normalized, ")") +} + func isOracleBinaryColumnType(columnTypeName string) bool { normalized := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(columnTypeName), " ", "")) switch normalized { diff --git a/agents/drivers/oracle-go/main_test.go b/agents/drivers/oracle-go/main_test.go index 63e532ff4..057bb0784 100644 --- a/agents/drivers/oracle-go/main_test.go +++ b/agents/drivers/oracle-go/main_test.go @@ -242,6 +242,36 @@ func TestNormalizeValueKeepsNonBinaryBytesAsText(t *testing.T) { } } +func TestNormalizeValueFormatsOracleTimezoneLessDateTimesAsWallClock(t *testing.T) { + value := time.Date(2026, time.July, 23, 13, 42, 13, 123456000, time.FixedZone("CST", 8*60*60)) + tests := []string{ + "DATE", + "TIMESTAMP", + "TIMESTAMP(6)", + "TimeStampDTY", + } + + for _, columnType := range tests { + if got := normalizeValue(value, columnType); got != "2026-07-23T13:42:13.123456" { + t.Fatalf("normalizeValue time for %q = %#v, want wall-clock value", columnType, got) + } + } +} + +func TestNormalizeValueKeepsOracleZonedDateTimeOffsets(t *testing.T) { + value := time.Date(2026, time.July, 23, 13, 42, 13, 123456000, time.FixedZone("CST", 8*60*60)) + tests := []string{ + "TimeStampTZ_DTY", + "TIMESTAMP WITH TIME ZONE", + } + + for _, columnType := range tests { + if got := normalizeValue(value, columnType); got != "2026-07-23T13:42:13.123456+08:00" { + t.Fatalf("normalizeValue time for %q = %#v, want RFC3339 offset", columnType, got) + } + } +} + func TestNormalizeDDLObjectType(t *testing.T) { tests := map[string]string{ "": "",