Skip to content

feat(scheduler): add flexible cron format for scheduled messages - #256

Open
hickey wants to merge 5 commits into
agessaman:devfrom
hickey:feat/flexible-schedule
Open

hickey wants to merge 5 commits into
agessaman:devfrom
hickey:feat/flexible-schedule

Conversation

@hickey

@hickey hickey commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

Adds a flexible cron format: unordered fields with suffixes (9h, 30m, 15d, 3w for ISO week) plus month/day-of-week names, ordinal prefixes (1st/2nd/3rd/4th/5th/last), and optional start:/end: date bounds. The web viewer gets a new "Flexible (cron)" mode alongside the existing Advanced (cron) editor, and the edit modal now auto-detects which mode a stored schedule belongs to instead of always defaulting to Advanced. In addition, the month/day-of-week names can be used in regular cron entries and is suggested to avoid confusion.

Why

Standard 5-field cron cannot express schedules like "the 4th Tuesday of every month" or "the last Friday" — patterns that come up constantly for nets and other recurring meetings. Users were stuck hand-rolling day-of-month lists that drift across months with different lengths.

Testing

Added unit tests for the flexible schedule code. Running live on my meshcore-bot instance.

Checklist

  • Branched from dev and targeting dev
  • make test passes
  • make lint passes (ruff + mypy)
  • Frontend lint passes if templates changed (npm run lint:frontend)
  • Tests added or updated for behavior changes
  • CHANGELOG.md updated under ## [Unreleased] if user-visible
  • Config changes are reflected in config.ini.example (and the minimal/quickstart
    templates where relevant) — CI validates these with validate_config.py --strict
  • New docs pages are added to nav: in mkdocs.yml
  • Any new command justifies its airtime and defaults conservatively

@hickey

hickey commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Still have one more update to resolve a failing test due to word boundary issue in regex. Everything else is there to proceed with the code review.

@agessaman

Copy link
Copy Markdown
Owner

Will you take a look at the changes in #260? Would this serve your purposes? It would be a significantly simpler surface to maintain, because it uses existing APScheduler capabilities within the already built cron format.

@hickey

hickey commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

I have been on vacation with the family for this week and been meaning to respond to your query. I will be on vacation for the rest of the week, but I will try to respond with a few thoughts in the next day or two.

@hickey

hickey commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Well I have looked over your feat/flexible-scheduling branch. I would argue and disagree that it has a "significantly simpler surface"--just on the fact that you are trying to take a complete independent format and shoehorn it into the cron format because trying to force one format into another always works out so well for maintenance and producing no tech debt. The other part of the argument that I see is that by forcing it into crontab format it actually makes the understanding slight more complicated and not everyone is thrilled with the crontab format.

There are a few things that you don't support such as months of the year and the last day of the month. Both of which can be added and supported. But that still leaves the fact of trying to encode everything in the cron format and I just think that is just a bad idea over the long run.

In addition, if you notice I have extended ScheduleParseResult with an error field so that more verbose error reporting can be supplied back to the front end.

OK, I am not trying to nit-pick here and want to be objective. So I had Claude look at both branches with the following prompt:

I would like you to compare the origin/feat/flexible-schedule with the upstream/feat/flexible-scheduling branches.
I am looking for an evaluation as to which branch is better for code quality and code maintenance.

Here is the response that I received back:


⏺ Based on my analysis of both branches, here's my evaluation:

Recommendation: origin/feat/flexible-schedule (your branch) is better

Key Differences

origin/feat/flexible-schedule (your branch):

  • 274 lines of code in scheduled_message_cron.py
  • 230 lines of dedicated unit tests in test_scheduled_message_cron.py
  • 485-line comprehensive documentation in docs/schedule-messages.md
  • Supports flexible cron format (e.g., 4th tue 14:00 jan-oct, mon,wed,fri 18:00)
  • More extensive test coverage in test_scheduler_logic.py and test_scheduled_message_admin.py
  • Cleaner configuration file organization

upstream/feat/flexible-scheduling:

  • 241 lines of code in scheduled_message_cron.py
  • NO dedicated unit tests for the cron parsing module
  • NO documentation file - only inline comments
  • Supports positional day-of-month (e.g., last-fri, 4th-tue)
  • Supports date bounds via option values (start=YYYY-MM-DD, end=YYYY-MM-DD)
  • Minimal test additions

