Skip to content
This repository has been archived by the owner on Dec 13, 2022. It is now read-only.

Commit

Permalink
Support for -year flag.
Browse files Browse the repository at this point in the history
Also cleaned up and commented some code.
Year is a little more tricky since they can be invalid when in the past.
  • Loading branch information
jorinvo committed May 13, 2017
1 parent b9a7f61 commit ee71061
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 39 deletions.
14 changes: 14 additions & 0 deletions flags/flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Common test helpers
package flags

func equal(a []int, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
1 change: 1 addition & 0 deletions flags/intlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (l *intlist) Set(s string) error {
}

// Intlist defines a flag for a comma-separated list of integers.
// min and max restrict the size of the integer values.
// Call the returned function after flag.Parse to get the value.
func Intlist(name, usage string, min, max int) func() []int {
l := &intlist{min: min, max: max}
Expand Down
12 changes: 0 additions & 12 deletions flags/intlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,3 @@ Got %v`, i, tt.text, tt.parsed, l.list)
t.Errorf("Non empty String() output: %s", (&intlist{}).String())
}
}

func equal(a []int, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
4 changes: 2 additions & 2 deletions flags/monthlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func (l *monthlist) Set(s string) error {
// Monthlist defines a flag for a comma-separated list of months.
// Valid values are between 1 and 12.
// Call the returned function after flag.Parse to get the value.
func Monthlist(name, usage string) func() []time.Month {
func Monthlist(name string) func() []time.Month {
l := &monthlist{}
flag.Var(l, name, usage)
flag.Var(l, name, "1 to 12")
return func() []time.Month {
return l.list
}
Expand Down
4 changes: 2 additions & 2 deletions flags/weekdaylist.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ func (l *weekdaylist) Set(s string) error {
// Weekdaylist defines a flag for a comma-separated list of week days.
// Valid values are mo, tu, we, th, fr, sa, su.
// Call the returned function after flag.Parse to get the value.
func Weekdaylist(name, usage string) func() []time.Weekday {
func Weekdaylist(name string) func() []time.Weekday {
l := &weekdaylist{}
flag.Var(l, name, usage)
flag.Var(l, name, "mo,tu,we,th,fr,sa,su")
return func() []time.Weekday {
return l.list
}
Expand Down
42 changes: 42 additions & 0 deletions flags/yearlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package flags

import (
"flag"
"fmt"
"strconv"
"strings"
)

type yearlist struct {
list []int
}

func (l *yearlist) String() string {
s := make([]string, len(l.list))
for i := range l.list {
s[i] = strconv.Itoa(l.list[i])
}
return strings.Join(s, ",")
}

func (l *yearlist) Set(s string) error {
parts := strings.Split(s, ",")
for i, p := range parts {
x, err := strconv.Atoi(p)
if err != nil {
return fmt.Errorf("no integer at index %d: %s", i, p)
}
l.list = append(l.list, x)
}
return nil
}

// Yearlist defines a flag for a comma-separated list of integers.
// Call the returned function after flag.Parse to get the value.
func Yearlist(name string) func() []int {
l := &yearlist{}
flag.Var(l, name, "list of years")
return func() []int {
return l.list
}
}
44 changes: 44 additions & 0 deletions flags/yearlist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package flags

import "testing"

func TestYearlist(t *testing.T) {
tests := []struct {
text string
parsed []int
invalid bool
}{
{text: "0,2000", parsed: []int{0, 2000}},
{text: "2017", parsed: []int{2017}},
{text: "1999,57", parsed: []int{1999, 57}},
{text: "-2000,2000", parsed: []int{-2000, 2000}},
{text: "", invalid: true},
{text: "-1, 2", invalid: true},
{text: "hello", invalid: true},
}

for i, tt := range tests {
l := yearlist{}
if err := l.Set(tt.text); err != nil {
if !tt.invalid {
t.Errorf("parsing %s failed unexpectedly: %v", tt.text, err)
}
continue
}
if tt.invalid {
t.Errorf("parsing %s should have failed", tt.text)
continue
}
if !equal(l.list, tt.parsed) {
t.Errorf(`
%d.
Input: %s
Expected: %v
Got %v`, i, tt.text, tt.parsed, l.list)
}
}

if (&yearlist{}).String() != "" {
t.Errorf("Non empty String() output: %s", (&yearlist{}).String())
}
}
15 changes: 13 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ All flags are optional and can be used in any combination.
The condition flags take one or more value each.
Values are separated by comma.
Note that conditions match not the current, but the next possible match.
When the current date is March 2017
and you run 'sleepto -month 3' the execution time is March 1, 2018.
A command can be specified optionally.
All arguments following the command are passed to it.
Expand All @@ -52,8 +56,9 @@ func main() {
var (
silent = flag.Bool("silent", false, "Suppress all output")
versionFlag = flag.Bool("version", false, "Print binary version")
month = flags.Monthlist("month", "1 to 12")
weekday = flags.Weekdaylist("weekday", "mo,tu,we,th,fr,sa,su")
year = flags.Yearlist("year")
month = flags.Monthlist("month")
weekday = flags.Weekdaylist("weekday")
day = flags.Intlist("day", "1 to 31", 1, 31)
hour = flags.Intlist("hour", "0 to 23", 0, 23)
minute = flags.Intlist("minute", "0 to 59", 0, 59)
Expand All @@ -75,6 +80,7 @@ func main() {
now := time.Now()

next := match.Next(now, match.Condition{
Year: year(),
Month: month(),
Weekday: weekday(),
Day: day(),
Expand All @@ -88,6 +94,11 @@ func main() {
flag.Usage()
os.Exit(1)
}
// No matching conditions
if next.IsZero() {
fmt.Fprintf(os.Stderr, "year must be > current year (%d)", now.Year())
os.Exit(1)
}

if !*silent {
fmt.Fprintf(os.Stderr, "sleeping until: %s\n", next.Format(time.RFC1123))
Expand Down
22 changes: 22 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ func TestEcho(t *testing.T) {
}
}

func TestInvalid(t *testing.T) {
done := make(chan struct{})

// Run binary
go func() {
now := time.Now()
y := strconv.Itoa(now.Year())
cmd := exec.Command(tmpbin, "-year", y)
var stderr bytes.Buffer
cmd.Stderr = &stderr
out, err := cmd.Output()
equal(t, "exit status 1", err.Error(), "exit code")
equal(t, "", string(out), "stdout")
equal(t, "year must be > current year ("+y+")", stderr.String(), "stderr")
close(done)
}()

if err := timing(done, 0, 1); err != nil {
t.Error(err)
}
}

func TestAlarm(t *testing.T) {
done := make(chan struct{})
m := strconv.Itoa(int(time.Now().Month()))
Expand Down
64 changes: 54 additions & 10 deletions match/next.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "time"
// For each field one value of the list has
// to match to find a match for the condition.
type Condition struct {
Year []int
Month []time.Month
Day []int // 1 to 31
Weekday []time.Weekday
Expand All @@ -18,7 +19,13 @@ type Condition struct {
}

// Next finds the next time the passed condition matches.
// Returns an empty time.Time when no possible match can be found.
// This can only happen when there is no future year condition.
// Use .IsZero() to test if result is empty.
func Next(start time.Time, c Condition) time.Time {
if noMatch(start, c) {
return time.Time{}
}
t := setBase(start, c)
// Stop when when no condition
if t.Equal(start) {
Expand All @@ -29,16 +36,18 @@ func Next(start time.Time, c Condition) time.Time {
// Adjust biggest unit first.
for {
switch {
case wrong(c.Year, t.Year()):
t = addYear(t)
case wrongMonth(c.Month, t.Month()):
t = t.AddDate(0, 1, 1-t.Day()).Truncate(time.Hour * 24)
t = addMonth(t)
case wrong(c.Day, t.Day()) || wrongWeekday(c.Weekday, t.Weekday()):
t = t.AddDate(0, 0, 1).Truncate(time.Hour * 24)
t = addDay(t)
case wrong(c.Hour, t.Hour()):
t = t.Add(time.Hour).Truncate(time.Hour)
t = addHour(t)
case wrong(c.Minute, t.Minute()):
t = t.Add(time.Minute).Truncate(time.Minute)
t = addMinute(t)
case wrong(c.Second, t.Second()):
t = t.Add(time.Second).Truncate(time.Second)
t = addSecond(t)
default:
// Found matching time.
return t
Expand All @@ -51,20 +60,31 @@ func Next(start time.Time, c Condition) time.Time {
func setBase(t time.Time, c Condition) time.Time {
switch {
case len(c.Second) > 0:
return t.Add(time.Second).Truncate(time.Second)
return addSecond(t)
case len(c.Minute) > 0:
return t.Add(time.Minute).Truncate(time.Minute)
return addMinute(t)
case len(c.Hour) > 0:
return t.Add(time.Hour).Truncate(time.Hour)
return addHour(t)
case len(c.Day) > 0 || len(c.Weekday) > 0:
return t.AddDate(0, 0, 1).Truncate(time.Hour * 24)
return addDay(t)
case len(c.Month) > 0:
return t.AddDate(0, 1, 1-t.Day()).Truncate(time.Hour * 24)
return addMonth(t)
case len(c.Year) > 0:
return addYear(t)
default:
return t
}
}

func noMatch(t time.Time, c Condition) bool {
for _, y := range c.Year {
if y <= t.Year() {
return true
}
}
return false
}

func wrong(xs []int, x int) bool {
if len(xs) == 0 {
return false
Expand Down Expand Up @@ -92,3 +112,27 @@ func wrongWeekday(ds []time.Weekday, d time.Weekday) bool {
}
return wrong(xs, int(d))
}

func addYear(t time.Time) time.Time {
return t.AddDate(1, 1-int(t.Month()), 1-t.Day()).Truncate(time.Hour * 24)
}

func addMonth(t time.Time) time.Time {
return t.AddDate(0, 1, 1-t.Day()).Truncate(time.Hour * 24)
}

func addDay(t time.Time) time.Time {
return t.AddDate(0, 0, 1).Truncate(time.Hour * 24)
}

func addHour(t time.Time) time.Time {
return t.Add(time.Hour).Truncate(time.Hour)
}

func addMinute(t time.Time) time.Time {
return t.Add(time.Minute).Truncate(time.Minute)
}

func addSecond(t time.Time) time.Time {
return t.Add(time.Second).Truncate(time.Second)
}
Loading

0 comments on commit ee71061

Please sign in to comment.