Skip to content

Commit

Permalink
Create clock package
Browse files Browse the repository at this point in the history
  • Loading branch information
sunshineplan committed Jan 12, 2024
1 parent 072ab95 commit 15bbcaa
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 73 deletions.
148 changes: 148 additions & 0 deletions clock/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package clock

import (
"cmp"
"encoding"
"fmt"
"time"
)

const (
secondsPerMinute = 60
secondsPerHour = 60 * secondsPerMinute
secondsPerDay = 24 * secondsPerHour
)

var (
_ encoding.TextMarshaler = Clock{}
_ encoding.TextUnmarshaler = new(Clock)
)

type Clock struct {
wall uint64
}

func abs[Int int | uint64](wall Int) uint64 {
for wall < 0 {
wall += secondsPerDay
}
return uint64(wall % secondsPerDay)
}

func New(hour, min, sec int) Clock {
return Clock{abs(hour*secondsPerHour + min*secondsPerMinute + sec)}
}

var clockLayout = []string{
"15:04",
time.TimeOnly,
time.Kitchen,
"03:04PM",
"03:04:05PM",
}

func Parse(v string) (Clock, error) {
for _, layout := range clockLayout {
if t, err := time.Parse(layout, v); err == nil {
return ParseTime(t), nil
}
}
return Clock{}, fmt.Errorf("cannot parse %q as clock", v)
}

func ParseTime(t time.Time) Clock {
return New(t.Clock())
}

func Now() Clock {
return ParseTime(time.Now())
}

func (c Clock) Time() time.Time {
return time.Unix(int64(c.wall), 0).UTC()
}

func (c Clock) Clock() (hour, min, sec int) {
sec = int(c.wall)
hour = sec / secondsPerHour
sec -= hour * secondsPerHour
min = sec / secondsPerMinute
sec -= min * secondsPerMinute
return
}

func (c Clock) Hour() int {
return int(c.wall%secondsPerDay) / secondsPerHour
}

func (c Clock) Minute() int {
return int(c.wall%secondsPerHour) / secondsPerMinute
}

func (c Clock) Second() int {
return int(c.wall % secondsPerMinute)
}

func (c Clock) String() string {
return fmt.Sprintf("%d:%02d:%02d", c.Hour(), c.Minute(), c.Second())
}

func (c Clock) MarshalText() (text []byte, err error) {
text = []byte(c.String())
return
}

func (c *Clock) UnmarshalText(text []byte) error {
clock, err := Parse(string(text))
if err != nil {
return err
}
*c = clock
return nil
}

func (c Clock) After(u Clock) bool {
return c.wall > u.wall
}

func (c Clock) Before(u Clock) bool {
return c.wall < u.wall
}

func (c Clock) Equal(u Clock) bool {
return c.wall == u.wall
}

func (c Clock) Compare(u Clock) int {
return cmp.Compare(c.wall, u.wall)
}

func (c Clock) Add(d time.Duration) Clock {
return Clock{abs(c.wall + uint64(d.Seconds()))}
}

func (c Clock) Sub(u Clock) time.Duration {
return time.Duration(int64(c.wall)-int64(u.wall)) * time.Second
}

func (c Clock) Since(u Clock) time.Duration {
return u.Until(c)
}

func (c Clock) Until(u Clock) time.Duration {
d := int64(u.wall) - int64(c.wall)
if d == 0 {
return 0
} else if d < 0 {
d += secondsPerDay
}
return time.Duration(d) * time.Second
}

func Since(c Clock) time.Duration {
return c.Until(Now())
}

func Until(c Clock) time.Duration {
return Now().Until(c)
}
97 changes: 97 additions & 0 deletions clock/clock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package clock

import (
"testing"
"time"
)

func TestClock(t *testing.T) {
for i, tc := range []struct {
hour, min, sec int
c Clock
str string
}{
{0, 0, 0, Clock{}, "0:00:00"},
{1, 2, 3, New(1, 2, 3), "1:02:03"},
{24, 0, 0, Clock{}, "0:00:00"},
{0, 60, 0, New(1, 0, 0), "1:00:00"},
{0, 0, 60, New(0, 1, 0), "0:01:00"},
{0, 0, -1, New(23, 59, 59), "23:59:59"},
} {
if got := New(tc.hour, tc.min, tc.sec); got != tc.c {
t.Errorf("#%d: New(%d, %d, %d): got %v; want %v", i, tc.hour, tc.min, tc.sec, got, tc.c)
} else if got.String() != tc.str {
t.Errorf("#%d: New(%d, %d, %d): got %q; want %q", i, tc.hour, tc.min, tc.sec, got.String(), tc.str)
}
}
}

func TestParse(t *testing.T) {
for i, testcase := range []struct {
s string
expected Clock
str string
}{
{"7:01", New(7, 1, 0), "7:01:00"},
{"7:01:02", New(7, 1, 2), "7:01:02"},
{"7:02PM", New(19, 2, 0), "19:02:00"},
{"07:03", New(7, 3, 0), "7:03:00"},
{"07:04:02", New(7, 4, 2), "7:04:02"},
{"19:04:30", New(19, 4, 30), "19:04:30"},
} {
if res, err := Parse(testcase.s); err != nil {
t.Error(err)
} else if res != testcase.expected {
t.Errorf("%s expected %v; got %v", testcase.s, testcase.expected, res)
} else if res.String() != testcase.str {
t.Errorf("#%d: got %q; want %q", i, res.String(), testcase.str)
}
}
for _, testcase := range []string{
"",
"abc",
"24:00",
} {
if _, err := Parse(testcase); err == nil {
t.Errorf("%s expected error; got nil", testcase)
}
}
}

func TestSub(t *testing.T) {
for i, tc := range []struct {
c Clock
u Clock
d time.Duration
}{
{Clock{}, Clock{}, 0},
{New(0, 0, 1), Clock{}, time.Second},
{Clock{}, New(0, 0, 1), -time.Second},
{New(6, 5, 4), Clock{}, 6*time.Hour + 5*time.Minute + 4*time.Second},
{New(1, 0, 0), New(0, 30, 0), 30 * time.Minute},
{New(12, 0, 0), New(12, 30, 0), -30 * time.Minute},
} {
if got := tc.c.Sub(tc.u); got != tc.d {
t.Errorf("#%d: Sub(%v, %v): got %v; want %v", i, tc.c, tc.u, got, tc.d)
}
}
}

func TestUntil(t *testing.T) {
for i, tc := range []struct {
c Clock
u Clock
d time.Duration
}{
{Clock{}, Clock{}, 0},
{Clock{}, New(0, 0, 1), time.Second},
{New(0, 0, 1), Clock{}, 23*time.Hour + 59*time.Minute + 59*time.Second},
{Clock{}, New(6, 5, 4), 6*time.Hour + 5*time.Minute + 4*time.Second},
{New(0, 30, 0), New(1, 0, 0), 30 * time.Minute},
{New(12, 30, 0), New(12, 0, 0), 23*time.Hour + 30*time.Minute},
} {
if got := tc.c.Until(tc.u); got != tc.d {
t.Errorf("#%d: Sub(%v, %v): got %v; want %v", i, tc.c, tc.u, got, tc.d)
}
}
}
Loading

0 comments on commit 15bbcaa

Please sign in to comment.