diff --git a/clockface.go b/clockface.go index 642b645..00b9b97 100644 --- a/clockface.go +++ b/clockface.go @@ -14,9 +14,21 @@ func secondsInRadians(t time.Time) float64 { return math.Pi / (30 / (float64(t.Second()))) } -func secondHandPoint(t time.Time) Point { - angle := secondsInRadians(t) +func minutesInRadians(t time.Time) float64 { + return secondsInRadians(t)/60 + math.Pi/(30/(float64(t.Minute()))) +} + +func angleToPoint(angle float64) Point { x := math.Sin(angle) y := math.Cos(angle) + return Point{x, y} } + +func secondHandPoint(t time.Time) Point { + return angleToPoint(secondsInRadians(t)) +} + +func minuteHandPoint(t time.Time) Point { + return angleToPoint(minutesInRadians(t)) +} diff --git a/clockface_acceptance_test.go b/clockface_acceptance_test.go index 24e804b..ea89920 100644 --- a/clockface_acceptance_test.go +++ b/clockface_acceptance_test.go @@ -7,49 +7,84 @@ import ( "time" ) -type Svg struct { +type SVG struct { XMLName xml.Name `xml:"svg"` - Text string `xml:",chardata"` Xmlns string `xml:"xmlns,attr"` Width string `xml:"width,attr"` Height string `xml:"height,attr"` ViewBox string `xml:"viewBox,attr"` Version string `xml:"version,attr"` - Circle struct { - Text string `xml:",chardata"` - Cx string `xml:"cx,attr"` - Cy string `xml:"cy,attr"` - R string `xml:"r,attr"` - Style string `xml:"style,attr"` - } `xml:"circle"` - Line []struct { - Text string `xml:",chardata"` - X1 string `xml:"x1,attr"` - Y1 string `xml:"y1,attr"` - X2 string `xml:"x2,attr"` - Y2 string `xml:"y2,attr"` - Style string `xml:"style,attr"` - } `xml:"line"` + Circle Circle `xml:"circle"` + Line []Line `xml:"line"` +} + +type Circle struct { + Cx float64 `xml:"cx,attr"` + Cy float64 `xml:"cy,attr"` + R float64 `xml:"r,attr"` +} + +type Line struct { + X1 float64 `xml:"x1,attr"` + Y1 float64 `xml:"y1,attr"` + X2 float64 `xml:"x2,attr"` + Y2 float64 `xml:"y2,attr"` +} + +func containsLine(l Line, ls []Line) bool { + for _, line := range ls { + if line == l { + return true + } + } + return false } func TestSVGWriterAtMidnight(t *testing.T) { - tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC) - b := bytes.Buffer{} - - SVGWriter(&b, tm) - - svg := Svg{} - - xml.Unmarshal(b.Bytes(), &svg) - - x2 := "150.000" - y2 := "60.000" - - for _, line := range svg.Line { - if line.X2 == x2 && line.Y2 == y2 { - return - } + cases := []struct { + time time.Time + line Line + }{ + {simpleTime(0, 0, 0), + Line{150, 150, 150, 60}}, + {simpleTime(0, 0, 30), + Line{150, 150, 150, 240}}, } - t.Errorf("Expected to find the second hand with x2 of %v and y2 of %v, in the SVG output %v", x2, y2, b.String()) + for _, c := range cases { + t.Run(testName(c.time), func(t *testing.T) { + b := bytes.Buffer{} + SVGWriter(&b, c.time) + + svg := SVG{} + xml.Unmarshal(b.Bytes(), &svg) + + if !containsLine(c.line, svg.Line) { + t.Errorf("Expected to find the second hand line %+v, in the SVG lines %+v", c.line, svg.Line) + } + }) + } +} +func TestSVGWriterMinuteHand(t *testing.T) { + cases := []struct { + time time.Time + line Line + }{ + {simpleTime(0, 0, 0), + Line{150, 150, 150, 70}}, + } + + for _, c := range cases { + t.Run(testName(c.time), func(t *testing.T) { + b := bytes.Buffer{} + SVGWriter(&b, c.time) + + svg := SVG{} + xml.Unmarshal(b.Bytes(), &svg) + + if !containsLine(c.line, svg.Line) { + t.Errorf("Expected to find the second hand line %+v, in the SVG lines %+v", c.line, svg.Line) + } + }) + } } diff --git a/clockface_test.go b/clockface_test.go index 6bed993..47beb94 100644 --- a/clockface_test.go +++ b/clockface_test.go @@ -27,6 +27,25 @@ func TestSecondsInRadians(t *testing.T) { } } +func TestMinutesInRadians(t *testing.T) { + cases := []struct { + time time.Time + angle float64 + }{ + {simpleTime(0, 30, 0), math.Pi}, + {simpleTime(0, 0, 7), 7 * (math.Pi / (30 * 60))}, + } + + for _, c := range cases { + t.Run(testName(c.time), func(t *testing.T) { + got := minutesInRadians(c.time) + if got != c.angle { + t.Fatalf("Wanted %v radians, but got %v", c.angle, got) + } + }) + } +} + func TestSecondHandPoint(t *testing.T) { cases := []struct { time time.Time @@ -46,6 +65,25 @@ func TestSecondHandPoint(t *testing.T) { } } +func TestMinuteHandPoint(t *testing.T) { + cases := []struct { + time time.Time + point Point + }{ + {simpleTime(0, 30, 0), Point{0, -1}}, + {simpleTime(0, 45, 0), Point{-1, 0}}, + } + + for _, c := range cases { + t.Run(testName(c.time), func(t *testing.T) { + got := minuteHandPoint(c.time) + if !roughlyEqualPoint(got, c.point) { + t.Fatalf("Wanted %v Point, but got %v", c.point, got) + } + }) + } +} + func roughlyEqualFloat64(a, b float64) bool { const equalityThreshold = 1e-7 return math.Abs(a-b) < equalityThreshold diff --git a/svgWriter.go b/svgWriter.go index 5143bf6..dc697c0 100644 --- a/svgWriter.go +++ b/svgWriter.go @@ -7,6 +7,7 @@ import ( ) const secondHandLength = 90 +const minuteHandLength = 80 const clockCentreX = 150 const clockCentreY = 150 @@ -27,13 +28,22 @@ func SVGWriter(w io.Writer, t time.Time) { io.WriteString(w, svgStart) io.WriteString(w, bezel) SecondHand(w, t) + MinuteHand(w, t) io.WriteString(w, svgEnd) } +func makeHand(p Point, length float64) Point { + p = Point{p.X * length, p.Y * length} + p = Point{p.X, -p.Y} + return Point{p.X + clockCentreX, p.Y + clockCentreY} +} + func SecondHand(w io.Writer, t time.Time) { - p := secondHandPoint(t) - p = Point{p.X * secondHandLength, p.Y * secondHandLength} //scale - p = Point{p.X, -p.Y} //flip - p = Point{p.X + clockCentreX, p.Y + clockCentreY} //translate + p := makeHand(secondHandPoint(t), secondHandLength) fmt.Fprintf(w, ``, p.X, p.Y) } + +func MinuteHand(w io.Writer, t time.Time) { + p := makeHand(minuteHandPoint(t), minuteHandLength) + fmt.Fprintf(w, ``, p.X, p.Y) +}