Skip to content

Commit 317cf2e

Browse files
committed
Update Go version requirement to 1.21.0 for go-yaml compatibility
- Update go.mod to require Go 1.21.0+ (minimum for go-yaml v1.18.0) - Remove toolchain directive to maximize compatibility - Update README.md to reflect new Go version requirement - Update go.sum with dependency checksums
1 parent 388600c commit 317cf2e

File tree

5 files changed

+197
-2
lines changed

5 files changed

+197
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ To install the `utc` package, use the following command:
4242
go get github.com/agentstation/utc
4343
```
4444
45-
**Requirements**: Go 1.18 or later (uses `any` type and other modern Go features)
45+
**Requirements**: Go 1.21.0 or later (required by go-yaml dependency)
4646
4747
## Usage
4848

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/agentstation/utc
22

3-
go 1.18
3+
go 1.21.0
4+
5+
require github.com/goccy/go-yaml v1.18.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
2+
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=

utc.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,39 @@ func (t *Time) UnmarshalText(text []byte) error {
216216
return nil
217217
}
218218

219+
// UnmarshalYAML implements the yaml.Unmarshaler interface for utc.Time
220+
func (t *Time) UnmarshalYAML(unmarshal func(any) error) error {
221+
var s string
222+
if err := unmarshal(&s); err != nil {
223+
return err
224+
}
225+
226+
// Handle empty string
227+
if s == "" {
228+
t.Time = time.Time{}
229+
return nil
230+
}
231+
232+
// Parse the time string using our flexible parser
233+
parsed, err := parse(s)
234+
if err != nil {
235+
return fmt.Errorf("failed to parse time %q: %w", s, err)
236+
}
237+
238+
t.Time = parsed
239+
return nil
240+
}
241+
242+
// MarshalYAML implements the yaml.Marshaler interface for utc.Time
243+
func (t Time) MarshalYAML() (any, error) {
244+
if t.Time.IsZero() {
245+
return nil, nil
246+
}
247+
248+
// Use RFC3339 format for YAML output
249+
return t.Time.Format(time.RFC3339), nil
250+
}
251+
219252
// String implements the Stringer interface for utc.Time. It prints the time in RFC3339 format.
220253
//
221254
// Unlike many Go types that panic on nil receivers, this method returns "<nil>" to match
@@ -554,6 +587,8 @@ func parse(s string) (time.Time, error) {
554587
time.RFC3339,
555588
"2006-01-02 15:04:05",
556589
"2006-01-02",
590+
"2006-01", // YYYY-MM format
591+
"2006", // YYYY format
557592
}
558593
var firstErr error
559594
for _, layout := range tryLayouts {

utc_test.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"strings"
77
"testing"
88
"time"
9+
10+
"github.com/goccy/go-yaml"
911
)
1012

1113
func TestUTC_UnmarshalJSON(t *testing.T) {
@@ -1235,3 +1237,157 @@ func TestUTC_TimezoneInitErrors(t *testing.T) {
12351237
}
12361238
}
12371239
}
1240+
1241+
func TestUTC_UnmarshalYAML(t *testing.T) {
1242+
tests := []struct {
1243+
name string
1244+
input string
1245+
want time.Time
1246+
wantErr bool
1247+
}{
1248+
{
1249+
name: "RFC3339 format",
1250+
input: `"2023-01-01T12:00:00Z"`,
1251+
want: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
1252+
wantErr: false,
1253+
},
1254+
{
1255+
name: "Date only format",
1256+
input: `"2023-06-15"`,
1257+
want: time.Date(2023, 6, 15, 0, 0, 0, 0, time.UTC),
1258+
wantErr: false,
1259+
},
1260+
{
1261+
name: "Year-month format",
1262+
input: `"2024-06"`,
1263+
want: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
1264+
wantErr: false,
1265+
},
1266+
{
1267+
name: "Year only format",
1268+
input: `"2024"`,
1269+
want: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
1270+
wantErr: false,
1271+
},
1272+
{
1273+
name: "Date time format",
1274+
input: `"2023-01-01 15:30:45"`,
1275+
want: time.Date(2023, 1, 1, 15, 30, 45, 0, time.UTC),
1276+
wantErr: false,
1277+
},
1278+
{
1279+
name: "Empty string",
1280+
input: `""`,
1281+
want: time.Time{},
1282+
wantErr: false,
1283+
},
1284+
{
1285+
name: "Invalid format",
1286+
input: `"not-a-date"`,
1287+
wantErr: true,
1288+
},
1289+
{
1290+
name: "Invalid month",
1291+
input: `"2023-13-01"`,
1292+
wantErr: true,
1293+
},
1294+
}
1295+
1296+
for _, tt := range tests {
1297+
t.Run(tt.name, func(t *testing.T) {
1298+
var ut Time
1299+
err := yaml.Unmarshal([]byte(tt.input), &ut)
1300+
if (err != nil) != tt.wantErr {
1301+
t.Errorf("UnmarshalYAML() error = %v, wantErr %v", err, tt.wantErr)
1302+
return
1303+
}
1304+
if !tt.wantErr && !ut.Time.Equal(tt.want) {
1305+
t.Errorf("UnmarshalYAML() = %v, want %v", ut.Time, tt.want)
1306+
}
1307+
})
1308+
}
1309+
}
1310+
1311+
func TestUTC_MarshalYAML(t *testing.T) {
1312+
tests := []struct {
1313+
name string
1314+
time Time
1315+
want string
1316+
wantErr bool
1317+
}{
1318+
{
1319+
name: "Normal time",
1320+
time: Time{time.Date(2023, 6, 15, 12, 30, 45, 0, time.UTC)},
1321+
want: "\"2023-06-15T12:30:45Z\"\n",
1322+
wantErr: false,
1323+
},
1324+
{
1325+
name: "Zero time",
1326+
time: Time{time.Time{}},
1327+
want: "null\n",
1328+
wantErr: false,
1329+
},
1330+
{
1331+
name: "Time with nanoseconds",
1332+
time: Time{time.Date(2023, 6, 15, 12, 30, 45, 123456789, time.UTC)},
1333+
want: "\"2023-06-15T12:30:45Z\"\n",
1334+
wantErr: false,
1335+
},
1336+
}
1337+
1338+
for _, tt := range tests {
1339+
t.Run(tt.name, func(t *testing.T) {
1340+
data, err := yaml.Marshal(tt.time)
1341+
if (err != nil) != tt.wantErr {
1342+
t.Errorf("MarshalYAML() error = %v, wantErr %v", err, tt.wantErr)
1343+
return
1344+
}
1345+
if !tt.wantErr && string(data) != tt.want {
1346+
t.Errorf("MarshalYAML() = %q, want %q", string(data), tt.want)
1347+
}
1348+
})
1349+
}
1350+
}
1351+
1352+
func TestUTC_YAMLRoundTrip(t *testing.T) {
1353+
// Test that we can marshal and unmarshal back to the same value
1354+
tests := []struct {
1355+
name string
1356+
time Time
1357+
}{
1358+
{
1359+
name: "Normal time",
1360+
time: Time{time.Date(2023, 6, 15, 12, 30, 45, 0, time.UTC)},
1361+
},
1362+
{
1363+
name: "Date only",
1364+
time: Time{time.Date(2023, 6, 15, 0, 0, 0, 0, time.UTC)},
1365+
},
1366+
{
1367+
name: "Year month",
1368+
time: Time{time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC)},
1369+
},
1370+
}
1371+
1372+
for _, tt := range tests {
1373+
t.Run(tt.name, func(t *testing.T) {
1374+
// Marshal to YAML
1375+
data, err := yaml.Marshal(tt.time)
1376+
if err != nil {
1377+
t.Fatalf("Failed to marshal: %v", err)
1378+
}
1379+
1380+
// Unmarshal back
1381+
var result Time
1382+
err = yaml.Unmarshal(data, &result)
1383+
if err != nil {
1384+
t.Fatalf("Failed to unmarshal: %v", err)
1385+
}
1386+
1387+
// Compare (ignoring nanoseconds which might be lost in formatting)
1388+
if !result.Time.Truncate(time.Second).Equal(tt.time.Time.Truncate(time.Second)) {
1389+
t.Errorf("Round trip failed: got %v, want %v", result.Time, tt.time.Time)
1390+
}
1391+
})
1392+
}
1393+
}

0 commit comments

Comments
 (0)