From 8f4411ffd06316ac4bc98b65ceabcdbe06fbd00d Mon Sep 17 00:00:00 2001 From: Omid Rad Date: Fri, 7 Apr 2023 10:08:09 +0200 Subject: [PATCH] Prepare 0.11 release by updating chrono-tz Prepare 0.11 release by updating chrono-tz Additionally, this commit includes: - Bumping MSRV to v1.64.0 - Temporarily allows clippy warnings in CI - General cleanup work --- .cargo/config.toml | 15 ++++ .clippy.toml | 5 ++ .github/workflows/ci.yml | 8 +- CHANGELOG.md | 18 ++-- Cargo.toml | 5 ++ Makefile | 1 + README.md | 8 +- SECURITY.md | 14 +-- rrule-afl-fuzz/Cargo.toml | 5 +- rrule-afl-fuzz/src/take_data.rs | 16 ++-- rrule-afl-fuzz/src/take_rrule.rs | 2 +- rrule-debugger/Cargo.toml | 7 +- rrule-debugger/src/debug.rs | 6 +- rrule-debugger/src/iter_rrule.rs | 8 +- rrule-debugger/src/main.rs | 17 ++-- rrule-debugger/src/parser_rrule.rs | 12 +-- rrule-debugger/src/simple_logger.rs | 2 +- rrule/Cargo.toml | 12 +-- rrule/examples/manual_rrule.rs | 4 +- rrule/examples/manual_rrule_set.rs | 8 +- rrule/examples/timezone_support.rs | 8 +- rrule/src/core/rrule.rs | 64 +++++++------- rrule/src/core/rruleset.rs | 10 ++- rrule/src/core/timezone.rs | 8 +- rrule/src/core/timezone_impl.rs | 6 +- rrule/src/core/utils.rs | 85 +++++++++---------- rrule/src/iter/counter_date.rs | 55 ++++++------ rrule/src/iter/easter.rs | 2 +- rrule/src/iter/iterinfo.rs | 69 ++++++++------- rrule/src/iter/mod.rs | 2 +- rrule/src/iter/monthinfo.rs | 12 +-- rrule/src/iter/pos_list.rs | 2 +- rrule/src/iter/rrule_iter.rs | 6 +- rrule/src/iter/rruleset_iter.rs | 33 ++++--- rrule/src/iter/utils.rs | 26 +++--- rrule/src/iter/yearinfo.rs | 30 ++++--- rrule/src/lib.rs | 8 +- .../parser/content_line/date_content_line.rs | 12 +-- .../content_line/start_date_content_line.rs | 8 +- rrule/src/parser/datetime.rs | 79 +++++++++-------- rrule/src/parser/error.rs | 24 +++--- rrule/src/parser/mod.rs | 21 +++-- rrule/src/parser/regex.rs | 5 +- rrule/src/tests/common.rs | 4 +- rrule/src/tests/rfc_tests.rs | 6 +- rrule/src/tests/rrule.rs | 6 +- rrule/src/tests/rruleset.rs | 2 +- rrule/src/validator/error.rs | 6 +- rrule/src/validator/validate_rrule.rs | 34 ++++---- 49 files changed, 440 insertions(+), 366 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .clippy.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..8d3300a --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,15 @@ +[target.'cfg(all())'] +rustflags = [ + "-Funsafe_code", + "-Wclippy::missing_panics_doc", + "-Wclippy::panic_in_result_fn", + "-Wclippy::panic", + "-Wclippy::panicking_unwrap", + "-Wclippy::todo", + "-Wclippy::unimplemented", + "-Wclippy::unwrap_used", + "-Wclippy::use_self", + # "-Wmissing_debug_implementations", + # "-Wmissing_docs", + "-Wunused_qualifications", +] diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..fd21e66 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,5 @@ +warn-on-all-wildcard-imports = true +allow-expect-in-tests = true +allow-unwrap-in-tests = true +allow-dbg-in-tests = true +allow-print-in-tests = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c408dc5..c19bf1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,9 @@ jobs: - name: Linting run: | cd rrule - cargo clippy -- -D warnings + # revert it back to the following line, the deprecated functions are removed from chrono + # cargo clippy -- -D warnings + cargo clippy - name: Cargo Doc run: | cargo doc --no-deps --all-features --examples @@ -38,7 +40,7 @@ jobs: - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } version: - - 1.57.0 # MSRV + - 1.64.0 # MSRV - stable steps: - uses: actions/checkout@v2 @@ -63,5 +65,5 @@ jobs: cd rrule cargo update cargo audit - # Allowed to fail but this will notify us that some dependency might need an update. + # Allowed to fail, but this will notify us that some dependency might need an update. continue-on-error: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 32efa03..8ac3b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] (2022-0X-YY) +## 0.11.0 (2023-0X-YY) ### Changed - `RRuleSet::all` returns an `RRuleResult` struct instead of a tuple. +- Update `chrono-tz` version to 0.8 +- MSRV is bumped to `v1.64.0` from `v1.56.1` ## 0.10.0 (2022-08-08) @@ -22,8 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `no-validation-limits` feature is removed and is replaced by arguments to `RRuleSet`. -- `RRuleSet::just_before` was removed to keep the public API more lean. Please use the iterator API directly if you need this use-case. -- `RRuleSet::just_after` was removed to keep the public API more lean. Please use the iterator API directly if you need this use-case. +- `RRuleSet::just_before` was removed to keep the public API leaner. Please use the iterator API directly if you need this use-case. +- `RRuleSet::just_after` was removed to keep the public API leaner. Please use the iterator API directly if you need this use-case. - `RRuleSet::all_between` was replaced by `rrule_set.before(dt).after(dt).all(limit)`. ### Added @@ -43,7 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes - Fixed typos in security docs -- Fixed an infinite loop issue in the iteration phase where the counter date increment method did not increment the counter date and it was unable to make progress. This was solved by using a custom date time implementation for the counter date. +- Fixed an infinite loop issue in the iteration phase where the counter date increment method didn't increment the counter date, and it was unable to make progress. This was solved by using a custom date time implementation for the counter date. - Fixes issue where iterations that passed a daylight saving time had incorrect hour. ## 0.9.0 (2022-07-18) @@ -62,7 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes -- The `DTSTART` and `UNTIL` values were not synced before this release. They are now synced according to the RFC. +- The `DTSTART` and `UNTIL` values weren't synced before this release. They're now synced according to the RFC. - Fix #61 where `collect_with_error` would not return an error in the case where `RRuleSet` iteration had an error. ## 0.8.0 (2022-06-21) @@ -99,7 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix [#33](https://github.com/fmeringdal/rust-rrule/issues/33) - Fix [#34](https://github.com/fmeringdal/rust-rrule/issues/34) -- Many tests were ignored because they were either invalid or the test didn't pass. Most ignored tests are now deleted or the code is fixed so that they pass. +- Many tests were ignored because they were either invalid or the test didn't pass. Most ignored tests are now deleted, or the code is fixed so that they pass. - Better error handling ### Changed @@ -142,7 +144,7 @@ Internal refactorings: - `RRule` can only be crated using `new` function with a valid `RRuleProperties`. - `RRule.option` is no longer public, but can be read by using `get_properties()`. - `RRuleIter` and `RRuleSetIter` are now part of the public API. -- `NWeekday` has totally changed, but serves the same purpose. +- `NWeekday` has totally changed but serves the same purpose. - Updated `chrono-tz` from `0.5.3` to `0.6.0`. - Function `all` was moved to `DateFilter` and returns a `Result` now. @@ -174,4 +176,4 @@ Internal refactorings: ## Pre 0.6.0 (2021-07-02) All changes before 2021-07-02 where not documented. -This is everything before and including: fa8308944a4d2ead0a6ccfa6ee53b76b399e045f +This is everything before and including fa8308944a4d2ead0a6ccfa6ee53b76b399e045f diff --git a/Cargo.toml b/Cargo.toml index 1d6d6cb..d7a69c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,8 @@ +[workspace.package] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.64.0" + [workspace] members = [ "rrule", diff --git a/Makefile b/Makefile index 4187070..aafa021 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ check: @cargo clippy --fix --allow-dirty --allow-staged --all-targets --all-features @cargo update --dry-run @cargo outdated -wR + @cargo machete @cargo doc --no-deps --all-features --examples --document-private-items @cargo +nightly udeps --all-targets --all-features diff --git a/README.md b/README.md index 5ba7c21..a5b3a76 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ See [Chrono-Tz's limits for more info](https://github.com/chronotope/chrono-tz/# ### Validation Limits -Because the specifications does give a lot of flexibility this can be [abused very easily](#Security). -In order to prevent most of the abuse we have imposed arbitrary limitation when on the `RRuleSet::all` -method. The validation limits are not enforced for the `RRuleSet::all_unchecked` method or when +Because the specifications do give a lot of flexibilities this can be [abused very easily](#Security). +In order to prevent most of the abuse we've imposed arbitrary limitation when on the `RRuleSet::all` +method. The validation limits aren't enforced for the `RRuleSet::all_unchecked` method or when using the `Iterator` api directly. Limitations: @@ -100,7 +100,7 @@ Limitations: | Max interval with freq Secondly | 50_000 (~13 hours) | 65_535 (u16::MAX) | | Iteration limit | 100_000 | 4_294_967_295 (u32::MAX) | -By default, the "Arbitrary Limit" are used. If you instead want to use the "Crate Limit". +By default, the "Arbitrary Limit" is used. If you instead want to use the "Crate Limit". Make sure you [understand the risks that come with this](#safety). ## Inspired by diff --git a/SECURITY.md b/SECURITY.md index 90fccd8..5932508 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,8 +8,8 @@ The 3 problems we are currently mostly worried about are: ### Denial of Service (DoS) by panic -If the user is able to trigger a case where is it able to panic -the code it could crash the application or server. +If the user is able to trigger a case where it able to panic +the code, it could crash the application or server. This can be limited by using [catch_unwind](https://doc.rust-lang.org/std/panic/fn.catch_unwind.html). Make sure to read the documentation! This does not work if the [`panic = "abort"`](https://doc.rust-lang.org/cargo/reference/profiles.html#panic) @@ -19,14 +19,14 @@ It is not advised to reuse `RRule`, `RRuleSet` or iterators after it has panicke Disabling overflow checks using compiler flags might result in unexpected results and crashes. So this is strongly discouraged. -When the [validation limits](#validation_limits) are disabled this problem will be much more +When the [validation limits](#validation_limits) are disabled, this problem will be much more prevalent. Numbers might overflow in some cases. ### Denial of Service (DoS) by CPU exhaustion The spec allows for infinitely recurring events or searches for a datetime that meets the -requirements but does not exist. There are various protections for this built into the crate. -But in order to hit these limits it might take a few seconds depending on the CPU speed. +requirements but doesn't exist. There are various protections for this built into the crate. +But in order to hit these limits, it might take a few seconds depending on the CPU speed. This problem can be mitigated by spawning the process in a separate thread and stopping the thread if it hits the timeout. On decent CPUs this might not be a big issue. @@ -37,8 +37,8 @@ made MUCH more significant. ### Denial of Service (DoS) by memory exhaustion The spec allows for infinitely recurring events. Thus, the iterator might be practically infinite. -So when not setting a limit over the iterator it might create a list of events that practically +So when not setting a limit over the iterator, it might create a list of events that practically never ends. And thus will continue until it crashes or hangs the system. -This problem can be easily mitigated by limiting the amount of events expected. +This problem can be easily mitigated by limiting the number of events expected. This is also the reason why the `rrule.all(limit)` function takes a limit. \ No newline at end of file diff --git a/rrule-afl-fuzz/Cargo.toml b/rrule-afl-fuzz/Cargo.toml index 3f6886d..7861d10 100644 --- a/rrule-afl-fuzz/Cargo.toml +++ b/rrule-afl-fuzz/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "rrule-afl-fuzz" version = "0.1.0" -edition = "2021" authors = ["Ralph Bisschops "] publish = false +license.workspace = true +edition.workspace = true +rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] afl = "*" chrono = "0.4.19" -chrono-tz = "0.6.0" num-traits = "0.2.15" [dependencies.rrule] diff --git a/rrule-afl-fuzz/src/take_data.rs b/rrule-afl-fuzz/src/take_data.rs index 91a4eba..ec43798 100644 --- a/rrule-afl-fuzz/src/take_data.rs +++ b/rrule-afl-fuzz/src/take_data.rs @@ -128,7 +128,7 @@ pub fn take_data_i8(input: &mut &[u8]) -> i8 { i8::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses 2 byte +/// Uses 2 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_i16(input: &mut &[u8]) -> i16 { let byte_len = std::mem::size_of::(); @@ -140,7 +140,7 @@ pub fn take_data_i16(input: &mut &[u8]) -> i16 { i16::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses 4 byte +/// Uses 4 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_i32(input: &mut &[u8]) -> i32 { let byte_len = std::mem::size_of::(); @@ -152,7 +152,7 @@ pub fn take_data_i32(input: &mut &[u8]) -> i32 { i32::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses 8 byte +/// Uses 8 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_i64(input: &mut &[u8]) -> i64 { let byte_len = std::mem::size_of::(); @@ -164,7 +164,7 @@ pub fn take_data_i64(input: &mut &[u8]) -> i64 { i64::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses 2 byte +/// Uses 2 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_u16(input: &mut &[u8]) -> u16 { let byte_len = std::mem::size_of::(); @@ -176,7 +176,7 @@ pub fn take_data_u16(input: &mut &[u8]) -> u16 { u16::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses 4 byte +/// Uses 4 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_u32(input: &mut &[u8]) -> u32 { let byte_len = std::mem::size_of::(); @@ -188,7 +188,7 @@ pub fn take_data_u32(input: &mut &[u8]) -> u32 { u32::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses 8 byte +/// Uses 8 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_u64(input: &mut &[u8]) -> u64 { let byte_len = std::mem::size_of::(); @@ -200,7 +200,7 @@ pub fn take_data_u64(input: &mut &[u8]) -> u64 { u64::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses max 8 byte +/// Uses max 8 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_usize(input: &mut &[u8]) -> usize { let byte_len = std::mem::size_of::(); @@ -212,7 +212,7 @@ pub fn take_data_usize(input: &mut &[u8]) -> usize { usize::from_be_bytes(int_bytes.try_into().expect("Failed to convert to u8")) } -/// Uses max 8 byte +/// Uses max 8 bytes /// If no bytes left it will always return default (`0`) pub fn take_data_isize(input: &mut &[u8]) -> isize { let byte_len = std::mem::size_of::(); diff --git a/rrule-afl-fuzz/src/take_rrule.rs b/rrule-afl-fuzz/src/take_rrule.rs index b6f1f2b..cc63164 100644 --- a/rrule-afl-fuzz/src/take_rrule.rs +++ b/rrule-afl-fuzz/src/take_rrule.rs @@ -33,7 +33,7 @@ pub fn take_rrule_from_data(mut data: &[u8]) -> Option { // Total: 166 bytes // // We use at least x bytes of data. - // If we don't have enough data it will just use default data (`0` or `vec![]`). + // If we don't have enough data, it will just use default data (`0` or `vec![]`). // if data.len() < 166 { // return None; // } diff --git a/rrule-debugger/Cargo.toml b/rrule-debugger/Cargo.toml index 35abbdf..0a40ab7 100644 --- a/rrule-debugger/Cargo.toml +++ b/rrule-debugger/Cargo.toml @@ -1,17 +1,18 @@ [package] name = "rrule-debugger" version = "0.1.0" -edition = "2021" authors = ["Ralph Bisschops "] publish = false +license.workspace = true +edition.workspace = true +rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rrule = { path = "../rrule" } chrono = "0.4.19" -chrono-tz = "0.6.1" -clap = { version = "3.2.5", features = ["derive"] } +clap = { version = "4.1.9", features = ["derive"] } rrule-afl-fuzz = { version = "0.1.0", path = "../rrule-afl-fuzz" } log = "0.4.16" yansi = "0.5.1" diff --git a/rrule-debugger/src/debug.rs b/rrule-debugger/src/debug.rs index e523d97..1f03c44 100644 --- a/rrule-debugger/src/debug.rs +++ b/rrule-debugger/src/debug.rs @@ -14,7 +14,7 @@ fn test_from_string() { RRULE:FREQ=YEARLY;BYDAY=20MO" .parse() .unwrap(); - println!("RRule: {:#?}", rrule); + println!("RRule: {rrule:#?}"); let result = rrule.all(20); println!("Limited: {}", result.limited); crate::print_all_datetimes(&result.dates); @@ -36,5 +36,7 @@ fn test_parsed_rrule() { } fn ymd_hms(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> DateTime { - Tz::UTC.ymd(year, month, day).and_hms(hour, minute, second) + Tz::UTC + .with_ymd_and_hms(year, month, day, hour, minute, second) + .unwrap() } diff --git a/rrule-debugger/src/iter_rrule.rs b/rrule-debugger/src/iter_rrule.rs index dbd481c..0774740 100644 --- a/rrule-debugger/src/iter_rrule.rs +++ b/rrule-debugger/src/iter_rrule.rs @@ -1,17 +1,17 @@ pub fn from_crash_file(id: u32, data: &[u8]) { - println!("--------- Test file {} -----------", id); + println!("--------- Test file {id} -----------"); let result = std::panic::catch_unwind(|| { rrule_from_bin(data); }); - println!("Test {} status: {:?}", id, result); - println!("--------- Done test {} -----------", id); + println!("Test {id} status: {result:?}"); + println!("--------- Done test {id} -----------"); } pub fn rrule_from_bin(data: &[u8]) { match rrule_afl_fuzz::take_rrule::take_rrule_from_data(data) { Some(rule) => { - println!("RRule data: {:#?}", rule); + println!("RRule data: {rule:#?}"); let result = rule.all(50); crate::print_all_datetimes(&result.dates); if result.limited { diff --git a/rrule-debugger/src/main.rs b/rrule-debugger/src/main.rs index 0db0608..ae22b7a 100644 --- a/rrule-debugger/src/main.rs +++ b/rrule-debugger/src/main.rs @@ -1,4 +1,8 @@ -#![allow(clippy::cast_possible_truncation, clippy::doc_markdown)] +#![allow( + clippy::cast_possible_truncation, + clippy::doc_markdown, + clippy::unwrap_used +)] mod debug; mod iter_rrule; @@ -6,6 +10,7 @@ mod parser_rrule; mod simple_logger; use chrono::DateTime; +use clap::ArgAction; use clap::Parser; use log::LevelFilter; use rrule::Tz; @@ -28,7 +33,7 @@ struct Opts { debug: bool, /// Verbose mode (-v, -vv, -vvv, etc.) - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, action = ArgAction::Count)] verbose: u8, /// Run id @@ -52,7 +57,7 @@ enum Commands { /// Parse data from raw binary RRule data Rrule {}, /// Used for debugging particular parts of the code, - /// for example when a test fails. + /// for example, when a test fails. Debug {}, } @@ -96,8 +101,8 @@ fn read_crash_file(id: u32) -> Option> { continue; } let filename = path.file_name().unwrap().to_str().unwrap(); - if filename.starts_with(&format!("id:{:06},", id)) { - println!("Reading file {:?}", path); + if filename.starts_with(&format!("id:{id:06},")) { + println!("Reading file {path:?}"); return Some(std::fs::read(path).expect("Something went wrong reading the file")); } } @@ -115,7 +120,7 @@ fn read_all_crash_file() -> Vec> { } let filename = path.file_name().unwrap().to_str().unwrap(); if filename.starts_with("id:") { - println!("Reading file {:?}", path); + println!("Reading file {path:?}"); list.push(std::fs::read(path).expect("Something went wrong reading the file")); } } diff --git a/rrule-debugger/src/parser_rrule.rs b/rrule-debugger/src/parser_rrule.rs index 9b102a3..7b4f4f7 100644 --- a/rrule-debugger/src/parser_rrule.rs +++ b/rrule-debugger/src/parser_rrule.rs @@ -2,27 +2,27 @@ use rrule::RRuleSet; use std::str::FromStr; pub fn from_crash_file(id: u32, data: &[u8]) { - println!("--------- Test file {} -----------", id); + println!("--------- Test file {id} -----------"); let data_string = std::str::from_utf8(data).unwrap(); - println!("Test {} String: \n\n{:?}\n", id, data_string); + println!("Test {id} String: \n\n{data_string:?}\n"); let result = std::panic::catch_unwind(|| { parse_rrule_from_string(data_string); }); - println!("Test {} status: {:?}", id, result); - println!("--------- Done test {} -----------", id); + println!("Test {id} status: {result:?}"); + println!("--------- Done test {id} -----------"); } pub fn parse_rrule_from_string(rrule: &str) { match RRuleSet::from_str(rrule) { Ok(rrule) => { - println!("RRule data: {:#?}", rrule); + println!("RRule data: {rrule:#?}"); let result = rrule.all(50); crate::print_all_datetimes(&result.dates); if result.limited { println!("RRule was limited"); } } - Err(err) => println!("RRule could not be parsed: {}", err), + Err(err) => println!("RRule could not be parsed: {err}"), }; } diff --git a/rrule-debugger/src/simple_logger.rs b/rrule-debugger/src/simple_logger.rs index ab28cc1..a210014 100644 --- a/rrule-debugger/src/simple_logger.rs +++ b/rrule-debugger/src/simple_logger.rs @@ -3,7 +3,7 @@ pub use yansi::Paint; /// An instance of the `Logger`. pub static LOGGER: Logger = Logger; -/// The log collector and handler for most printed messages in terminal. +/// The log collector and handler for the most printed messages in the terminal. pub struct Logger; impl log::Log for Logger { diff --git a/rrule/Cargo.toml b/rrule/Cargo.toml index e17306a..0dc56dc 100644 --- a/rrule/Cargo.toml +++ b/rrule/Cargo.toml @@ -1,26 +1,26 @@ [package] name = "rrule" description = "A pure Rust implementation of recurrence rules as defined in the iCalendar RFC." -license = "MIT OR Apache-2.0" version = "0.10.0" documentation = "https://docs.rs/rrule" repository = "https://github.com/fmeringdal/rust-rrule" authors = ["Fredrik Meringdal", "Ralph Bisschops "] -edition = "2021" readme = "../README.md" keywords = ["calendar", "rrule", "ical"] categories = ["date-and-time"] -rust-version = "1.57.0" +license.workspace = true +rust-version.workspace = true +edition.workspace = true [dependencies] chrono = "0.4.19" -chrono-tz = "0.6.1" +chrono-tz = "0.8.1" lazy_static = "1.4.0" log = "0.4.16" regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] } -clap = { version = "3.2.5", optional = true, features = ["derive"] } +clap = { version = "4.1.9", optional = true, features = ["derive"] } thiserror = "1.0.30" -serde_with = { version = "1.14.0", optional = true } +serde_with = { version = "2.3.1", optional = true } [dev-dependencies] serde_json = "1.0.80" diff --git a/rrule/examples/manual_rrule.rs b/rrule/examples/manual_rrule.rs index b0a44ae..72552d7 100644 --- a/rrule/examples/manual_rrule.rs +++ b/rrule/examples/manual_rrule.rs @@ -6,8 +6,8 @@ use chrono::{Datelike, TimeZone, Timelike}; use rrule::{Frequency, RRule, Tz}; fn main() { - // Build an RRuleSet that starts first day in 2020 at 9:00AM and occurs daily 5 times - let start_date = Tz::UTC.ymd(2020, 1, 1).and_hms(9, 0, 0); + // Build an RRuleSet that starts the first day in 2020 at 9:00AM and occurs daily 5 times + let start_date = Tz::UTC.with_ymd_and_hms(2020, 1, 1, 9, 0, 0).unwrap(); let rrule_set = RRule::default() .count(5) .freq(Frequency::Daily) diff --git a/rrule/examples/manual_rrule_set.rs b/rrule/examples/manual_rrule_set.rs index 054868a..3396974 100644 --- a/rrule/examples/manual_rrule_set.rs +++ b/rrule/examples/manual_rrule_set.rs @@ -3,7 +3,7 @@ //! Create an [`rrule::RRuleSet`] object manually. /// ## Construct [`rrule::RRuleSet`] from one `rrule` and `exrule` -/// The rrule will occur weekly on Tuesday and Wednesday and the exrule +/// The rrule will occur weekly on Tuesday and Wednesday, and the exrule /// will occur weekly on Wednesday, and therefore the end result will contain /// weekly recurrences on Wednesday only. fn main() { @@ -11,7 +11,7 @@ fn main() { { use chrono::{Datelike, TimeZone}; use rrule::{Frequency, NWeekday, RRule, Tz, Weekday}; - // Build rrule set that occurs weekly on Tuesday and Wednesday + // Build an rrule set that occurs weekly on Tuesday and Wednesday let rrule_set = RRule::default() .count(4) .freq(Frequency::Weekly) @@ -19,7 +19,7 @@ fn main() { NWeekday::Every(Weekday::Tue), NWeekday::Every(Weekday::Wed), ]) - .build(Tz::UTC.ymd(2020, 1, 1).and_hms(9, 0, 0)) + .build(Tz::UTC.with_ymd_and_hms(2020, 1, 1, 9, 0, 0).unwrap()) .expect("RRule invalid"); // Build exrule that occurs weekly on Wednesday @@ -27,7 +27,7 @@ fn main() { .count(4) .freq(Frequency::Weekly) .by_weekday(vec![NWeekday::Every(Weekday::Wed)]) - .validate(Tz::UTC.ymd(2020, 1, 1).and_hms(9, 0, 0)) + .validate(Tz::UTC.with_ymd_and_hms(2020, 1, 1, 9, 0, 0).unwrap()) .expect("RRule invalid"); let recurrences = rrule_set.exrule(exrule).all(10).dates; diff --git a/rrule/examples/timezone_support.rs b/rrule/examples/timezone_support.rs index 74b0c0d..7710df5 100644 --- a/rrule/examples/timezone_support.rs +++ b/rrule/examples/timezone_support.rs @@ -9,10 +9,10 @@ use rrule::{Frequency, RRule, Tz}; fn main() { let tz = Tz::Europe__Berlin; - let start_date = tz.ymd(2020, 1, 1).and_hms(9, 0, 0); - let exdate = Tz::UTC.ymd(2020, 1, 2).and_hms(8, 0, 0); + let start_date = tz.with_ymd_and_hms(2020, 1, 1, 9, 0, 0).unwrap(); + let exdate = Tz::UTC.with_ymd_and_hms(2020, 1, 2, 8, 0, 0).unwrap(); - // Build rrule set that occurs daily at 9:00 for 4 times + // Build an rrule set that occurs daily at 9:00 for 4 times let rrule_set = RRule::default() .count(4) .freq(Frequency::Daily) @@ -26,7 +26,7 @@ fn main() { // RRule contained 4 recurrences but 1 was filtered away by the exdate assert_eq!(recurrences.len(), 3); - // If you want to get back the DateTimes in another timezone you can just iterate over the result + // If you want to get back the DateTimes in another timezone, you can just iterate over the result // and convert them to another timezone by using the with_timezone method provided by the DateTime type. // Refer to the chrono and chrono-tz crates for more documentation on working with the DateTime type. diff --git a/rrule/src/core/rrule.rs b/rrule/src/core/rrule.rs index 3d0d353..d7cfd2a 100644 --- a/rrule/src/core/rrule.rs +++ b/rrule/src/core/rrule.rs @@ -43,13 +43,13 @@ pub enum Frequency { impl Display for Frequency { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let name = match self { - Frequency::Yearly => "yearly", - Frequency::Monthly => "monthly", - Frequency::Weekly => "weekly", - Frequency::Daily => "daily", - Frequency::Hourly => "hourly", - Frequency::Minutely => "minutely", - Frequency::Secondly => "secondly", + Self::Yearly => "yearly", + Self::Monthly => "monthly", + Self::Weekly => "weekly", + Self::Daily => "daily", + Self::Hourly => "hourly", + Self::Minutely => "minutely", + Self::Secondly => "secondly", }; write!(f, "{}", name) } @@ -60,13 +60,13 @@ impl FromStr for Frequency { fn from_str(value: &str) -> Result { let freq = match &value.to_uppercase()[..] { - "YEARLY" => Frequency::Yearly, - "MONTHLY" => Frequency::Monthly, - "WEEKLY" => Frequency::Weekly, - "DAILY" => Frequency::Daily, - "HOURLY" => Frequency::Hourly, - "MINUTELY" => Frequency::Minutely, - "SECONDLY" => Frequency::Secondly, + "YEARLY" => Self::Yearly, + "MONTHLY" => Self::Monthly, + "WEEKLY" => Self::Weekly, + "DAILY" => Self::Daily, + "HOURLY" => Self::Hourly, + "MINUTELY" => Self::Minutely, + "SECONDLY" => Self::Secondly, val => return Err(ParseError::InvalidFrequency(val.to_string())), }; Ok(freq) @@ -116,7 +116,7 @@ impl PartialOrd for NWeekday { } impl Ord for NWeekday { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> Ordering { n_weekday_cmp(*self, *other) } } @@ -163,9 +163,9 @@ impl FromStr for NWeekday { let nth = value[..(length - 2)].parse::().unwrap_or_default(); if nth == 0 { - Ok(NWeekday::Every(wd)) + Ok(Self::Every(wd)) } else { - Ok(NWeekday::new(Some(nth), wd)) + Ok(Self::new(Some(nth), wd)) } } } @@ -183,8 +183,8 @@ impl Display for NWeekday { /// ``` fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let weekday = match self { - NWeekday::Every(wd) => weekday_to_str(*wd), - NWeekday::Nth(number, wd) => { + Self::Every(wd) => weekday_to_str(*wd), + Self::Nth(number, wd) => { let mut wd_str = weekday_to_str(*wd); if *number != 1 { wd_str = format!("{}{}", number, wd_str); @@ -218,17 +218,17 @@ fn weekday_to_str(d: Weekday) -> String { #[cfg_attr(feature = "serde", derive(DeserializeFromStr, SerializeDisplay))] pub struct RRule { /// The frequency of the rrule. - /// For example: yearly, weekly, hourly + /// For example, yearly, weekly, hourly pub(crate) freq: Frequency, /// The interval between each frequency iteration. - /// For example: + /// For example, /// - A yearly frequency with an interval of `2` creates 1 event every two years. /// - An hourly frequency with an interval of `2` created 1 event every two hours. pub(crate) interval: u16, /// How many occurrences will be generated. pub(crate) count: Option, /// The end date after which new events will no longer be generated. - /// If the `DateTime` is equal to an instance of the event it will be the last event. + /// If the `DateTime` is equal to an instance of the event, it will be the last event. #[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))] pub(crate) until: Option, /// The start day of the week. @@ -251,7 +251,7 @@ pub struct RRule { pub(crate) by_year_day: Vec, /// The week numbers to apply the recurrence to. /// Week numbers have the meaning described in ISO8601, that is, - /// the first week of the year is that containing at least four days of the new year. + /// the first week of the year is that it contains at least four days of the new year. /// Week day starts counting on from `week_start` value. /// Can be a value from -53 to -1 and 1 to 53. pub(crate) by_week_no: Vec, @@ -309,7 +309,7 @@ impl RRule { pub fn new(freq: Frequency) -> Self { Self { freq, - ..RRule::default() + ..Self::default() } } @@ -391,7 +391,7 @@ impl RRule { /// If given, it must be either an integer, or a sequence of integers, meaning /// the week numbers to apply the recurrence to. Week numbers have the meaning - /// described in ISO8601, that is, the first week of the year is that containing + /// described in ISO8601, that is, the first week of the year is that it contains /// at least four days of the new year. #[must_use] pub fn by_week_no(mut self, by_week_no: Vec) -> Self { @@ -442,8 +442,8 @@ impl RRule { } /// Fills in some additional fields in order to make iter work correctly. - pub(crate) fn finalize_parsed_rrule(mut self, dt_start: &DateTime) -> RRule { - // TEMP: move negative months to other list + pub(crate) fn finalize_parsed_rrule(mut self, dt_start: &DateTime) -> Self { + // TEMP: move negative months to another list let mut by_month_day = vec![]; let mut by_n_month_day = self.by_n_month_day; for by_month_day_item in self.by_month_day { @@ -456,14 +456,14 @@ impl RRule { self.by_month_day = by_month_day; self.by_n_month_day = by_n_month_day; - // Can only be set to true if feature flag is set. + // Can only be set to true if the feature flag is set. let by_easter_is_some = if cfg!(feature = "by-easter") { self.by_easter.is_some() } else { false }; - // Add some freq specific additional properties + // Add some freq-specific additional properties if !(!self.by_week_no.is_empty() || !self.by_year_day.is_empty() || !self.by_month_day.is_empty() @@ -547,7 +547,7 @@ impl RRule { /// /// # Errors /// - /// If the properties are not valid it will return [`RRuleError`]. + /// If the properties aren't valid, it will return [`RRuleError`]. pub fn validate(self, dt_start: DateTime) -> Result, RRuleError> { let rrule = self.finalize_parsed_rrule(&dt_start); @@ -621,7 +621,7 @@ impl FromStr for RRule { fn from_str(s: &str) -> Result { let parts = ContentLineCaptures::new(s)?; - RRule::try_from(parts).map_err(From::from) + Self::try_from(parts).map_err(From::from) } } @@ -629,7 +629,7 @@ impl Display for RRule { /// Generates a string based on the [iCalendar RRULE spec](https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3). /// It doesn't prepend "RRULE:" to the string. /// When you call this function on [`RRule`], it can generate an invalid string, like 'FREQ=YEARLY;INTERVAL=-1' - /// But it supposed to always generate a valid string on [`RRule`]. + /// But it is supposed to always generate a valid string on [`RRule`]. /// So if you want a valid string, it's smarter to always use `rrule.validate(ds_start)?.to_string()`. #[allow(clippy::too_many_lines)] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { diff --git a/rrule/src/core/rruleset.rs b/rrule/src/core/rruleset.rs index 18d45eb..32915f1 100644 --- a/rrule/src/core/rruleset.rs +++ b/rrule/src/core/rruleset.rs @@ -32,10 +32,12 @@ pub struct RRuleSet { } /// The return result of `RRuleSet::all`. +#[derive(Clone, Debug, PartialEq, Eq)] pub struct RRuleResult { /// List of recurrences. pub dates: Vec, - /// It is be true, if the list of dates is limited. To indicate that it can potentially contain more dates. + /// It is being true if the list of dates is limited. + /// To indicate that it can potentially contain more dates. pub limited: bool, } @@ -226,7 +228,7 @@ impl FromStr for RRuleSet { } = Grammar::from_str(s)?; content_lines.into_iter().try_fold( - RRuleSet::new(start.datetime), + Self::new(start.datetime), |rrule_set, content_line| match content_line { ContentLine::RRule(rrule) => rrule .validate(start.datetime) @@ -246,10 +248,10 @@ impl FromStr for RRuleSet { } } ContentLine::ExDate(exdates) => { - Ok(exdates.into_iter().fold(rrule_set, RRuleSet::exdate)) + Ok(exdates.into_iter().fold(rrule_set, Self::exdate)) } ContentLine::RDate(rdates) => { - Ok(rdates.into_iter().fold(rrule_set, RRuleSet::rdate)) + Ok(rdates.into_iter().fold(rrule_set, Self::rdate)) } }, ) diff --git a/rrule/src/core/timezone.rs b/rrule/src/core/timezone.rs index 28309ef..0878de4 100644 --- a/rrule/src/core/timezone.rs +++ b/rrule/src/core/timezone.rs @@ -26,8 +26,8 @@ impl Tz { #[must_use] pub fn name(&self) -> &str { match self { - Tz::Local(_) => "Local", - Tz::Tz(tz) => tz.name(), + Self::Local(_) => "Local", + Self::Tz(tz) => tz.name(), } } @@ -35,8 +35,8 @@ impl Tz { #[must_use] pub fn is_local(&self) -> bool { match self { - Tz::Local(_) => true, - Tz::Tz(_) => false, + Self::Local(_) => true, + Self::Tz(_) => false, } } diff --git a/rrule/src/core/timezone_impl.rs b/rrule/src/core/timezone_impl.rs index 077cef1..8417481 100644 --- a/rrule/src/core/timezone_impl.rs +++ b/rrule/src/core/timezone_impl.rs @@ -75,8 +75,8 @@ impl std::fmt::Display for RRuleOffset { impl Offset for RRuleOffset { fn fix(&self) -> FixedOffset { match self { - RRuleOffset::Local(tz) => tz.fix(), - RRuleOffset::Tz(tz) => tz.fix(), + Self::Local(tz) => tz.fix(), + Self::Tz(tz) => tz.fix(), } } } @@ -91,6 +91,7 @@ impl TimeZone for Tz { } } + #[allow(deprecated)] fn offset_from_local_date( &self, local: &chrono::NaiveDate, @@ -119,6 +120,7 @@ impl TimeZone for Tz { } } + #[allow(deprecated)] fn offset_from_utc_date(&self, utc: &chrono::NaiveDate) -> Self::Offset { match self { Self::Local(tz) => RRuleOffset::Local(*tz.from_utc_date(utc).offset()), diff --git a/rrule/src/core/utils.rs b/rrule/src/core/utils.rs index f9a67f0..15ed87d 100644 --- a/rrule/src/core/utils.rs +++ b/rrule/src/core/utils.rs @@ -8,7 +8,7 @@ use std::ops::{ /// Helper function to collect dates given some filters. /// -/// In case where the iterator ended with an errors the error will be included, +/// In the case where the iterator ended with errors, the error will be included, /// otherwise the second value of the return tuple will be `None`. pub(super) fn collect_with_error( mut iterator: T, @@ -25,20 +25,17 @@ where // This loop should always end because `.next()` has build in limits // Once a limit is tripped it will break in the `None` case. while limit.is_none() || matches!(limit, Some(limit) if usize::from(limit) > list.len()) { - match iterator.next() { - Some(value) => { - if is_in_range(&value, start, end, inclusive) { - list.push(value); - } - if has_reached_the_end(&value, end, inclusive) { - // Date is after end date, so can stop iterating - break; - } + if let Some(value) = iterator.next() { + if is_in_range(&value, start, end, inclusive) { + list.push(value); } - None => { - was_limited = iterator.was_limited(); + if has_reached_the_end(&value, end, inclusive) { + // Date is after end date, so can stop iterating break; } + } else { + was_limited = iterator.was_limited(); + break; } } @@ -102,26 +99,26 @@ mod tests { #[test] fn in_range_exclusive_start_to_end() { let inclusive = false; - let start = UTC.ymd(2021, 10, 1).and_hms(8, 0, 0); - let end = UTC.ymd(2021, 10, 1).and_hms(10, 0, 0); + let start = UTC.with_ymd_and_hms(2021, 10, 1, 8, 0, 0).unwrap(); + let end = UTC.with_ymd_and_hms(2021, 10, 1, 10, 0, 0).unwrap(); // In middle assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &Some(start), &Some(end), inclusive, )); // To small assert!(!is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(7, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 7, 0, 0).unwrap(), &Some(start), &Some(end), inclusive, )); // To big assert!(!is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(11, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 11, 0, 0).unwrap(), &Some(start), &Some(end), inclusive, @@ -135,25 +132,25 @@ mod tests { #[test] fn in_range_exclusive_start() { let inclusive = false; - let start = UTC.ymd(2021, 10, 1).and_hms(8, 0, 0); + let start = UTC.with_ymd_and_hms(2021, 10, 1, 8, 0, 0).unwrap(); // Just after assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &Some(start), &None, inclusive, )); // To small assert!(!is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(7, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 7, 0, 0).unwrap(), &Some(start), &None, inclusive, )); // Bigger assert!(is_in_range( - &UTC.ymd(2021, 10, 2).and_hms(8, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 2, 8, 0, 0).unwrap(), &Some(start), &None, inclusive, @@ -165,25 +162,25 @@ mod tests { #[test] fn in_range_exclusive_end() { let inclusive = false; - let end = UTC.ymd(2021, 10, 1).and_hms(10, 0, 0); + let end = UTC.with_ymd_and_hms(2021, 10, 1, 10, 0, 0).unwrap(); // Just before assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &None, &Some(end), inclusive, )); // Smaller assert!(is_in_range( - &UTC.ymd(2021, 9, 20).and_hms(10, 0, 0), + &UTC.with_ymd_and_hms(2021, 9, 20, 10, 0, 0).unwrap(), &None, &Some(end), inclusive, )); // Bigger assert!(!is_in_range( - &UTC.ymd(2021, 10, 2).and_hms(8, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 2, 8, 0, 0).unwrap(), &None, &Some(end), inclusive, @@ -198,21 +195,21 @@ mod tests { // Some date assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &None, &None, inclusive, )); // Smaller assert!(is_in_range( - &UTC.ymd(2021, 9, 20).and_hms(10, 0, 0), + &UTC.with_ymd_and_hms(2021, 9, 20, 10, 0, 0).unwrap(), &None, &None, inclusive, )); // Bigger assert!(is_in_range( - &UTC.ymd(2021, 10, 2).and_hms(8, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 2, 8, 0, 0).unwrap(), &None, &None, inclusive, @@ -224,26 +221,26 @@ mod tests { #[test] fn in_range_inclusive_start_to_end() { let inclusive = true; - let start = UTC.ymd(2021, 10, 1).and_hms(8, 0, 0); - let end = UTC.ymd(2021, 10, 1).and_hms(10, 0, 0); + let start = UTC.with_ymd_and_hms(2021, 10, 1, 8, 0, 0).unwrap(); + let end = UTC.with_ymd_and_hms(2021, 10, 1, 10, 0, 0).unwrap(); // In middle assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &Some(start), &Some(end), inclusive, )); // To small assert!(!is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(7, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 7, 0, 0).unwrap(), &Some(start), &Some(end), inclusive, )); // To big assert!(!is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(11, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 11, 0, 0).unwrap(), &Some(start), &Some(end), inclusive, @@ -257,25 +254,25 @@ mod tests { #[test] fn in_range_inclusive_start() { let inclusive = true; - let start = UTC.ymd(2021, 10, 1).and_hms(8, 0, 0); + let start = UTC.with_ymd_and_hms(2021, 10, 1, 8, 0, 0).unwrap(); // Just after assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &Some(start), &None, inclusive, )); // To small assert!(!is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(7, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 7, 0, 0).unwrap(), &Some(start), &None, inclusive, )); // Bigger assert!(is_in_range( - &UTC.ymd(2021, 10, 2).and_hms(8, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 2, 8, 0, 0).unwrap(), &Some(start), &None, inclusive, @@ -287,25 +284,25 @@ mod tests { #[test] fn in_range_inclusive_end() { let inclusive = true; - let end = UTC.ymd(2021, 10, 1).and_hms(10, 0, 0); + let end = UTC.with_ymd_and_hms(2021, 10, 1, 10, 0, 0).unwrap(); // Just before assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &None, &Some(end), inclusive, )); // Smaller assert!(is_in_range( - &UTC.ymd(2021, 9, 20).and_hms(10, 0, 0), + &UTC.with_ymd_and_hms(2021, 9, 20, 10, 0, 0).unwrap(), &None, &Some(end), inclusive, )); // Bigger assert!(!is_in_range( - &UTC.ymd(2021, 10, 2).and_hms(8, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 2, 8, 0, 0).unwrap(), &None, &Some(end), inclusive, @@ -320,21 +317,21 @@ mod tests { // Some date assert!(is_in_range( - &UTC.ymd(2021, 10, 1).and_hms(9, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 1, 9, 0, 0).unwrap(), &None, &None, inclusive, )); // Smaller assert!(is_in_range( - &UTC.ymd(2021, 9, 20).and_hms(10, 0, 0), + &UTC.with_ymd_and_hms(2021, 9, 20, 10, 0, 0).unwrap(), &None, &None, inclusive, )); // Bigger assert!(is_in_range( - &UTC.ymd(2021, 10, 2).and_hms(8, 0, 0), + &UTC.with_ymd_and_hms(2021, 10, 2, 8, 0, 0).unwrap(), &None, &None, inclusive, diff --git a/rrule/src/iter/counter_date.rs b/rrule/src/iter/counter_date.rs index b162dc7..b704ee9 100644 --- a/rrule/src/iter/counter_date.rs +++ b/rrule/src/iter/counter_date.rs @@ -29,9 +29,9 @@ impl DateTimeIter { /// Increments the datetime based on the [`RRule`] values. /// /// If `increment_day` is set to `true`, then the method will also make sure that - /// the day is incremented. This is useful is the case when `rrule.freq` + /// the day is incremented. This is useful is the case where `rrule.freq` /// is higher than daily (e.g. hourly) where this function might return a date with the - /// same day, but the iterator already knows that the current day cannot + /// same day, but the iterator already knows that the current day can't /// be part of the result. pub fn increment(&mut self, rrule: &RRule, increment_day: bool) -> Result<(), RRuleError> { let RRule { @@ -89,7 +89,12 @@ impl DateTimeIter { }; let year_day = u32::from(month_range_mask[self.month as usize - 1]) + self.day - 1; let year_day_mod = year_day % 7; - let year_start_weekday = Utc.ymd(self.year, 1, 1).weekday().num_days_from_monday(); + let year_start_weekday = Utc + .with_ymd_and_hms(self.year, 1, 1, 0, 0, 0) + // It should never fail, since there is always a 1st of January, is there? + .unwrap() + .weekday() + .num_days_from_monday(); (year_day_mod + year_start_weekday) % 7 } @@ -98,7 +103,7 @@ impl DateTimeIter { let weekday = self.get_weekday(); let option_week_start = week_start.num_days_from_monday(); let interval = u32::from(interval); - // Calculate amount of day to move forward. + // Calculate the amount of day to move forward. let day_delta = if option_week_start > weekday { // `weekday` and `option_week_start` can only be in range `0..=6` // `option_week_start` > `weekday` so: @@ -158,7 +163,7 @@ impl DateTimeIter { increment_day: bool, ) -> Result<(), RRuleError> { if increment_day { - // Jump to one iteration before next day + // Jump to one iteration before the next day let temp_value = (23 - self.hour) / u32::from(interval); self.hour += checked_mul_u32( temp_value, @@ -174,7 +179,7 @@ impl DateTimeIter { u32::from(interval), Some("please decrease `INTERVAL`"), )?; - let new_hours = u8::try_from(self.hour % 24).expect("range 0 - 23 is covered by u8"); + let new_hours = u8::try_from(self.hour % 24).expect("range 0-23 is covered by u8"); if by_hour.is_empty() || by_hour.iter().any(|bh| *bh == new_hours) { break; } @@ -205,7 +210,7 @@ impl DateTimeIter { increment_day: bool, ) -> Result<(), RRuleError> { if increment_day { - // Jump to one iteration before next day + // Jump to one iteration before the next day let minutes_total = self.hour * 60 + self.minute; let temp_value = (MINUTES_IN_A_DAY - 1 - minutes_total) / u32::from(interval); self.minute += checked_mul_u32( @@ -226,8 +231,8 @@ impl DateTimeIter { self.increment_hourly(hours_div, by_hour, increment_day)?; } - let hours = u8::try_from(self.hour % 24).expect("range 0 - 23 is covered by u8"); - let minutes = u8::try_from(self.minute % 60).expect("range 0 - 59 is covered by u8"); + let hours = u8::try_from(self.hour % 24).expect("range 0-23 is covered by u8"); + let minutes = u8::try_from(self.minute % 60).expect("range 0-59 is covered by u8"); if (by_hour.is_empty() || by_hour.contains(&hours)) && (by_minute.is_empty() || by_minute.contains(&minutes)) @@ -255,7 +260,7 @@ impl DateTimeIter { increment_day: bool, ) -> Result<(), RRuleError> { if increment_day { - // Jump to one iteration before next day + // Jump to one iteration before the next day let total_seconds = self.hour * 3600 + self.minute * 60 + self.second; let temp_value = (SECONDS_IN_A_DAY - 1 - total_seconds) / u32::from(interval); self.second += checked_mul_u32( @@ -276,9 +281,9 @@ impl DateTimeIter { self.increment_minutely(minutes_div, by_hour, by_minute, increment_day)?; } - let hours = u8::try_from(self.hour % 24).expect("range 0 - 23 is covered by u8"); - let minutes = u8::try_from(self.minute % 60).expect("range 0 - 59 is covered by u8"); - let seconds = u8::try_from(self.second % 60).expect("range 0 - 59 is covered by u8"); + let hours = u8::try_from(self.hour % 24).expect("range 0-23 is covered by u8"); + let minutes = u8::try_from(self.minute % 60).expect("range 0-59 is covered by u8"); + let seconds = u8::try_from(self.second % 60).expect("range 0-59 is covered by u8"); if (by_hour.is_empty() || by_hour.contains(&hours)) && (by_minute.is_empty() || by_minute.contains(&minutes)) @@ -322,7 +327,9 @@ mod tests { const UTC: Tz = Tz::UTC; fn ymd_hms(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTimeIter { - let dt = UTC.ymd(year, month, day).and_hms(hour, min, sec); + let dt = UTC + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap(); DateTimeIter::from(&dt) } @@ -356,7 +363,7 @@ mod tests { freq: Frequency::Yearly, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, false); @@ -400,7 +407,7 @@ mod tests { freq: Frequency::Monthly, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, false); @@ -444,7 +451,7 @@ mod tests { freq: Frequency::Weekly, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, false); @@ -483,7 +490,7 @@ mod tests { freq: Frequency::Daily, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, false); @@ -539,7 +546,7 @@ mod tests { by_hour, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, false); @@ -588,7 +595,7 @@ mod tests { freq: Frequency::Hourly, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, true); @@ -627,7 +634,7 @@ mod tests { freq: Frequency::Minutely, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, false); @@ -661,7 +668,7 @@ mod tests { freq: Frequency::Minutely, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, true); @@ -700,7 +707,7 @@ mod tests { freq: Frequency::Secondly, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, false); @@ -734,7 +741,7 @@ mod tests { freq: Frequency::Secondly, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(1, 1, 1)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 1, 1, 1).unwrap()) .unwrap(); let res = counter_date.increment(&rrule, true); diff --git a/rrule/src/iter/easter.rs b/rrule/src/iter/easter.rs index 8ea6c93..7d73485 100644 --- a/rrule/src/iter/easter.rs +++ b/rrule/src/iter/easter.rs @@ -19,7 +19,7 @@ pub(crate) fn easter(year: i32, offset: i16) -> i32 { let l = (32 + 2 * e + 2 * i - h - k) % 7; let m = (a + 11 * h + 22 * l) / 451; let month = usize::try_from((h + l - 7 * m + 114) / 31) - .expect("the algorithm makes sure this is between 1 - 12 which is covered by usize"); + .expect("the algorithm makes sure this is between 1-12 which is covered by usize"); let day = ((h + l - 7 * m + 114) % 31) + 1 + i32::from(offset); let month_range_mask = if is_leap_year(year) { diff --git a/rrule/src/iter/iterinfo.rs b/rrule/src/iter/iterinfo.rs index 1c6ed78..d2976d7 100644 --- a/rrule/src/iter/iterinfo.rs +++ b/rrule/src/iter/iterinfo.rs @@ -125,8 +125,13 @@ impl<'a> IterInfo<'a> { pub fn weekday_set(&self, year: i32, month: u32, day: u32) -> Vec { let set_len = usize::from(self.year_len() + 7); - let mut date_ordinal = usize::try_from(chrono::Utc.ymd(year, month, day).ordinal0()) - .expect("target arch should have at least 32 bits"); + let mut date_ordinal = usize::try_from( + chrono::Utc + .with_ymd_and_hms(year, month, day, 0, 0, 0) + .unwrap() + .ordinal0(), + ) + .expect("target arch should have at least 32 bits"); let mut set = vec![]; @@ -147,7 +152,10 @@ impl<'a> IterInfo<'a> { } pub fn day_dayset(year: i32, month: u32, day: u32) -> Vec { - let date_ordinal = chrono::Utc.ymd(year, month, day).ordinal0(); + let date_ordinal = chrono::Utc + .with_ymd_and_hms(year, month, day, 0, 0, 0) + .unwrap() + .ordinal0(); vec![usize::try_from(date_ordinal).expect("target arch should have at least 32 bits")] } @@ -164,18 +172,20 @@ impl<'a> IterInfo<'a> { self.rrule .by_second .iter() - .map(|second| { - NaiveTime::from_hms(u32::from(hour), u32::from(minute), u32::from(*second)) + .filter_map(|second| { + NaiveTime::from_hms_opt(u32::from(hour), u32::from(minute), u32::from(*second)) }) .collect() } pub fn sec_timeset(hour: u8, minute: u8, second: u8) -> Vec { - vec![NaiveTime::from_hms( - u32::from(hour), - u32::from(minute), - u32::from(second), - )] + if let Some(time) = + NaiveTime::from_hms_opt(u32::from(hour), u32::from(minute), u32::from(second)) + { + vec![time] + } else { + vec![] + } } pub fn get_dayset(&self, freq: Frequency, year: i32, month: u32, day: u32) -> Vec { @@ -192,11 +202,11 @@ impl<'a> IterInfo<'a> { dayset } - /// Gets a timeset without checking if the hour, minute and second are valid according + /// Gets a timeset without checking if the hour, minute and second are valid, according /// to the `RRule`. /// /// This is usually called after calling the `increment_counter_date` where we know - /// that we get a valid `DateTime` back and there is no need to do any duplicate + /// that we get a valid `DateTime` back, and there is no need to do any duplicate /// validation. pub fn get_timeset_unchecked(&self, hour: u8, minute: u8, second: u8) -> Vec { match self.rrule.freq { @@ -211,7 +221,7 @@ impl<'a> IterInfo<'a> { /// Gets a timeset. /// - /// An empty set is returned if the hour, minute and second are not valid + /// An empty set is returned if the hour, minute and second aren't valid, /// according to the `RRule`. pub fn get_timeset(&self, hour: u8, minute: u8, second: u8) -> Vec { match self.rrule.freq { @@ -236,22 +246,23 @@ impl<'a> IterInfo<'a> { self.get_timeset_unchecked(hour, minute, second) } _ => { - let mut timeset = Vec::with_capacity( - self.rrule.by_hour.len() - * self.rrule.by_minute.len() - * self.rrule.by_second.len(), - ); - for hour in &self.rrule.by_hour { - for minute in &self.rrule.by_minute { - for second in &self.rrule.by_second { - timeset.push(NaiveTime::from_hms( - u32::from(*hour), - u32::from(*minute), - u32::from(*second), - )); - } - } - } + let timeset = self + .rrule + .by_hour + .iter() + .flat_map(|hour| { + self.rrule.by_minute.iter().flat_map(move |minute| { + self.rrule.by_second.iter().filter_map(move |second| { + NaiveTime::from_hms_opt( + u32::from(*hour), + u32::from(*minute), + u32::from(*second), + ) + }) + }) + }) + .collect(); + timeset } } diff --git a/rrule/src/iter/mod.rs b/rrule/src/iter/mod.rs index d6fb824..0376fe3 100644 --- a/rrule/src/iter/mod.rs +++ b/rrule/src/iter/mod.rs @@ -20,6 +20,6 @@ pub(crate) use rrule_iter::RRuleIter; pub use rruleset_iter::RRuleSetIter; /// Prevent loops when searching for the next event in the iterator. -/// If after X amount of iterations it still has not found an event +/// If after X number of iterations it still has not found an event, /// we can assume it will not find an event. static MAX_ITER_LOOP: u32 = 100_000; diff --git a/rrule/src/iter/monthinfo.rs b/rrule/src/iter/monthinfo.rs index 0bf7c91..9266580 100644 --- a/rrule/src/iter/monthinfo.rs +++ b/rrule/src/iter/monthinfo.rs @@ -75,7 +75,7 @@ impl MonthInfo { _ => continue, }; let nth_last_weekday = match weekday_mask.get(index) { - Some(val) => i16::try_from(*val).expect("values in weekday mask are all between 0 - 6 which is covered by i32"), + Some(val) => i16::try_from(*val).expect("values in weekday mask are all between 0-6 which is covered by i32"), None => continue, }; @@ -97,7 +97,7 @@ impl MonthInfo { _ => continue, }; let nth_first_day_weekday = match weekday_mask.get(index) { - Some(val) => i16::try_from(*val).expect("values in weekday mask are all between 0 - 6 which is covered by i32"), + Some(val) => i16::try_from(*val).expect("values in weekday mask are all between 0-6 which is covered by i32"), None => continue, }; @@ -135,7 +135,7 @@ mod tests { freq: Frequency::Daily, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(0, 0, 0)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 0, 0, 0).unwrap()) .unwrap(); let year_info = YearInfo::new(1997, &rrule); @@ -150,7 +150,7 @@ mod tests { freq: Frequency::Yearly, ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(0, 0, 0)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 0, 0, 0).unwrap()) .unwrap(); let year_info = YearInfo::new(1997, &rrule); @@ -171,7 +171,7 @@ mod tests { ], ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(0, 0, 0)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 0, 0, 0).unwrap()) .unwrap(); let year_info = YearInfo::new(1997, &rrule); @@ -198,7 +198,7 @@ mod tests { ], ..Default::default() } - .validate(UTC.ymd(1997, 1, 1).and_hms(0, 0, 0)) + .validate(UTC.with_ymd_and_hms(1997, 1, 1, 0, 0, 0).unwrap()) .unwrap(); let year_info = YearInfo::new(1997, &rrule); diff --git a/rrule/src/iter/pos_list.rs b/rrule/src/iter/pos_list.rs index b3f2dd3..f06644f 100644 --- a/rrule/src/iter/pos_list.rs +++ b/rrule/src/iter/pos_list.rs @@ -25,7 +25,7 @@ pub(crate) fn build_pos_list( let pos = if *pos > 0 { pos - 1 } else { *pos }; let day_pos = (f64::from(pos) / timeset_len_float).floor() as isize; let time_pos = usize::try_from(pymod(pos, timeset_len_int)) - .expect("modulus is a positive number and within range of usize"); + .expect("modulus is a positive number and within the range of usize"); let day_idx = if day_pos < 0 { let dayset_len = isize::try_from(dayset.len()) diff --git a/rrule/src/iter/rrule_iter.rs b/rrule/src/iter/rrule_iter.rs index 742902f..3c36c82 100644 --- a/rrule/src/iter/rrule_iter.rs +++ b/rrule/src/iter/rrule_iter.rs @@ -14,7 +14,7 @@ pub(crate) struct RRuleIter<'a> { pub(crate) ii: IterInfo<'a>, pub(crate) timeset: Vec, pub(crate) dt_start: DateTime, - /// Buffer of datetimes not yet yielded + /// Buffer of datetimes is not yet yielded pub(crate) buffer: VecDeque, /// Indicate of iterator should not return more items. /// Once set `true` is will always return `None`. @@ -128,13 +128,13 @@ impl<'a> RRuleIter<'a> { // Loop over `start..end` for current_day in &dayset { let current_day = i64::try_from(*current_day).expect( - "We control the dayset and we know that it will always fit within an i64", + "We control the dayset, and we know that it will always fit within an i64", ); let year_ordinal = self.ii.year_ordinal(); // Ordinal conversion uses UTC: if we apply local-TZ here, then // just below we'll end up double-applying. let date = from_ordinal(year_ordinal + current_day); - // We apply the local-TZ here, + // We apply the local-TZ here. let date = self .dt_start .timezone() diff --git a/rrule/src/iter/rruleset_iter.rs b/rrule/src/iter/rruleset_iter.rs index b16e466..b777a73 100644 --- a/rrule/src/iter/rruleset_iter.rs +++ b/rrule/src/iter/rruleset_iter.rs @@ -117,24 +117,23 @@ impl<'a> Iterator for RRuleSetIter<'a> { for (i, rrule_iter) in self.rrule_iters.iter_mut().enumerate() { let rrule_queue = self.queue.remove(&i); - let next_rrule_date = match rrule_queue { - Some(d) => Some(d), - None => { - // should be method on self - let (date, was_limited) = Self::generate( - rrule_iter, - &mut self.exrules, - &mut self.exdates, - self.limited, - ); - - if was_limited { - self.was_limited = true; - return None; - } - - date + let next_rrule_date = if let Some(d) = rrule_queue { + Some(d) + } else { + // should be method on self + let (date, was_limited) = Self::generate( + rrule_iter, + &mut self.exrules, + &mut self.exdates, + self.limited, + ); + + if was_limited { + self.was_limited = true; + return None; } + + date }; if let Some(next_rrule_date) = next_rrule_date { diff --git a/rrule/src/iter/utils.rs b/rrule/src/iter/utils.rs index 94a7610..7f300a8 100644 --- a/rrule/src/iter/utils.rs +++ b/rrule/src/iter/utils.rs @@ -10,7 +10,7 @@ const DAY_SECS: i64 = 24 * 60 * 60; /// Converts number of days since unix epoch back to `DataTime` pub(crate) fn from_ordinal(ordinal: i64) -> DateTime { let timestamp = ordinal * DAY_SECS; - UTC.timestamp(timestamp, 0) + UTC.timestamp_opt(timestamp, 0).unwrap() } /// Returns number of days since unix epoch (rounded down) @@ -20,12 +20,12 @@ pub(crate) fn days_since_unix_epoch(date: &chrono::DateTime) -> i64 { /// Returns true if given year is a leap year pub(crate) fn is_leap_year(year: i32) -> bool { - // Every 4 years, and every 100 years + // Every 4 years, and every 100 years, // but not if dividable by 400. year.trailing_zeros() >= 2 && (year % 25 != 0 || year.trailing_zeros() >= 4) } -/// Returns amount of days in year, +/// Returns number of days in year, /// So 365 or 366 depending on the year pub(crate) fn get_year_len(year: i32) -> u16 { if is_leap_year(year) { @@ -77,7 +77,7 @@ pub(crate) fn add_time_to_date(date: Date, time: NaiveTime) -> Option BaseMasks { pub(crate) struct YearInfo { /// The year pub year: i32, - /// Amount of days in the current year (365 or 366) + /// Number of days in the current year (365 or 366) pub year_len: u16, - /// Amount of days in the next year (365 or 366) + /// Number of days in the next year (365 or 366) pub next_year_len: u16, /// Number of days since Unix epoch pub year_ordinal: i64, @@ -55,7 +55,8 @@ pub(crate) struct YearInfo { impl YearInfo { pub fn new(year: i32, rrule: &RRule) -> Self { - let first_year_day = Utc.ymd(year, 1, 1).and_hms(0, 0, 0); + // It should never fail, since there is always a 1st of January, is there? + let first_year_day = Utc.with_ymd_and_hms(year, 1, 1, 0, 0, 0).unwrap(); let year_len = get_year_len(year); let next_year_len = get_year_len(year + 1); @@ -65,7 +66,7 @@ impl YearInfo { let base_masks = base_year_masks(year_start_weekday, year_len); - let mut result = YearInfo { + let mut result = Self { year, year_len, next_year_len, @@ -116,8 +117,8 @@ impl YearInfo { } let i = if n > 1 { - let n = u16::try_from(n) - .expect("We know that 1 < n < i8::MAX which is in covered by u16"); + let n = + u16::try_from(n).expect("We know that 1 < n < i8::MAX which is covered by u16"); let mut i = no1_week_start + ((n - 1) * 7); if no1_week_start != first_week_start { i -= 7 - first_week_start; @@ -137,7 +138,7 @@ impl YearInfo { } if rrule.by_week_no.contains(&1) { - // Check week number 1 of next year as well + // Check week number 1 of next years as well let mut i = no1_week_start + num_weeks * 7; if no1_week_start != first_week_start { i -= 7 - first_week_start; @@ -165,9 +166,14 @@ impl YearInfo { let l_num_weeks = if rrule.by_week_no.contains(&-1) { -1 } else { - let l_year_weekday = - u16::try_from(Utc.ymd(year - 1, 1, 1).weekday().num_days_from_monday()) - .expect("num_days_from_monday is between 0 and 6 which is covered by u16"); + let l_year_weekday = u16::try_from( + Utc.with_ymd_and_hms(year - 1, 1, 1, 0, 0, 0) + // It should never fail, since there is always a 1st of January, is there? + .unwrap() + .weekday() + .num_days_from_monday(), + ) + .expect("num_days_from_monday is between 0 and 6 which is covered by u16"); let rrule_week_start = u16::try_from(rrule.week_start.num_days_from_monday()) .expect("num_days_from_monday is between 0 and 6 which is covered by u16"); @@ -180,13 +186,13 @@ impl YearInfo { i32::from(l_year_weekday) - i32::from(rrule_week_start), 7, )) - .expect("7 is the modulo so range is 0-6 and u16 covers that range") + .expect("7 is the modulo, so the range is 0-6, and u16 covers that range") } else { year_len - no1_week_start }; 52 + i8::try_from(pymod(week_start, 7) / 4) - .expect("7 is the modulo so range is 0-6 and i8 covers that range") + .expect("7 is the modulo, so the range is 0-6, and i8 covers that range") }; if rrule.by_week_no.contains(&l_num_weeks) { diff --git a/rrule/src/lib.rs b/rrule/src/lib.rs index bdfa2ee..1fbdc91 100644 --- a/rrule/src/lib.rs +++ b/rrule/src/lib.rs @@ -25,7 +25,7 @@ //! RDATE:20120701T023000Z,20120702T023000Z\n\ //! EXDATE:20120601T023000Z".parse().unwrap(); //! -//! assert_eq!(*rrule_set.get_dt_start(), Tz::UTC.ymd(2012, 2, 1).and_hms(2, 30, 0)); +//! assert_eq!(*rrule_set.get_dt_start(), Tz::UTC.with_ymd_and_hms(2012, 2, 1,2, 30, 0).unwrap()); //! assert_eq!(rrule_set.get_rrule().len(), 1); //! assert_eq!(rrule_set.get_rdate().len(), 2); //! assert_eq!(rrule_set.get_exdate().len(), 1); @@ -44,7 +44,7 @@ //! //! If you have some additional filters or want to work with infinite recurrence rules //! [`RRuleSet`] implements the `IntoIterator` trait which allows for very flexible queries. -//! All the methods above uses the iterator trait in its implementation as shown below. +//! All the methods above use the iterator trait in its implementation as shown below. //! ```rust //! use chrono::{DateTime, TimeZone}; //! use rrule::{RRuleSet, Tz}; @@ -70,8 +70,8 @@ //! let rrule: RRuleSet = "DTSTART:20120201T093000Z\nRRULE:FREQ=DAILY;COUNT=3".parse().unwrap(); //! //! // Between two dates -//! let after = Tz::UTC.ymd(2012, 2, 1).and_hms(10, 0, 0); -//! let before = Tz::UTC.ymd(2012, 4, 1).and_hms(9, 0, 0); +//! let after = Tz::UTC.with_ymd_and_hms(2012, 2, 1,10, 0, 0).unwrap(); +//! let before = Tz::UTC.with_ymd_and_hms(2012, 4, 1,9, 0, 0).unwrap(); //! //! let rrule = rrule.after(after).before(before); //! let result = rrule.all(100); diff --git a/rrule/src/parser/content_line/date_content_line.rs b/rrule/src/parser/content_line/date_content_line.rs index 22c791a..e246d5b 100644 --- a/rrule/src/parser/content_line/date_content_line.rs +++ b/rrule/src/parser/content_line/date_content_line.rs @@ -77,7 +77,7 @@ mod tests { parameters: None, value: "19970714T123000Z", }, - vec![UTC.ymd(1997, 7, 14).and_hms(12, 30, 0)], + vec![UTC.with_ymd_and_hms(1997, 7, 14, 12, 30, 0).unwrap()], ), ( ContentLineCaptures { @@ -85,7 +85,7 @@ mod tests { parameters: None, value: "19970714T123000", }, - vec![Tz::LOCAL.ymd(1997, 7, 14).and_hms(12, 30, 0)], + vec![Tz::LOCAL.with_ymd_and_hms(1997, 7, 14, 12, 30, 0).unwrap()], ), ( ContentLineCaptures { @@ -94,10 +94,10 @@ mod tests { value: "19970101,19970120,19970217,19970421", }, vec![ - UTC.ymd(1997, 1, 1).and_hms(0, 0, 0), - UTC.ymd(1997, 1, 20).and_hms(0, 0, 0), - UTC.ymd(1997, 2, 17).and_hms(0, 0, 0), - UTC.ymd(1997, 4, 21).and_hms(0, 0, 0), + UTC.with_ymd_and_hms(1997, 1, 1, 0, 0, 0).unwrap(), + UTC.with_ymd_and_hms(1997, 1, 20, 0, 0, 0).unwrap(), + UTC.with_ymd_and_hms(1997, 2, 17, 0, 0, 0).unwrap(), + UTC.with_ymd_and_hms(1997, 4, 21, 0, 0, 0).unwrap(), ], ), ]; diff --git a/rrule/src/parser/content_line/start_date_content_line.rs b/rrule/src/parser/content_line/start_date_content_line.rs index bf0cf9e..6c96793 100644 --- a/rrule/src/parser/content_line/start_date_content_line.rs +++ b/rrule/src/parser/content_line/start_date_content_line.rs @@ -58,7 +58,7 @@ impl<'a> TryFrom<&ContentLineCaptures<'a>> for StartDateContentLine { let datetime = datestring_to_date(content_line.value, timezone, "DTSTART")?; - Ok(StartDateContentLine { + Ok(Self { datetime, timezone, value, @@ -84,7 +84,7 @@ mod tests { value: "19970714T123000Z", }, StartDateContentLine { - datetime: UTC.ymd(1997, 7, 14).and_hms(12, 30, 0), + datetime: UTC.with_ymd_and_hms(1997, 7, 14, 12, 30, 0).unwrap(), timezone: Some(UTC), value: "DATE-TIME", }, @@ -96,7 +96,7 @@ mod tests { value: "19970101", }, StartDateContentLine { - datetime: UTC.ymd(1997, 1, 1).and_hms(0, 0, 0), + datetime: UTC.with_ymd_and_hms(1997, 1, 1, 0, 0, 0).unwrap(), timezone: Some(UTC), value: "DATE", }, @@ -108,7 +108,7 @@ mod tests { value: "19970101", }, StartDateContentLine { - datetime: UTC.ymd(1997, 1, 1).and_hms(0, 0, 0), + datetime: UTC.with_ymd_and_hms(1997, 1, 1, 0, 0, 0).unwrap(), timezone: Some(UTC), value: "DATE", }, diff --git a/rrule/src/parser/datetime.rs b/rrule/src/parser/datetime.rs index 6c2379f..b8580f6 100644 --- a/rrule/src/parser/datetime.rs +++ b/rrule/src/parser/datetime.rs @@ -65,45 +65,42 @@ pub(crate) fn datestring_to_date( use chrono::offset::LocalResult; // Get datetime in local time or machine local time. // So this also takes into account daylight or standard time (summer/winter). - match tz { - Some(tz) => { - // Use the timezone specified in the `tz` - match tz.from_local_datetime(&datetime) { - LocalResult::None => Err(ParseError::InvalidDateTimeInLocalTimezone { + if let Some(tz) = tz { + // Use the timezone specified in the `tz` + match tz.from_local_datetime(&datetime) { + LocalResult::None => Err(ParseError::InvalidDateTimeInLocalTimezone { + value: dt.into(), + property: property.into(), + }), + LocalResult::Single(date) => Ok(date), + LocalResult::Ambiguous(date1, date2) => { + Err(ParseError::DateTimeInLocalTimezoneIsAmbiguous { value: dt.into(), property: property.into(), - }), - LocalResult::Single(date) => Ok(date), - LocalResult::Ambiguous(date1, date2) => { - Err(ParseError::DateTimeInLocalTimezoneIsAmbiguous { - value: dt.into(), - property: property.into(), - date1: date1.to_rfc3339(), - date2: date2.to_rfc3339(), - }) - } - }? - } - None => { - // Use current system timezone - // TODO Add option to always use UTC when this is executed on a server. - let local = Tz::LOCAL; - match local.from_local_datetime(&datetime) { - LocalResult::None => { - return Err(ParseError::InvalidDateTimeInLocalTimezone { - value: dt.into(), - property: property.into(), - }) - } - LocalResult::Single(date) => date, - LocalResult::Ambiguous(date1, date2) => { - return Err(ParseError::DateTimeInLocalTimezoneIsAmbiguous { - value: dt.into(), - property: property.into(), - date1: date1.to_rfc3339(), - date2: date2.to_rfc3339(), - }) - } + date1: date1.to_rfc3339(), + date2: date2.to_rfc3339(), + }) + } + }? + } else { + // Use current system timezone + // TODO Add option to always use UTC when this is executed on a server. + let local = Tz::LOCAL; + match local.from_local_datetime(&datetime) { + LocalResult::None => { + return Err(ParseError::InvalidDateTimeInLocalTimezone { + value: dt.into(), + property: property.into(), + }) + } + LocalResult::Single(date) => date, + LocalResult::Ambiguous(date1, date2) => { + return Err(ParseError::DateTimeInLocalTimezoneIsAmbiguous { + value: dt.into(), + property: property.into(), + date1: date1.to_rfc3339(), + date2: date2.to_rfc3339(), + }) } } } @@ -217,23 +214,23 @@ mod tests { ( "19970902T090000Z", None, - Tz::UTC.ymd(1997, 9, 2).and_hms(9, 0, 0), + Tz::UTC.with_ymd_and_hms(1997, 9, 2, 9, 0, 0).unwrap(), ), ( "19970902T090000", Some(Tz::UTC), - Tz::UTC.ymd(1997, 9, 2).and_hms(9, 0, 0), + Tz::UTC.with_ymd_and_hms(1997, 9, 2, 9, 0, 0).unwrap(), ), ( "19970902T090000", Some(US_PACIFIC), - US_PACIFIC.ymd(1997, 9, 2).and_hms(9, 0, 0), + US_PACIFIC.with_ymd_and_hms(1997, 9, 2, 9, 0, 0).unwrap(), ), ( "19970902T090000Z", Some(US_PACIFIC), // Timezone is overwritten by the zulu specified in the datetime string - Tz::UTC.ymd(1997, 9, 2).and_hms(9, 0, 0), + Tz::UTC.with_ymd_and_hms(1997, 9, 2, 9, 0, 0).unwrap(), ), ]; diff --git a/rrule/src/parser/error.rs b/rrule/src/parser/error.rs index 68e1f90..5a1a5c5 100644 --- a/rrule/src/parser/error.rs +++ b/rrule/src/parser/error.rs @@ -30,23 +30,23 @@ pub enum ParseError { InvalidInterval(String), #[error("`{0}` is not a valid COUNT value.")] InvalidCount(String), - #[error("`{0}` is not a valid BYHOUR value. Expected a comma separated list of values in range 0..=23, e.g. `1,3,4`")] + #[error("`{0}` is not a valid BYHOUR value. Expected a comma-separated list of values in range 0..=23, e.g. `1,3,4`")] InvalidByHour(String), - #[error("`{0}` is not a valid BYWEEKNO value. Expected a comma separated list of values in range -53..=53, e.g. `-1,30,53`")] + #[error("`{0}` is not a valid BYWEEKNO value. Expected a comma-separated list of values in range -53..=53, e.g. `-1,30,53`")] InvalidByWeekNo(String), - #[error("`{0}` is not a valid BYYEARDAY value. Expected a comma separated list of values in range -366..=366, e.g. `-100,`")] + #[error("`{0}` is not a valid BYYEARDAY value. Expected a comma-separated list of values in range -366..=366, e.g. `-100,`")] InvalidByYearDay(String), - #[error("`{0}` is not a valid BYMONTHDAY value. Expected a comma separated list of values in range -31..=31, e.g. `-30,10`")] + #[error("`{0}` is not a valid BYMONTHDAY value. Expected a comma-separated list of values in range -31..=31, e.g. `-30,10`")] InvalidByMonthDay(String), - #[error("`{0}` is not a valid BYMONTH value. Expected a comma separated list of values in range 1..=12, e.g. `6,9,10`")] + #[error("`{0}` is not a valid BYMONTH value. Expected a comma-separated list of values in range 1..=12, e.g. `6,9,10`")] InvalidByMonth(String), - #[error("`{0}` is not a valid BYMINUTE value. Expected a comma separated list of values in range 0..=59, e.g. `0,15,30,45`")] + #[error("`{0}` is not a valid BYMINUTE value. Expected a comma-separated list of values in range 0..=59, e.g. `0,15,30,45`")] InvalidByMinute(String), - #[error("`{0}` is not a valid BYSECOND value. Expected a comma separated list of values in range 0..=59, e.g. `0,15,30,45`")] + #[error("`{0}` is not a valid BYSECOND value. Expected a comma-separated list of values in range 0..=59, e.g. `0,15,30,45`")] InvalidBySecond(String), - #[error("`{0}` is not a valid BYSETPOS value. Expected a comma separated list of integers, e.g. `-3,1`")] + #[error("`{0}` is not a valid BYSETPOS value. Expected a comma-separated list of integers, e.g. `-3,1`")] InvalidBySetPos(String), - #[error("The property `{0}` was not found and it is required.")] + #[error("The property `{0}` wasn't found, and it is required.")] MissingProperty(String), #[error( "`{0}` is a malformed property parameter. Parameter should be specified as `key=value`" @@ -54,13 +54,13 @@ pub enum ParseError { InvalidParameterFormat(String), #[error("`{0}` is not a valid property parameter.")] UnrecognizedParameter(String), - #[error("Found duplicate property for `{0}`, properties and parameters needs to be unique.")] + #[error("Found duplicate property for `{0}`, properties and parameters need to be unique.")] DuplicateProperty(String), #[error("Missing start date. There needs to be a unique start date which the iteration can start from.")] MissingStartDate, #[error("Missing date generation property. There needs to be at least one `RRULE` or `RDATE` to generate occurrences.")] MissingDateGenerationRules, - #[error("Property parameters are not supported for RRULE / EXRULE, found parameters: `{0}`")] + #[error("Property parameters aren't supported for RRULE / EXRULE, found parameters: `{0}`")] PropertyParametersNotSupported(String), #[error( "`{0}` is not a valid property name, expected one of: `RRULE,EXRULE,DTSTART,RDATE,EXDATE`" @@ -71,7 +71,7 @@ pub enum ParseError { )] DtStartUntilMismatchValue, #[error( - "The value of `DTSTART` was specified in local timezone, but `UNTIL` was specified with a zulu time when it has to be specified in local time as well" + "The value of `DTSTART` was specified in local timezone, but `UNTIL` was specified with a zulu time when it had to be specified in local time as well" )] DtStartUntilMismatchTimezone, #[error("Property parameter `{parameter}` was set to have value `{parameter_value}`, but found `{found_value}` ")] diff --git a/rrule/src/parser/mod.rs b/rrule/src/parser/mod.rs index 4bfe3e8..d990f92 100644 --- a/rrule/src/parser/mod.rs +++ b/rrule/src/parser/mod.rs @@ -16,7 +16,7 @@ use crate::RRule; use self::content_line::{PropertyName, StartDateContentLine}; -/// Grammar represents a well formatted rrule input. +/// Grammar represents a well-formatted rrule input. #[derive(Debug, PartialEq)] pub(crate) struct Grammar { pub start: StartDateContentLine, @@ -29,7 +29,6 @@ impl FromStr for Grammar { fn from_str(s: &str) -> Result { let content_lines_parts = s .lines() - .into_iter() .map(ContentLineCaptures::new) .collect::, _>>()?; @@ -91,7 +90,7 @@ mod test { let tests = [ ( "DTSTART:19970902T090000Z\nRRULE:FREQ=YEARLY;COUNT=3\n", Grammar { - start: StartDateContentLine { datetime: UTC.ymd(1997, 9, 2).and_hms(9, 0, 0), timezone: Some(UTC), value: "DATE-TIME" }, + start: StartDateContentLine { datetime: UTC.with_ymd_and_hms(1997, 9, 2,9, 0, 0).unwrap(), timezone: Some(UTC), value: "DATE-TIME" }, content_lines: vec![ ContentLine::RRule(RRule { freq: Frequency::Yearly, @@ -102,19 +101,19 @@ mod test { } ), ("DTSTART:20120201T093000Z\nRRULE:FREQ=WEEKLY;INTERVAL=5;UNTIL=20130130T230000Z;BYDAY=MO,FR", Grammar { - start: StartDateContentLine { datetime: UTC.ymd(2012, 2, 1).and_hms(9, 30, 0), timezone: Some(UTC), value: "DATE-TIME" }, + start: StartDateContentLine { datetime: UTC.with_ymd_and_hms(2012, 2, 1,9, 30, 0).unwrap(), timezone: Some(UTC), value: "DATE-TIME" }, content_lines: vec![ ContentLine::RRule(RRule { freq: Frequency::Weekly, interval: 5, - until: Some(UTC.ymd(2013, 1, 30).and_hms(23, 0, 0)), + until: Some(UTC.with_ymd_and_hms(2013, 1, 30,23, 0, 0).unwrap()), by_weekday: vec![NWeekday::Every(Weekday::Mon), NWeekday::Every(Weekday::Fri)], ..Default::default() }) ] }), ("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5\nEXDATE;TZID=Europe/Berlin:20120202T130000,20120203T130000", Grammar { - start: StartDateContentLine { datetime: UTC.ymd(2012, 2, 1).and_hms(12, 0, 0), timezone: Some(UTC), value: "DATE-TIME" }, + start: StartDateContentLine { datetime: UTC.with_ymd_and_hms(2012, 2, 1,12, 0, 0).unwrap(), timezone: Some(UTC), value: "DATE-TIME" }, content_lines: vec![ ContentLine::RRule(RRule { freq: Frequency::Daily, @@ -122,13 +121,13 @@ mod test { ..Default::default() }), ContentLine::ExDate(vec![ - BERLIN.ymd(2012, 2, 2).and_hms(13, 0, 0), - BERLIN.ymd(2012, 2, 3).and_hms(13, 0, 0), + BERLIN.with_ymd_and_hms(2012, 2, 2,13, 0, 0).unwrap(), + BERLIN.with_ymd_and_hms(2012, 2, 3,13, 0, 0).unwrap(), ]) ] }), ("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5\nEXDATE;TZID=Europe/Berlin:20120202T130000,20120203T130000\nEXRULE:FREQ=WEEKLY;COUNT=10", Grammar { - start: StartDateContentLine { datetime: UTC.ymd(2012, 2, 1).and_hms(12, 0, 0), timezone: Some(UTC), value: "DATE-TIME" }, + start: StartDateContentLine { datetime: UTC.with_ymd_and_hms(2012, 2, 1,12, 0, 0).unwrap(), timezone: Some(UTC), value: "DATE-TIME" }, content_lines: vec![ ContentLine::RRule(RRule { freq: Frequency::Daily, @@ -136,8 +135,8 @@ mod test { ..Default::default() }), ContentLine::ExDate(vec![ - BERLIN.ymd(2012, 2, 2).and_hms(13, 0, 0), - BERLIN.ymd(2012, 2, 3).and_hms(13, 0, 0), + BERLIN.with_ymd_and_hms(2012, 2, 2,13, 0, 0).unwrap(), + BERLIN.with_ymd_and_hms(2012, 2, 3,13, 0, 0).unwrap(), ]), ContentLine::ExRule(RRule { freq: Frequency::Weekly, diff --git a/rrule/src/parser/regex.rs b/rrule/src/parser/regex.rs index e10488c..cc252c6 100644 --- a/rrule/src/parser/regex.rs +++ b/rrule/src/parser/regex.rs @@ -9,7 +9,7 @@ use super::{content_line::PropertyName, ParseError}; lazy_static! { static ref DATESTR_RE: Regex = Regex::new(r"(?m)^([0-9]{4})([0-9]{2})([0-9]{2})(T([0-9]{2})([0-9]{2})([0-9]{2})(Z?))?$") - .unwrap(); + .expect("DATESTR_RE regex failed"); } #[derive(Debug, PartialEq)] @@ -85,7 +85,8 @@ impl ParsedDateString { } lazy_static! { - static ref PARSE_PROPERTY_NAME_RE: Regex = Regex::new(r"(?m)^([A-Z]+?)[:;]").unwrap(); + static ref PARSE_PROPERTY_NAME_RE: Regex = + Regex::new(r"(?m)^([A-Z]+?)[:;]").expect("PARSE_PROPERTY_NAME_RE regex failed"); } /// Get the line property name, the `RRULE:`, `EXRULE:` etc part. diff --git a/rrule/src/tests/common.rs b/rrule/src/tests/common.rs index dc9e09b..2ab303f 100644 --- a/rrule/src/tests/common.rs +++ b/rrule/src/tests/common.rs @@ -13,7 +13,9 @@ pub fn ymd_hms( minute: u32, second: u32, ) -> DateTime { - Tz::UTC.ymd(year, month, day).and_hms(hour, minute, second) + Tz::UTC + .with_ymd_and_hms(year, month, day, hour, minute, second) + .unwrap() } pub fn test_recurring_rrule( diff --git a/rrule/src/tests/rfc_tests.rs b/rrule/src/tests/rfc_tests.rs index f5d7a67..798c799 100644 --- a/rrule/src/tests/rfc_tests.rs +++ b/rrule/src/tests/rfc_tests.rs @@ -170,7 +170,7 @@ fn every_10_days_5_occurrences() { /// Every day in January, for 3 years #[test] fn every_days_in_jan_for_3_years() { - // To patterns that have same result + // To patterns that have the same result let dates = "DTSTART;TZID=America/New_York:19980101T090000\n\ RRULE:FREQ=YEARLY;UNTIL=20000131T140000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA" .parse::() @@ -831,7 +831,7 @@ fn every_friday_the_13th_forever() { ); } -/// The first Saturday that follows the first Sunday of the month, forever +/// The first Saturday that follows the first Sunday of the month forever #[test] fn first_sat_follows_first_sunday_of_month_forever() { let dates = "DTSTART;TZID=America/New_York:19970913T090000\n\ @@ -1031,7 +1031,7 @@ fn every_20_min_at_time_every_day() { common::check_occurrences(&dates_alt, &expected); } -/// An example where the days generated makes a difference because of `WKST` (week start) +/// An example where the days generated make a difference because of `WKST` (week start) #[test] fn week_day_start_monday_generated_days() { let dates = "DTSTART;TZID=America/New_York:19970805T090000\n\ diff --git a/rrule/src/tests/rrule.rs b/rrule/src/tests/rrule.rs index eb319cc..3581f8e 100644 --- a/rrule/src/tests/rrule.rs +++ b/rrule/src/tests/rrule.rs @@ -3777,7 +3777,7 @@ fn test_timezones_weekly() { .freq(Frequency::Weekly) .by_weekday(vec![NWeekday::Every(Sat)]); let rrule_set = rrule - .build(NEW_YORK.ymd(2021, 1, 1).and_hms(9, 0, 0)) + .build(NEW_YORK.with_ymd_and_hms(2021, 1, 1, 9, 0, 0).unwrap()) .unwrap(); for o in &rrule_set { assert_eq!(o.weekday(), Sat); @@ -3789,7 +3789,7 @@ fn test_timezones_weekly() { .freq(Frequency::Weekly) .by_weekday(vec![NWeekday::Every(Sat)]); let rrule_set = rrule - .build(BERLIN.ymd(2021, 1, 1).and_hms(9, 0, 0)) + .build(BERLIN.with_ymd_and_hms(2021, 1, 1, 9, 0, 0).unwrap()) .unwrap(); for o in &rrule_set { assert_eq!(o.weekday(), Sat); @@ -3801,7 +3801,7 @@ fn test_timezones_weekly() { .freq(Frequency::Weekly) .by_weekday(vec![NWeekday::Every(Sat)]); let rrule_set = rrule - .build(LOS_ANGELES.ymd(2021, 1, 1).and_hms(9, 0, 0)) + .build(LOS_ANGELES.with_ymd_and_hms(2021, 1, 1, 9, 0, 0).unwrap()) .unwrap(); for o in &rrule_set { assert_eq!(o.weekday(), Sat); diff --git a/rrule/src/tests/rruleset.rs b/rrule/src/tests/rruleset.rs index 51622c2..eab80d2 100644 --- a/rrule/src/tests/rruleset.rs +++ b/rrule/src/tests/rruleset.rs @@ -143,7 +143,7 @@ fn rrule_and_exdate_2() { .unwrap() .all(u16::MAX) .dates; - // This results in following set (minus exdate) + // This results in the following set (minus exdate) // [ // 2020-12-14T09:30:00CET, // 2020-12-28T09:30:00CET, // Removed because of exdate diff --git a/rrule/src/validator/error.rs b/rrule/src/validator/error.rs index f5993d0..bac5f76 100644 --- a/rrule/src/validator/error.rs +++ b/rrule/src/validator/error.rs @@ -8,9 +8,11 @@ use crate::Frequency; pub enum ValidationError { #[error("BYSETPOS should only be used in conjunction with another BYxxx rule part.")] BySetPosWithoutByRule, - #[error("`{field}` can not be `{value}`, must be larger or smaller then `{value}`.")] + #[error("`{field}` can't be `{value}`, must be larger or smaller then `{value}`.")] InvalidFieldValue { field: String, value: String }, - #[error("`{field}` is `{value}`, but is not allowed outside of the range: `{start_idx}..={end_idx}`.")] + #[error( + "`{field}` is `{value}`, but is not allowed outside the range: `{start_idx}..={end_idx}`." + )] InvalidFieldValueRange { field: String, value: String, diff --git a/rrule/src/validator/validate_rrule.rs b/rrule/src/validator/validate_rrule.rs index 617cb3b..2a5e93d 100644 --- a/rrule/src/validator/validate_rrule.rs +++ b/rrule/src/validator/validate_rrule.rs @@ -206,7 +206,7 @@ fn validate_by_week_number( // By_weekday: // - Check if value for `Nth` is within range. -// Range depends on frequency and can only happen weekly, so `/7` from normal count. +// The Range depends on frequency and can only happen weekly, so `/7` from normal count. fn validate_by_weekday( rrule: &RRule, _dt_start: &DateTime, @@ -372,16 +372,16 @@ mod tests { by_set_pos: vec![-1], ..Default::default() }; - let dt_start = UTC.ymd(1970, 1, 1).and_hms(0, 0, 0); + let dt_start = UTC.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); let res = validate_rrule_forced(&rrule, &dt_start); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err, ValidationError::BySetPosWithoutByRule); // When creating RRule directly, - // if `rrule.by_set_hour` is empty then it is going to default to - // `dt_start.hour()`, therefore there is always a BYXXX - // rule and the rrule is accepted. + // if `rrule.by_set_hour` is empty, then it is going to default to + // `dt_start.hour()`, therefore, there is always a BYXXX + // rule, and the rrule is accepted. let res = rrule.build(dt_start); assert!(res.is_ok()); } @@ -419,7 +419,8 @@ mod tests { ), ]; for (field, rrule) in tests { - let res = validate_rrule_forced(&rrule, &UTC.ymd(1970, 1, 1).and_hms(0, 0, 0)); + let res = + validate_rrule_forced(&rrule, &UTC.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!( @@ -457,7 +458,8 @@ mod tests { ), ]; for (field, rrule, value, start_idx, end_idx) in tests { - let res = validate_rrule_forced(&rrule, &UTC.ymd(1970, 1, 1).and_hms(0, 0, 0)); + let res = + validate_rrule_forced(&rrule, &UTC.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!( @@ -499,7 +501,8 @@ mod tests { ), ]; for (field, rrule, value, start_idx, end_idx) in tests { - let res = validate_rrule_forced(&rrule, &UTC.ymd(1970, 1, 1).and_hms(0, 0, 0)); + let res = + validate_rrule_forced(&rrule, &UTC.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!( @@ -552,7 +555,8 @@ mod tests { ), ]; for (field, rrule) in tests { - let res = validate_rrule_forced(&rrule, &UTC.ymd(1970, 1, 1).and_hms(0, 0, 0)); + let res = + validate_rrule_forced(&rrule, &UTC.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap()); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!( @@ -568,10 +572,10 @@ mod tests { #[test] fn rejects_start_date_after_until() { let rrule = RRule { - until: Some(UTC.ymd_opt(2020, 1, 1).and_hms_opt(0, 0, 0).unwrap()), + until: Some(UTC.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap()), ..Default::default() }; - let dt_start = UTC.ymd_opt(2020, 1, 2).and_hms_opt(0, 0, 0).unwrap(); + let dt_start = UTC.with_ymd_and_hms(2020, 1, 2, 0, 0, 0).unwrap(); let res = validate_rrule_forced(&rrule, &dt_start); assert!(res.is_err()); let err = res.unwrap_err(); @@ -588,8 +592,8 @@ mod tests { fn allows_until_with_compatible_timezone() { fn t(start_tz: Tz, until_tz: Tz) -> (DateTime, DateTime) { ( - start_tz.ymd_opt(2020, 1, 1).and_hms_opt(0, 0, 0).unwrap(), - until_tz.ymd_opt(2020, 1, 1).and_hms_opt(0, 0, 0).unwrap(), + start_tz.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(), + until_tz.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(), ) } @@ -609,8 +613,8 @@ mod tests { fn rejects_until_with_incompatible_timezone() { fn t(start_tz: Tz, until_tz: Tz) -> (DateTime, DateTime) { ( - start_tz.ymd_opt(2020, 1, 1).and_hms_opt(0, 0, 0).unwrap(), - until_tz.ymd_opt(2020, 1, 1).and_hms_opt(0, 0, 0).unwrap(), + start_tz.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(), + until_tz.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(), ) }