<file_sep>package gotimer

// NewTime - 新しいgotimer.Timeを生成する
func NewTime(hour, minute, second int) Time {
	sec := (hour*60*60 + minute*60 + second) % (24 * 60 * 60)
	if sec < 0 {
		sec = 24*60*60 + sec
	}

	return Time(sec)
}

// Time - 時分秒を秒にした構造体
type Time int

func (t Time) hour() int {
	return int(t) / 60 / 60
}

func (t Time) minute() int {
	return (int(t) / 60) % 60
}

func (t Time) second() int {
	return int(t) % 60
}
<file_sep>module gitlab.com/tsuchinaga/gotimer

go 1.15
<file_sep>package gotimer

import "time"

// NewTerm - 新しい期間を返す
func NewTerm(start, stop Time) Term {
	return Term{start: start, stop: stop}
}

// Term - 期間の設定
type Term struct {
	start Time
	stop  Time
}

// runnable - startとstopの間にnowがあれば実行可能
func (t *Term) runnable(now time.Time) bool {
	n := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location())
	start := time.Date(now.Year(), now.Month(), now.Day(), t.start.hour(), t.start.minute(), t.start.second(), 0, now.Location())
	stop := time.Date(now.Year(), now.Month(), now.Day(), t.stop.hour(), t.stop.minute(), t.stop.second(), 0, now.Location())
	if t.stop < t.start {
		nt := NewTime(now.Hour(), now.Minute(), now.Second())
		if nt <= t.stop { // now <= stop なら、startを前日にする
			start = start.AddDate(0, 0, -1)
		} else {
			stop = stop.AddDate(0, 0, 1)
		}
	}
	return !n.Before(start) && !n.After(stop)
}

// runnableSecond - 実行可能期間を秒で返す
func (t *Term) runnableSecond() int {
	sec := int(t.stop) - int(t.start) + 1
	if sec <= 0 {
		sec += 24 * 60 * 60
	}
	return sec
}

func (t *Term) Equal(term Term) bool {
	return t.start == term.start && t.stop == term.stop
}

func (t *Term) In(time Time) bool {
	if t.stop < t.start { // stop < start
		return time <= t.stop || t.start <= time // start <= time || time <= stop
	} else { // start <= stop
		return t.start <= time && time <= t.stop // start <= time <= stop
	}
}
<file_sep>package gotimer

import (
	"context"
	"reflect"
	"sync"
	"testing"
	"time"
)

func Test_Timer_Run(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name     string
		timer    *Timer
		ctx      context.Context
		interval time.Duration
		task     func()
		want     error
	}{
		{name: "ctxが未設定ならerror", timer: &Timer{}, want: TimerNotSetContextError},
		{name: "intervalが1未満ならerror", timer: &Timer{}, ctx: context.Background(), want: TimerNotSetIntervalError},
		{name: "taskがnilならerror", timer: &Timer{}, ctx: context.Background(), interval: 15 * time.Second, want: TimerNotSetTaskError},
		{name: "isRunningがtrueならerror", timer: &Timer{timerRunning: true}, ctx: context.Background(), interval: 15 * time.Second, task: func() {}, want: TimerIsRunningError},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.timer.Run(test.ctx, test.interval, test.task)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Timer_Run_Cancel(t *testing.T) {
	t.Parallel()
	var count int
	timer := new(Timer)
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	interval := 3 * time.Second
	task := func() { count++ }
	got := timer.Run(ctx, interval, task)
	if count != 0 || got != nil {
		t.Errorf("%s error\nwant: %+v, %+v\ngot: %+v, %+v\n", t.Name(), 0, nil, count, got)
	}
	cancel()
}

func Test_Timer_Run_Task(t *testing.T) {
	t.Parallel()
	now := time.Now()
	var count int
	timer := new(Timer)
	timer.AddTerm(NewTerm(NewTime(now.Hour(), now.Minute(), now.Second()+1), NewTime(now.Hour(), now.Minute(), now.Second()+10)))
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	interval := 1 * time.Second
	task := func() { count++ }
	got := timer.Run(ctx, interval, task)
	if count != 3 || got != nil {
		t.Errorf("%s error\nwant: %+v, %+v\ngot: %+v, %+v\n", t.Name(), 3, nil, count, got)
	}
	cancel()
}

func Test_Timer_Run_Parallel(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name     string
		parallel bool
		timeout  time.Duration
		interval time.Duration
		want     int
	}{
		{name: "Parallelが許可されている場合、複数回実行される", parallel: true, timeout: 1 * time.Second, interval: 100 * time.Millisecond, want: 10},
		{name: "Parallelが許可されていない場合、1回しか実行されない", parallel: false, timeout: 1 * time.Second, interval: 100 * time.Millisecond, want: 1},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			var count int
			var mtx sync.Mutex
			task := func() {
				mtx.Lock()
				count++
				mtx.Unlock()
				time.Sleep(test.timeout)
			}
			timer := new(Timer)
			ctx, cancel := context.WithTimeout(context.Background(), test.timeout)
			defer cancel()
			err := timer.SetParallelRunnable(test.parallel).Run(ctx, test.interval, task)
			if test.want != count && err != nil {
				t.Errorf("%s error\nwant: %+v\ngot: %+v, %+v\n", t.Name(), test.want, count, err)
			}
		})
	}
}

