Skip to content

Commit 43fa972

Browse files
dannyotaclaude
andcommitted
Fix: Date.Value() timezone day-shift with PostgreSQL simple protocol
- Date.Value() now returns "YYYY-MM-DD" string instead of time.Time to prevent database drivers from applying timezone conversions that shift the date by a day. - Date.Scan() handles time.Time, string, and []byte inputs for compatibility with all database drivers. - Add GormDBDataType() for explicit provider column types. - Test asserts date components only (year/month/day), not timezone. Fixes #309 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 160cb6b commit 43fa972

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

date.go

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,69 @@ package datatypes
33
import (
44
"database/sql"
55
"database/sql/driver"
6+
"fmt"
67
"time"
8+
9+
"gorm.io/gorm"
10+
"gorm.io/gorm/schema"
711
)
812

913
type Date time.Time
1014

15+
// Scan implements sql.Scanner. Values returned from the database are always in UTC,
16+
// as Date.Value() stores dates as timezone-free "YYYY-MM-DD" strings.
1117
func (date *Date) Scan(value interface{}) (err error) {
12-
nullTime := &sql.NullTime{}
13-
err = nullTime.Scan(value)
14-
*date = Date(nullTime.Time)
18+
switch v := value.(type) {
19+
case time.Time:
20+
*date = Date(v)
21+
case string:
22+
t, err := time.ParseInLocation("2006-01-02", v, time.UTC)
23+
if err != nil {
24+
return err
25+
}
26+
*date = Date(t)
27+
case []byte:
28+
t, err := time.ParseInLocation("2006-01-02", string(v), time.UTC)
29+
if err != nil {
30+
return err
31+
}
32+
*date = Date(t)
33+
default:
34+
nullTime := &sql.NullTime{}
35+
err = nullTime.Scan(value)
36+
*date = Date(nullTime.Time)
37+
}
1538
return
1639
}
1740

41+
// Value implements driver.Valuer. Returns the date as a "YYYY-MM-DD" string
42+
// to prevent database drivers from applying timezone conversions that shift the date.
1843
func (date Date) Value() (driver.Value, error) {
1944
y, m, d := time.Time(date).Date()
20-
return time.Date(y, m, d, 0, 0, 0, 0, time.Time(date).Location()), nil
45+
return fmt.Sprintf("%04d-%02d-%02d", y, m, d), nil
2146
}
2247

2348
// GormDataType gorm common data type
2449
func (date Date) GormDataType() string {
2550
return "date"
2651
}
2752

53+
// GormDBDataType gorm db data type
54+
func (Date) GormDBDataType(db *gorm.DB, field *schema.Field) string {
55+
switch db.Dialector.Name() {
56+
case "mysql":
57+
return "DATE"
58+
case "postgres":
59+
return "DATE"
60+
case "sqlserver":
61+
return "DATE"
62+
case "sqlite":
63+
return "date"
64+
default:
65+
return ""
66+
}
67+
}
68+
2869
func (date Date) GobEncode() ([]byte, error) {
2970
return time.Time(date).GobEncode()
3071
}

date_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestDate(t *testing.T) {
3535
t.Fatalf("Failed to find record with date")
3636
}
3737

38-
AssertEqual(t, result.Date, beginningOfDay)
38+
AssertEqual(t, time.Time(result.Date).Format("2006-01-02"), beginningOfDay.Format("2006-01-02"))
3939
}
4040

4141
func TestGobEncoding(t *testing.T) {

0 commit comments

Comments
 (0)