Skip to content

U/krall/croniter - #1108

Open
EvanKrall wants to merge 4 commits into
masterfrom
u/krall/croniter
Open

U/krall/croniter#1108
EvanKrall wants to merge 4 commits into
masterfrom
u/krall/croniter

Conversation

@EvanKrall

Copy link
Copy Markdown
Member

This should be backwards-compatible, as we're already validating cron expressions in yelpsoa-configs using croniter. This PR provides a few benefits:

  1. Gets rid of our home-grown cron parser, which has been the source of issues in the past.
  2. Croniter supports things like "2nd monday" (mon#2 or 1#2), so once we ship this, we can migrate anything the groc syntax to croniter, then delete even more code.
  3. Croniter supports Jenkins-style hashed expressions, where it can use a hash key (I'm using the job name here) to choose an arbitrary minute within the hour or hour within the day for e.g. daily/hourly jobs. This might help us improve node utilization by having fewer jobs all starting at the same time. (We currently support jitter, but we never use it - and a consistent time of day seems nicer for users than random-per-run offsets anyway.)

@EvanKrall
EvanKrall requested a review from a team as a code owner July 22, 2026 00:03
sidtuladhar
sidtuladhar previously approved these changes Jul 22, 2026
Comment thread tron/scheduler.py
name="cron",
original=config.original,
jitter=config.jitter,
hash_id=job_name,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we end up using the Jenkins-style hashed expressions in any capacity I think equivalent Airflow support should be a prerequisite (or...postrequisite?).

They don't support the H syntax, so either we'd need to implement something like this that returns an Airflow CronTriggerTimetable or Airflow folks would need to implement something vaguely similar (coincidentally, there is a recent PR up to add JitteredCronTimetable, though that wouldn't be 1:1 in it's current approach).

Comment thread tron/scheduler.py
else:
start_time = start_time.astimezone(self.time_zone)

it = croniter(self.cron_expression, start_time, hash_id=self.hash_id)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think replacing self.time_spec.get_match(start_time) with a direct call to croniter changes our documented DST behaviour (for tz aware jobs that include a run in the transition hours).

The GeneralScheduler calculates the next match as naive local time and then localizes it in trontimespec. Right now it catches ambiguous times and selects the first occurrence with is_dst=True (sg).

Passing a tz aware time directly to croniter lets it hit both instances, so 30 1 * * * will schedule both 1:30 PDT and 1:30 PST. I think we want croniter to calculate against naive local time, then apply Tron’s existing timezone policy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we have test_fall_back, but that just looks at generalscheduler

@EvanKrall

Copy link
Copy Markdown
Member Author

in light of #1072 being closed, I might want to add a test for Feb 30

)
except ValueError as e:
expression = re.sub(r"\s*,\s*", ",", config.value.strip())
if not croniter.is_valid(expression, hash_id="validation_placeholder"):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use the actual hash_id instead of a placeholder? If we have someone doing 0 0 H 2 * we could end up with 30/31 since Croniter hashes H across the full 1–31 range. A less likely but funnier risk would be 0 0 31 H *

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting point.

Comment thread tests/scheduler_test.py
with pytest.raises(CroniterBadDateError):
sched.next_run_time(start_time)


Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on repurposing some of these GeneralScheduler tests for CronScheduler?
I think some of these should be carried forward. E.g.

  • test_fall_back should look at CronScheduler("30 1 * * *")
  • test_spring_forward should look at CronScheduler("30 2 * * *") to ensure we shift nonexistent times forward
  • test_handles_unsetting_the_time_zone and test_handles_changing_the_time_zone both test behaviour we should maintain

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeez, test_handles_unsetting_the_time_zone is just incorrect in its current form. I like the spirit of it, but imo it shouldn't start with UTC and it shouldn't assert hour == 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JEEZ, test_handles_changing_the_time_zone is also funky. I don't think this should look at datetime.now and I very much disagree with asserting on just the hour.

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.

3 participants