func Test_Timer_SetParallelRunnable(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name         string
		timerRunning bool
		want         bool
	}{
		{name: "timerRunningでなければ設定が反映される", timerRunning: false, want: true},
		{name: "timerRunningであれば設定が反映されない", timerRunning: true, want: false},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			timer := &Timer{timerRunning: test.timerRunning}
			timer.SetParallelRunnable(true)
			got := timer.parallelRunnable
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Timer_AddTerm(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name         string
		timerRunning bool
		terms        []Term
		term         Term
		want         []Term
	}{
		{name: "timerRunningであれば変更が反映されない",
			timerRunning: true,
			terms:        []Term{NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0))},
			term:         NewTerm(NewTime(16, 30, 0), NewTime(5, 30, 0)),
			want:         []Term{NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0))}},
		{name: "timerRunningでなければ期間が追加される",
			timerRunning: false,
			terms:        []Term{NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0))},
			term:         NewTerm(NewTime(16, 30, 0), NewTime(5, 30, 0)),
			want:         []Term{NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0)), NewTerm(NewTime(16, 30, 0), NewTime(5, 30, 0))}},
		{name: "同じ期間があれば追加されない",
			timerRunning: false,
			terms:        []Term{NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0))},
			term:         NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0)),
			want:         []Term{NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0))}},
		{name: "追加後、開始時刻の昇順、停止時刻の降順で並び変えられる",
			timerRunning: false,
			terms:        []Term{NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0))},
			term:         NewTerm(NewTime(8, 45, 0), NewTime(5, 30, 0)),
			want:         []Term{NewTerm(NewTime(8, 45, 0), NewTime(5, 30, 0)), NewTerm(NewTime(8, 45, 0), NewTime(15, 15, 0))}},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			timer := Timer{timerRunning: test.timerRunning, terms: test.terms}
			timer.AddTerm(test.term)
			got := timer.terms
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Timer_nextTime(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name  string
		timer *Timer
		now   time.Time
		want  time.Time
	}{
		{name: "前回の実行日時が分からなければ次の開始日時を返す",
			timer: &Timer{terms: []Term{NewTerm(NewTime(12, 0, 0), NewTime(13, 0, 0))}},
			now:   time.Date(2020, 12, 21, 11, 0, 0, 0, time.Local),
			want:  time.Date(2020, 12, 21, 12, 0, 0, 0, time.Local)},
		{name: "前回の実行日時からinterval後の日時を返す",
			timer: &Timer{
				interval: 15 * time.Second,
				terms:    []Term{NewTerm(NewTime(11, 0, 0), NewTime(12, 0, 0))},
				next:     time.Date(2020, 12, 21, 11, 0, 0, 0, time.Local)},
			now:  time.Date(2020, 12, 21, 11, 0, 0, 0, time.Local),
			want: time.Date(2020, 12, 21, 11, 0, 15, 0, time.Local)},
		{name: "前回の実行日時からinterval後の日時の間に停止日時があれば次の開始時刻を返す",
			timer: &Timer{
				interval: 15 * time.Second,
				terms:    []Term{NewTerm(NewTime(11, 0, 0), NewTime(11, 0, 10))},
				next:     time.Date(2020, 12, 21, 11, 0, 0, 0, time.Local)},
			now:  time.Date(2020, 12, 21, 11, 0, 0, 0, time.Local),
			want: time.Date(2020, 12, 22, 11, 0, 0, 0, time.Local)},
		{name: "前回の実行日時が分からなくて、即時実行可能なら現在日時を返す",
			timer: &Timer{
				terms:    []Term{NewTerm(NewTime(10, 0, 0), NewTime(12, 0, 0))},
				startNow: true},
			now:  time.Date(2020, 12, 21, 11, 0, 0, 0, time.Local),
			want: time.Date(2020, 12, 21, 11, 0, 0, 0, time.Local)},
		{name: "前回の実行日時が分からなくて、即時実行可能でもtermsがなければ次の開始日時を返す",
			timer: &Timer{
				terms:    []Term{NewTerm(NewTime(11, 0, 0), NewTime(12, 0, 0))},
				startNow: true},
			now:  time.Date(2020, 12, 21, 13, 0, 0, 0, time.Local),
			want: time.Date(2020, 12, 22, 11, 0, 0, 0, time.Local)},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.timer.nextTime(test.now)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Timer_runnable(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name  string
		terms []Term
		now   time.Time
		want  bool
	}{
		{name: "termsのいずれかがtrueならtrueを返す",
			terms: []Term{
				NewTerm(NewTime(0, 0, 0), NewTime(1, 0, 0)),
				NewTerm(NewTime(2, 0, 0), NewTime(3, 0, 0)),
				NewTerm(NewTime(4, 0, 0), NewTime(5, 0, 0)),
				NewTerm(NewTime(6, 0, 0), NewTime(7, 0, 0)),
			},
			now:  time.Date(2020, 12, 28, 6, 30, 0, 0, time.Local),
			want: true},
		{name: "termsのいずれもfalseならfalseを返す",
			terms: []Term{
				NewTerm(NewTime(0, 0, 0), NewTime(1, 0, 0)),
				NewTerm(NewTime(2, 0, 0), NewTime(3, 0, 0)),
				NewTerm(NewTime(4, 0, 0), NewTime(5, 0, 0)),
				NewTerm(NewTime(6, 0, 0), NewTime(7, 0, 0)),
			},
			now:  time.Date(2020, 12, 28, 5, 30, 0, 0, time.Local),
			want: false},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := (&Timer{terms: test.terms}).runnable(test.now)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Timer_nextStart(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name  string
		timer *Timer
		now   time.Time
		want  time.Time
	}{
		{name: "開始時刻がなければ翌日の00:00:00が返される",
			timer: &Timer{interval: 15 * time.Second, terms: []Term{}},
			now:   time.Date(2020, 12, 21, 10, 05, 30, 123456789, time.Local),
			want:  time.Date(2020, 12, 22, 0, 0, 0, 0, time.Local)},
		{name: "当日に未来の開始時刻があればその開始日時が返される",
			timer: &Timer{interval: 15 * time.Second, terms: []Term{NewTerm(NewTime(10, 10, 0), NewTime(10, 20, 0))}},
			now:   time.Date(2020, 12, 21, 10, 05, 30, 123456789, time.Local),
			want:  time.Date(2020, 12, 21, 10, 10, 0, 0, time.Local)},
		{name: "開始時刻がすべて過去なら翌日の開始日時が返される",
			timer: &Timer{interval: 15 * time.Second, terms: []Term{
				NewTerm(NewTime(1, 0, 0), NewTime(23, 59, 59)),
				NewTerm(NewTime(2, 0, 0), NewTime(23, 59, 59)),
				NewTerm(NewTime(10, 0, 0), NewTime(23, 59, 59)),
			}},
			now:  time.Date(2020, 12, 21, 10, 05, 30, 123456789, time.Local),
			want: time.Date(2020, 12, 22, 1, 0, 0, 0, time.Local)},
		{name: "現在日時と重なった開始時刻は返されない",
			timer: &Timer{interval: 15 * time.Second, terms: []Term{
				NewTerm(NewTime(10, 0, 0), NewTime(23, 59, 59)),
				NewTerm(NewTime(10, 5, 30), NewTime(23, 59, 59)),
				NewTerm(NewTime(10, 6, 0), NewTime(23, 59, 59)),
			}},
			now:  time.Date(2020, 12, 21, 10, 05, 30, 123456789, time.Local),
			want: time.Date(2020, 12, 21, 10, 6, 0, 0, time.Local)},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.timer.nextStart(test.now)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Timer_incrementTaskRunning(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name             string
		taskRunning      int
		parallelRunnable bool
		want1            bool
		want2            int
	}{
		{name: "多重起動不可で実行数が1なら実行できないのでfalse",
			taskRunning: 1, parallelRunnable: false, want1: false, want2: 1},
		{name: "多重起動不可で実行数が0なら実行できるのでtrue",
			taskRunning: 0, parallelRunnable: false, want1: true, want2: 1},
		{name: "多重起動可で実行数が1なら実行できるのでtrue",
			taskRunning: 1, parallelRunnable: true, want1: true, want2: 2},
		{name: "多重起動可で実行数が0なら実行できるのでtrue",
			taskRunning: 0, parallelRunnable: true, want1: true, want2: 1},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			timer := &Timer{taskRunning: test.taskRunning, parallelRunnable: test.parallelRunnable}
			got := timer.incrementTaskRunning()
			if !reflect.DeepEqual(test.want1, got) || !reflect.DeepEqual(test.want2, timer.taskRunning) {
				t.Errorf("%s error\nwant: %+v, %+v\ngot: %+v, %+v\n", t.Name(), test.want1, test.want2, got, timer.taskRunning)
			}
		})
	}
}

