github.com/gogf/gf@v1.16.9/os/gcron/gcron_schedule.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gcron 8 9 import ( 10 "github.com/gogf/gf/errors/gcode" 11 "github.com/gogf/gf/errors/gerror" 12 "github.com/gogf/gf/os/gtime" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/gogf/gf/text/gregex" 18 ) 19 20 // cronSchedule is the schedule for cron job. 21 type cronSchedule struct { 22 create int64 // Created timestamp. 23 every int64 // Running interval in seconds. 24 pattern string // The raw cron pattern string. 25 second map[int]struct{} // Job can run in these second numbers. 26 minute map[int]struct{} // Job can run in these minute numbers. 27 hour map[int]struct{} // Job can run in these hour numbers. 28 day map[int]struct{} // Job can run in these day numbers. 29 week map[int]struct{} // Job can run in these week numbers. 30 month map[int]struct{} // Job can run in these moth numbers. 31 } 32 33 const ( 34 // regular expression for cron pattern, which contains 6 parts of time units. 35 regexForCron = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,A-Za-z]+)\s+([\-/\d\*\?,A-Za-z]+)$` 36 37 patternItemTypeUnknown = iota 38 patternItemTypeWeek 39 patternItemTypeMonth 40 ) 41 42 var ( 43 // Predefined pattern map. 44 predefinedPatternMap = map[string]string{ 45 "@yearly": "0 0 0 1 1 *", 46 "@annually": "0 0 0 1 1 *", 47 "@monthly": "0 0 0 1 * *", 48 "@weekly": "0 0 0 * * 0", 49 "@daily": "0 0 0 * * *", 50 "@midnight": "0 0 0 * * *", 51 "@hourly": "0 0 * * * *", 52 } 53 // Short month name to its number. 54 monthMap = map[string]int{ 55 "jan": 1, 56 "feb": 2, 57 "mar": 3, 58 "apr": 4, 59 "may": 5, 60 "jun": 6, 61 "jul": 7, 62 "aug": 8, 63 "sep": 9, 64 "oct": 10, 65 "nov": 11, 66 "dec": 12, 67 } 68 // Short week name to its number. 69 weekMap = map[string]int{ 70 "sun": 0, 71 "mon": 1, 72 "tue": 2, 73 "wed": 3, 74 "thu": 4, 75 "fri": 5, 76 "sat": 6, 77 } 78 ) 79 80 // newSchedule creates and returns a schedule object for given cron pattern. 81 func newSchedule(pattern string) (*cronSchedule, error) { 82 // Check if the predefined patterns. 83 if match, _ := gregex.MatchString(`(@\w+)\s*(\w*)\s*`, pattern); len(match) > 0 { 84 key := strings.ToLower(match[1]) 85 if v, ok := predefinedPatternMap[key]; ok { 86 pattern = v 87 } else if strings.Compare(key, "@every") == 0 { 88 if d, err := gtime.ParseDuration(match[2]); err != nil { 89 return nil, err 90 } else { 91 return &cronSchedule{ 92 create: time.Now().Unix(), 93 every: int64(d.Seconds()), 94 pattern: pattern, 95 }, nil 96 } 97 } else { 98 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern: "%s"`, pattern) 99 } 100 } 101 // Handle the common cron pattern, like: 102 // 0 0 0 1 1 2 103 if match, _ := gregex.MatchString(regexForCron, pattern); len(match) == 7 { 104 schedule := &cronSchedule{ 105 create: time.Now().Unix(), 106 every: 0, 107 pattern: pattern, 108 } 109 // Second. 110 if m, err := parsePatternItem(match[1], 0, 59, false); err != nil { 111 return nil, err 112 } else { 113 schedule.second = m 114 } 115 // Minute. 116 if m, err := parsePatternItem(match[2], 0, 59, false); err != nil { 117 return nil, err 118 } else { 119 schedule.minute = m 120 } 121 // Hour. 122 if m, err := parsePatternItem(match[3], 0, 23, false); err != nil { 123 return nil, err 124 } else { 125 schedule.hour = m 126 } 127 // Day. 128 if m, err := parsePatternItem(match[4], 1, 31, true); err != nil { 129 return nil, err 130 } else { 131 schedule.day = m 132 } 133 // Month. 134 if m, err := parsePatternItem(match[5], 1, 12, false); err != nil { 135 return nil, err 136 } else { 137 schedule.month = m 138 } 139 // Week. 140 if m, err := parsePatternItem(match[6], 0, 6, true); err != nil { 141 return nil, err 142 } else { 143 schedule.week = m 144 } 145 return schedule, nil 146 } else { 147 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern: "%s"`, pattern) 148 } 149 } 150 151 // parsePatternItem parses every item in the pattern and returns the result as map, which is used for indexing. 152 func parsePatternItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) { 153 m := make(map[int]struct{}, max-min+1) 154 if item == "*" || (allowQuestionMark && item == "?") { 155 for i := min; i <= max; i++ { 156 m[i] = struct{}{} 157 } 158 } else { 159 // Like: MON,FRI 160 for _, item := range strings.Split(item, ",") { 161 var ( 162 interval = 1 163 intervalArray = strings.Split(item, "/") 164 ) 165 if len(intervalArray) == 2 { 166 if number, err := strconv.Atoi(intervalArray[1]); err != nil { 167 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item) 168 } else { 169 interval = number 170 } 171 } 172 var ( 173 rangeMin = min 174 rangeMax = max 175 itemType = patternItemTypeUnknown 176 rangeArray = strings.Split(intervalArray[0], "-") // Like: 1-30, JAN-DEC 177 ) 178 switch max { 179 case 6: 180 // It's checking week field. 181 itemType = patternItemTypeWeek 182 case 12: 183 // It's checking month field. 184 itemType = patternItemTypeMonth 185 } 186 // Eg: */5 187 if rangeArray[0] != "*" { 188 if number, err := parsePatternItemValue(rangeArray[0], itemType); err != nil { 189 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item) 190 } else { 191 rangeMin = number 192 rangeMax = number 193 } 194 } 195 if len(rangeArray) == 2 { 196 if number, err := parsePatternItemValue(rangeArray[1], itemType); err != nil { 197 return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item) 198 } else { 199 rangeMax = number 200 } 201 } 202 for i := rangeMin; i <= rangeMax; i += interval { 203 m[i] = struct{}{} 204 } 205 } 206 } 207 return m, nil 208 } 209 210 // parsePatternItemValue parses the field value to a number according to its field type. 211 func parsePatternItemValue(value string, itemType int) (int, error) { 212 if gregex.IsMatchString(`^\d+$`, value) { 213 // It is pure number. 214 if number, err := strconv.Atoi(value); err == nil { 215 return number, nil 216 } 217 } else { 218 // Check if it contains letter, 219 // it converts the value to number according to predefined map. 220 switch itemType { 221 case patternItemTypeWeek: 222 if number, ok := monthMap[strings.ToLower(value)]; ok { 223 return number, nil 224 } 225 case patternItemTypeMonth: 226 if number, ok := weekMap[strings.ToLower(value)]; ok { 227 return number, nil 228 } 229 } 230 } 231 return 0, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern value: "%s"`, value) 232 } 233 234 // meet checks if the given time `t` meets the runnable point for the job. 235 func (s *cronSchedule) meet(t time.Time) bool { 236 if s.every != 0 { 237 // It checks using interval. 238 diff := t.Unix() - s.create 239 if diff > 0 { 240 return diff%s.every == 0 241 } 242 return false 243 } else { 244 // It checks using normal cron pattern. 245 if _, ok := s.second[t.Second()]; !ok { 246 return false 247 } 248 if _, ok := s.minute[t.Minute()]; !ok { 249 return false 250 } 251 if _, ok := s.hour[t.Hour()]; !ok { 252 return false 253 } 254 if _, ok := s.day[t.Day()]; !ok { 255 return false 256 } 257 if _, ok := s.month[int(t.Month())]; !ok { 258 return false 259 } 260 if _, ok := s.week[int(t.Weekday())]; !ok { 261 return false 262 } 263 return true 264 } 265 }