Skip to content

Commit 398d72e

Browse files
committed
fix string to time conversion
1 parent 64fb0c5 commit 398d72e

2 files changed

Lines changed: 137 additions & 21 deletions

File tree

helper/time.go

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,27 @@ func UnixStringToTime(in string) (time.Time, error) {
2424
}
2525

2626
// ISO8601StringToTime converts an ISO8601 date string to a time.Time object.
27-
// It checks the format of the string and parses it accordingly.
28-
// It supports various formats including local time, UTC time, with and without microseconds.
27+
// It supports RFC3339 format timestamps including timezone offsets,
28+
// as well as database formats without timezone information.
2929
func ISO8601StringToTime(in string) (time.Time, error) {
30-
layout := ""
31-
// Iso8601 date string in local time (yyyy-MM-ddTHH:mm:ss.mmmuuu)
32-
match, _ := regexp.MatchString("^[-:.T0-9]{26}$", in)
33-
if match {
34-
layout = "2006-01-02T15:04:05.000000"
30+
// RFC3339Nano handles all formats with timezone (Z or +/-HH:MM) including nanoseconds
31+
// Examples: 2026-01-19T14:37:45.673212+01:00, 2026-01-19T14:37:45.123Z, 2026-01-19T14:37:45Z
32+
if date, err := time.Parse(time.RFC3339Nano, in); err == nil {
33+
return date, nil
3534
}
36-
37-
// Iso8601 date string in UTC time (yyyy-MM-ddTHH:mm:ss.mmmuuuZ)
38-
match, _ = regexp.MatchString("^[-:.T0-9]{26}Z$", in)
39-
if match {
40-
layout = "2006-01-02T15:04:05.000000Z"
35+
if date, err := time.Parse(time.RFC3339, in); err == nil {
36+
return date, nil
4137
}
4238

43-
// Iso8601 date string in local time without microseconds (yyyy-MM-ddTHH:mm:ss.mmm)
44-
match, _ = regexp.MatchString("^[-:.T0-9]{23}$", in)
45-
if match {
39+
// Database formats without timezone - local time only
40+
layout := ""
41+
switch len(in) {
42+
case 26: // yyyy-MM-ddTHH:mm:ss.mmmuuu (microseconds)
43+
layout = "2006-01-02T15:04:05.000000"
44+
case 23: // yyyy-MM-ddTHH:mm:ss.mmm (milliseconds)
4645
layout = "2006-01-02T15:04:05.000"
47-
}
48-
49-
// Iso8601 date string in UTC time without microseconds (yyyy-MM-ddTHH:mm:ss.mmmZ)
50-
match, _ = regexp.MatchString("^[-:.T0-9]{23}Z$", in)
51-
if match {
52-
layout = "2006-01-02T15:04:05.000Z"
46+
default:
47+
return time.Time{}, fmt.Errorf("error parsing iso8601 string to time: unsupported format")
5348
}
5449

5550
date, err := time.Parse(layout, in)

helper/time_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package helper
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestISO8601StringToTime(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input string
13+
wantError bool
14+
}{
15+
{
16+
name: "RFC3339 with timezone offset and nanoseconds",
17+
input: "2026-01-19T14:37:45.673212+01:00",
18+
wantError: false,
19+
},
20+
{
21+
name: "RFC3339 with Z suffix",
22+
input: "2026-01-19T14:37:45Z",
23+
wantError: false,
24+
},
25+
{
26+
name: "RFC3339 with Z suffix and nanoseconds",
27+
input: "2026-01-19T14:37:45.123456789Z",
28+
wantError: false,
29+
},
30+
{
31+
name: "Zero time with Z suffix",
32+
input: "0001-01-01T00:00:00Z",
33+
wantError: false,
34+
},
35+
{
36+
name: "RFC3339 with negative timezone offset",
37+
input: "2026-01-19T14:37:45-05:00",
38+
wantError: false,
39+
},
40+
{
41+
name: "DB format - local time with microseconds",
42+
input: "2026-01-19T14:37:45.123456",
43+
wantError: false,
44+
},
45+
{
46+
name: "DB format - UTC time with microseconds",
47+
input: "2026-01-19T14:37:45.123456Z",
48+
wantError: false,
49+
},
50+
{
51+
name: "DB format - local time with milliseconds",
52+
input: "2026-01-19T14:37:45.123",
53+
wantError: false,
54+
},
55+
{
56+
name: "DB format - UTC time with milliseconds",
57+
input: "2026-01-19T14:37:45.123Z",
58+
wantError: false,
59+
},
60+
{
61+
name: "Invalid format",
62+
input: "not-a-date",
63+
wantError: true,
64+
},
65+
}
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
result, err := ISO8601StringToTime(tt.input)
70+
if tt.wantError {
71+
assert.Error(t, err)
72+
return
73+
}
74+
75+
assert.NoError(t, err)
76+
assert.False(t, result.IsZero() && tt.input != "0001-01-01T00:00:00Z")
77+
})
78+
}
79+
}
80+
81+
func TestUnixStringToTime(t *testing.T) {
82+
tests := []struct {
83+
name string
84+
input string
85+
wantError bool
86+
}{
87+
{
88+
name: "Valid Unix timestamp",
89+
input: "1737292665",
90+
wantError: false,
91+
},
92+
{
93+
name: "Zero timestamp",
94+
input: "0",
95+
wantError: false,
96+
},
97+
{
98+
name: "Invalid format with letters",
99+
input: "12345abc",
100+
wantError: true,
101+
},
102+
{
103+
name: "Empty string",
104+
input: "",
105+
wantError: true,
106+
},
107+
}
108+
109+
for _, tt := range tests {
110+
t.Run(tt.name, func(t *testing.T) {
111+
result, err := UnixStringToTime(tt.input)
112+
if tt.wantError {
113+
assert.Error(t, err)
114+
return
115+
}
116+
117+
assert.NoError(t, err)
118+
assert.False(t, result.IsZero() && tt.input != "0")
119+
})
120+
}
121+
}

0 commit comments

Comments
 (0)