func Test_Timer_SetStartNow(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name         string
		timerRunning bool
		want         bool
	}{
		{name: "timerRunningでなければ設定が反映される", timerRunning: false, want: true},
		{name: "timerRunningであれば設定が反映されない", timerRunning: true, want: false},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			timer := &Timer{timerRunning: test.timerRunning}
			timer.SetStartNow(true)
			got := timer.startNow
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}
<file_sep>package gotimer

import (
	"context"
	"errors"
	"sort"
	"sync"
	"time"
)

var (
	TimerNotSetContextError  = errors.New("not set ctx")
	TimerNotSetIntervalError = errors.New("not set interval")
	TimerNotSetTaskError     = errors.New("not set task")
	TimerIsRunningError      = errors.New("timer is running now")
)

// Timer - タイマー
type Timer struct {
	interval         time.Duration
	terms            []Term
	currentTerm      int
	ch               chan time.Time
	timerRunning     bool
	taskRunning      int
	parallelRunnable bool
	startNow         bool
	next             time.Time
	timer            time.Timer
	mtx              sync.Mutex
}

// SetParallelRunnable - タスクの多重実行を許容するか
func (t *Timer) SetParallelRunnable(runnable bool) *Timer {
	t.mtx.Lock()
	defer t.mtx.Unlock()

	if t.timerRunning {
		return t
	}

	t.parallelRunnable = runnable
	return t
}

