use cronspeak::{describe, describe_with, CronError, Options, TimeFormat};
fn d(expr: &str) -> String {
describe(expr).unwrap_or_else(|e| panic!("describe({expr:?}) failed: {e}"))
}
#[test]
fn every_minute() {
assert_eq!(d("* * * * *"), "Every minute");
}
#[test]
fn every_n_minutes() {
assert_eq!(d("*/5 * * * *"), "Every 5 minutes");
assert_eq!(d("*/15 * * * *"), "Every 15 minutes");
}
#[test]
fn minutes_past_the_hour() {
assert_eq!(d("0 * * * *"), "At 0 minutes past the hour");
assert_eq!(d("5 * * * *"), "At 5 minutes past the hour");
}
#[test]
fn at_specific_time_12h() {
assert_eq!(d("0 0 * * *"), "At 12:00 AM");
assert_eq!(d("30 9 * * *"), "At 09:30 AM");
assert_eq!(d("0 12 * * *"), "At 12:00 PM");
assert_eq!(d("15 14 * * *"), "At 02:15 PM");
assert_eq!(d("0 22 * * *"), "At 10:00 PM");
assert_eq!(d("5 4 * * *"), "At 04:05 AM");
}
#[test]
fn weekdays_range() {
assert_eq!(d("0 9 * * 1-5"), "At 09:00 AM, Monday through Friday");
assert_eq!(d("0 22 * * 1-5"), "At 10:00 PM, Monday through Friday");
}
#[test]
fn single_weekday_numeric_and_named() {
assert_eq!(d("0 0 * * 0"), "At 12:00 AM, only on Sunday");
assert_eq!(d("0 0 * * 7"), "At 12:00 AM, only on Sunday");
assert_eq!(d("0 0 * * sun"), "At 12:00 AM, only on Sunday");
assert_eq!(d("5 4 * * sun"), "At 04:05 AM, only on Sunday");
}
#[test]
fn day_of_month() {
assert_eq!(d("0 0 1 * *"), "At 12:00 AM, on day 1 of the month");
assert_eq!(d("15 14 1 * *"), "At 02:15 PM, on day 1 of the month");
assert_eq!(
d("0 0 1,15 * *"),
"At 12:00 AM, on days 1 and 15 of the month"
);
}
#[test]
fn month_single_range_and_step() {
assert_eq!(
d("0 0 1 1 *"),
"At 12:00 AM, on day 1 of the month, only in January"
);
assert_eq!(d("0 0 * jan-mar *"), "At 12:00 AM, January through March");
assert_eq!(
d("0 0 1 */2 *"),
"At 12:00 AM, on day 1 of the month, every 2 months"
);
}
#[test]
fn combined_step_range_weekday() {
assert_eq!(
d("*/15 9-17 * * 1-5"),
"Every 15 minutes, between 09:00 AM and 05:59 PM, Monday through Friday"
);
}
#[test]
fn hour_list_with_fixed_minute() {
assert_eq!(d("0 0,12 * * *"), "At 12:00 AM and 12:00 PM");
assert_eq!(d("30 9,17 * * *"), "At 09:30 AM and 05:30 PM");
}
#[test]
fn three_item_list_uses_oxford_style() {
assert_eq!(
d("0 0 * * 1,3,5"),
"At 12:00 AM, only on Monday, Wednesday and Friday"
);
}
#[test]
fn macros() {
assert_eq!(d("@hourly"), "At 0 minutes past the hour");
assert_eq!(d("@daily"), "At 12:00 AM");
assert_eq!(d("@midnight"), "At 12:00 AM");
assert_eq!(d("@weekly"), "At 12:00 AM, only on Sunday");
assert_eq!(d("@monthly"), "At 12:00 AM, on day 1 of the month");
assert_eq!(
d("@yearly"),
"At 12:00 AM, on day 1 of the month, only in January"
);
assert_eq!(
d("@annually"),
"At 12:00 AM, on day 1 of the month, only in January"
);
}
#[test]
fn twenty_four_hour_format() {
let opts = Options::new().time_format(TimeFormat::H24);
assert_eq!(
describe_with("0 9 * * 1-5", &opts).unwrap(),
"At 09:00, Monday through Friday"
);
assert_eq!(describe_with("0 0 * * *", &opts).unwrap(), "At 00:00");
assert_eq!(describe_with("30 22 * * *", &opts).unwrap(), "At 22:30");
}
#[test]
fn extra_whitespace_is_tolerated() {
assert_eq!(
d(" 0 9 * * 1-5 "),
"At 09:00 AM, Monday through Friday"
);
}
#[test]
fn rejects_empty() {
assert!(matches!(describe(""), Err(CronError::Empty)));
assert!(matches!(describe(" "), Err(CronError::Empty)));
}
#[test]
fn rejects_wrong_field_count() {
assert!(matches!(
describe("* * *"),
Err(CronError::FieldCount { .. })
));
assert!(matches!(
describe("* * * * * *"),
Err(CronError::FieldCount { .. })
));
}
#[test]
fn rejects_out_of_range() {
assert!(describe("60 * * * *").is_err()); assert!(describe("* 24 * * *").is_err()); assert!(describe("* * 0 * *").is_err()); assert!(describe("* * 32 * *").is_err()); assert!(describe("* * * 13 *").is_err()); assert!(describe("* * * * 8").is_err()); }
#[test]
fn rejects_invalid_tokens() {
assert!(describe("a * * * *").is_err());
assert!(matches!(
describe("*/0 * * * *"),
Err(CronError::InvalidStep(_))
));
assert!(describe("5-1 * * * *").is_err()); }
#[test]
fn dom_and_dow_both_restricted_use_or_semantics() {
assert_eq!(
d("0 0 13 * 5"),
"At 12:00 AM, on day 13 of the month or on Friday"
);
assert_eq!(
d("0 0 13 6 5"),
"At 12:00 AM, on day 13 of the month or on Friday, only in June"
);
}
#[test]
fn dow_range_including_sunday_as_seven() {
assert_eq!(d("0 0 * * 5-7"), "At 12:00 AM, Friday through Sunday");
assert_eq!(d("0 0 * * 6-7"), "At 12:00 AM, Saturday through Sunday");
assert_eq!(d("0 0 * * 1-7"), "At 12:00 AM, Monday through Sunday");
}
#[test]
fn dow_step_shorthand_renders_named_days() {
assert_eq!(
d("0 0 * * 0/2"),
"At 12:00 AM, every 2 days from Sunday through Saturday"
);
}
#[test]
fn hour_range_with_fixed_minute_uses_hour_bounds() {
assert_eq!(
d("0 9-17 * * *"),
"At minute 0, between 09:00 AM and 05:00 PM"
);
assert_eq!(
d("* 9-17 * * *"),
"Every minute, between 09:00 AM and 05:59 PM"
);
}
#[test]
fn pluralization_is_grammatical() {
assert_eq!(d("1 * * * *"), "At 1 minute past the hour");
assert_eq!(
d("0 0 * * 0/1"),
"At 12:00 AM, every day from Sunday through Saturday"
);
}
#[test]
fn minute_list_across_all_hours_keeps_context() {
assert_eq!(d("0,30 * * * *"), "At minutes 0 and 30 past the hour");
}
#[test]
fn rejects_garbage_names() {
assert!(describe("0 0 1 janetjackson *").is_err());
assert!(describe("0 0 * * saturnalia").is_err());
assert!(describe("0 0 1 janx *").is_err());
}
#[test]
fn accepts_full_month_and_day_names() {
assert_eq!(
d("0 0 1 january *"),
"At 12:00 AM, on day 1 of the month, only in January"
);
assert_eq!(d("0 0 * * sunday"), "At 12:00 AM, only on Sunday");
}
#[test]
fn invalid_field_payload_is_not_empty() {
match describe("1- * * * *") {
Err(CronError::InvalidField(s)) => assert!(!s.is_empty(), "payload should name the field"),
other => panic!("expected InvalidField, got {other:?}"),
}
}