Skip to content

Commit c6d364f

Browse files
wlynchwillnorris
authored andcommitted
Add timestamp struct to handle different formats
Added a timestamp struct to handle different incoming time formats from GitHub. Only modified the repo struct to reflect this new time struct for now since it is currently the only location I am currently aware of where this is an issue. Issue: google#1
1 parent beaff13 commit c6d364f

File tree

3 files changed

+210
-3
lines changed

3 files changed

+210
-3
lines changed

github/github.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,29 @@ func cloneRequest(r *http.Request) *http.Request {
393393
}
394394
return r2
395395
}
396+
397+
// Timestamp represents a time that can be unmarshalled from a JSON string
398+
// formatted as either an RFC3339 or Unix timestamp. This is necessary for some
399+
// fields since the GitHub API is inconsistent in how it represents times. All
400+
// exported methods of time.Time can be called on Timestamp.
401+
type Timestamp struct {
402+
time.Time
403+
}
404+
405+
// UnmarshalJSON implements the json.Unmarshaler interface.
406+
// Time is expected in RFC3339 or Unix format.
407+
func (t *Timestamp) UnmarshalJSON(data []byte) (err error) {
408+
str := string(data)
409+
i, err := strconv.ParseInt(str, 10, 64)
410+
if err == nil {
411+
(*t).Time = time.Unix(i, 0)
412+
} else {
413+
(*t).Time, err = time.Parse(`"`+time.RFC3339+`"`, str)
414+
}
415+
return
416+
}
417+
418+
// Equal reports whether t and u are equal based on time.Equal
419+
func (t Timestamp) Equal(u Timestamp) bool {
420+
return t.Time.Equal(u.Time)
421+
}

github/repos.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ type Repository struct {
2626
Owner *User `json:"owner,omitempty"`
2727
Name string `json:"name,omitempty"`
2828
Description string `json:"description,omitempty"`
29-
CreatedAt *time.Time `json:"created_at,omitempty"`
30-
PushedAt *time.Time `json:"pushed_at,omitempty"`
31-
UpdatedAt *time.Time `json:"updated_at,omitempty"`
29+
CreatedAt *Timestamp `json:"created_at,omitempty"`
30+
PushedAt *Timestamp `json:"pushed_at,omitempty"`
31+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
3232
}
3333

3434
// RepositoryListOptions specifies the optional parameters to the

