8000 impl DoubleEndedIterator for ScheduleIterator by deankarn · Pull Request #66 · zslayton/cron · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

impl DoubleEndedIterator for ScheduleIterator #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 4, 2021

Conversation

deankarn
Copy link

This PR implements DoubleEndedIterator for ScheduleIterator in order to retrieve previous scheduled times.

My use case is I'm building a CRON scheduler using this crate where each run of a scheduled job will save the last time it was successfully run. This saved time will be used by a service using this scheduler to know on startup, either due to scheduled maintenance, downtime or otherwise, If a scheduled job was missed being run during that downtime and should be run immediately to make up for it.

So this will allow checking the previous scheduled run time against the last time it was run in order to make that determination.

P.S. The function name upcoming used to get a ScheduleIterator does read strange when iterating in reverse over schedule times and perhaps should change with a major version change, but until that time didn't want to make breaking changes.

Dean Karn added 3 commits January 29, 2021 22:58
This implements DoubleEndedIterator for ScheduleIterator in order to
get previous run times.
@zslayton
Copy link
Owner
zslayton commented Feb 1, 2021

This is excellent! I always wanted an implementation of this but never got around to writing it. Thanks for putting it together! I agree that this will likely require reconsidering some method names, but we can take care of that in a separate PR.

It'll be a day or two before I have time to read through the diff properly. Could you make sure that there's a test for iterating backwards over a schedule that includes non-existent times? (e.g. Hours that are skipped due to daylight saving time.) Here's the corresponding forward-iteration unit test.

@zslayton zslayton mentioned this pull request Feb 1, 2021
7 tasks
@deankarn
Copy link
Author
deankarn commented Feb 2, 2021

@zslayton I have added the test for non-existent times and fixed a bug causing it to misreport.

let me know if there's anything you need.

@zslayton
Copy link
Owner
zslayton commented Feb 2, 2021

I read through it this morning and it looks great. Thanks!

Regarding naming, I think I'd like cron to end up with an API something like this:

// Iterates forwards
schedule.after(datetime)

// Iterates backwards
schedule.before(datetime)

// Double-ended iterator
schedule.range(start, end)

// Equivalent to schedule.after(Tz.now())
schedule.upcoming(<
8000
span class="pl-v">Tz)

// Equivalent to schedule.before(Tz.now())
schedule.history(Tz)

I think this makes the iterator behavior associated with each method more obvious. In particular, you only get a double-ended iterator a range that, well, has two ends. I'm not married to any of those names, but that's the gist.

What're your thoughts?

@deankarn
Copy link
Author
deankarn commented Feb 2, 2021

I was thinking of perhaps simplifying the API to something like:

// Returns an iterator without specifying direction, but like most forward by default but can use .rev() to go backwards.
schedule.using(datetime)

This keeps the API surface area tiny and simple.

Here are my thoughts on the proposed:

schedule.after(datetime)
^ implies we can only go forward, which isn't true of the iterator

schedule.before(datetime)
^ implies we can only go backward, which isn't true of the iterator

schedule.range(start, end)
^ not sure why we would need a range as the CRON itself specifies a range or you can limit using .take(n)

schedule.upcoming(Tz)
schedule.history(Tz)
^^ both of these don't seem to be necessary since using a date time or Tz you need chrono as a dependancy in the calling program already. Why not just let people create the date time themselves and eliminate 2 more methods and shrink the API surface.

In a bit of a rush this morning so will give above even more thought later tonight. Hope this doesn't seem too drastic.

@zslayton
Copy link
Owner
zslayton commented Feb 2, 2021

schedule.after(datetime)
^ implies we can only go forward, which isn't true of the iterator

Now that we have a complete implementation of both forward and backward iteration, it would be trivial to return a single-direction iterator. My thinking is that there aren't common use cases for switching directions mid-stream (i.e. iterate over several datetimes, then call rev() and process the same datetimes again), so the clearest behavior is to have some convenience methods that only produce a single-direction iterator.

schedule.range(start, end)
^ not sure why we would need a range as the CRON itself specifies a range or you can limit using .take(n)

The cron schedule itself can include a range, but it often doesn't. For example, 0 15 * * * *. When answering queries like "What are my schedule's matching datetimes for the first week of October?", take(n) requires the user to calculate how many datetimes they'll need to take. take_until(|d| d > end) would work, and schedule.range(start, end) would be a convenience method offering that functionality.

// Returns an iterator without specifying direction, but like most forward by default but can use .rev() to go backwards.
schedule.using(datetime)

I agree that this is functionally equivalent, but I find:

schedule.using(datetime).rev()

to be much less self-explanatory than:

schedule.before(datetime)

Maybe there's a clearer name than using that's not occurring to me.

schedule.upcoming(Tz)
schedule.history(Tz)

^^ both of these don't seem to be necessary since using a date time or Tz you need chrono as a dependancy in the calling program already.

I think you're asking why the crate doesn't just require users to do:

schedule.after(Tz.now())

Is that right? If so, I agree that this is a minor convenience.

Why not just let people create the date time themselves and eliminate 2 more methods and shrink the API surface.

Keeping a library's API surface small is wise, but in this case I'm not concerned about the library going from 2 methods to ~5. Most of them would just be convenience methods provided in the interest of code readability.

@deankarn
Copy link
Author
deankarn commented Feb 2, 2021

@zslayton all makes sense to me. Ultimately it's your library and choice so take everything I say/said with a grain of salt :)

I just like keeping the surface as small as possible until I know people actually want/need the convenience abstractions. To me using take and take_until seems more intuitive to me but again that's just me.

I agree using might not be the best name, was just an example name, I had also considered schedule.from(datetime) which is more generic, but indication schedule from this date ....

In any case I think your proposed API is good and think below should still be removed:

schedule.upcoming(Tz)
schedule.history(Tz)

love the library, learning a lot and may take a stab at a CRON parser myself to learn some more too :)

@zslayton zslayton merged commit 9e1eb68 into zslayton:master Feb 4, 2021
@zslayton
Copy link
Owner
zslayton commented Feb 4, 2021

I just like keeping the surface as small as possible until I know people actually want/need the convenience abstractions. To me using take and take_until seems more intuitive to me but again that's just me.

I definitely see the value this approach! I'll try to think of some version of using (maybe iter_from(date)?) to be the primary interface in a v1.0 API so folks can leverage that if they prefer. Then I can implement any convenience functions that seem worth it in terms of iter_from() and rev().

In any case I think your proposed API is good and think below should still be removed:

schedule.upcoming(Tz)
schedule.history(Tz)

Yeah, I think the case for these is the weakest. upcoming(Tz) isn't substantially more obvious than something like after(Tz.now()) or iter_from(Tz.now()).

love the library, learning a lot and may take a stab at a CRON parser myself to learn some more too :)

Thanks! Do check out nom v6+ (reddit discussion), it replaced the macros this library uses (from nom ~4) with a really clever application of generics. I never have as much project time as I'd like, but I've been considering updating cron to take advantage of them.

Thanks again for this (major!) contribution. It's great to finally have this feature.

@deankarn
Copy link
Author
deankarn commented Feb 5, 2021

BTW love item_from(datetime) it's very clear!

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
0