github.com/zhongdalu/gf@v1.0.0/g/os/gcron/gcron_schedule.go (about)

     1  // Copyright 2018 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf.
     6  
     7  package gcron
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"github.com/zhongdalu/gf/g/text/gregex"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  // 运行时间管理对象
    19  type cronSchedule struct {
    20  	create  int64  // 创建时间戳(秒)
    21  	every   int64  // 运行时间间隔(秒)
    22  	pattern string // 原始注册字符串
    23  	second  map[int]struct{}
    24  	minute  map[int]struct{}
    25  	hour    map[int]struct{}
    26  	day     map[int]struct{}
    27  	week    map[int]struct{}
    28  	month   map[int]struct{}
    29  }
    30  
    31  const (
    32  	gREGEX_FOR_CRON = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)$`
    33  )
    34  
    35  var (
    36  	// 预定义的定时格式
    37  	predefinedPatternMap = map[string]string{
    38  		"@yearly":   "0 0 0 1 1 *",
    39  		"@annually": "0 0 0 1 1 *",
    40  		"@monthly":  "0 0 0 1 * *",
    41  		"@weekly":   "0 0 0 * * 0",
    42  		"@daily":    "0 0 0 * * *",
    43  		"@midnight": "0 0 0 * * *",
    44  		"@hourly":   "0 0 * * * *",
    45  	}
    46  	// 月份与数字对应表
    47  	monthMap = map[string]int{
    48  		"jan": 1,
    49  		"feb": 2,
    50  		"mar": 3,
    51  		"apr": 4,
    52  		"may": 5,
    53  		"jun": 6,
    54  		"jul": 7,
    55  		"aug": 8,
    56  		"sep": 9,
    57  		"oct": 10,
    58  		"nov": 11,
    59  		"dec": 12,
    60  	}
    61  	// 星期与数字对应表
    62  	weekMap = map[string]int{
    63  		"sun": 0,
    64  		"mon": 1,
    65  		"tue": 2,
    66  		"wed": 3,
    67  		"thu": 4,
    68  		"fri": 5,
    69  		"sat": 6,
    70  	}
    71  )
    72  
    73  // 解析定时格式为cronSchedule对象
    74  func newSchedule(pattern string) (*cronSchedule, error) {
    75  	// 处理预定义的定时格式
    76  	if match, _ := gregex.MatchString(`(@\w+)\s*(\w*)\s*`, pattern); len(match) > 0 {
    77  		key := strings.ToLower(match[1])
    78  		if v, ok := predefinedPatternMap[key]; ok {
    79  			pattern = v
    80  		} else if strings.Compare(key, "@every") == 0 {
    81  			if d, err := time.ParseDuration(match[2]); err != nil {
    82  				return nil, err
    83  			} else {
    84  				return &cronSchedule{
    85  					create:  time.Now().Unix(),
    86  					every:   int64(d.Seconds()),
    87  					pattern: pattern,
    88  				}, nil
    89  			}
    90  		} else {
    91  			return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
    92  		}
    93  	}
    94  	// 处理通用的定时格式定义
    95  	if match, _ := gregex.MatchString(gREGEX_FOR_CRON, pattern); len(match) == 7 {
    96  		schedule := &cronSchedule{
    97  			create:  time.Now().Unix(),
    98  			every:   0,
    99  			pattern: pattern,
   100  		}
   101  		// 秒
   102  		if m, err := parseItem(match[1], 0, 59, false); err != nil {
   103  			return nil, err
   104  		} else {
   105  			schedule.second = m
   106  		}
   107  		// 分
   108  		if m, err := parseItem(match[2], 0, 59, false); err != nil {
   109  			return nil, err
   110  		} else {
   111  			schedule.minute = m
   112  		}
   113  		// 时
   114  		if m, err := parseItem(match[3], 0, 23, false); err != nil {
   115  			return nil, err
   116  		} else {
   117  			schedule.hour = m
   118  		}
   119  		// 天
   120  		if m, err := parseItem(match[4], 1, 31, true); err != nil {
   121  			return nil, err
   122  		} else {
   123  			schedule.day = m
   124  		}
   125  		// 月
   126  		if m, err := parseItem(match[5], 1, 12, false); err != nil {
   127  			return nil, err
   128  		} else {
   129  			schedule.month = m
   130  		}
   131  		// 周
   132  		if m, err := parseItem(match[6], 0, 6, true); err != nil {
   133  			return nil, err
   134  		} else {
   135  			schedule.week = m
   136  		}
   137  		return schedule, nil
   138  	} else {
   139  		return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
   140  	}
   141  }
   142  
   143  // 解析定时格式中的每一项定时配置
   144  func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
   145  	m := make(map[int]struct{}, max-min+1)
   146  	if item == "*" || (allowQuestionMark && item == "?") {
   147  		for i := min; i <= max; i++ {
   148  			m[i] = struct{}{}
   149  		}
   150  	} else {
   151  		for _, item := range strings.Split(item, ",") {
   152  			interval := 1
   153  			intervalArray := strings.Split(item, "/")
   154  			if len(intervalArray) == 2 {
   155  				if i, err := strconv.Atoi(intervalArray[1]); err != nil {
   156  					return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
   157  				} else {
   158  					interval = i
   159  				}
   160  			}
   161  			rangeMin := min
   162  			rangeMax := max
   163  			rangeArray := strings.Split(intervalArray[0], "-")
   164  			valueType := byte(0)
   165  			switch max {
   166  			case 6:
   167  				valueType = 'w'
   168  			case 11:
   169  				valueType = 'm'
   170  			}
   171  			// 例如: */5
   172  			if rangeArray[0] != "*" {
   173  				if i, err := parseItemValue(rangeArray[0], valueType); err != nil {
   174  					return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
   175  				} else {
   176  					rangeMin = i
   177  					rangeMax = i
   178  				}
   179  			}
   180  			if len(rangeArray) == 2 {
   181  				if i, err := parseItemValue(rangeArray[1], valueType); err != nil {
   182  					return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
   183  				} else {
   184  					rangeMax = i
   185  				}
   186  			}
   187  			for i := rangeMin; i <= rangeMax; i += interval {
   188  				m[i] = struct{}{}
   189  			}
   190  		}
   191  	}
   192  	return m, nil
   193  }
   194  
   195  // 将配置项值转换为数字
   196  func parseItemValue(value string, valueType byte) (int, error) {
   197  	if gregex.IsMatchString(`^\d+$`, value) {
   198  		// 纯数字
   199  		if i, err := strconv.Atoi(value); err == nil {
   200  			return i, nil
   201  		}
   202  	} else {
   203  		// 英文字母
   204  		switch valueType {
   205  		case 'm':
   206  			if i, ok := monthMap[strings.ToLower(value)]; ok {
   207  				return int(i), nil
   208  			}
   209  		case 'w':
   210  			if i, ok := weekMap[strings.ToLower(value)]; ok {
   211  				return int(i), nil
   212  			}
   213  		}
   214  	}
   215  	return 0, errors.New(fmt.Sprintf(`invalid pattern value: "%s"`, value))
   216  }
   217  
   218  // 判断给定的时间是否满足schedule
   219  func (s *cronSchedule) meet(t time.Time) bool {
   220  	if s.every != 0 {
   221  		diff := t.Unix() - s.create
   222  		if diff > 0 {
   223  			return diff%s.every == 0
   224  		}
   225  		return false
   226  	} else {
   227  		if _, ok := s.second[t.Second()]; !ok {
   228  			return false
   229  		}
   230  		if _, ok := s.minute[t.Minute()]; !ok {
   231  			return false
   232  		}
   233  		if _, ok := s.hour[t.Hour()]; !ok {
   234  			return false
   235  		}
   236  		if _, ok := s.day[t.Day()]; !ok {
   237  			return false
   238  		}
   239  		if _, ok := s.month[int(t.Month())]; !ok {
   240  			return false
   241  		}
   242  		if _, ok := s.week[int(t.Weekday())]; !ok {
   243  			return false
   244  		}
   245  		return true
   246  	}
   247  }