Code Quality Assessment

origin/feat/flexible-schedule wins on:

  1. Test Coverage - 230 lines of unit tests vs. 0 dedicated tests
  2. Documentation - Comprehensive user guide vs. none
  3. User Experience - More intuitive flexible syntax (mon 14:00 vs 0 14 * * 0)
  4. Error Reporting - Includes error messages in parse results
  5. Maintainability - Better separation of concerns with encoding/decoding functions for INI safety

upstream/feat/flexible-scheduling has:

  1. Date Bounds Feature - Can limit schedules to date ranges (a useful feature your branch lacks)
  2. Simpler Implementation - Fewer lines of code (but at the cost of features and tests)

Verdict

For code quality and maintainability, origin/feat/flexible-schedule is significantly superior due to comprehensive testing and documentation. The upstream branch introduces a useful date bounds feature but lacks the quality infrastructure (tests, docs) needed for long-term maintenance.


Now, Claude did miss the fact that my branch does support the start and end date functionality. I figure because it was slightly obscured with the regex.

One final thought concerning trying to force the flexible cron format into the crontab format. It limits and greatly complicates any future features that apscheduler decides to implement. Let's just say for the sake of argument that apscheduler releases a new version that now supports a standard set of holidays as valid arguments for the flexible cron format. Using the crontab format it would not be possible to do something like "14:00 easter" or "0800 labor_day".

Standard 5-field cron cannot express schedules like "the 4th Tuesday of
every month" or "the last Friday" — patterns that come up constantly for
ARES nets and other recurring meetings. Users were stuck hand-rolling
day-of-month lists that drift across months with different lengths.

Adds a flexible cron format: unordered fields with suffixes (9h, 30m,
15d, 3w for ISO week) plus month/day-of-week names, ordinal prefixes
(1st/2nd/3rd/4th/5th/last), and optional start:/end: date bounds. The
web viewer gets a new "Flexible (cron)" mode alongside the existing
Advanced (cron) editor, and the edit modal now auto-detects which mode
a stored schedule belongs to instead of always defaulting to Advanced.

Storing a flexible-cron key verbatim in config.ini breaks the moment it
contains an HH:MM time, since ":" is the INI key/value separator (the
key becomes unparsable, e.g. "4th tue 14:00 jan-oct = Volusia ARES:...").
Fixed by encoding ":" as "!" in the schedule key when writing
(encode_schedule_key_for_ini) and decoding it back on every read path:
the web admin's read_entries() and the bot's own
setup_scheduled_messages(). HHMM (no colon) needs no encoding.

Also tightens the standard cron path to validate against a real regex
(cron_re) before handing off to CronTrigger.from_crontab, and threads a
specific error message back through ScheduleParseResult instead of a
generic "not a valid schedule" string.

Adds unit coverage for the new parse_flexible_cron/encode/decode helpers
and a scheduler-level regression test that reproduces the HH!MM
round-trip end to end. Adds docs/schedule-messages.md documenting all
three schedule formats (simplified UI modes, standard cron, flexible
cron) and how they're represented in config.ini.

Known gap: */Nm and */Nh step syntax in flexible cron doesn't match
when preceded by another field (e.g. "last sat 9h */15m") because \b
can't anchor on "*" — two admin tests are marked accordingly and left
for a follow-up.

Signed-off-by: Gerard Hickey <hickey@kinetic-compute.com>
Signed-off-by: Gerard Hickey <hickey@kinetic-compute.com>
Signed-off-by: Gerard Hickey <hickey@kinetic-compute.com>
@hickey
hickey force-pushed the feat/flexible-schedule branch 4 times, most recently from 0aa43da to c46d6ab Compare September 8, 2026 14:55
Signed-off-by: Gerard Hickey <hickey@kinetic-compute.com>
@hickey
hickey force-pushed the feat/flexible-schedule branch 2 times, most recently from 0e478f3 to d8c1973 Compare September 8, 2026 15:48
@hickey

hickey commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Thought that there was a easy to trip over case where minutes do not get specified and cron would trigger every minute causing the network to be overwhelmed with messages.

After pushing a fix, found that apscheduler actually handles it correctly and I have dropped the commit.

In other words, if "9h" is specified, apscheduler will trigger at 9:00 AM every day rather than every minute during 9-10 AM every day.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants