zslayton / cron Goto Github PK
View Code? Open in Web Editor NEWA cron expression parser in Rust
License: Apache License 2.0
A cron expression parser in Rust
License: Apache License 2.0
The 'any' marker (*
) is currently unhandled.
Hello,
First off great library, thanks for all the work you've done.
I am having an issue where I am trying to implement schedule as a custom parser for structopt/clap. Unfortunately it won't let me because schedule needs to implement debug in order to printed by the library. I'm kinda new to rust thought so if there is a good reason that it doesn't implement debug that totally fine.
Hi bro I'm trying to write a new crate base on your crate cron, but use some function, it's not a success because that not public such as from_str I wanna create a new Struct Schedule from str, but it's just private function I can't use it.
Receiving the following error on rustc nightly-2017-07-09:
error: use of unstable library feature 'iterator_step_by': unstable replacement of Range::step_by (see issue #27741)
--> /Users/joshua.holmer/.cargo/registry/src/github.com-1ecc6299db9ec823/cron-0.3.0/src/time_unit/mod.rs:219:55
|
219 | Ok((start..Self::inclusive_max() + 1).step_by(step).collect())
| ^^^^^^^
|
= help: add #![feature(iterator_step_by)] to the crate attributes to enable
error[E0308]: mismatched types
--> /Users/joshua.holmer/.cargo/registry/src/github.com-1ecc6299db9ec823/cron-0.3.0/src/time_unit/mod.rs:219:63
|
219 | Ok((start..Self::inclusive_max() + 1).step_by(step).collect())
| ^^^^ expected usize, found u32
error: aborting due to 2 previous errors
error: Could not compile `cron`.
cron 0.3.0 compiles successfully on rustc nightly-2017-07-02.
This field is currently ignored.
Because I fixed two of the metaissues in #67 and I would like to use them easily in my own personal project. I was wondering what could be done to make this project ready for v0.9.0
I was thinking a couple of things:
.map(f).unwrap_or_else(g)
into .map_or_else(g,f)
, or with ranges: (min..max + 1)
into (min..=max)
FromString for Schedule
to the schedule.rs file.If there are any other issues that need to be addressed I would love to see it.
Edit:
The following app will panic when tests are run. I believe this is an internal error in the cron library that isn't being handled. This is probably related to the fact that the time being used here is the DST change-over time for the target timezone (Europe/London).
I attempted to hack around this in my app by using catch_unwind
(https://doc.rust-lang.org/std/panic/fn.catch_unwind.html), but unfortunately chrono Tz objects are not UnwindSafe.
use cron::Schedule;
use chrono::{DateTime};
use chrono::offset::TimeZone;
pub fn currently_in_schedule<Z>(schedules: &[Schedule], now: &DateTime<Z>) -> bool
where
Z: TimeZone,
{
for schedule in schedules {
if let Some(next) = schedule.after(&now).next() {
let duration = next.clone() - now.clone();
if duration.num_seconds() <= 1 {
return true
}
}
}
false
}
#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;
use std::str::FromStr;
use chrono::Utc;
use chrono_tz::Tz;
#[test]
fn repro_crash_sre3898() {
let schedule_tz: Tz = "Europe/London".parse().unwrap();
let dt = schedule_tz
.ymd(2019, 10, 27)
.and_hms(0, 3, 29)
.checked_add_signed(Duration::hours(1)) // puts it in the middle of the DST transition
.unwrap();
let schedule = Schedule::from_str("* * * * * Sat,Sun *").unwrap();
// true, false, isn't really relevant... not panic'ing is
assert_eq!(currently_in_schedule(&[schedule], &dt), true);
}
}
Panic w/ backtrace:
---- schedule::tests::repro_crash_sre3898 stdout ----
thread 'schedule::tests::repro_crash_sre3898' panicked at 'invalid time', src/libcore/option.rs:1166:5
stack backtrace:
0: backtrace::backtrace::libunwind::trace
at /Users/vsts/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.34/src/backtrace/libunwind.rs:88
1: backtrace::backtrace::trace_unsynchronized
at /Users/vsts/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.34/src/backtrace/mod.rs:66
2: std::sys_common::backtrace::_print
at src/libstd/sys_common/backtrace.rs:47
3: std::sys_common::backtrace::print
at src/libstd/sys_common/backtrace.rs:36
4: std::panicking::default_hook::{{closure}}
at src/libstd/panicking.rs:200
5: std::panicking::default_hook
at src/libstd/panicking.rs:211
6: std::panicking::rust_panic_with_hook
at src/libstd/panicking.rs:477
7: std::panicking::continue_panic_fmt
at src/libstd/panicking.rs:384
8: rust_begin_unwind
at src/libstd/panicking.rs:311
9: core::panicking::panic_fmt
at src/libcore/panicking.rs:85
10: core::option::expect_failed
at src/libcore/option.rs:1166
11: core::option::Option<T>::expect
at /rustc/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/libcore/option.rs:345
12: chrono::date::Date<Tz>::and_hms
at /Users/mathieu.fenniak/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.7/src/date.rs:78
13: cron::schedule::Schedule::next_after
at /Users/mathieu.fenniak/.cargo/registry/src/github.com-1ecc6299db9ec823/cron-0.6.0/src/schedule.rs:214
14: <cron::schedule::ScheduleIterator<Z> as core::iter::traits::iterator::Iterator>::next
at /Users/mathieu.fenniak/.cargo/registry/src/github.com-1ecc6299db9ec823/cron-0.6.0/src/schedule.rs:338
15: schedulevacuum::schedule::currently_in_schedule
at src/schedule.rs:10
16: schedulevacuum::schedule::tests::repro_crash_sre3898
at src/schedule.rs:91
17: schedulevacuum::schedule::tests::repro_crash_sre3898::{{closure}}
at src/schedule.rs:79
18: core::ops::function::FnOnce::call_once
at /rustc/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/libcore/ops/function.rs:235
19: <alloc::boxed::Box<F> as core::ops::function::FnOnce<A>>::call_once
at /rustc/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/liballoc/boxed.rs:787
20: __rust_maybe_catch_panic
at src/libpanic_unwind/lib.rs:80
21: std::panicking::try
at /rustc/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/libstd/panicking.rs:275
22: std::panic::catch_unwind
at /rustc/625451e376bb2e5283fc4741caa0a3e8a2ca4d54/src/libstd/panic.rs:394
23: test::run_test::run_test_inner::{{closure}}
at src/libtest/lib.rs:1408
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Here is a small code to reproduce, note that I dont know precisely if the bug is in this project or chrono_tz
...
Cargo.toml:
[dependencies]
cron = "0.9.0"
chrono = "0.4.19"
chrono-tz = "0.5.3"
Code:
extern crate cron;
extern crate chrono;
extern crate chrono_tz;
use cron::Schedule;
use chrono::{ DateTime, TimeZone };
use std::str::FromStr;
pub fn next_ticks<Z>(schedule: &str, from_time: &DateTime<Z>, take: usize) -> Vec<DateTime::<Z>> where Z: TimeZone {
// sec min hour day of month month day of week year
// let expression = "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri 2018/2";
match Schedule::from_str(schedule) {
Ok(schedule) => schedule.after(from_time).take(take).collect::<Vec<DateTime::<Z>>>(),
Err(_e) => Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::SecondsFormat;
use chrono_tz::Europe::Paris;
#[test]
fn test_time_zone_change_up() {
// During the night from the 28 to 29 march 2020, France has changed its time from 2am to 3am
// So when we reached "2020-03-29T02:00:00 Europe/Paris" we advanced to "2020-03-29T03:00:00 Europe/Paris"
let results = next_ticks("0 * * * * * *", &Paris.ymd(2020, 3, 29).and_hms(1, 55, 0), 10);
assert_eq!(results.len(), 10);
assert_eq!(results[0].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:56:00.000+01:00");
assert_eq!(results[1].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:57:00.000+01:00");
assert_eq!(results[2].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:58:00.000+01:00");
assert_eq!(results[3].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T01:59:00.000+01:00");
// Time zone shift here
assert_eq!(results[4].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:00:00.000+02:00");
assert_eq!(results[5].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:01:00.000+02:00");
assert_eq!(results[6].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:02:00.000+02:00");
assert_eq!(results[7].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:03:00.000+02:00");
assert_eq!(results[8].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:04:00.000+02:00");
assert_eq!(results[9].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-03-29T03:05:00.000+02:00");
}
#[test]
fn test_time_zone_change_down() {
// During the night from the 24 to 25 october 2020, France has changed its time from 3am to 2am
// So when we reached "2020-10-25T03:00:00 Europe/Paris" we went back to "2020-10-25T02:00:00 Europe/Paris"
let results = next_ticks("0 * * * * * *", &Paris.ymd(2020, 10, 25).and_hms(2, 55, 0), 1);
assert_eq!(results.len(), 10);
assert_eq!(results[0].to_rfc3339_opts(SecondsFormat::Millis, false), "2020-10-25T02:56:00.000+02:00");
}
}
The first works, the second test_time_zone_change_down
panic with "thread 'tests::test_time_zone_change_down' panicked at 'invalid time', /Users/<>/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.19/src/date.rs:83:42"
A downstream crate I use exposes an API that takes strings, converts them to Schedules, which may fail.
Ideally, I'd like to construct Schedules myself and pass them to the crate's functions, and handle possible errors before the library has a chance to produce errors.
For this to be practical, it would need to avoid breaking API compatibility so severely, and the best way to do that would be for their functions to take the schedule argument as a S: TryInto<cron::Schedule>
.
If TryFrom<&str> for Schedule
is implemented, this will allow the functions to take &str
s like they do currently, as well as support taking cron::Schedule
, since it has a blanket impl for TryFrom<T> for T
.
I tried to use the contructors from and from_field_list but the structs I need for that are in an private module. Why do these constructors exist? These constructors are also not used in the tests.
I’d like to use this tool as a CLI, but I don’t know it’s possible since its README doesn’t mention about it.
If it’s not possible for now, is there a plan to implement that?
Thank you!
Going from a cron string to a Schedule
struct is nice and easy, but why can't you easily go from a Schedule
back to its string representation?
Hi, first thanks for this crate. When testing it I've found that a shorthand (e.g. @weekly
) followed by any characters (e.g. @weeklyFoo
) will be parsed as @weekly
.
To be more strict it probably should return an error in this case or what do you think?
The string 1/0
causes a panic when fed to cron::Schedule::from_str
(0.11.0):
thread 'testing' panicked at 'assertion failed: step != 0', /rustc/6dd68402c5d7da168f87d8551dd9aed1d8a21893/library/core/src/iter/adapters/step_by.rs:21:9
stack backtrace:
0: rust_begin_unwind
at /rustc/6dd68402c5d7da168f87d8551dd9aed1d8a21893/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt
at /rustc/6dd68402c5d7da168f87d8551dd9aed1d8a21893/library/core/src/panicking.rs:142:14
2: core::panicking::panic
at /rustc/6dd68402c5d7da168f87d8551dd9aed1d8a21893/library/core/src/panicking.rs:48:5
3: core::iter::adapters::step_by::StepBy<I>::new
at /rustc/6dd68402c5d7da168f87d8551dd9aed1d8a21893/library/core/src/iter/adapters/step_by.rs:21:9
4: core::iter::traits::iterator::Iterator::step_by
at /rustc/6dd68402c5d7da168f87d8551dd9aed1d8a21893/library/core/src/iter/traits/iterator.rs:384:9
5: cron::time_unit::TimeUnitField::ordinals_from_root_specifier
at /home/timo/.cargo/registry/src/github.com-1ecc6299db9ec823/cron-0.11.0/src/time_unit/mod.rs:310:17
6: <T as cron::parsing::FromField>::from_field
at /home/timo/.cargo/registry/src/github.com-1ecc6299db9ec823/cron-0.11.0/src/parsing.rs:60:50
This also happens in earlier versions (I tested 0.6.0), though that requires subexpressions to be present: 1/0 * * * * *
The dependency on 0.3 is causing dependency issues in my app. Can we get this upgraded? I know the implications of upgrading require a rename from UTC to Utc. Not sure of there are any changes required.
Hi,
First of all, thanks for this great crate.
However, the numbering of weekdays seems unusual to me. On the machines I usually work, the crontab uses a 0 for Sunday, 1 for Monday and so on.
This crate seems to use 1
for Sunday and not accept 0
.
Maybe this should be documented somewhere.
e.g.
let schedule = from_str("@weekly");
if schedule.includes(UTC.now()) {
// ....
}
Items in this list need to be completed before 1.0 is cut, but they will be released incrementally in minor version bumps.
TODO:
PartialEq
and Eq
implementations for Schedule
(See #61)
Allow users to test whether a given DateTime
matches their schedule (See #16)
Allow users to iterate over a Schedule
backwards, viewing matching historical DateTime
s (See #66)
Provide a convenience method for iterating over a window of time (schedule.from(time1).to(time2)
)
ScheduleParserBuilder
that allows users to specify different configuration options. Options included TBD, but ideas include:
No individual option is required for 1.0, but introducing a ScheduleParserBuilder
is. Individual options can be added without it being a breaking change.
Proper documentation
Re-license to dual MIT/Apache2
The year
field is currently optional, with expressions being comprised of six or seven fields accordingly. I would like to also accept expressions which have neither seconds
nor years
, for a total of five fields. Importantly, expressions that do not include seconds
but which do include years
would be illegal.
extern crate cron;
extern crate chrono;
fn main() {
use chrono::TimeZone;
let schedule = "0 0/10 * * * * *".parse::<cron::Schedule>().unwrap();
let dt = chrono::NaiveDate::from_ymd(2017, 10, 24).and_hms(0, 0, 59);
let dt = chrono::Utc.from_utc_datetime(&dt);
println!("{}", schedule.after(&dt).next().unwrap());
let dt = chrono::NaiveDate::from_ymd(2017, 10, 24).and_hms(0, 1, 0);
let dt = chrono::Utc.from_utc_datetime(&dt);
println!("{}", schedule.after(&dt).next().unwrap());
}
Both line should print 2017-10-24 00:10:00 UTC
. But I got this:
2017-10-24 00:10:00 UTC
2017-10-24 00:20:00 UTC
I am observing an issue where using a cron set to a date next year but already passed this years results in no upcoming date.
Observed behavior:
I am posting this issue on 2021-11-24, yesterday was 2021-11-23. Setting a cron to yesterday next year (2022) results in no upcoming date.
let schedule = Schedule::from_str("0 0 1 23 11 * 2022").unwrap();
println!("Upcoming fire times:");
for datetime in schedule.upcoming(Utc).take(3) {
println!("-> {}", datetime);
}
Upcoming fire times:
Workaround:
This is solved by not setting the year or setting a repeated year. e.g.
let schedule = Schedule::from_str("0 0 1 23 11 * 2022/1").unwrap();
// equivalent: let schedule = Schedule::from_str("0 0 1 23 11 * * ").unwrap();
// equivalent: let schedule = Schedule::from_str("0 0 1 23 11 * ").unwrap();
println!("Upcoming fire times:");
for datetime in schedule.upcoming(Utc).take(3) {
println!("-> {}", datetime);
}
Upcoming fire times:
-> 2023-11-23 01:00:00 UTC
-> 2024-11-23 01:00:00 UTC
-> 2025-11-23 01:00:00 UTC
Heya @zslayton is there a chance to get a quick 0.6.1 release with the clone derive? :) pleeeaaaasseee?
It might be interesting to create a ScheduleFields struct like such:
pub struct ScheduleFields {
years: Years,
days_of_week: DaysOfWeek,
months: Months,
days_of_month: DaysOfMonth,
hours: Hours,
minutes: Minutes,
seconds: Seconds,
}
And change the Schedule field to:
pub struct Schedule {
source: String,
fields: ScheduleFields
}
This means that you can change the FromStr implementation for Schedule to:
fn from_str(expression: &str) -> Result<Self, Self::Err> {
match schedule(Input(expression)) {
Ok((_, schedule_fields)) => {
Ok(Schedule::new(
String::from(expression),
schedule_fields)
)
}, // Extract from nom tuple
Err(_) => Err(ErrorKind::Expression("Invalid cron expression.".to_owned()).into()), //TODO: Details
}
}
And because of that, source doesn't need to be an Option anymore.
To prevent any breaking changes you can change the TimeSpec functions in Schedule to
pub fn years(&self) -> &impl TimeUnitSpec {
&self.fields.years // NB: instead of &self.hours
}
// etc
Lastly If you expose ScheduleFields (and add a getter to Schedule), it can be used for equality. This means that you can use the Schedule::source for a quick equality check of Schedules, and if you have a use case where you need to check for equivalence, you can manually choose to use the more "expensive" ScheduleFields equality check. This might be useful for #61
Hi,
I see the spec for DaysOfWeek
defines the rangs as 1 -> 7
. I'm using this project to parse cronfiles on a debian/ubuntu
, which uses the range 0 -> 6
. I guess this means this project is not based on the same specification ?
The documentation is therefore not generated for the type
Hi!
First of all, thanks for your effort in this crate. We've been using it for a while with great success!
However, since the latest nightly we've been experiencing some problems with it:
error[E0432]: unresolved import `std::collections::Bound`
--> /opt/app-root/src/.cargo/registry/src/github.com-1ecc6299db9ec823/cron-0.8.0/src/schedule.rs:6:23
|
6 | use std::collections::Bound::{Included, Unbounded};
| ^^^^^ `Bound` is a type alias, not a module
error: aborting due to previous error
It looks like the deprecated alias has been removed.
We saw that the problem has been fixed by #84 . Would it be possible to make a 0.8.1
release that contains this change?
Thank you!
It would be usefull to be able to clone a Schedule
and not to have to parse a string multiple times.
It seems that we have supported predefined schedules like @yearly
. Any plan to support @every <duration>
?
The MIT license requires reproducing countless copies of the same copyright
header with different names in the copyright field, for every MIT library in
use. The Apache license does not have this drawback, and has protections from
patent trolls and an explicit contribution licensing clause. However, the
Apache license is incompatible with GPLv2. This is why Rust is dual-licensed as
MIT/Apache (the "primary" license being Apache, MIT only for GPLv2 compat), and
doing so would be wise for this project. This also makes this crate suitable
for inclusion in the Rust standard distribution and other project using dual
MIT/Apache.
To do this, get explicit approval from each contributor of copyrightable work
(as not all contributions qualify for copyright) and then add the following to
your README:
## License
Licensed under either of
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as above, without any
additional terms or conditions.
and in your license headers, use the following boilerplate (based on that used in Rust):
// Copyright (c) 2015 t developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
And don't forget to update the license
metadata in your Cargo.toml
!
The current version doesn't work with the current rust version
Make the struct generic over Tz
.
Range iterators need to be progressed each time a parent range is complete.
This is a fantastic crate! However, I'm running into some issues because it seems that Schedule::after doesn't take milliseconds into account. This shows up when iterating over the upcoming times in reverse order to get previous times.
let schedule = Schedule::from_str("* * * * * * *").unwrap();
let start_time = Utc.ymd(2022, 1, 1).and_hms_milli(12, 0, 0, 500);
println!("{}", schedule.after(&start_time).rev().next().unwrap());
I would expect this code to print "2022-01-01 12:00:00 UTC", but it prints "2022-01-01 11:59:59 UTC" instead.
PrevFromQuery and NextAfterQuery are almost the same. This is also true for next_after(...) and prev_from(...) in Schedule. This seems to break with the DRY principle.
You could implement Query as follows:
pub enum QueryDirection {
Next,
Previous,
}
pub struct Query<Z>
where
Z: TimeZone,
{
initial_datetime: DateTime<Z>,
first_month: bool,
first_day_of_month: bool,
first_hour: bool,
first_minute: bool,
first_second: bool,
direction: QueryDirection,
}
impl<Z> Query<Z>
where
Z: TimeZone,
{
pub fn from(date: &DateTime<Z>, direction: QueryDirection) -> Query<Z> {
let new_date = match direction {
QueryDirection::Next => date.clone() + Duration::seconds(1),
QueryDirection::Previous => date.clone() - Duration::seconds(1)
};
Query {
initial_datetime: new_date,
first_month: true,
first_day_of_month: true,
first_hour: true,
first_minute: true,
first_second: true,
direction: direction
}
}
pub fn year_bound(&self) -> Ordinal {
// Unlike the other units, years will never wrap around.
self.initial_datetime.year() as u32
}
pub fn month_bound(&mut self) -> Ordinal {
if self.first_month {
self.first_month = false;
return self.initial_datetime.month();
}
match &self.direction {
QueryDirection::Next => Months::inclusive_min(),
QueryDirection::Previous => Months::inclusive_max()
}
}
pub fn reset_month(&mut self) {
self.first_month = false;
self.reset_day_of_month();
}
// etc..
}
This still leaves the functions breaking the DRY principle. You could also factor out Query and reimplement the functions with a combinator like so (NB this will currently fail the tests, but I think thats because of nanosecond issues)
fn next_after<Z>(&self, after: &DateTime<Z>) -> Option<DateTime<Z>>
where
Z: TimeZone,
{
self.years()
.iter()
.skip_while(|year| *year < after.year() as u32)
.flat_map(|year| {
iter::repeat(after.with_year(year as i32))
.zip(self.months().iter())
})
.skip_while(|(date, month)| {
date.as_ref().unwrap() < after ||
(date.as_ref().unwrap() == after &&
*month < after.month())
})
.flat_map(|(date, month)| {
iter::repeat(date.unwrap().with_month(month))
.zip(self.days_of_month().iter())
})
.skip_while(|(date, day)| {
date.as_ref().unwrap() < after ||
(date.as_ref().unwrap() == after &&
*day < after.day())
})
.map(|(date, day)| {
date.unwrap().with_day(day)
})
.filter(|date| {
self.days_of_week().includes(
date.as_ref().unwrap().weekday().number_from_sunday()
)
})
.flat_map(|date| {
iter::repeat(date).zip(self.hours().iter())
})
.skip_while(|(date, hour)| {
date.as_ref().unwrap() < after ||
(date.as_ref().unwrap() == after &&
*hour < after.hour())
})
.flat_map(|(date, hour)| {
iter::repeat(date.unwrap().with_hour(hour)).zip(self.minutes().iter())
})
.skip_while(|(date, minute)| {
date.as_ref().unwrap() < after ||
(date.as_ref().unwrap() == after &&
*minute < after.minute())
})
.flat_map(|(date, minute)| {
iter::repeat(date.unwrap().with_minute(minute)).zip(self.seconds().iter())
})
.skip_while(|(date, second)| {
date.as_ref().unwrap() < after ||
(date.as_ref().unwrap() == after &&
*second <= after.second())
})
.map(|(date, second)| {
date.unwrap().with_second(second)
})
.next()
.flatten()
}
This releaves the function of some indentation, and if you take a QueryDirection
enum as an argument and slap some match
clauses in there, you might be able to merge both functions together.
https://www.freebsd.org/cgi/man.cgi?crontab%285%29
Step values can be used in conjunction with ranges. Following a range
with/<number>
specifies skips of the number's value through the
range. For example,0-23/2
can be used in the hours field to specify
command execution every other hour (the alternative in the V7 standard is
0,2,4,6,8,10,12,14,16,18,20,22
). Steps are also permitted after an
asterisk, so if you want to say ``every two hours'', just use*/2
.
Disclaimer: I'm rather new to rust's world. My question might come from a lack of knowledge of how things work.
I'm interested in using this lib is a small pet project. I have been using rust's stable release
so far for that project, which means it will fail to compile cron
, which requires some nightly (?) features.
Is this permanent, or does cron
aim to support rust stable at some point ?
More generally, is the choice to use nightly features guided by some kind of recommendation ? Should I myself follow those ?
Thanks for your help !
Support expressions like @hourly
and @weekly
.
Follow the Vixie cron policy of permitting the first three letters of any day or month. By convention, US/English locale only.
Looks like the API for the collections_range feature has changed on nightly.
I've created a pull request fixing this at #34
Hi! I am working on something and it would be nice to be able to do comparisons with the Schedule types.
It is sometimes useful to get the past expected runs of a cron expression.
So it would be great to have a Schedule::before
, working the same as Schedule::after
, but going backwards in time.
Due to my unfamiliarity with nom
's error handling, the meaningful messages produced when errors occur are lost when bubbling up. The map_res!
macro discards them.
cron
expression features are supported and which ones (all non-standard) aren't.rustdoc
comments on the few public facing types/methods.If 7 fields are specified, treat the first as a seconds
field.
extern crate cron;
extern crate chrono;
use cron::Schedule;
use chrono::Utc;
use std::str::FromStr;
fn main() {
// sec min hour day of month month day of week year
let expression = "* 0/2 * * * * *";
let schedule = Schedule::from_str(expression).unwrap();
println!("Upcoming fire times:");
for datetime in schedule.upcoming(Utc).take(10) {
println!("-> {}", datetime);
}
}
produces
-> 2018-01-23 13:08:59 UTC
-> 2018-01-23 13:10:00 UTC
-> 2018-01-23 13:10:01 UTC
-> 2018-01-23 13:10:02 UTC
-> 2018-01-23 13:10:03 UTC
-> 2018-01-23 13:10:04 UTC
-> 2018-01-23 13:10:05 UTC
-> 2018-01-23 13:10:06 UTC
-> 2018-01-23 13:10:07 UTC
-> 2018-01-23 13:10:08 UTC
Partially implemented, needs to play nicely with the seconds
field that will be introduced in #5. If 8 fields are present, treat the last field as a years
field.
I can use values greater than 59 for seconds and minutes, and greater than 24 for hours, in a cron expressions, and that gives surprising values without errors.
Example of accepted expression:
0/100 0/100 0/100 * * *
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.