From abcaeda771fbc47ecb90faa31e2cb5b2adf737ab Mon Sep 17 00:00:00 2001 From: Xu Jay Date: Fri, 15 Nov 2024 00:02:18 +0800 Subject: [PATCH 1/4] Implement LastNDayOfMonthExpression --- src/apscheduler/triggers/cron/expressions.py | 17 +++++++++++++++++ src/apscheduler/triggers/cron/fields.py | 3 ++- tests/triggers/test_cron.py | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/apscheduler/triggers/cron/expressions.py b/src/apscheduler/triggers/cron/expressions.py index 71966b47..ff60f83b 100644 --- a/src/apscheduler/triggers/cron/expressions.py +++ b/src/apscheduler/triggers/cron/expressions.py @@ -251,3 +251,20 @@ def get_next_value(self, dateval: datetime, field: BaseField) -> int | None: def __str__(self) -> str: return "last" + + +class LastNDayOfMonthExpression(AllExpression): + value_re = re.compile(r"last-(?P[0-9]+)", re.IGNORECASE) + + def __init__(self, last_day): + super(LastNDayOfMonthExpression, self).__init__(None) + self.last_day = as_int(last_day) + + def get_next_value(self, date, field): + currval = field.get_value(date) + nextval = monthrange(date.year, date.month)[1]-self.last_day + + return nextval if currval <= nextval else None + + def __str__(self): + return f"last-{self.last_day}" \ No newline at end of file diff --git a/src/apscheduler/triggers/cron/fields.py b/src/apscheduler/triggers/cron/fields.py index 4a2e44b8..543955cc 100644 --- a/src/apscheduler/triggers/cron/fields.py +++ b/src/apscheduler/triggers/cron/fields.py @@ -13,6 +13,7 @@ from .expressions import ( WEEKDAYS, AllExpression, + LastNDayOfMonthExpression, LastDayOfMonthExpression, MonthRangeExpression, RangeExpression, @@ -122,7 +123,7 @@ def get_value(self, dateval: datetime) -> int: class DayOfMonthField( - BaseField, extra_compilers=(WeekdayPositionExpression, LastDayOfMonthExpression) + BaseField, extra_compilers=(WeekdayPositionExpression, LastNDayOfMonthExpression, LastDayOfMonthExpression) ): __slots__ = () diff --git a/tests/triggers/test_cron.py b/tests/triggers/test_cron.py index 58b46291..ae1661b1 100644 --- a/tests/triggers/test_cron.py +++ b/tests/triggers/test_cron.py @@ -160,6 +160,22 @@ def test_cron_trigger_4(timezone, serializer): ) +def test_cron_trigger_5(timezone, serializer): + start_time = datetime(2012, 2, 1, tzinfo=timezone) + trigger = CronTrigger( + year="2012", month="2", day="last-6", start_time=start_time, timezone=timezone + ) + if serializer: + trigger = serializer.deserialize(serializer.serialize(trigger)) + + assert trigger.next() == datetime(2012, 2, 23, tzinfo=timezone) + assert repr(trigger) == ( + "CronTrigger(year='2012', month='2', day='last-6', week='*', " + "day_of_week='*', hour='0', minute='0', second='0', " + "start_time='2012-02-01T00:00:00+01:00', timezone='Europe/Berlin')" + ) + + @pytest.mark.parametrize("expr", ["3-5", "wed-fri"], ids=["numeric", "text"]) def test_weekday_overlap(timezone, serializer, expr): start_time = datetime(2009, 1, 1, tzinfo=timezone) From b8de248ef238c3781f8e4c89679e75e45636c546 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:12:02 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/apscheduler/triggers/cron/expressions.py | 4 ++-- src/apscheduler/triggers/cron/fields.py | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/apscheduler/triggers/cron/expressions.py b/src/apscheduler/triggers/cron/expressions.py index ff60f83b..988fd626 100644 --- a/src/apscheduler/triggers/cron/expressions.py +++ b/src/apscheduler/triggers/cron/expressions.py @@ -262,9 +262,9 @@ def __init__(self, last_day): def get_next_value(self, date, field): currval = field.get_value(date) - nextval = monthrange(date.year, date.month)[1]-self.last_day + nextval = monthrange(date.year, date.month)[1] - self.last_day return nextval if currval <= nextval else None def __str__(self): - return f"last-{self.last_day}" \ No newline at end of file + return f"last-{self.last_day}" diff --git a/src/apscheduler/triggers/cron/fields.py b/src/apscheduler/triggers/cron/fields.py index 543955cc..a777fa62 100644 --- a/src/apscheduler/triggers/cron/fields.py +++ b/src/apscheduler/triggers/cron/fields.py @@ -13,8 +13,8 @@ from .expressions import ( WEEKDAYS, AllExpression, - LastNDayOfMonthExpression, LastDayOfMonthExpression, + LastNDayOfMonthExpression, MonthRangeExpression, RangeExpression, WeekdayPositionExpression, @@ -123,7 +123,12 @@ def get_value(self, dateval: datetime) -> int: class DayOfMonthField( - BaseField, extra_compilers=(WeekdayPositionExpression, LastNDayOfMonthExpression, LastDayOfMonthExpression) + BaseField, + extra_compilers=( + WeekdayPositionExpression, + LastNDayOfMonthExpression, + LastDayOfMonthExpression, + ), ): __slots__ = () From 3107d944855c33765c67eef23d89509e5ee1219f Mon Sep 17 00:00:00 2001 From: Xu Jay Date: Fri, 15 Nov 2024 00:14:01 +0800 Subject: [PATCH 3/4] Implement LastNDayOfMonthExpression --- src/apscheduler/triggers/cron/expressions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apscheduler/triggers/cron/expressions.py b/src/apscheduler/triggers/cron/expressions.py index ff60f83b..a21af7b8 100644 --- a/src/apscheduler/triggers/cron/expressions.py +++ b/src/apscheduler/triggers/cron/expressions.py @@ -257,7 +257,7 @@ class LastNDayOfMonthExpression(AllExpression): value_re = re.compile(r"last-(?P[0-9]+)", re.IGNORECASE) def __init__(self, last_day): - super(LastNDayOfMonthExpression, self).__init__(None) + super().__init__(None) self.last_day = as_int(last_day) def get_next_value(self, date, field): From 440226a4c040432e716418458f062520247fd5b3 Mon Sep 17 00:00:00 2001 From: Xu Jay Date: Fri, 15 Nov 2024 00:17:36 +0800 Subject: [PATCH 4/4] Implement LastNDayOfMonthExpression --- src/apscheduler/triggers/cron/expressions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/apscheduler/triggers/cron/expressions.py b/src/apscheduler/triggers/cron/expressions.py index 5443367f..e04f0a72 100644 --- a/src/apscheduler/triggers/cron/expressions.py +++ b/src/apscheduler/triggers/cron/expressions.py @@ -256,15 +256,15 @@ def __str__(self) -> str: class LastNDayOfMonthExpression(AllExpression): value_re = re.compile(r"last-(?P[0-9]+)", re.IGNORECASE) - def __init__(self, last_day): + def __init__(self, last_day: str): super().__init__(None) self.last_day = as_int(last_day) - def get_next_value(self, date, field): - currval = field.get_value(date) - nextval = monthrange(date.year, date.month)[1] - self.last_day + def get_next_value(self, dateval: datetime, field: BaseField) -> int | None: + currval = field.get_value(dateval) + nextval = monthrange(dateval.year, dateval.month)[1] - self.last_day return nextval if currval <= nextval else None - def __str__(self): + def __str__(self) -> str: return f"last-{self.last_day}"