Skip to content

Commit

Permalink
Merge pull request #1 from avalchev94/master
Browse files Browse the repository at this point in the history
Added interface implementations for Clock - good work, thanks :)
  • Loading branch information
rickb777 committed Sep 20, 2018
2 parents ee30139 + f815151 commit 3cbe88b
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
43 changes: 43 additions & 0 deletions clock/marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package clock

import (
"errors"
)

// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (c Clock) MarshalBinary() ([]byte, error) {
enc := []byte{
byte(c >> 24),
byte(c >> 16),
byte(c >> 8),
byte(c),
}
return enc, nil
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (c *Clock) UnmarshalBinary(data []byte) error {
if len(data) == 0 {
return errors.New("Clock.UnmarshalBinary: no data")
}
if len(data) != 4 {
return errors.New("Clock.UnmarshalBinary: invalid length")
}

*c = Clock(data[3]) | Clock(data[2])<<8 | Clock(data[1])<<16 | Clock(data[0])<<24
return nil
}

// MarshalText implements the encoding.TextMarshaler interface.
func (c Clock) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (c *Clock) UnmarshalText(data []byte) (err error) {
clock, err := Parse(string(data))
if err == nil {
*c = clock
}
return err
}
149 changes: 149 additions & 0 deletions clock/marshal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package clock

import (
"bytes"
"encoding/gob"
"encoding/json"
"strings"
"testing"
)

func TestGobEncoding(t *testing.T) {
var b bytes.Buffer
encoder := gob.NewEncoder(&b)
decoder := gob.NewDecoder(&b)
cases := []Clock{
New(-1, -1, -1, -1),
New(0, 0, 0, 0),
New(12, 40, 40, 80),
New(13, 55, 0, 20),
New(16, 20, 0, 0),
New(20, 60, 59, 59),
New(24, 0, 0, 0),
New(24, 0, 0, 1),
}
for _, c := range cases {
var clock Clock
err := encoder.Encode(&c)
if err != nil {
t.Errorf("Gob(%v) encode error %v", c, err)
} else {
err = decoder.Decode(&clock)
if err != nil {
t.Errorf("Gob(%v) decode error %v", c, err)
} else if clock != c {
t.Errorf("Gob(%v) decode got %v", c, clock)
}
}
}
}

func TestJSONMarshalling(t *testing.T) {
cases := []struct {
value Clock
want string
}{
{New(-1, -1, -1, -1), `"22:58:58.999"`},
{New(0, 0, 0, 0), `"00:00:00.000"`},
{New(12, 40, 40, 80), `"12:40:40.080"`},
{New(13, 55, 0, 20), `"13:55:00.020"`},
{New(16, 20, 0, 0), `"16:20:00.000"`},
{New(20, 60, 59, 59), `"21:00:59.059"`},
{New(24, 0, 0, 0), `"24:00:00.000"`},
{New(24, 0, 0, 1), `"00:00:00.001"`},
}
for _, c := range cases {
bb, err := json.Marshal(c.value)
if err != nil {
t.Errorf("JSON(%v) marshal error %v", c, err)
} else if string(bb) != c.want {
t.Errorf("JSON(%v) == %v, want %v", c.value, string(bb), c.want)
}
}
}

func TestJSONUnmarshalling(t *testing.T) {
cases := []struct {
values []string
want Clock
}{
{[]string{`"22:58:58.999"`, `"10:58:58.999pm"`}, New(-1, -1, -1, -1)},
{[]string{`"00:00:00.000"`, `"00:00:00.000AM"`}, New(0, 0, 0, 0)},
{[]string{`"12:40:40.080"`, `"12:40:40.080PM"`}, New(12, 40, 40, 80)},
{[]string{`"13:55:00.020"`, `"01:55:00.020PM"`}, New(13, 55, 0, 20)},
{[]string{`"16:20:00.000"`, `"04:20:00.000pm"`}, New(16, 20, 0, 0)},
{[]string{`"21:00:59.059"`, `"09:00:59.059PM"`}, New(20, 60, 59, 59)},
{[]string{`"24:00:00.000"`, `"00:00:00.000am"`}, New(24, 0, 0, 0)},
{[]string{`"00:00:00.001"`, `"00:00:00.001AM"`}, New(24, 0, 0, 1)},
}

for _, c := range cases {
for _, v := range c.values {
var clock Clock
err := json.Unmarshal([]byte(v), &clock)
if err != nil {
t.Errorf("JSON(%v) unmarshal error %v", v, err)
} else if c.want.Mod24() != clock.Mod24() {
t.Errorf("JSON(%v) == %v, want %v", v, clock, c.want)
}
}
}
}

