OLD | NEW |
(Empty) | |
| 1 """ |
| 2 This module contains the expressions applicable for CronTrigger's fields. |
| 3 """ |
| 4 from calendar import monthrange |
| 5 import re |
| 6 |
| 7 from apscheduler.util import asint |
| 8 |
| 9 __all__ = ('AllExpression', 'RangeExpression', 'WeekdayRangeExpression', |
| 10 'WeekdayPositionExpression') |
| 11 |
| 12 WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] |
| 13 |
| 14 |
| 15 class AllExpression(object): |
| 16 value_re = re.compile(r'\*(?:/(?P<step>\d+))?$') |
| 17 |
| 18 def __init__(self, step=None): |
| 19 self.step = asint(step) |
| 20 if self.step == 0: |
| 21 raise ValueError('Increment must be higher than 0') |
| 22 |
| 23 def get_next_value(self, date, field): |
| 24 start = field.get_value(date) |
| 25 minval = field.get_min(date) |
| 26 maxval = field.get_max(date) |
| 27 start = max(start, minval) |
| 28 |
| 29 if not self.step: |
| 30 next = start |
| 31 else: |
| 32 distance_to_next = (self.step - (start - minval)) % self.step |
| 33 next = start + distance_to_next |
| 34 |
| 35 if next <= maxval: |
| 36 return next |
| 37 |
| 38 def __str__(self): |
| 39 if self.step: |
| 40 return '*/%d' % self.step |
| 41 return '*' |
| 42 |
| 43 def __repr__(self): |
| 44 return "%s(%s)" % (self.__class__.__name__, self.step) |
| 45 |
| 46 |
| 47 class RangeExpression(AllExpression): |
| 48 value_re = re.compile( |
| 49 r'(?P<first>\d+)(?:-(?P<last>\d+))?(?:/(?P<step>\d+))?$') |
| 50 |
| 51 def __init__(self, first, last=None, step=None): |
| 52 AllExpression.__init__(self, step) |
| 53 first = asint(first) |
| 54 last = asint(last) |
| 55 if last is None and step is None: |
| 56 last = first |
| 57 if last is not None and first > last: |
| 58 raise ValueError('The minimum value in a range must not be ' |
| 59 'higher than the maximum') |
| 60 self.first = first |
| 61 self.last = last |
| 62 |
| 63 def get_next_value(self, date, field): |
| 64 start = field.get_value(date) |
| 65 minval = field.get_min(date) |
| 66 maxval = field.get_max(date) |
| 67 |
| 68 # Apply range limits |
| 69 minval = max(minval, self.first) |
| 70 if self.last is not None: |
| 71 maxval = min(maxval, self.last) |
| 72 start = max(start, minval) |
| 73 |
| 74 if not self.step: |
| 75 next = start |
| 76 else: |
| 77 distance_to_next = (self.step - (start - minval)) % self.step |
| 78 next = start + distance_to_next |
| 79 |
| 80 if next <= maxval: |
| 81 return next |
| 82 |
| 83 def __str__(self): |
| 84 if self.last != self.first and self.last is not None: |
| 85 range = '%d-%d' % (self.first, self.last) |
| 86 else: |
| 87 range = str(self.first) |
| 88 |
| 89 if self.step: |
| 90 return '%s/%d' % (range, self.step) |
| 91 return range |
| 92 |
| 93 def __repr__(self): |
| 94 args = [str(self.first)] |
| 95 if self.last != self.first and self.last is not None or self.step: |
| 96 args.append(str(self.last)) |
| 97 if self.step: |
| 98 args.append(str(self.step)) |
| 99 return "%s(%s)" % (self.__class__.__name__, ', '.join(args)) |
| 100 |
| 101 |
| 102 class WeekdayRangeExpression(RangeExpression): |
| 103 value_re = re.compile(r'(?P<first>[a-z]+)(?:-(?P<last>[a-z]+))?', |
| 104 re.IGNORECASE) |
| 105 |
| 106 def __init__(self, first, last=None): |
| 107 try: |
| 108 first_num = WEEKDAYS.index(first.lower()) |
| 109 except ValueError: |
| 110 raise ValueError('Invalid weekday name "%s"' % first) |
| 111 |
| 112 if last: |
| 113 try: |
| 114 last_num = WEEKDAYS.index(last.lower()) |
| 115 except ValueError: |
| 116 raise ValueError('Invalid weekday name "%s"' % last) |
| 117 else: |
| 118 last_num = None |
| 119 |
| 120 RangeExpression.__init__(self, first_num, last_num) |
| 121 |
| 122 def __str__(self): |
| 123 if self.last != self.first and self.last is not None: |
| 124 return '%s-%s' % (WEEKDAYS[self.first], WEEKDAYS[self.last]) |
| 125 return WEEKDAYS[self.first] |
| 126 |
| 127 def __repr__(self): |
| 128 args = ["'%s'" % WEEKDAYS[self.first]] |
| 129 if self.last != self.first and self.last is not None: |
| 130 args.append("'%s'" % WEEKDAYS[self.last]) |
| 131 return "%s(%s)" % (self.__class__.__name__, ', '.join(args)) |
| 132 |
| 133 |
| 134 class WeekdayPositionExpression(AllExpression): |
| 135 options = ['1st', '2nd', '3rd', '4th', '5th', 'last'] |
| 136 value_re = re.compile(r'(?P<option_name>%s) +(?P<weekday_name>(?:\d+|\w+))' |
| 137 % '|'.join(options), re.IGNORECASE) |
| 138 |
| 139 def __init__(self, option_name, weekday_name): |
| 140 try: |
| 141 self.option_num = self.options.index(option_name.lower()) |
| 142 except ValueError: |
| 143 raise ValueError('Invalid weekday position "%s"' % option_name) |
| 144 |
| 145 try: |
| 146 self.weekday = WEEKDAYS.index(weekday_name.lower()) |
| 147 except ValueError: |
| 148 raise ValueError('Invalid weekday name "%s"' % weekday_name) |
| 149 |
| 150 def get_next_value(self, date, field): |
| 151 # Figure out the weekday of the month's first day and the number |
| 152 # of days in that month |
| 153 first_day_wday, last_day = monthrange(date.year, date.month) |
| 154 |
| 155 # Calculate which day of the month is the first of the target weekdays |
| 156 first_hit_day = self.weekday - first_day_wday + 1 |
| 157 if first_hit_day <= 0: |
| 158 first_hit_day += 7 |
| 159 |
| 160 # Calculate what day of the month the target weekday would be |
| 161 if self.option_num < 5: |
| 162 target_day = first_hit_day + self.option_num * 7 |
| 163 else: |
| 164 target_day = first_hit_day + ((last_day - first_hit_day) / 7) * 7 |
| 165 |
| 166 if target_day <= last_day and target_day >= date.day: |
| 167 return target_day |
| 168 |
| 169 def __str__(self): |
| 170 return '%s %s' % (self.options[self.option_num], |
| 171 WEEKDAYS[self.weekday]) |
| 172 |
| 173 def __repr__(self): |
| 174 return "%s('%s', '%s')" % (self.__class__.__name__, |
| 175 self.options[self.option_num], |
| 176 WEEKDAYS[self.weekday]) |
OLD | NEW |