Skip to content

Commit 29c8279

Browse files
authored
Merge pull request #38 from mosaxiv/date_sql
2 parents 5109fce + 9205efa commit 29c8279

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed

iso8601/sql.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package iso8601
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"database/sql/driver"
8+
)
9+
10+
// Scan implements the sql.Scanner interface.
11+
func (d *Date) Scan(src any) error {
12+
switch s := src.(type) {
13+
case nil:
14+
*d = Date{}
15+
case time.Time:
16+
*d = DateOf(s)
17+
case string:
18+
dl, err := ParseDate[string](s)
19+
if err != nil {
20+
return err
21+
}
22+
*d = dl.Date()
23+
return dl.Validate()
24+
case []byte:
25+
dl, err := ParseDate[[]byte](s)
26+
if err != nil {
27+
return err
28+
}
29+
*d = dl.Date()
30+
return dl.Validate()
31+
default:
32+
return fmt.Errorf("unknown type of: %T", s)
33+
}
34+
35+
return nil
36+
}
37+
38+
// Value implements the driver.Valuer interface.
39+
func (d *Date) Value() (driver.Value, error) {
40+
return d.String(), nil
41+
}
42+
43+
// NullDate represents a Date that may be null.
44+
type NullDate struct {
45+
Date Date
46+
Valid bool // Valid is true if Time is not NULL
47+
}
48+
49+
// Scan implements the sql.Scanner interface.
50+
func (n *NullDate) Scan(src any) error {
51+
if src == nil {
52+
n.Date, n.Valid = Date{}, false
53+
return nil
54+
}
55+
n.Valid = true // almost the same behavior as sql.NullTime
56+
return n.Date.Scan(src)
57+
}
58+
59+
// Value implements the driver.Valuer interface.
60+
func (n NullDate) Value() (driver.Value, error) {
61+
if !n.Valid {
62+
return nil, nil
63+
}
64+
return n.Date.Value()
65+
}

iso8601/sql_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package iso8601
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"database/sql"
8+
"database/sql/driver"
9+
)
10+
11+
var _ interface {
12+
sql.Scanner
13+
driver.Valuer
14+
} = (*Date)(nil)
15+
16+
var _ interface {
17+
sql.Scanner
18+
driver.Valuer
19+
} = (*NullDate)(nil)
20+
21+
func TestDate_Scan(t *testing.T) {
22+
testCases := []struct {
23+
Name string
24+
Value any
25+
Error bool
26+
Expected Date
27+
}{
28+
{
29+
Name: "Valid string date",
30+
Value: "2025-11-13",
31+
Expected: Date{2025, 11, 13},
32+
},
33+
{
34+
Name: "Valid time.Time date",
35+
Value: time.Date(2056, 10, 31, 0, 0, 0, 0, time.UTC),
36+
Expected: Date{2056, 10, 31},
37+
},
38+
{
39+
Name: "Valid byte slice date",
40+
Value: []byte("2157-12-31"),
41+
Expected: Date{2157, 12, 31},
42+
},
43+
{
44+
Name: "Invalid byte slice date",
45+
Value: []byte("xxx"),
46+
Error: true,
47+
},
48+
{
49+
Name: "Nil value",
50+
Value: nil,
51+
Expected: Date{},
52+
},
53+
}
54+
55+
for _, tc := range testCases {
56+
t.Run(tc.Name, func(t *testing.T) {
57+
var d Date
58+
err := d.Scan(tc.Value)
59+
if tc.Error {
60+
if err == nil {
61+
t.Errorf("expected error for value %v, but got none", tc.Value)
62+
}
63+
} else {
64+
if err != nil {
65+
t.Errorf("unexpected error for value %v: %v", tc.Value, err)
66+
} else if d != tc.Expected {
67+
t.Errorf("expected %v, but got %v", tc.Expected, d)
68+
}
69+
}
70+
})
71+
}
72+
}
73+
74+
func TestNullDate_Scan(t *testing.T) {
75+
testCases := []struct {
76+
Name string
77+
Value any
78+
Expected NullDate
79+
}{
80+
{
81+
Name: "Valid string date",
82+
Value: "2056-11-13",
83+
Expected: NullDate{Date: Date{2056, 11, 13}, Valid: true},
84+
},
85+
{
86+
Name: "Nil value",
87+
Value: nil,
88+
Expected: NullDate{Valid: false},
89+
},
90+
}
91+
92+
for _, tc := range testCases {
93+
t.Run(tc.Name, func(t *testing.T) {
94+
var nd NullDate
95+
err := nd.Scan(tc.Value)
96+
if err != nil {
97+
t.Errorf("unexpected error for value %v: %v", tc.Value, err)
98+
} else if nd != tc.Expected {
99+
t.Errorf("expected %v, but got %v", tc.Expected, nd)
100+
}
101+
})
102+
}
103+
}
104+
105+
func TestDate_Value(t *testing.T) {
106+
testCases := []struct {
107+
Name string
108+
Date Date
109+
Expected driver.Value
110+
}{
111+
{
112+
Name: "Valid date",
113+
Date: Date{2056, 11, 13},
114+
Expected: "2056-11-13",
115+
},
116+
{
117+
Name: "Zero date",
118+
Date: Date{},
119+
Expected: "0000-00-00",
120+
},
121+
}
122+
123+
for _, tc := range testCases {
124+
t.Run(tc.Name, func(t *testing.T) {
125+
value, err := tc.Date.Value()
126+
if err != nil {
127+
t.Errorf("unexpected error for date %v: %v", tc.Date, err)
128+
}
129+
if value != tc.Expected {
130+
t.Errorf("expected %v, but got %v", tc.Expected, value)
131+
}
132+
})
133+
}
134+
}
135+
136+
func TestNullDate_Value(t *testing.T) {
137+
testCases := []struct {
138+
Name string
139+
NullDate NullDate
140+
Expected driver.Value
141+
}{
142+
{
143+
Name: "Valid NullDate",
144+
NullDate: NullDate{Date: Date{2056, 11, 13}, Valid: true},
145+
Expected: "2056-11-13",
146+
},
147+
{
148+
Name: "Invalid NullDate",
149+
NullDate: NullDate{Valid: false},
150+
Expected: nil,
151+
},
152+
}
153+
154+
for _, tc := range testCases {
155+
t.Run(tc.Name, func(t *testing.T) {
156+
value, err := tc.NullDate.Value()
157+
if err != nil {
158+
t.Errorf("unexpected error for NullDate %v: %v", tc.NullDate, err)
159+
}
160+
if value != tc.Expected {
161+
t.Errorf("expected %v, but got %v", tc.Expected, value)
162+
}
163+
})
164+
}
165+
}

0 commit comments

Comments
 (0)