github/timestamp_test.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2013 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"encoding/json"
10+
"fmt"
11+
"testing"
12+
"time"
13+
)
14+
15+
const (
16+
emptyTimeStr = `"0001-01-01T00:00:00Z"`
17+
referenceTimeStr = `"2006-01-02T15:04:05Z"`
18+
referenceUnixTimeStr = `1136214245`
19+
)
20+
21+
var (
22+
referenceTime = time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)
23+
unixOrigin = time.Unix(0, 0).In(time.UTC)
24+
)
25+
26+
func TestTimestamp_Marshal(t *testing.T) {
27+
testCases := []struct {
28+
desc string
29+
data Timestamp
30+
want string
31+
wantErr bool
32+
equal bool
33+
}{
34+
{"Reference", Timestamp{referenceTime}, referenceTimeStr, false, true},
35+
{"Empty", Timestamp{}, emptyTimeStr, false, true},
36+
{"Mismatch", Timestamp{}, referenceTimeStr, false, false},
37+
}
38+
for _, tc := range testCases {
39+
out, err := json.Marshal(tc.data)
40+
if gotErr := err != nil; gotErr != tc.wantErr {
41+
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
42+
}
43+
got := string(out)
44+
equal := got == tc.want
45+
if (got == tc.want) != tc.equal {
46+
t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
47+
}
48+
}
49+
}
50+
51+
func TestTimestamp_Unmarshal(t *testing.T) {
52+
testCases := []struct {
53+
desc string
54+
data string
55+
want Timestamp
56+
wantErr bool
57+
equal bool
58+
}{
59+
{"Reference", referenceTimeStr, Timestamp{referenceTime}, false, true},
60+
{"ReferenceUnix", `1136214245`, Timestamp{referenceTime}, false, true},
61+
{"Empty", emptyTimeStr, Timestamp{}, false, true},
62+
{"UnixStart", `0`, Timestamp{unixOrigin}, false, true},
63+
{"Mismatch", referenceTimeStr, Timestamp{}, false, false},
64+
{"MismatchUnix", `0`, Timestamp{}, false, false},
65+
{"Invalid", `"asdf"`, Timestamp{referenceTime}, true, false},
66+
}
67+
for _, tc := range testCases {
68+
var got Timestamp
69+
err := json.Unmarshal([]byte(tc.data), &got)
70+
if gotErr := err != nil; gotErr != tc.wantErr {
71+
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
72+
continue
73+
}
74+
equal := got.Equal(tc.want)
75+
if equal != tc.equal {
76+
t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
77+
}
78+
}
79+
}
80+
81+
func TestTimstamp_MarshalReflexivity(t *testing.T) {
82+
testCases := []struct {
83+
desc string
84+
data Timestamp
85+
}{
86+
{"Reference", Timestamp{referenceTime}},
87+
{"Empty", Timestamp{}},
88+
}
89+
for _, tc := range testCases {
90+
data, err := json.Marshal(tc.data)
91+
if err != nil {
92+
t.Errorf("%s: Marshal err=%v", tc.desc, err)
93+
}
94+
var got Timestamp
95+
err = json.Unmarshal(data, &got)
96+
if !got.Equal(tc.data) {
97+
t.Errorf("%s: %+v != %+v", tc.desc, got, data)
98+
}
99+
}
100+
}
101+
102+
type WrappedTimestamp struct {
103+
A int
104+
Time Timestamp
105+
}
106+
107+
func TestWrappedTimstamp_Marshal(t *testing.T) {
108+
testCases := []struct {
109+
desc string
110+
data WrappedTimestamp
111+
want string
112+
wantErr bool
113+
equal bool
114+
}{
115+
{"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, true},
116+
{"Empty", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, emptyTimeStr), false, true},
117+
{"Mismatch", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, false},
118+
}
119+
for _, tc := range testCases {
120+
out, err := json.Marshal(tc.data)
121+
if gotErr := err != nil; gotErr != tc.wantErr {
122+
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
123+
}
124+
got := string(out)
125+
equal := got == tc.want
126+
if equal != tc.equal {
127+
t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
128+
}
129+
}
130+
}
131+
132+
func TestWrappedTimstamp_Unmarshal(t *testing.T) {
133+
testCases := []struct {
134+
desc string
135+
data string
136+
want WrappedTimestamp
137+
wantErr bool
138+
equal bool
139+
}{
140+
{"Reference", referenceTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true},
141+
{"ReferenceUnix", referenceUnixTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true},
142+
{"Empty", emptyTimeStr, WrappedTimestamp{0, Timestamp{}}, false, true},
143+
{"UnixStart", `0`, WrappedTimestamp{0, Timestamp{unixOrigin}}, false, true},
144+
{"Mismatch", referenceTimeStr, WrappedTimestamp{0, Timestamp{}}, false, false},
145+
{"MismatchUnix", `0`, WrappedTimestamp{0, Timestamp{}}, false, false},
146+
{"Invalid", `"asdf"`, WrappedTimestamp{0, Timestamp{referenceTime}}, true, false},
147+
}
148+
for _, tc := range testCases {
149+
var got Timestamp
150+
err := json.Unmarshal([]byte(tc.data), &got)
151+
if gotErr := err != nil; gotErr != tc.wantErr {
152+
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
153+
continue
154+
}
155+
equal := got.Time.Equal(tc.want.Time.Time)
156+
if equal != tc.equal {
157+
t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
158+
}
159+
}
160+
}
161+
162+
func TestWrappedTimstamp_MarshalReflexivity(t *testing.T) {
163+
testCases := []struct {
164+
desc string
165+
data WrappedTimestamp
166+
}{
167+
{"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}},
168+
{"Empty", WrappedTimestamp{0, Timestamp{}}},
169+
}
170+
for _, tc := range testCases {
171+
bytes, err := json.Marshal(tc.data)
172+
if err != nil {
173+
t.Errorf("%s: Marshal err=%v", tc.desc, err)
174+
}
175+
var got WrappedTimestamp
176+
err = json.Unmarshal(bytes, &got)
177+
if !got.Time.Equal(tc.data.Time) {
178+
t.Errorf("%s: %+v != %+v", tc.desc, got, tc.data)
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)