因为APScheduler的CronTrigger本身的DayOfMonthField就不能去匹配last-n这种表达式, 所以要改造他.先放上源码:

class DayOfMonthField(BaseField):
    COMPILERS = BaseField.COMPILERS + [WeekdayPositionExpression, LastDayOfMonthExpression]

    def get_max(self, dateval):
        return monthrange(dateval.year, dateval.month)[1]

这里的BaseField.COMPILERS, WeekdayPositionExpression,LastDayOfMonthExpression都是用来匹配表达式, 然后获取值的.如LastDayOfMonthExpression:

class LastDayOfMonthExpression(AllExpression):
    value_re = re.compile(r'last', re.IGNORECASE)

    def __init__(self):
        super(LastDayOfMonthExpression, self).__init__(None)

    def get_next_value(self, date, field):
        return monthrange(date.year, date.month)[1]

    def __str__(self):
        return 'last'

    def __repr__(self):
        return "%s()" % self.__class__.__name__

作用就是匹配last, 然后通过get_next_value获取到要执行的值.那我们要做的就是在正则表示式上进行改造:

from apscheduler.triggers.cron.expressions import AllExpression


class LastNDayOfMonthExpression(AllExpression):
    value_re = re.compile(r'last(-\d+)?', re.IGNORECASE)

    def __init__(self):
        super(LastNDayOfMonthExpression, self).__init__(None)

    def get_next_value(self, date, field):
        _expr = self.value_re.match(field.exprs).group(1)
        day = monthrange(date.year, date.month)[1]
        if _expr:
            day -= int(_expr[1:])
        return day

    def __str__(self):
        return 'last-n'

    def __repr__(self):
        return "%s()" % self.__class__.__name__

然后将其替换掉LastDayOfMonthExpression就行了

import re
from calendar import monthrange

from apscheduler.triggers.cron import CronTrigger as _CronTrigger, DayOfMonthField as _DayOfMonthField, BaseField
from apscheduler.triggers.cron.expressions import AllExpression, WeekdayPositionExpression


class DayOfMonthField(_DayOfMonthField):
    COMPILERS = BaseField.COMPILERS + [WeekdayPositionExpression, LastNDayOfMonthExpression]

    def __init__(self, name, exprs, is_default=False):
        self.exprs = exprs
        super().__init__(name, exprs, is_default=is_default)


class CronTrigger(_CronTrigger):
    _CronTrigger.FIELDS_MAP.update({'day': DayOfMonthField})

这样我们就完成成功改造了CronTrigger, 投入使用也是很简单

from apscheduler.schedulers.background import BackgroundScheduler as _BackgroundScheduler

from utils.scheduler import CronTrigger


class BackgroundScheduler(_BackgroundScheduler):
    _trigger_classes = {'cron': CronTrigger}

最后我们在创建BackgroundScheduler调度器的时候就可以使用我们自己改造的了, 是不是很简单.


二十一
1.4k 声望867 粉丝

无论遇到多大的困难,你总是能扛过去,坚持一件事,对自己