// SetStartNow - タスクの初回実行をすぐに行うか
func (t *Timer) SetStartNow(startNow bool) *Timer {
	t.mtx.Lock()
	defer t.mtx.Unlock()

	if t.timerRunning {
		return t
	}

	t.startNow = startNow
	return t
}

// AddTerm - 実行期間を追加する
func (t *Timer) AddTerm(term Term) *Timer {
	t.mtx.Lock()
	defer t.mtx.Unlock()

	if t.timerRunning {
		return t
	}

	if t.terms == nil {
		t.terms = []Term{}
	}
	// 重複チェック 同じ期間があれば追加しない
	for _, tt := range t.terms {
		if tt.Equal(term) {
			return t
		}
	}
	t.terms = append(t.terms, term)

	// startが小さいか、startが同じなら実行時間が長いのを前にする
	sort.Slice(t.terms, func(i, j int) bool {
		return t.terms[i].start < t.terms[j].start ||
			(t.terms[i].start == t.terms[j].start && t.terms[i].runnableSecond() > t.terms[j].runnableSecond())
	})
	return t
}

// Run - タイマーの開始
func (t *Timer) Run(ctx context.Context, interval time.Duration, task func()) error {
	if ctx == nil {
		return TimerNotSetContextError
	}
	if interval <= 0 {
		return TimerNotSetIntervalError
	}
	if task == nil {
		return TimerNotSetTaskError
	}

	// 開始してフラグを立てるまでは排他ロック
	t.mtx.Lock()
	if t.timerRunning {
		t.mtx.Unlock()
		return TimerIsRunningError
	}
	t.timerRunning = true
	defer func() {
		t.mtx.Lock()
		t.timerRunning = false
		t.mtx.Unlock()
	}()
	t.mtx.Unlock()
	t.interval = interval
	if t.terms == nil {
		t.terms = append(t.terms, NewTerm(NewTime(0, 0, 0), NewTime(23, 59, 59)))
	}

	for {
		now := time.Now()

		// 次の実行時刻を決定
		t.next = t.nextTime(now)
		d := t.next.Sub(now)
		tm := time.NewTimer(d)
		select {
		case <-tm.C: // 実行時間が来たら非同期で実行
			go func() {
				if t.incrementTaskRunning() { // タスク実行中でないか、多重起動許容の場合にタスクを実行する
					defer t.decrementTaskRunning()
					task()
				}
			}()
		case <-ctx.Done(): // ctxの終了ならreturn nil
			tm.Stop() // そのまま捨てられるタイマーなので発火済みかなど気にしない
			return nil
		}
	}
}