func TestBinaryMarshalling(t *testing.T) {
cases := []Clock{
New(-1, -1, -1, -1),
New(0, 0, 0, 0),
New(12, 40, 40, 80),
New(13, 55, 0, 20),
New(16, 20, 0, 0),
New(20, 60, 59, 59),
New(24, 0, 0, 0),
New(24, 0, 0, 1),
}
for _, c := range cases {
bb, err := c.MarshalBinary()
if err != nil {
t.Errorf("Binary(%v) marshal error %v", c, err)
} else {
var clock Clock
err = clock.UnmarshalBinary(bb)
if err != nil {
t.Errorf("Binary(% v) unmarshal error %v", c, err)
} else if clock.Mod24() != c.Mod24() {
t.Errorf("Binary(%v) unmarshal got %v", c, clock)
}
}
}
}

func TestBinaryUnmarshallingErrors(t *testing.T) {
var c Clock
err1 := c.UnmarshalBinary([]byte{})
if err1 == nil {
t.Errorf("unmarshal no empty data error")
}

err2 := c.UnmarshalBinary([]byte("12345"))
if err2 == nil {
t.Errorf("unmarshal no wrong length error")
}
}

func TestInvalidClockText(t *testing.T) {
cases := []struct {
value string
want string
}{
{`not-a-clock`, `clock.Clock: cannot parse not-a-clock`},
{`00:50:100.0`, `clock.Clock: cannot parse 00:50:100.0`},
{`24:00:00.0pM`, `clock.Clock: cannot parse 24:00:00.0pM: strconv.Atoi: parsing "0pM": invalid syntax`},
}
for _, c := range cases {
var clock Clock
err := clock.UnmarshalText([]byte(c.value))
if err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("InvalidText(%v) == %v, want %v", c.value, err, c.want)
}
}
}
41 changes: 41 additions & 0 deletions clock/sql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package clock

import (
"database/sql/driver"
"fmt"
"time"
)

// Scan parses some value. It implements sql.Scanner,
// https://golang.org/pkg/database/sql/#Scanner
func (c *Clock) Scan(value interface{}) (err error) {
if value == nil {
return nil
}

return c.scanAny(value)
}

func (c *Clock) scanAny(value interface{}) (err error) {
err = nil
switch value.(type) {
case int64:
*c = Clock(value.(int64))
case []byte:
*c, err = Parse(string(value.([]byte)))
case string:
*c, err = Parse(value.(string))
case time.Time:
*c = NewAt(value.(time.Time))
default:
err = fmt.Errorf("%T %+v is not a meaningful clock", value, value)
}
return
}

// Value converts the value to an int64. It implements driver.Valuer,
// https://golang.org/pkg/database/sql/driver/#Valuer
func (c Clock) Value() (driver.Value, error) {

return int64(c), nil
}
73 changes: 73 additions & 0 deletions clock/sql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package clock

import (
"database/sql/driver"
"testing"
"time"
)

func TestClockScan(t *testing.T) {
now := time.Now()

cases := []struct {
v interface{}
expected Clock
}{
{int64(New(-1, -1, -1, -1)), New(-1, -1, -1, -1)},
{int64(New(10, 60, 10, 0)), New(10, 60, 10, 0)},
{int64(New(24, 10, 0, 10)), New(0, 10, 0, 10)},
{"12:00:00.400", New(12, 0, 0, 400)},
{"01:40:50.000pm", New(13, 40, 50, 0)},
{"4:20:00.000pm", New(16, 20, 0, 0)},
{[]byte("23:60:60.000"), New(0, 1, 0, 0)},
{now, NewAt(now)},
}

for i, c := range cases {
var clock Clock
e := clock.Scan(c.v)
if e != nil {
t.Errorf("%d: Got %v for %d", i, e, c.expected)
} else if clock.Mod24() != c.expected.Mod24() {
t.Errorf("%d: Got %v, want %d", i, clock, c.expected)
}

var d driver.Valuer = clock

q, e := d.Value()
if e != nil {
t.Errorf("%d: Got %v for %d", i, e, c.expected)
} else if Clock(q.(int64)).Mod24() != c.expected.Mod24() {
t.Errorf("%d: Got %v, want %d", i, q, c.expected)
}
}
}

func TestClockScanWithJunk(t *testing.T) {
cases := []struct {
v interface{}
expected string
}{
{true, "bool true is not a meaningful clock"},
{false, "bool false is not a meaningful clock"},
}

for i, c := range cases {
var clock Clock
e := clock.Scan(c.v)
if e.Error() != c.expected {
t.Errorf("%d: Got %q, want %q", i, e.Error(), c.expected)
}
}
}

func TestClockScanWithNil(t *testing.T) {
var r *Clock
e := r.Scan(nil)
if e != nil {
t.Errorf("Got %v", e)
}
if r != nil {
t.Errorf("Got %v", r)
}
}

0 comments on commit 3cbe88b

Please sign in to comment.