1package v2
2
3import (
4	"strings"
5	"time"
6)
7
8// Validate ensures that all the time windows in t can be parsed.
9func (t *TimeWindowWhen) Validate() error {
10	if t == nil {
11		return nil
12	}
13	for _, windows := range t.MapTimeWindows() {
14		for _, window := range windows {
15			if err := window.Validate(); err != nil {
16				return err
17			}
18		}
19	}
20	return nil
21}
22
23// Validate ensures the TimeWindowTimeRange is valid.
24func (t *TimeWindowTimeRange) Validate() error {
25	_, err := t.InWindow(time.Now())
26	return err
27}
28
29// MapTimeWindows returns a map of all the time windows in t.
30func (t *TimeWindowWhen) MapTimeWindows() map[string][]*TimeWindowTimeRange {
31	d := t.Days
32	return map[string][]*TimeWindowTimeRange{
33		"All":       d.All,
34		"Sunday":    d.Sunday,
35		"Monday":    d.Monday,
36		"Tuesday":   d.Tuesday,
37		"Wednesday": d.Wednesday,
38		"Thursday":  d.Thursday,
39		"Friday":    d.Friday,
40		"Saturday":  d.Saturday,
41	}
42}
43
44// InWindow determines if the current time falls between the provided time
45// window. Current should typically be time.Now() but to allow easier tests, it
46// must be provided as a parameter. Begin and end parameters must be strings
47// representing an hour of the day in the time.Kitchen format (e.g. "3:04PM")
48func (t *TimeWindowTimeRange) InWindow(current time.Time) (bool, error) {
49	// Get the year, month and day of the provided current time (e.g. 2016, 01 &
50	// 02)
51	year, month, day := current.Date()
52
53	// Remove any whitespaces in the begin and end times, for backward
54	// compatibility with Sensu v1 so "3:00 PM" becomes "3:00PM" and satisfies the
55	// time.Kitchen format
56	begin := strings.Replace(t.Begin, " ", "", -1)
57	end := strings.Replace(t.End, " ", "", -1)
58
59	// Parse the beginning of the provided time window in order to retrieve the
60	// hour and minute and apply it to current year, month and day so we end up
61	// with a date that corresponds to today (e.g. 2006-01-02T15:00:00Z)
62	beginTime, err := time.Parse(time.Kitchen, begin)
63	if err != nil {
64		return false, err
65	}
66	beginHour, beginMin, _ := beginTime.Clock()
67	beginTime = time.Date(year, month, day, beginHour, beginMin, 0, 0, time.UTC)
68
69	// Parse the ending of the provided time window in order to retrieve the
70	// hour and minute and apply it to current year, month and day so we end up
71	// with a date that corresponds to today (e.g. 2006-01-02T21:00:00Z)
72	endTime, err := time.Parse(time.Kitchen, end)
73	if err != nil {
74		return false, err
75	}
76	endHour, endMin, _ := endTime.Clock()
77	endTime = time.Date(year, month, day, endHour, endMin, 0, 0, time.UTC)
78
79	// Verify if the end of the time window is actually before the beginning of
80	// it, which means that the window ends the next day (e.g. 3:00PM to 8:00AM)
81	if endTime.Before(beginTime) {
82		// Verify if the current time is before the end of the time window, which
83		// means that we are already on the second day of the specified time window,
84		// therefore we just need to move the start of this window to the beginning
85		// of this second day (e.g. 3:00PM to 8:00AM, it's currently 5:00AM so let's
86		// move the beginning to 0:00AM)
87		if current.Before(endTime) {
88			beginTime = time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
89		} else {
90			// We are currently on the first day of the window so we just need to move
91			// the end of this window to the end of the first day (e.g. 3:00PM to
92			// 8:00AM, it's currently 5:00PM so let's move the ending to 11:59PM)
93			endTime = time.Date(year, month, day, 23, 59, 59, 999999999, time.UTC)
94		}
95	}
96
97	return (current.After(beginTime) || current.Equal(beginTime)) &&
98		(current.Before(endTime) || current.Equal(endTime)), nil
99}
100
101// InWindows determines if the current time falls between the provided time
102// windows. Current should typically be time.Now() but to allow easier tests, it
103// must be provided as a parameter. The function returns a positive value as
104// soon the current time falls within a time window
105func (t *TimeWindowWhen) InWindows(current time.Time) (bool, error) {
106	windowsByDay := t.MapTimeWindows()
107
108	var windows []*TimeWindowTimeRange
109	windows = append(windows, windowsByDay["All"]...)
110	windows = append(windows, windowsByDay[current.Weekday().String()]...)
111
112	// Go through the set of matching windows and process all the individual
113	// time windows. If the current time is within a time window in the selection,
114	// then the loop returns early with true and nil error.
115	for _, window := range windows {
116		// Determine if the current time falls between this specific time window
117		isInWindow, err := window.InWindow(current)
118		if err != nil {
119			return false, err
120		}
121
122		// Immediately return with a positive value if this time window conditions are
123		// met
124		if isInWindow {
125			return true, nil
126		}
127	}
128
129	// At this point no time windows conditions were met, return a negative value
130	return false, nil
131}
132