// incrementTaskRunning - 実行中のタスクのカウントを増やす
//   ただし、多重起動不可なら複数起動はしないので、その場合は実質上限1
func (t *Timer) incrementTaskRunning() bool {
	t.mtx.Lock()
	defer t.mtx.Unlock()

	// タスク実行中かつ、多重起動不可ならロックが取れない
	if t.taskRunning > 0 && !t.parallelRunnable {
		return false
	}

	t.taskRunning++
	return true
}

// decrementTaskRunning - 実行中のタスクのカウントを減らす
func (t *Timer) decrementTaskRunning() {
	t.mtx.Lock()
	defer t.mtx.Unlock()
	t.taskRunning--
}

// nextTime - 次回実行日時を取得する
func (t *Timer) nextTime(now time.Time) time.Time {
	// nextがzeroタイムなら直近の開始日時を設定する、zeroタイムでなければ前回実行日時 + intervalを設定する
	if t.next.IsZero() {
		if t.startNow && t.runnable(now) {
			return now
		} else {
			return t.nextStart(now)
		}
	} else {
		nt := t.next.Add(t.interval)

		// 次回実行日時が実行可能でなければ、次の開始時刻を採用する
		if !t.runnable(nt) {
			nt = t.nextStart(now)
		}
		return nt
	}
}

// nextStart - 次の開始日時を取得する
//   期間から次の開始日時が取れなかった場合、翌日の0時を返す
func (t *Timer) nextStart(now time.Time) time.Time {
	for i := 0; i <= 1; i++ {
		for _, term := range t.terms {
			nt := time.Date(now.Year(), now.Month(), now.Day(), term.start.hour(), term.start.minute(), term.start.second(), 0, time.Local)
			nt = nt.AddDate(0, 0, i)
			if nt.After(now) {
				return nt
			}
		}
	}
	return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.Local)
}

// runnable - Timerの持つtermsをすべて見て、実行可能かを返す
func (t *Timer) runnable(now time.Time) bool {
	for _, term := range t.terms {
		if term.runnable(now) {
			return true
		}
	}

	return false
}
<file_sep># gotimer

![pipeline.svg](https://gitlab.com/tsuchinaga/gotimer/badges/master/pipeline.svg)
![coverage.svg](https://gitlab.com/tsuchinaga/gotimer/badges/master/coverage.svg)

Go言語のタイマー / ティッカー  
設定方法が違うcronのようなライブラリで、定期的にタスクを実行するのに使う
<file_sep>package gotimer

import (
	"reflect"
	"testing"
	"time"
)

func Test_Term_runnable(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		term Term
		now  time.Time
		want bool
	}{
		{name: "start == stopで、now < startならfalse",
			term: Term{Time(9 * 60 * 60), Time(9 * 60 * 60)},
			now:  time.Date(2020, 12, 25, 8, 59, 59, 0, time.Local),
			want: false},
		{name: "start == stopで、now == startならtrue",
			term: Term{Time(9 * 60 * 60), Time(9 * 60 * 60)},
			now:  time.Date(2020, 12, 25, 9, 0, 0, 0, time.Local),
			want: true},
		{name: "start == stopで、stop < nowならfalse",
			term: Term{Time(9 * 60 * 60), Time(9 * 60 * 60)},
			now:  time.Date(2020, 12, 25, 9, 0, 1, 0, time.Local),
			want: false},
		{name: "start < stopで、now < startならfalse",
			term: Term{Time(9 * 60 * 60), Time(11*60*60 + 30*60)},
			now:  time.Date(2020, 12, 25, 8, 59, 59, 0, time.Local),
			want: false},
		{name: "start < stopで、now == startならtrue",
			term: Term{Time(9 * 60 * 60), Time(11*60*60 + 30*60)},
			now:  time.Date(2020, 12, 25, 9, 0, 0, 0, time.Local),
			want: true},
		{name: "start < stopで、start < now < stopならtrue",
			term: Term{Time(9 * 60 * 60), Time(11*60*60 + 30*60)},
			now:  time.Date(2020, 12, 25, 10, 15, 0, 0, time.Local),
			want: true},
		{name: "start < stopで、now == stopならtrue",
			term: Term{Time(9 * 60 * 60), Time(11*60*60 + 30*60)},
			now:  time.Date(2020, 12, 25, 11, 30, 0, 0, time.Local),
			want: true},
		{name: "start < stopで、stop < nowならfalse",
			term: Term{Time(9 * 60 * 60), Time(11*60*60 + 30*60)},
			now:  time.Date(2020, 12, 25, 11, 30, 1, 0, time.Local),
			want: false},
		{name: "stop < startで、stop < now < startならfalse",
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 24*60 + 59)},
			now:  time.Date(2020, 12, 25, 16, 29, 59, 0, time.Local),
			want: false},
		{name: "stop < startで、now == startならtrue",
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 24*60 + 59)},
			now:  time.Date(2020, 12, 25, 16, 30, 0, 0, time.Local),
			want: true},
		{name: "stop < startで、start < now < 24:00:00ならtrue",
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 24*60 + 59)},
			now:  time.Date(2020, 12, 25, 23, 59, 59, 0, time.Local),
			want: true},
		{name: "stop < startで、00:00:00 < now < stopならtrue",
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 24*60 + 59)},
			now:  time.Date(2020, 12, 26, 0, 0, 1, 0, time.Local),
			want: true},
		{name: "stop < startで、now == stopならtrue",
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 24*60 + 59)},
			now:  time.Date(2020, 12, 26, 5, 24, 59, 0, time.Local),
			want: true},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.term.runnable(test.now)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Term_Equal(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		a, b Term
		want bool
	}{
		{name: "startとstopが一致しているならtrue", want: true,
			a: Term{Time(9 * 60 * 60), Time(15 * 60 * 60)},
			b: Term{Time(9 * 60 * 60), Time(15 * 60 * 60)}},
		{name: "startだけが一致しているならfalse", want: false,
			a: Term{Time(9 * 60 * 60), Time(15 * 60 * 60)},
			b: Term{Time(9 * 60 * 60), Time(11*60*60 + 30*60)}},
		{name: "stopだけが一致しているならfalse", want: false,
			a: Term{Time(9 * 60 * 60), Time(15 * 60 * 60)},
			b: Term{Time(12*60*60 + 30*60), Time(15 * 60 * 60)}},
		{name: "両方一致していないならfalse", want: false,
			a: Term{Time(9 * 60 * 60), Time(11*60*60 + 30*60)},
			b: Term{Time(12*60*60 + 30*60), Time(15 * 60 * 60)}},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.a.Equal(test.b)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Term_In(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		term Term
		time Time
		want bool
	}{
		{name: "start == stopでtime < startならfalse", want: false,
			term: Term{Time(8 * 60 * 60), Time(8 * 60 * 60)},
			time: Time(7*60*60 + 59*60 + 59)},
		{name: "start == stopでstart == time == stopならtrue", want: true,
			term: Term{Time(8 * 60 * 60), Time(8 * 60 * 60)},
			time: Time(8 * 60 * 60)},
		{name: "start == stopでstop < timeならfalse", want: false,
			term: Term{Time(8 * 60 * 60), Time(8 * 60 * 60)},
			time: Time(8*60*60 + 1)},
		{name: "start < stopでtime < stopならfalse", want: false,
			term: Term{Time(8*60*60 + 45*60), Time(15*60*60 + 15*60)},
			time: Time(8*60*60 + 44*60 + 59)},
		{name: "start < stopでstart == timeならtrue", want: true,
			term: Term{Time(8*60*60 + 45*60), Time(15*60*60 + 15*60)},
			time: Time(8*60*60 + 45*60)},
		{name: "start < stopでstart < time < stopならtrue", want: true,
			term: Term{Time(8*60*60 + 45*60), Time(15*60*60 + 15*60)},
			time: Time(9 * 60 * 60)},
		{name: "start < stopでstop == timeならtrue", want: true,
			term: Term{Time(8*60*60 + 45*60), Time(15*60*60 + 15*60)},
			time: Time(15*60*60 + 15*60)},
		{name: "start < stopでstop < timeならfalse", want: false,
			term: Term{Time(8*60*60 + 45*60), Time(15*60*60 + 15*60)},
			time: Time(15*60*60 + 15*60 + 1)},
		{name: "stop < startでstop < time < startならfalse", want: false,
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 30*60)},
			time: Time(16*60*60 + 29*60 + 59)},
		{name: "stop < startでstart == timeならtrue", want: true,
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 30*60)},
			time: Time(16*60*60 + 30*60)},
		{name: "stop < startでstart < timeならtrue", want: true,
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 30*60)},
			time: Time(23*60*60 + 59*60 + 59)},
		{name: "stop < startでtime < stopならtrue", want: true,
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 30*60)},
			time: Time(0)},
		{name: "stop < startでstop == timeならtrue", want: true,
			term: Term{Time(16*60*60 + 30*60), Time(5*60*60 + 30*60)},
			time: Time(5*60*60 + 30*60)},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.term.In(test.time)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_NewTerm(t *testing.T) {
	t.Parallel()
	start := NewTime(12, 30, 0)
	stop := NewTime(15, 0, 0)
	want := Term{start: start, stop: stop}
	got := NewTerm(start, stop)

	if !reflect.DeepEqual(want, got) {
		t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), want, got)
	}
}

