From fe2b3d491e8d38df62c4585542e17665a790a59e Mon Sep 17 00:00:00 2001 From: Samuele Esposito <36164633+esposm03@users.noreply.github.com> Date: Sun, 31 May 2020 19:43:08 +0200 Subject: [PATCH] feat(time): Show module with time range (#992) * Creation of range field in TimeConfig * time_range parsing * Hide time module if outside of time_range * Tidying of code, and properly handling more cases * is_inside_time_range function * Tests and fmt * Update docs * The configuration needs the 24-hours format * Fix clippy errors --- docs/config/README.md | 6 +- src/configs/time.rs | 2 + src/modules/time.rs | 152 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 157 insertions(+), 3 deletions(-) diff --git a/docs/config/README.md b/docs/config/README.md index 1d331f00..d47df257 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -1327,11 +1327,12 @@ To enable it, set `disabled` to `false` in your configuration file. | Variable | Default | Description | | ----------------- | --------------- | ------------------------------------------------------------------------------------------------------------------- | -| `use_12hr` | `false` | Enables 12 hour formatting | +| `use_12hr` | `false` | Enables 12 hour formatting. | | `format` | see below | The [chrono format string](https://docs.rs/chrono/0.4.7/chrono/format/strftime/index.html) used to format the time. | -| `style` | `"bold yellow"` | The style for the module time | +| `style` | `"bold yellow"` | The style for the module time. | | `utc_time_offset` | `"local"` | Sets the UTC offset to use. Range from -24 < x < 24. Allows floats to accommodate 30/45 minute timezone offsets. | | `disabled` | `true` | Disables the `time` module. | +| `time_range` | `"-"` | Sets the time range during which the module will be shown. Times must be specified in 24-hours format | If `use_12hr` is `true`, then `format` defaults to `"%r"`. Otherwise, it defaults to `"%T"`. Manually setting `format` will override the `use_12hr` setting. @@ -1345,6 +1346,7 @@ Manually setting `format` will override the `use_12hr` setting. disabled = false format = "🕙[ %T ]" utc_time_offset = "-5" +time_range = "10:00:00-14:00:00" ``` ## Username diff --git a/src/configs/time.rs b/src/configs/time.rs index 74909c81..69dffa39 100644 --- a/src/configs/time.rs +++ b/src/configs/time.rs @@ -10,6 +10,7 @@ pub struct TimeConfig<'a> { pub style: Style, pub disabled: bool, pub utc_time_offset: &'a str, + pub time_range: &'a str, } impl<'a> RootModuleConfig<'a> for TimeConfig<'a> { @@ -20,6 +21,7 @@ impl<'a> RootModuleConfig<'a> for TimeConfig<'a> { style: Color::Yellow.bold(), disabled: true, utc_time_offset: "local", + time_range: "-", } } } diff --git a/src/modules/time.rs b/src/modules/time.rs index 4f5889a9..96310b9b 100644 --- a/src/modules/time.rs +++ b/src/modules/time.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, FixedOffset, Local, Utc}; +use chrono::{DateTime, FixedOffset, Local, NaiveTime, Utc}; use super::{Context, Module}; @@ -15,6 +15,13 @@ pub fn module<'a>(context: &'a Context) -> Option> { return None; }; + // Hide prompt if current time is not inside time_range + let (display_start, display_end) = parse_time_range(config.time_range); + let time_now = Local::now().time(); + if !is_inside_time_range(time_now, display_start, display_end) { + return None; + } + let default_format = if config.use_12hr { "%r" } else { "%T" }; let time_format = config.format.unwrap_or(default_format); @@ -87,6 +94,52 @@ fn format_time_fixed_offset(time_format: &str, utc_time: DateTime) utc_time.format(time_format).to_string() } +/// Returns true if time_now is between time_start and time_end. +/// If one of these values is not given, then it is ignored. +/// It also handles cases where time_start and time_end have a midnight in between +fn is_inside_time_range( + time_now: NaiveTime, + time_start: Option, + time_end: Option, +) -> bool { + match (time_start, time_end) { + (None, None) => true, + (Some(i), None) => time_now > i, + (None, Some(i)) => time_now < i, + (Some(i), Some(j)) => { + if i < j { + i < time_now && time_now < j + } else { + time_now > i || time_now < j + } + } + } +} + +/// Parses the config's time_range field and returns the starting time and ending time. +/// The range is in the format START_TIME-END_TIME, with START_TIME and END_TIME being optional. +/// +/// If one of the ranges is invalid or not provided, then the corresponding field in the output +/// tuple is None +fn parse_time_range(time_range: &str) -> (Option, Option) { + let value = String::from(time_range); + + // Check if there is exactly one hyphen, and fail otherwise + if value.matches('-').count() != 1 { + return (None, None); + } + + // Split time_range into the two ranges + let (start, end) = value.split_at(value.find('-').unwrap()); + let end = &end[1..]; + + // Parse the ranges + let start_time = NaiveTime::parse_from_str(start, "%H:%M:%S").ok(); + let end_time = NaiveTime::parse_from_str(end, "%H:%M:%S").ok(); + + (start_time, end_time) +} + /* Because we cannot make acceptance tests for the time module, these unit tests become extra important */ #[cfg(test)] @@ -308,4 +361,101 @@ mod tests { .err() .expect("Invalid timezone offset."); } + + #[test] + fn test_parse_invalid_time_range() { + let time_range = "10:00:00-12:00:00-13:00:00"; + let time_range_2 = "10:00:00"; + + assert_eq!(parse_time_range(time_range), (None, None)); + assert_eq!(parse_time_range(time_range_2), (None, None)); + } + + #[test] + fn test_parse_start_time_range() { + let time_range = "10:00:00-"; + + assert_eq!( + parse_time_range(time_range), + (Some(NaiveTime::from_hms(10, 00, 00)), None) + ); + } + + #[test] + fn test_parse_end_time_range() { + let time_range = "-22:00:00"; + + assert_eq!( + parse_time_range(time_range), + (None, Some(NaiveTime::from_hms(22, 00, 00))) + ); + } + + #[test] + fn test_parse_both_time_ranges() { + let time_range = "10:00:00-16:00:00"; + + assert_eq!( + parse_time_range(time_range), + ( + Some(NaiveTime::from_hms(10, 00, 00)), + Some(NaiveTime::from_hms(16, 00, 00)) + ) + ); + } + + #[test] + fn test_is_inside_time_range_with_no_range() { + let time_start = None; + let time_end = None; + let time_now = NaiveTime::from_hms(10, 00, 00); + + assert_eq!(is_inside_time_range(time_now, time_start, time_end), true); + } + + #[test] + fn test_is_inside_time_range_with_start_range() { + let time_start = Some(NaiveTime::from_hms(10, 00, 00)); + let time_now = NaiveTime::from_hms(12, 00, 00); + let time_now2 = NaiveTime::from_hms(8, 00, 00); + + assert_eq!(is_inside_time_range(time_now, time_start, None), true); + assert_eq!(is_inside_time_range(time_now2, time_start, None), false); + } + + #[test] + fn test_is_inside_time_range_with_end_range() { + let time_end = Some(NaiveTime::from_hms(16, 00, 00)); + let time_now = NaiveTime::from_hms(15, 00, 00); + let time_now2 = NaiveTime::from_hms(19, 00, 00); + + assert_eq!(is_inside_time_range(time_now, None, time_end), true); + assert_eq!(is_inside_time_range(time_now2, None, time_end), false); + } + + #[test] + fn test_is_inside_time_range_with_complete_range() { + let time_start = Some(NaiveTime::from_hms(9, 00, 00)); + let time_end = Some(NaiveTime::from_hms(18, 00, 00)); + let time_now = NaiveTime::from_hms(3, 00, 00); + let time_now2 = NaiveTime::from_hms(13, 00, 00); + let time_now3 = NaiveTime::from_hms(20, 00, 00); + + assert_eq!(is_inside_time_range(time_now, time_start, time_end), false); + assert_eq!(is_inside_time_range(time_now2, time_start, time_end), true); + assert_eq!(is_inside_time_range(time_now3, time_start, time_end), false); + } + + #[test] + fn test_is_inside_time_range_with_complete_range_passing_midnight() { + let time_start = Some(NaiveTime::from_hms(19, 00, 00)); + let time_end = Some(NaiveTime::from_hms(12, 00, 00)); + let time_now = NaiveTime::from_hms(3, 00, 00); + let time_now2 = NaiveTime::from_hms(13, 00, 00); + let time_now3 = NaiveTime::from_hms(20, 00, 00); + + assert_eq!(is_inside_time_range(time_now, time_start, time_end), true); + assert_eq!(is_inside_time_range(time_now2, time_start, time_end), false); + assert_eq!(is_inside_time_range(time_now3, time_start, time_end), true); + } }