From 6837cd9fc16e8f85d13de6ae7a563d707a73809b Mon Sep 17 00:00:00 2001 From: vinchent Date: Mon, 23 Sep 2024 22:31:05 +0200 Subject: [PATCH] clockface: add hours --- clockface/clockface.go | 16 +++++ clockface/clockface_acceptance_test.go | 28 ++++++++- clockface/clockface_test.go | 85 +++++++++++++++++++------- 3 files changed, 106 insertions(+), 23 deletions(-) diff --git a/clockface/clockface.go b/clockface/clockface.go index 71366aa..745765b 100644 --- a/clockface/clockface.go +++ b/clockface/clockface.go @@ -14,6 +14,7 @@ type Point struct { } const ( + hourHandLength = 50 minuteHandLength = 80 secondHandLength = 90 clockCentreX = 150 @@ -37,6 +38,7 @@ func SVGWriter(w io.Writer, t time.Time) { io.WriteString(w, bezel) secondHand(w, t) minuteHand(w, t) + hourHand(w, t) io.WriteString(w, svgEnd) } @@ -52,6 +54,12 @@ func minuteHand(w io.Writer, t time.Time) { makeHand(w, minuteHandLength, p) } +// MinuteHand is the unit vector of the minute hand of an analogue clock at the time `t` represented as a Point +func hourHand(w io.Writer, t time.Time) { + p := hourHandPoint(t) + makeHand(w, hourHandLength, p) +} + func makeHand(w io.Writer, length float64, p Point) { p = Point{p.X * length, p.Y * length} // scale p = Point{p.X, -p.Y} // flip @@ -64,6 +72,14 @@ func makeHand(w io.Writer, length float64, p Point) { ) } +func hoursInRadians(t time.Time) float64 { + return (minutesInRadians(t) / 12) + math.Pi/(6/float64(t.Hour()%12)) +} + +func hourHandPoint(t time.Time) Point { + return angleToPoint(hoursInRadians(t)) +} + func minutesInRadians(t time.Time) float64 { return (secondsInRadians(t) / 60) + math.Pi/(30/float64(t.Minute())) } diff --git a/clockface/clockface_acceptance_test.go b/clockface/clockface_acceptance_test.go index a036585..db8e5fd 100644 --- a/clockface/clockface_acceptance_test.go +++ b/clockface/clockface_acceptance_test.go @@ -65,7 +65,6 @@ func TestSVGWriterMinuteHand(t *testing.T) { line Line }{ {simpleTime(0, 0, 0), Line{150, 150, 150, 70}}, - // {simpleTime(0, 0, 30), Line{150, 150, 150, 240}}, } for _, c := range cases { @@ -87,6 +86,33 @@ func TestSVGWriterMinuteHand(t *testing.T) { } } +func TestSVGWriterHourHand(t *testing.T) { + cases := []struct { + time time.Time + line Line + }{ + {simpleTime(6, 0, 0), Line{150, 150, 150, 200}}, + } + + 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 hour hand line %+v in the SVG lines %v", + c.line, + svg.Line, + ) + } + }) + } +} + func containsLine(l Line, ls []Line) bool { for _, line := range ls { if line == l { diff --git a/clockface/clockface_test.go b/clockface/clockface_test.go index 4543800..780b7eb 100644 --- a/clockface/clockface_test.go +++ b/clockface/clockface_test.go @@ -6,6 +6,47 @@ import ( "time" ) +func TestHoursInRadians(t *testing.T) { + cases := []struct { + time time.Time + angle float64 + }{ + {simpleTime(6, 0, 0), math.Pi}, + {simpleTime(0, 0, 0), 0}, + {simpleTime(21, 0, 0), math.Pi * 1.5}, + {simpleTime(0, 1, 30), math.Pi / ((6 * 60 * 60) / 90)}, + } + + for _, c := range cases { + t.Run(testName(c.time), func(t *testing.T) { + got := hoursInRadians(c.time) + + if !roughlyEqualFloat64(c.angle, got) { + t.Fatalf("Wanted %v radians, but got %v", c.angle, got) + } + }) + } +} + +func TestHourHandPoint(t *testing.T) { + cases := []struct { + time time.Time + point Point + }{ + {simpleTime(6, 0, 0), Point{0, -1}}, + {simpleTime(9, 0, 0), Point{-1, 0}}, + } + + for _, c := range cases { + t.Run(testName(c.time), func(t *testing.T) { + got := hourHandPoint(c.time) + if !roughlyEqualPoint(got, c.point) { + t.Fatalf("Wanted %v Point, but got %v", c.point, got) + } + }) + } +} + func TestMinutesInRadians(t *testing.T) { cases := []struct { time time.Time @@ -26,28 +67,6 @@ func TestMinutesInRadians(t *testing.T) { } } -func TestSecondsInRadians(t *testing.T) { - cases := []struct { - time time.Time - angle float64 - }{ - {simpleTime(0, 0, 30), math.Pi}, - {simpleTime(0, 0, 0), 0}, - {simpleTime(0, 0, 45), (math.Pi / 2) * 3}, - {simpleTime(0, 0, 7), (math.Pi / 30) * 7}, - } - - for _, c := range cases { - t.Run(testName(c.time), func(t *testing.T) { - got := secondsInRadians(c.time) - - if c.angle != got { - t.Fatalf("Wanted %v radians, but got %v", c.angle, got) - } - }) - } -} - func TestMinuteHandPoint(t *testing.T) { cases := []struct { time time.Time @@ -67,6 +86,28 @@ func TestMinuteHandPoint(t *testing.T) { } } +func TestSecondsInRadians(t *testing.T) { + cases := []struct { + time time.Time + angle float64 + }{ + {simpleTime(0, 0, 30), math.Pi}, + {simpleTime(0, 0, 0), 0}, + {simpleTime(0, 0, 45), (math.Pi / 2) * 3}, + {simpleTime(0, 0, 7), (math.Pi / 30) * 7}, + } + + for _, c := range cases { + t.Run(testName(c.time), func(t *testing.T) { + got := secondsInRadians(c.time) + + if c.angle != got { + t.Fatalf("Wanted %v radians, but got %v", c.angle, got) + } + }) + } +} + func TestSecondHandPoint(t *testing.T) { cases := []struct { time time.Time