func Test_Term_runnableSecond(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		term Term
		want int
	}{
		{name: "00:00:00, 00:00:00は1", term: NewTerm(NewTime(0, 0, 0), NewTime(0, 0, 0)), want: 1},
		{name: "00:00:00, 00:00:01は2", term: NewTerm(NewTime(0, 0, 0), NewTime(0, 0, 1)), want: 2},
		{name: "00:00:00, 00:00:59は60", term: NewTerm(NewTime(0, 0, 0), NewTime(0, 0, 59)), want: 60},
		{name: "00:00:00, 00:01:00は61", term: NewTerm(NewTime(0, 0, 0), NewTime(0, 1, 0)), want: 61},
		{name: "00:00:00, 00:59:59は3600", term: NewTerm(NewTime(0, 0, 0), NewTime(0, 59, 59)), want: 3600},
		{name: "00:00:00, 01:00:00は3601", term: NewTerm(NewTime(0, 0, 0), NewTime(1, 0, 0)), want: 3601},
		{name: "00:00:00, 23:59:59は86400", term: NewTerm(NewTime(0, 0, 0), NewTime(23, 59, 59)), want: 24 * 60 * 60},
		{name: "00:00:01, 00:00:00は86400", term: NewTerm(NewTime(0, 0, 1), NewTime(0, 0, 0)), want: 24 * 60 * 60},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.term.runnableSecond()
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}
<file_sep>package gotimer

