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 }