github.com/simpleiot/simpleiot@v0.18.3/client/schedule.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"regexp"
     7  	"strconv"
     8  	"time"
     9  )
    10  
    11  type schedule struct {
    12  	startTime string
    13  	endTime   string
    14  	// A Weekday specifies a day of the week (Sunday = 0, ...).
    15  	weekdays []time.Weekday
    16  	dates    []string
    17  }
    18  
    19  func newSchedule(start, end string, weekdays []time.Weekday, dates []string) *schedule {
    20  	return &schedule{
    21  		startTime: start,
    22  		endTime:   end,
    23  		weekdays:  weekdays,
    24  		dates:     dates,
    25  	}
    26  }
    27  
    28  func (s *schedule) activeForTime(t time.Time) (bool, error) {
    29  	tUTC := t.UTC()
    30  
    31  	// parse out hour/minute
    32  	matches := reHourMin.FindStringSubmatch(s.startTime)
    33  	if len(matches) < 3 {
    34  		return false, fmt.Errorf("TimeRange: invalid start: %v ", s.startTime)
    35  	}
    36  
    37  	startHour, err := strconv.Atoi(matches[1])
    38  	if err != nil {
    39  		return false, fmt.Errorf("TimeRange: error parsing start hour: %v", matches[1])
    40  	}
    41  
    42  	startMin, err := strconv.Atoi(matches[2])
    43  	if err != nil {
    44  		return false, fmt.Errorf("TimeRange: error parsing start hour: %v", matches[1])
    45  	}
    46  
    47  	matches = reHourMin.FindStringSubmatch(s.endTime)
    48  	if len(matches) < 3 {
    49  		return false, fmt.Errorf("TimeRange: invalid end: %v ", s.endTime)
    50  	}
    51  
    52  	endHour, err := strconv.Atoi(matches[1])
    53  
    54  	if err != nil {
    55  		return false, fmt.Errorf("TimeRange: error parsing end hour: %v", matches[1])
    56  	}
    57  
    58  	endMin, err := strconv.Atoi(matches[2])
    59  
    60  	if err != nil {
    61  		return false, fmt.Errorf("TimeRange: error parsing end hour: %v", matches[1])
    62  	}
    63  
    64  	y := tUTC.Year()
    65  	m := tUTC.Month()
    66  	d := tUTC.Day()
    67  
    68  	start := time.Date(y, m, d, startHour, startMin, 0, 0, time.UTC)
    69  	end := time.Date(y, m, d, endHour, endMin, 0, 0, time.UTC)
    70  
    71  	timeRanges := timeRanges{
    72  		{start, end},
    73  	}
    74  
    75  	// adjust time ranges if end time is before start
    76  	if !end.After(start) {
    77  		timeRanges[0].end = timeRanges[0].end.AddDate(0, 0, 1)
    78  
    79  		timeRanges = append(timeRanges,
    80  			timeRange{start.AddDate(0, 0, -1), end},
    81  		)
    82  	}
    83  
    84  	timeRanges.filterWeekdays(s.weekdays)
    85  	err = timeRanges.filterDates(s.dates)
    86  	if err != nil {
    87  		return false, err
    88  	}
    89  
    90  	if timeRanges.in(t) {
    91  		return true, nil
    92  	}
    93  
    94  	return false, nil
    95  }
    96  
    97  var reHourMin = regexp.MustCompile(`(\d{1,2}):(\d\d)`)
    98  var reDate = regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
    99  
   100  type timeRange struct {
   101  	start time.Time
   102  	end   time.Time
   103  }
   104  
   105  // in returns true if date is in time range
   106  func (tr *timeRange) in(t time.Time) bool {
   107  	if tr.start.After(tr.end) {
   108  		log.Println("BUG: LocalTimeRange.In -- start is before end")
   109  		return false
   110  	}
   111  
   112  	// normal situation
   113  	if t.Before(tr.start) {
   114  		return false
   115  	}
   116  
   117  	if t.Before(tr.end) {
   118  		return true
   119  	}
   120  
   121  	return false
   122  }
   123  
   124  type timeRanges []timeRange
   125  
   126  // in returns true if time is in any of the time ranges
   127  func (trs *timeRanges) in(t time.Time) bool {
   128  	for _, tr := range *trs {
   129  		if tr.in(t) {
   130  			return true
   131  		}
   132  	}
   133  
   134  	return false
   135  }
   136  
   137  func (trs *timeRanges) filterDates(dates []string) error {
   138  	if len(dates) <= 0 {
   139  		return nil
   140  	}
   141  
   142  	var trsNew timeRanges
   143  	for _, tr := range *trs {
   144  		for _, d := range dates {
   145  			matches := reDate.FindStringSubmatch(d)
   146  			if len(matches) < 4 {
   147  				return fmt.Errorf("Invalid date: %v", d)
   148  			}
   149  
   150  			year, err := strconv.Atoi(matches[1])
   151  			if err != nil {
   152  				return fmt.Errorf("Invalid year: %v", d)
   153  			}
   154  
   155  			month, err := strconv.Atoi(matches[2])
   156  			if err != nil {
   157  				return fmt.Errorf("Invalid month: %v", d)
   158  			}
   159  
   160  			day, err := strconv.Atoi(matches[3])
   161  			if err != nil {
   162  				return fmt.Errorf("Invalid day: %v", d)
   163  			}
   164  
   165  			if year != tr.start.UTC().Year() {
   166  				continue
   167  			}
   168  
   169  			if month != int(tr.start.UTC().Month()) {
   170  				continue
   171  			}
   172  
   173  			if day != tr.start.UTC().Day() {
   174  				continue
   175  			}
   176  
   177  			trsNew = append(trsNew, tr)
   178  		}
   179  	}
   180  
   181  	*trs = trsNew
   182  
   183  	return nil
   184  }
   185  
   186  // filterWeekdays removes time ranges that do not have a Start time in the provided list of weekdays
   187  func (trs *timeRanges) filterWeekdays(weekdays []time.Weekday) {
   188  	if len(weekdays) <= 0 {
   189  		return
   190  	}
   191  
   192  	var trsNew timeRanges
   193  	for _, tr := range *trs {
   194  		wdFound := false
   195  		for _, wd := range weekdays {
   196  			if tr.start.Weekday() == wd {
   197  				wdFound = true
   198  				break
   199  			}
   200  		}
   201  		if wdFound {
   202  			trsNew = append(trsNew, tr)
   203  		}
   204  	}
   205  
   206  	*trs = trsNew
   207  }