import (
	"reflect"
	"testing"
)

func Test_Time_hour(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		time Time
		want int
	}{
		{name: "00:00:00なら0", time: NewTime(0, 0, 0), want: 0},
		{name: "23:59:59なら23", time: NewTime(23, 59, 59), want: 23},
		{name: "12:24:48は12", time: NewTime(12, 24, 48), want: 12},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.time.hour()
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Time_minute(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		time Time
		want int
	}{
		{name: "00:00:00は0", time: NewTime(0, 0, 0), want: 0},
		{name: "23:59:59は59", time: NewTime(23, 59, 59), want: 59},
		{name: "12:24:48は24", time: NewTime(12, 24, 48), want: 24},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.time.minute()
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_Time_second(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name string
		time Time
		want int
	}{
		{name: "00:00:00は0", time: NewTime(0, 0, 0), want: 0},
		{name: "23:59:59は59", time: NewTime(23, 59, 59), want: 59},
		{name: "12:24:48は48", time: NewTime(12, 24, 48), want: 48},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := test.time.second()
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}

func Test_NewTime(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name                 string
		hour, minute, second int
		want                 Time
	}{
		{name: "00:00:00 => 0, 0, 0", hour: 0, minute: 0, second: 0, want: Time(0)},
		{name: "23:59:59 => 23, 59, 59", hour: 23, minute: 59, second: 59, want: Time(23*60*60 + 59*60 + 59)},
		{name: "-24:00:00 => 0, 0, 0", hour: -24, minute: 0, second: 0, want: Time(0)},
		{name: "-01:00:00 => 23, 0, 0", hour: -1, minute: 0, second: 0, want: Time(23 * 60 * 60)},
		{name: "00:-01:00 => 23, 59, 0", hour: 0, minute: -1, second: 0, want: Time(23*60*60 + 59*60)},
		{name: "00:00:-01 => 23, 59, 59", hour: 0, minute: 0, second: -1, want: Time(23*60*60 + 59*60 + 59)},
		{name: "00:00:-61 => 23, 58, 59", hour: 0, minute: 0, second: -61, want: Time(23*60*60 + 58*60 + 59)},
		{name: "00:00:-86400 => 0, 0, 0", hour: 0, minute: 0, second: -86400, want: Time(0)},
		{name: "00:00:-86401 => 23, 59, 59", hour: 0, minute: 0, second: -86401, want: Time(23*60*60 + 59*60 + 59)},
		{name: "24:59:-86401 => 23, 59, 59", hour: 24, minute: 59, second: -86401, want: Time(58*60 + 59)},
		{name: "24:60:60 => 1, 1, 0", hour: 24, minute: 60, second: 60, want: Time(1*60*60 + 1*60)},
		{name: "24:00:00 => 0, 0, 0", hour: 24, minute: 0, second: 0, want: Time(0)},
		{name: "00:60:00 => 1, 0, 0", hour: 0, minute: 60, second: 0, want: Time(1 * 60 * 60)},
		{name: "00:00:60 => 0, 1, 0", hour: 0, minute: 0, second: 60, want: Time(1 * 60)},
		{name: "23:59:60 => 0, 0, 0", hour: 23, minute: 59, second: 60, want: Time(0)},
		{name: "24:00:-01 => 23, 59, 59", hour: 24, minute: 0, second: -1, want: Time(23*60*60 + 59*60 + 59)},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			got := NewTime(test.hour, test.minute, test.second)
			if !reflect.DeepEqual(test.want, got) {
				t.Errorf("%s error\nwant: %+v\ngot: %+v\n", t.Name(), test.want, got)
			}
		})
	}
}
