Skip to content

Commit

Permalink
Add timestamp struct to handle different formats
Browse files Browse the repository at this point in the history
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: #1
  • Loading branch information
wlynch authored and willnorris committed Jul 29, 2013
1 parent beaff13 commit c6d364f
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 3 deletions.
26 changes: 26 additions & 0 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,29 @@ func cloneRequest(r *http.Request) *http.Request {
}
return r2
}

// Timestamp represents a time that can be unmarshalled from a JSON string
// formatted as either an RFC3339 or Unix timestamp. This is necessary for some
// fields since the GitHub API is inconsistent in how it represents times. All
// exported methods of time.Time can be called on Timestamp.
type Timestamp struct {
time.Time
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// Time is expected in RFC3339 or Unix format.
func (t *Timestamp) UnmarshalJSON(data []byte) (err error) {
str := string(data)
i, err := strconv.ParseInt(str, 10, 64)
if err == nil {
(*t).Time = time.Unix(i, 0)
} else {
(*t).Time, err = time.Parse(`"`+time.RFC3339+`"`, str)
}
return
}

// Equal reports whether t and u are equal based on time.Equal
func (t Timestamp) Equal(u Timestamp) bool {
return t.Time.Equal(u.Time)
}
6 changes: 3 additions & 3 deletions github/repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ type Repository struct {
Owner *User `json:"owner,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
PushedAt *time.Time `json:"pushed_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
PushedAt *Timestamp `json:"pushed_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
}

// RepositoryListOptions specifies the optional parameters to the
Expand Down
181 changes: 181 additions & 0 deletions github/timestamp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
"encoding/json"
"fmt"
"testing"
"time"
)

const (
emptyTimeStr = `"0001-01-01T00:00:00Z"`
referenceTimeStr = `"2006-01-02T15:04:05Z"`
referenceUnixTimeStr = `1136214245`
)

var (
referenceTime = time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)
unixOrigin = time.Unix(0, 0).In(time.UTC)
)

func TestTimestamp_Marshal(t *testing.T) {
testCases := []struct {
desc string
data Timestamp
want string
wantErr bool
equal bool
}{
{"Reference", Timestamp{referenceTime}, referenceTimeStr, false, true},
{"Empty", Timestamp{}, emptyTimeStr, false, true},
{"Mismatch", Timestamp{}, referenceTimeStr, false, false},
}
for _, tc := range testCases {
out, err := json.Marshal(tc.data)
if gotErr := err != nil; gotErr != tc.wantErr {
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
}
got := string(out)
equal := got == tc.want
if (got == tc.want) != tc.equal {
t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
}
}
}

func TestTimestamp_Unmarshal(t *testing.T) {
testCases := []struct {
desc string
data string
want Timestamp
wantErr bool
equal bool
}{
{"Reference", referenceTimeStr, Timestamp{referenceTime}, false, true},
{"ReferenceUnix", `1136214245`, Timestamp{referenceTime}, false, true},
{"Empty", emptyTimeStr, Timestamp{}, false, true},
{"UnixStart", `0`, Timestamp{unixOrigin}, false, true},
{"Mismatch", referenceTimeStr, Timestamp{}, false, false},
{"MismatchUnix", `0`, Timestamp{}, false, false},
{"Invalid", `"asdf"`, Timestamp{referenceTime}, true, false},
}
for _, tc := range testCases {
var got Timestamp
err := json.Unmarshal([]byte(tc.data), &got)
if gotErr := err != nil; gotErr != tc.wantErr {
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
continue
}
equal := got.Equal(tc.want)
if equal != tc.equal {
t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
}
}
}

func TestTimstamp_MarshalReflexivity(t *testing.T) {
testCases := []struct {
desc string
data Timestamp
}{
{"Reference", Timestamp{referenceTime}},
{"Empty", Timestamp{}},
}
for _, tc := range testCases {
data, err := json.Marshal(tc.data)
if err != nil {
t.Errorf("%s: Marshal err=%v", tc.desc, err)
}
var got Timestamp
err = json.Unmarshal(data, &got)
if !got.Equal(tc.data) {
t.Errorf("%s: %+v != %+v", tc.desc, got, data)
}
}
}

type WrappedTimestamp struct {
A int
Time Timestamp
}

func TestWrappedTimstamp_Marshal(t *testing.T) {
testCases := []struct {
desc string
data WrappedTimestamp
want string
wantErr bool
equal bool
}{
{"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, true},
{"Empty", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, emptyTimeStr), false, true},
{"Mismatch", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, false},
}
for _, tc := range testCases {
out, err := json.Marshal(tc.data)
if gotErr := err != nil; gotErr != tc.wantErr {
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
}
got := string(out)
equal := got == tc.want
if equal != tc.equal {
t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
}
}
}

func TestWrappedTimstamp_Unmarshal(t *testing.T) {
testCases := []struct {
desc string
data string
want WrappedTimestamp
wantErr bool
equal bool
}{
{"Reference", referenceTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true},
{"ReferenceUnix", referenceUnixTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true},
{"Empty", emptyTimeStr, WrappedTimestamp{0, Timestamp{}}, false, true},
{"UnixStart", `0`, WrappedTimestamp{0, Timestamp{unixOrigin}}, false, true},
{"Mismatch", referenceTimeStr, WrappedTimestamp{0, Timestamp{}}, false, false},
{"MismatchUnix", `0`, WrappedTimestamp{0, Timestamp{}}, false, false},
{"Invalid", `"asdf"`, WrappedTimestamp{0, Timestamp{referenceTime}}, true, false},
}
for _, tc := range testCases {
var got Timestamp
err := json.Unmarshal([]byte(tc.data), &got)
if gotErr := err != nil; gotErr != tc.wantErr {
t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
continue
}
equal := got.Time.Equal(tc.want.Time.Time)
if equal != tc.equal {
t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
}
}
}

func TestWrappedTimstamp_MarshalReflexivity(t *testing.T) {
testCases := []struct {
desc string
data WrappedTimestamp
}{
{"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}},
{"Empty", WrappedTimestamp{0, Timestamp{}}},
}
for _, tc := range testCases {
bytes, err := json.Marshal(tc.data)
if err != nil {
t.Errorf("%s: Marshal err=%v", tc.desc, err)
}
var got WrappedTimestamp
err = json.Unmarshal(bytes, &got)
if !got.Time.Equal(tc.data.Time) {
t.Errorf("%s: %+v != %+v", tc.desc, got, tc.data)
}
}
}

0 comments on commit c6d364f

Please sign in to comment.