clockface: add hours

This commit is contained in:
vinchent 2024-09-23 22:31:05 +02:00
parent c6ef156d88
commit 6837cd9fc1
3 changed files with 106 additions and 23 deletions

View File

@ -14,6 +14,7 @@ type Point struct {
} }
const ( const (
hourHandLength = 50
minuteHandLength = 80 minuteHandLength = 80
secondHandLength = 90 secondHandLength = 90
clockCentreX = 150 clockCentreX = 150
@ -37,6 +38,7 @@ func SVGWriter(w io.Writer, t time.Time) {
io.WriteString(w, bezel) io.WriteString(w, bezel)
secondHand(w, t) secondHand(w, t)
minuteHand(w, t) minuteHand(w, t)
hourHand(w, t)
io.WriteString(w, svgEnd) io.WriteString(w, svgEnd)
} }
@ -52,6 +54,12 @@ func minuteHand(w io.Writer, t time.Time) {
makeHand(w, minuteHandLength, p) 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) { func makeHand(w io.Writer, length float64, p Point) {
p = Point{p.X * length, p.Y * length} // scale p = Point{p.X * length, p.Y * length} // scale
p = Point{p.X, -p.Y} // flip 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 { func minutesInRadians(t time.Time) float64 {
return (secondsInRadians(t) / 60) + math.Pi/(30/float64(t.Minute())) return (secondsInRadians(t) / 60) + math.Pi/(30/float64(t.Minute()))
} }

View File

@ -65,7 +65,6 @@ func TestSVGWriterMinuteHand(t *testing.T) {
line Line line Line
}{ }{
{simpleTime(0, 0, 0), Line{150, 150, 150, 70}}, {simpleTime(0, 0, 0), Line{150, 150, 150, 70}},
// {simpleTime(0, 0, 30), Line{150, 150, 150, 240}},
} }
for _, c := range cases { 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 { func containsLine(l Line, ls []Line) bool {
for _, line := range ls { for _, line := range ls {
if line == l { if line == l {

View File

@ -6,6 +6,47 @@ import (
"time" "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) { func TestMinutesInRadians(t *testing.T) {
cases := []struct { cases := []struct {
time time.Time 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) { func TestMinuteHandPoint(t *testing.T) {
cases := []struct { cases := []struct {
time time.Time 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) { func TestSecondHandPoint(t *testing.T) {
cases := []struct { cases := []struct {
time time.Time time time.Time