diff --git a/docs/config/README.md b/docs/config/README.md index f3907c27..620234cc 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -895,12 +895,22 @@ To enable it, set `disabled` to `false` in your configuration file. ### Options +<<<<<<< HEAD +| Variable | Default | Description | +| ----------------- | ------------- | ------------------------------------------------------------------------------------------------------------------- | +| `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 | +| `disabled` | `true` | Disables the `time` module. | +| `utc_time_offset` | `local` | Sets the UTC offset to use. Range from -24 < x < 24. Allows floats to accomodate 30/45 minute timezone offsets. | +======= | Variable | Default | Description | | ---------- | ------------- | ------------------------------------------------------------------------------------------------------------------- | | `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 | | `disabled` | `true` | Disables the `time` module. | +>>>>>>> master If `use_12hr` is `true`, then `format` defaults to `"%r"`. Otherwise, it defaults to `"%T"`. Manually setting `format` will override the `use_12hr` setting. diff --git a/src/configs/time.rs b/src/configs/time.rs index 95483eb6..74909c81 100644 --- a/src/configs/time.rs +++ b/src/configs/time.rs @@ -9,6 +9,7 @@ pub struct TimeConfig<'a> { pub format: Option<&'a str>, pub style: Style, pub disabled: bool, + pub utc_time_offset: &'a str, } impl<'a> RootModuleConfig<'a> for TimeConfig<'a> { @@ -18,6 +19,7 @@ impl<'a> RootModuleConfig<'a> for TimeConfig<'a> { format: None, style: Color::Yellow.bold(), disabled: true, + utc_time_offset: "local", } } } diff --git a/src/modules/time.rs b/src/modules/time.rs index 7009f984..4f5889a9 100644 --- a/src/modules/time.rs +++ b/src/modules/time.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, Local}; +use chrono::{DateTime, FixedOffset, Local, Utc}; use super::{Context, Module}; @@ -23,8 +23,19 @@ pub fn module<'a>(context: &'a Context) -> Option> { time_format ); - let local: DateTime = Local::now(); - let formatted_time_string = format_time(time_format, local); + let formatted_time_string = if config.utc_time_offset != "local" { + match create_offset_time_string(Utc::now(), &config.utc_time_offset, &time_format) { + Ok(formatted_string) => formatted_string, + Err(_) => { + log::warn!( + "Invalid utc_time_offset configuration provided! Falling back to \"local\"." + ); + format_time(&time_format, Local::now()) + } + } + } else { + format_time(&time_format, Local::now()) + }; module.set_style(config.style); @@ -41,12 +52,41 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } +fn create_offset_time_string( + utc_time: DateTime, + utc_time_offset_str: &str, + time_format: &str, +) -> Result { + // Using floats to allow 30/45 minute offsets: https://www.timeanddate.com/time/time-zones-interesting.html + let utc_time_offset_in_hours = match utc_time_offset_str.parse::() { + Ok(parsed_value) => parsed_value, + // Passing out of range value to force falling back to "local" + Err(_) => 25_f32, + }; + if utc_time_offset_in_hours < 24_f32 && utc_time_offset_in_hours > -24_f32 { + let utc_offset_in_seconds: i32 = (utc_time_offset_in_hours * 3600_f32) as i32; + let timezone_offset = FixedOffset::east(utc_offset_in_seconds); + log::trace!("Target timezone offset is {}", timezone_offset); + + let target_time = utc_time.with_timezone(&timezone_offset); + log::trace!("Time in target timezone now is {}", target_time); + + Ok(format_time_fixed_offset(&time_format, target_time)) + } else { + Err("Invalid timezone offset.") + } +} + /// Format a given time into the given string. This function should be referentially /// transparent, which makes it easy to test (unlike anything involving the actual time) fn format_time(time_format: &str, local_time: DateTime) -> String { local_time.format(time_format).to_string() } +fn format_time_fixed_offset(time_format: &str, utc_time: DateTime) -> String { + utc_time.format(time_format).to_string() +} + /* Because we cannot make acceptance tests for the time module, these unit tests become extra important */ #[cfg(test)] @@ -105,4 +145,167 @@ mod tests { let formatted = format_time("[%T]", time); assert_eq!(formatted, "[15:36:47]"); } + + #[test] + fn test_midnight_12hr_fixed_offset() { + let timezone_offset = FixedOffset::east(0); + let time = Utc + .ymd(2014, 7, 8) + .and_hms(0, 0, 0) + .with_timezone(&timezone_offset); + let formatted = format_time_fixed_offset(FMT_12, time); + assert_eq!(formatted, "12:00:00 AM"); + } + + #[test] + fn test_midnight_24hr_fixed_offset() { + let timezone_offset = FixedOffset::east(0); + let time = Utc + .ymd(2014, 7, 8) + .and_hms(0, 0, 0) + .with_timezone(&timezone_offset); + let formatted = format_time_fixed_offset(FMT_24, time); + assert_eq!(formatted, "00:00:00"); + } + + #[test] + fn test_noon_12hr_fixed_offset() { + let timezone_offset = FixedOffset::east(0); + let time = Utc + .ymd(2014, 7, 8) + .and_hms(12, 0, 0) + .with_timezone(&timezone_offset); + let formatted = format_time_fixed_offset(FMT_12, time); + assert_eq!(formatted, "12:00:00 PM"); + } + + #[test] + fn test_noon_24hr_fixed_offset() { + let timezone_offset = FixedOffset::east(0); + let time = Utc + .ymd(2014, 7, 8) + .and_hms(12, 0, 0) + .with_timezone(&timezone_offset); + let formatted = format_time_fixed_offset(FMT_24, time); + assert_eq!(formatted, "12:00:00"); + } + + #[test] + fn test_arbtime_12hr_fixed_offset() { + let timezone_offset = FixedOffset::east(0); + let time = Utc + .ymd(2014, 7, 8) + .and_hms(15, 36, 47) + .with_timezone(&timezone_offset); + let formatted = format_time_fixed_offset(FMT_12, time); + assert_eq!(formatted, "03:36:47 PM"); + } + + #[test] + fn test_arbtime_24hr_fixed_offset() { + let timezone_offset = FixedOffset::east(0); + let time = Utc + .ymd(2014, 7, 8) + .and_hms(15, 36, 47) + .with_timezone(&timezone_offset); + let formatted = format_time_fixed_offset(FMT_24, time); + assert_eq!(formatted, "15:36:47"); + } + + #[test] + fn test_format_with_paren_fixed_offset() { + let timezone_offset = FixedOffset::east(0); + let time = Utc + .ymd(2014, 7, 8) + .and_hms(15, 36, 47) + .with_timezone(&timezone_offset); + let formatted = format_time_fixed_offset("[%T]", time); + assert_eq!(formatted, "[15:36:47]"); + } + + #[test] + fn test_create_formatted_time_string_with_minus_3() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "-3"; + + let actual = create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12).unwrap(); + assert_eq!(actual, "12:36:47 PM"); + } + + #[test] + fn test_create_formatted_time_string_with_plus_5() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "+5"; + + let actual = create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12).unwrap(); + assert_eq!(actual, "08:36:47 PM"); + } + + #[test] + fn test_create_formatted_time_string_with_plus_9_30() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "+9.5"; + + let actual = create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12).unwrap(); + assert_eq!(actual, "01:06:47 AM"); + } + + #[test] + fn test_create_formatted_time_string_with_plus_5_45() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "+5.75"; + + let actual = create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12).unwrap(); + assert_eq!(actual, "09:21:47 PM"); + } + + #[test] + fn test_create_formatted_time_string_with_plus_24() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "+24"; + + create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12) + .err() + .expect("Invalid timezone offset."); + } + + #[test] + fn test_create_formatted_time_string_with_minus_24() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "-24"; + + create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12) + .err() + .expect("Invalid timezone offset."); + } + + #[test] + fn test_create_formatted_time_string_with_plus_9001() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "+9001"; + + create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12) + .err() + .expect("Invalid timezone offset."); + } + + #[test] + fn test_create_formatted_time_string_with_minus_4242() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "-4242"; + + create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12) + .err() + .expect("Invalid timezone offset."); + } + + #[test] + fn test_create_formatted_time_string_with_invalid_string() { + let utc_time: DateTime = Utc.ymd(2014, 7, 8).and_hms(15, 36, 47); + let utc_time_offset_str = "completely wrong config"; + + create_offset_time_string(utc_time, &utc_time_offset_str, FMT_12) + .err() + .expect("Invalid timezone offset."); + } }