github.com/songzhibin97/gkit@v1.2.13/distributed/schedule/spec.go (about)

     1  package schedule
     2  
     3  import "time"
     4  
     5  const (
     6  	// Set the top bit if a star was included in the expression.
     7  	starBit = 1 << 63
     8  )
     9  
    10  // SpecSchedule specifies a duty cycle (to the second granularity), based on a
    11  // traditional crontab specification. It is computed initially and stored as bit sets.
    12  type SpecSchedule struct {
    13  	Second, Minute, Hour, Dom, Month, Dow uint64
    14  
    15  	// Override location for this schedule.
    16  	Location *time.Location
    17  }
    18  
    19  // Next returns the next time this schedule is activated, greater than the given
    20  // time.  If no time can be found to satisfy the schedule, return the zero time.
    21  func (s *SpecSchedule) Next(t time.Time) time.Time {
    22  	// General approach
    23  	//
    24  	// For Month, Day, Hour, Minute, Second:
    25  	// Check if the time value matches.  If yes, continue to the next field.
    26  	// If the field doesn't match the schedule, then increment the field until it matches.
    27  	// While incrementing the field, a wrap-around brings it back to the beginning
    28  	// of the field list (since it is necessary to re-verify previous field
    29  	// values)
    30  
    31  	// Convert the given time into the schedule's timezone, if one is specified.
    32  	// Save the original timezone so we can convert back after we find a time.
    33  	// Note that schedules without a time zone specified (time.Local) are treated
    34  	// as local to the time provided.
    35  	origLocation := t.Location()
    36  	loc := s.Location
    37  	if loc == time.Local {
    38  		loc = t.Location()
    39  	}
    40  	if s.Location != time.Local {
    41  		t = t.In(s.Location)
    42  	}
    43  
    44  	// Start at the earliest possible time (the upcoming second).
    45  	t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
    46  
    47  	// This flag indicates whether a field has been incremented.
    48  	added := false
    49  
    50  	// If no time is found within five years, return zero.
    51  	yearLimit := t.Year() + 5
    52  
    53  WRAP:
    54  	if t.Year() > yearLimit {
    55  		return time.Time{}
    56  	}
    57  
    58  	// Find the first applicable month.
    59  	// If it's this month, then do nothing.
    60  	for 1<<uint(t.Month())&s.Month == 0 {
    61  		// If we have to add a month, reset the other parts to 0.
    62  		if !added {
    63  			added = true
    64  			// Otherwise, set the date at the beginning (since the current time is irrelevant).
    65  			t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc)
    66  		}
    67  		t = t.AddDate(0, 1, 0)
    68  
    69  		// Wrapped around.
    70  		if t.Month() == time.January {
    71  			goto WRAP
    72  		}
    73  	}
    74  
    75  	// Now get a day in that month.
    76  	//
    77  	// NOTE: This causes issues for daylight savings regimes where midnight does
    78  	// not exist.  For example: Sao Paulo has DST that transforms midnight on
    79  	// 11/3 into 1am. Handle that by noticing when the Hour ends up != 0.
    80  	for !dayMatches(s, t) {
    81  		if !added {
    82  			added = true
    83  			t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc)
    84  		}
    85  		t = t.AddDate(0, 0, 1)
    86  		// Notice if the hour is no longer midnight due to DST.
    87  		// Add an hour if it's 23, subtract an hour if it's 1.
    88  		if t.Hour() != 0 {
    89  			if t.Hour() > 12 {
    90  				t = t.Add(time.Duration(24-t.Hour()) * time.Hour)
    91  			} else {
    92  				t = t.Add(time.Duration(-t.Hour()) * time.Hour)
    93  			}
    94  		}
    95  
    96  		if t.Day() == 1 {
    97  			goto WRAP
    98  		}
    99  	}
   100  
   101  	for 1<<uint(t.Hour())&s.Hour == 0 {
   102  		if !added {
   103  			added = true
   104  			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, loc)
   105  		}
   106  		t = t.Add(1 * time.Hour)
   107  
   108  		if t.Hour() == 0 {
   109  			goto WRAP
   110  		}
   111  	}
   112  
   113  	for 1<<uint(t.Minute())&s.Minute == 0 {
   114  		if !added {
   115  			added = true
   116  			t = t.Truncate(time.Minute)
   117  		}
   118  		t = t.Add(1 * time.Minute)
   119  
   120  		if t.Minute() == 0 {
   121  			goto WRAP
   122  		}
   123  	}
   124  
   125  	for 1<<uint(t.Second())&s.Second == 0 {
   126  		if !added {
   127  			added = true
   128  			t = t.Truncate(time.Second)
   129  		}
   130  		t = t.Add(1 * time.Second)
   131  
   132  		if t.Second() == 0 {
   133  			goto WRAP
   134  		}
   135  	}
   136  
   137  	return t.In(origLocation)
   138  }
   139  
   140  // dayMatches returns true if the schedule's day-of-week and day-of-month
   141  // restrictions are satisfied by the given time.
   142  func dayMatches(s *SpecSchedule, t time.Time) bool {
   143  	var (
   144  		domMatch bool = 1<<uint(t.Day())&s.Dom > 0
   145  		dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
   146  	)
   147  	if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
   148  		return domMatch && dowMatch
   149  	}
   150  	return domMatch || dowMatch
   151  }