feat(time): improve timezone handling by switching to jiff (#7222)

This commit is contained in:
David Knaack
2026-06-06 18:44:15 +02:00
committed by GitHub
parent 033f20b461
commit 3dd8c14144
6 changed files with 314 additions and 216 deletions
Generated
+1 -44
View File
@@ -393,19 +393,6 @@ dependencies = [
"rand_core 0.10.1", "rand_core 0.10.1",
] ]
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.6.1" version = "4.6.1"
@@ -538,12 +525,6 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -1892,30 +1873,6 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "id-arena" name = "id-arena"
version = "2.3.0" version = "2.3.0"
@@ -3380,7 +3337,6 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
name = "starship" name = "starship"
version = "1.25.1" version = "1.25.1"
dependencies = [ dependencies = [
"chrono",
"clap", "clap",
"clap_complete", "clap_complete",
"clap_complete_nushell", "clap_complete_nushell",
@@ -3391,6 +3347,7 @@ dependencies = [
"guess_host_triple", "guess_host_triple",
"home", "home",
"indexmap", "indexmap",
"jiff",
"jsonc-parser", "jsonc-parser",
"log", "log",
"mockall", "mockall",
+1 -1
View File
@@ -35,7 +35,6 @@ config-schema = ["schemars"]
notify = ["notify-rust"] notify = ["notify-rust"]
[dependencies] [dependencies]
chrono = { version = "0.4.44", default-features = false, features = ["clock", "std", "wasmbind"] }
clap = { version = "4.6.1", features = ["derive", "cargo", "unicode"] } clap = { version = "4.6.1", features = ["derive", "cargo", "unicode"] }
clap_complete = "4.6.5" clap_complete = "4.6.5"
clap_complete_nushell = "4.6.0" clap_complete_nushell = "4.6.0"
@@ -44,6 +43,7 @@ dunce = "1.0.5"
# default feature restriction addresses https://github.com/starship/starship/issues/4251 # default feature restriction addresses https://github.com/starship/starship/issues/4251
gix = { version = "0.84.0", default-features = false, features = ["max-performance-safe", "revision", "zlib-rs", "status", "sha1"] } gix = { version = "0.84.0", default-features = false, features = ["max-performance-safe", "revision", "zlib-rs", "status", "sha1"] }
indexmap = { version = "2.14.0", features = ["serde"] } indexmap = { version = "2.14.0", features = ["serde"] }
jiff = { version = "0.2.24", features = ["serde"] }
jsonc-parser = { version = "0.32.4", features = ["serde"] } jsonc-parser = { version = "0.32.4", features = ["serde"] }
log = { version = "0.4.30", features = ["std"] } log = { version = "0.4.30", features = ["std"] }
# notify-rust is optional (on by default) because the crate doesn't currently build for darwin with nix # notify-rust is optional (on by default) because the crate doesn't currently build for darwin with nix
+17 -4
View File
@@ -4814,7 +4814,7 @@ format = 'via [$symbol$workspace]($style) '
## Time ## Time
The `time` module shows the current **local** time. The `time` module shows the current **local** time.
The `format` configuration value is used by the [`chrono`](https://crates.io/crates/chrono) crate to control how the time is displayed. Take a look [at the chrono strftime docs](https://docs.rs/chrono/0.4.7/chrono/format/strftime/index.html) to see what options are available. The `format` configuration value is used by the [`jiff`](https://crates.io/crates/jiff) crate to control how the time is displayed. Take a look [at the jiff strftime docs](https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html) to see what options are available.
> [!TIP] > [!TIP]
> This module is disabled by default. > This module is disabled by default.
@@ -4823,12 +4823,12 @@ The `format` configuration value is used by the [`chrono`](https://crates.io/cra
### Options ### Options
| Option | Default | Description | | Option | Default | Description |
| ----------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | | ----------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `format` | `'at [$time]($style) '` | The format string for the module. | | `format` | `'at [$time]($style) '` | The format string for the module. |
| `use_12hr` | `false` | Enables 12 hour formatting | | `use_12hr` | `false` | Enables 12 hour formatting |
| `time_format` | see below | The [chrono format string](https://docs.rs/chrono/0.4.7/chrono/format/strftime/index.html) used to format the time. | | `time_format` | see below | The [jiff format string](https://docs.rs/jiff/latest/jiff/fmt/strtime/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. | | `utc_time_offset` | `'local'` | Sets the UTC offset to use. Either an IANA time zone name or a range from -24 < x < 24. Allows floats to accommodate 30/45 minute timezone offsets. |
| `disabled` | `true` | Disables the `time` module. | | `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 | | `time_range` | `'-'` | Sets the time range during which the module will be shown. Times must be specified in 24-hours format |
@@ -4846,6 +4846,8 @@ Manually setting `time_format` will override the `use_12hr` setting.
### Example ### Example
#### With UTC offset
```toml ```toml
# ~/.config/starship.toml # ~/.config/starship.toml
@@ -4857,6 +4859,17 @@ utc_time_offset = '-5'
time_range = '10:00:00-14:00:00' time_range = '10:00:00-14:00:00'
``` ```
#### With Timezone name
```toml
# ~/.config/starship.toml
[time]
disabled = false
time_format = '%T'
utc_time_offset = 'Europe/Berlin'
```
## Typst ## Typst
The `typst` module shows the current installed version of Typst used in a project. The `typst` module shows the current installed version of Typst used in a project.
+11 -2
View File
@@ -1,5 +1,13 @@
use crate::config::Either;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
// Wrapper struct to enable serde serialization/deserialization for jiff::tz::TimeZone
#[derive(Clone, Deserialize, Serialize)]
#[serde(transparent)]
pub struct TimezoneWrapper(
#[serde(with = "jiff::fmt::serde::tz::required")] pub jiff::tz::TimeZone,
);
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, Deserialize, Serialize)]
#[cfg_attr( #[cfg_attr(
feature = "config-schema", feature = "config-schema",
@@ -14,7 +22,8 @@ pub struct TimeConfig<'a> {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub time_format: Option<&'a str>, pub time_format: Option<&'a str>,
pub disabled: bool, pub disabled: bool,
pub utc_time_offset: &'a str, #[cfg_attr(feature = "config-schema", schemars(with = "String"))]
pub utc_time_offset: Either<TimezoneWrapper, &'a str>,
pub time_range: &'a str, pub time_range: &'a str,
} }
@@ -26,7 +35,7 @@ impl Default for TimeConfig<'_> {
use_12hr: false, use_12hr: false,
time_format: None, time_format: None,
disabled: true, disabled: true,
utc_time_offset: "local", utc_time_offset: Either::Second("local"),
time_range: "-", time_range: "-",
} }
} }
+20 -48
View File
@@ -3,8 +3,8 @@ use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use chrono::DateTime;
use ini::Ini; use ini::Ini;
use jiff::{Timestamp, Zoned};
use serde_json as json; use serde_json as json;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
@@ -149,7 +149,7 @@ fn get_credentials_duration(
.find_map(|env_var| context.get_env(env_var)) .find_map(|env_var| context.get_env(env_var))
{ {
// get expiration from environment variables // get expiration from environment variables
chrono::DateTime::parse_from_rfc3339(&expiration_date).ok() expiration_date.parse::<Timestamp>().ok()
} else if let Some(section) = } else if let Some(section) =
get_creds(context, aws_creds).and_then(|creds| get_profile_creds(creds, aws_profile)) get_creds(context, aws_creds).and_then(|creds| get_profile_creds(creds, aws_profile))
{ {
@@ -158,7 +158,7 @@ fn get_credentials_duration(
expiration_keys expiration_keys
.iter() .iter()
.find_map(|expiration_key| section.get(expiration_key)) .find_map(|expiration_key| section.get(expiration_key))
.and_then(|expiration| DateTime::parse_from_rfc3339(expiration).ok()) .and_then(|expiration| expiration.parse::<Timestamp>().ok())
} else { } else {
// get expiration from cached SSO credentials // get expiration from cached SSO credentials
let config = get_config(context, aws_config)?; let config = get_config(context, aws_config)?;
@@ -172,10 +172,10 @@ fn get_credentials_duration(
let sso_cred_json: json::Value = let sso_cred_json: json::Value =
json::from_str(&crate::utils::read_file(&sso_cred_path).ok()?).ok()?; json::from_str(&crate::utils::read_file(&sso_cred_path).ok()?).ok()?;
let expires_at = sso_cred_json.get("expiresAt")?.as_str(); let expires_at = sso_cred_json.get("expiresAt")?.as_str();
DateTime::parse_from_rfc3339(expires_at?).ok() expires_at?.parse::<Timestamp>().ok()
}?; }?;
Some(expiration_date.timestamp() - chrono::Local::now().timestamp()) Some(expiration_date.as_second() - Zoned::now().timestamp().as_second())
} }
fn alias_name(name: Option<String>, aliases: &HashMap<String, &str>) -> Option<String> { fn alias_name(name: Option<String>, aliases: &HashMap<String, &str>) -> Option<String> {
@@ -332,6 +332,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use crate::test::ModuleRenderer; use crate::test::ModuleRenderer;
use nu_ansi_term::Color; use nu_ansi_term::Color;
use std::fs::{File, create_dir}; use std::fs::{File, create_dir};
@@ -736,21 +737,16 @@ credential_process = /opt/bin/awscreds-retriever
#[test] #[test]
fn expiration_date_set() { fn expiration_date_set() {
use chrono::{DateTime, SecondsFormat, Utc};
let expiration_env_vars = ["AWS_SESSION_EXPIRATION", "AWS_CREDENTIAL_EXPIRATION"]; let expiration_env_vars = ["AWS_SESSION_EXPIRATION", "AWS_CREDENTIAL_EXPIRATION"];
for env_var in expiration_env_vars { for env_var in expiration_env_vars {
let now_plus_half_hour: DateTime<Utc> = let now_plus_half_hour =
DateTime::from_timestamp(chrono::Local::now().timestamp() + 1800, 0).unwrap(); Timestamp::from_second(Zoned::now().timestamp().as_second() + 1800).unwrap();
let actual = ModuleRenderer::new("aws") let actual = ModuleRenderer::new("aws")
.env("AWS_PROFILE", "astronauts") .env("AWS_PROFILE", "astronauts")
.env("AWS_REGION", "ap-northeast-2") .env("AWS_REGION", "ap-northeast-2")
.env("AWS_ACCESS_KEY_ID", "dummy") .env("AWS_ACCESS_KEY_ID", "dummy")
.env( .env(env_var, now_plus_half_hour.to_string())
env_var,
now_plus_half_hour.to_rfc3339_opts(SecondsFormat::Secs, true),
)
.collect(); .collect();
let possible_values = [ let possible_values = [
@@ -772,16 +768,14 @@ credential_process = /opt/bin/awscreds-retriever
#[test] #[test]
fn expiration_date_set_from_file() -> io::Result<()> { fn expiration_date_set_from_file() -> io::Result<()> {
use chrono::{DateTime, Utc};
let dir = tempfile::tempdir()?; let dir = tempfile::tempdir()?;
let credentials_path = dir.path().join("credentials"); let credentials_path = dir.path().join("credentials");
let mut file = File::create(&credentials_path)?; let mut file = File::create(&credentials_path)?;
let now_plus_half_hour: DateTime<Utc> = let now_plus_half_hour =
DateTime::from_timestamp(chrono::Local::now().timestamp() + 1800, 0).unwrap(); Timestamp::from_second(Zoned::now().timestamp().as_second() + 1800).unwrap();
let expiration_date = now_plus_half_hour.to_rfc3339_opts(chrono::SecondsFormat::Secs, true); let expiration_date = now_plus_half_hour.to_string();
let expiration_keys = ["expiration", "x_security_token_expires"]; let expiration_keys = ["expiration", "x_security_token_expires"];
for key in expiration_keys { for key in expiration_keys {
@@ -847,10 +841,7 @@ aws_secret_access_key=dummy
#[test] #[test]
fn expiration_date_set_expired() { fn expiration_date_set_expired() {
use chrono::{DateTime, SecondsFormat, Utc}; let now = Timestamp::from_second(Zoned::now().timestamp().as_second() - 1800).unwrap();
let now: DateTime<Utc> =
DateTime::from_timestamp(chrono::Local::now().timestamp() - 1800, 0).unwrap();
let symbol = "!!!"; let symbol = "!!!";
@@ -862,10 +853,7 @@ aws_secret_access_key=dummy
.env("AWS_PROFILE", "astronauts") .env("AWS_PROFILE", "astronauts")
.env("AWS_REGION", "ap-northeast-2") .env("AWS_REGION", "ap-northeast-2")
.env("AWS_ACCESS_KEY_ID", "dummy") .env("AWS_ACCESS_KEY_ID", "dummy")
.env( .env("AWS_SESSION_EXPIRATION", now.to_string())
"AWS_SESSION_EXPIRATION",
now.to_rfc3339_opts(SecondsFormat::Secs, true),
)
.collect(); .collect();
let expected = Some(format!( let expected = Some(format!(
"on {}", "on {}",
@@ -1056,8 +1044,6 @@ credential_process = /opt/bin/awscreds-for-tests
#[test] #[test]
fn sso_legacy_set() -> io::Result<()> { fn sso_legacy_set() -> io::Result<()> {
use chrono::{DateTime, SecondsFormat, Utc};
let (module_renderer, dir) = ModuleRenderer::new_with_home("aws")?; let (module_renderer, dir) = ModuleRenderer::new_with_home("aws")?;
std::fs::create_dir_all(dir.path().join(".aws/sso/cache"))?; std::fs::create_dir_all(dir.path().join(".aws/sso/cache"))?;
@@ -1080,16 +1066,10 @@ sso_role_name = <AWS-ROLE-NAME>
.join(".aws/sso/cache/a47a4e57aecc96b31b4f083543924bd6f828e65a.json"), .join(".aws/sso/cache/a47a4e57aecc96b31b4f083543924bd6f828e65a.json"),
)?; )?;
let one_second_ago: DateTime<Utc> = let one_second_ago =
DateTime::from_timestamp(chrono::Local::now().timestamp() - 1, 0).unwrap(); Timestamp::from_second(Zoned::now().timestamp().as_second() - 1).unwrap();
file.write_all( file.write_all(format!(r#"{{"expiresAt": "{one_second_ago}"}}"#).as_bytes())?;
format!(
r#"{{"expiresAt": "{}"}}"#,
one_second_ago.to_rfc3339_opts(SecondsFormat::Secs, true)
)
.as_bytes(),
)?;
file.sync_all()?; file.sync_all()?;
let actual = module_renderer.collect(); let actual = module_renderer.collect();
@@ -1104,8 +1084,6 @@ sso_role_name = <AWS-ROLE-NAME>
#[test] #[test]
fn sso_set() -> io::Result<()> { fn sso_set() -> io::Result<()> {
use chrono::{DateTime, SecondsFormat, Utc};
let (module_renderer, dir) = ModuleRenderer::new_with_home("aws")?; let (module_renderer, dir) = ModuleRenderer::new_with_home("aws")?;
std::fs::create_dir_all(dir.path().join(".aws/sso/cache"))?; std::fs::create_dir_all(dir.path().join(".aws/sso/cache"))?;
@@ -1134,16 +1112,10 @@ sso_registration_scopes = sso:account:access
.join(".aws/sso/cache/7505d64a54e061b7acd54ccd58b49dc43500b635.json"), .join(".aws/sso/cache/7505d64a54e061b7acd54ccd58b49dc43500b635.json"),
)?; )?;
let one_second_ago: DateTime<Utc> = let one_second_ago =
DateTime::from_timestamp(chrono::Local::now().timestamp() - 1, 0).unwrap(); Timestamp::from_second(Zoned::now().timestamp().as_second() - 1).unwrap();
cache_file.write_all( cache_file.write_all(format!(r#"{{"expiresAt": "{one_second_ago}"}}"#).as_bytes())?;
format!(
r#"{{"expiresAt": "{}"}}"#,
one_second_ago.to_rfc3339_opts(SecondsFormat::Secs, true)
)
.as_bytes(),
)?;
cache_file.sync_all()?; cache_file.sync_all()?;
let actual = module_renderer let actual = module_renderer
+255 -108
View File
@@ -1,7 +1,12 @@
use chrono::{DateTime, FixedOffset, Local, NaiveTime, Utc}; use jiff::{
Timestamp, Zoned,
civil::Time,
tz::{Offset, TimeZone},
};
use super::{Context, Module, ModuleConfig}; use super::{Context, Module, ModuleConfig};
use crate::configs::time::TimeConfig; use crate::config::Either;
use crate::configs::time::{TimeConfig, TimezoneWrapper};
use crate::formatter::StringFormatter; use crate::formatter::StringFormatter;
/// Outputs the current time /// Outputs the current time
@@ -17,7 +22,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
// Hide prompt if current time is not inside time_range // Hide prompt if current time is not inside time_range
let (display_start, display_end) = parse_time_range(config.time_range); let (display_start, display_end) = parse_time_range(config.time_range);
let time_now = Local::now().time(); let time_now = Zoned::now().time();
if !is_inside_time_range(time_now, display_start, display_end) { if !is_inside_time_range(time_now, display_start, display_end) {
return None; return None;
} }
@@ -27,17 +32,26 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
log::trace!("Timer module is enabled with format string: {time_format}"); log::trace!("Timer module is enabled with format string: {time_format}");
let formatted_time_string = if config.utc_time_offset != "local" { let formatted_time_string = match &config.utc_time_offset {
create_offset_time_string(Utc::now(), config.utc_time_offset, time_format).unwrap_or_else( Either::First(TimezoneWrapper(tz)) => {
|_| { // Use IANA timezone name
let target_time = Timestamp::now().to_zoned(tz.clone());
format_time_fixed_offset(time_format, target_time)
}
Either::Second("local") => {
// Use local timezone
format_time(time_format, Zoned::now())
}
Either::Second(utc_time_offset) => {
// Use numeric offset
create_offset_time_string(Timestamp::now(), utc_time_offset, time_format)
.unwrap_or_else(|_| {
log::warn!( log::warn!(
"Invalid utc_time_offset configuration provided! Falling back to \"local\"." "Invalid utc_time_offset configuration provided! Falling back to \"local\"."
); );
format_time(time_format, Local::now()) format_time(time_format, Zoned::now())
}, })
) }
} else {
format_time(time_format, Local::now())
}; };
let parsed = StringFormatter::new(config.format).and_then(|formatter| { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
@@ -65,10 +79,10 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
fn create_offset_time_string( fn create_offset_time_string(
utc_time: DateTime<Utc>, utc_time: Timestamp,
utc_time_offset_str: &str, utc_time_offset_str: &str,
time_format: &str, time_format: &str,
) -> Result<String, &'static str> { ) -> Result<String, String> {
// Using floats to allow 30/45 minute offsets: https://www.timeanddate.com/time/time-zones-interesting.html // Using floats to allow 30/45 minute offsets: https://www.timeanddate.com/time/time-zones-interesting.html
let utc_time_offset_in_hours = utc_time_offset_str.parse::<f32>().unwrap_or( let utc_time_offset_in_hours = utc_time_offset_str.parse::<f32>().unwrap_or(
// Passing out of range value to force falling back to "local" // Passing out of range value to force falling back to "local"
@@ -76,38 +90,34 @@ fn create_offset_time_string(
); );
if utc_time_offset_in_hours < 24_f32 && utc_time_offset_in_hours > -24_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 utc_offset_in_seconds: i32 = (utc_time_offset_in_hours * 3600_f32) as i32;
let Some(timezone_offset) = FixedOffset::east_opt(utc_offset_in_seconds) else { let timezone_offset = Offset::from_seconds(utc_offset_in_seconds)
return Err("Invalid offset"); .map_err(|err| format!("Invalid timezone offset: {err:?}"))?;
}; let tz = TimeZone::fixed(timezone_offset);
log::trace!("Target timezone offset is {timezone_offset}"); log::trace!("Target timezone offset is {timezone_offset}");
let target_time = utc_time.with_timezone(&timezone_offset); let target_time = utc_time.to_zoned(tz);
log::trace!("Time in target timezone now is {target_time}"); log::trace!("Time in target timezone now is {target_time}");
Ok(format_time_fixed_offset(time_format, target_time)) Ok(format_time_fixed_offset(time_format, target_time))
} else { } else {
Err("Invalid timezone offset.") Err("Invalid timezone offset.".to_string())
} }
} }
/// Format a given time into the given string. This function should be referentially /// 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) /// transparent, which makes it easy to test (unlike anything involving the actual time)
fn format_time(time_format: &str, local_time: DateTime<Local>) -> String { fn format_time(time_format: &str, local_time: Zoned) -> String {
local_time.format(time_format).to_string() local_time.strftime(time_format).to_string()
} }
fn format_time_fixed_offset(time_format: &str, utc_time: DateTime<FixedOffset>) -> String { fn format_time_fixed_offset(time_format: &str, zoned_time: Zoned) -> String {
utc_time.format(time_format).to_string() zoned_time.strftime(time_format).to_string()
} }
/// Returns true if `time_now` is between `time_start` and `time_end`. /// Returns true if `time_now` is between `time_start` and `time_end`.
/// If one of these values is not given, then it is ignored. /// 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 /// It also handles cases where `time_start` and `time_end` have a midnight in between
fn is_inside_time_range( fn is_inside_time_range(time_now: Time, time_start: Option<Time>, time_end: Option<Time>) -> bool {
time_now: NaiveTime,
time_start: Option<NaiveTime>,
time_end: Option<NaiveTime>,
) -> bool {
match (time_start, time_end) { match (time_start, time_end) {
(None, None) => true, (None, None) => true,
(Some(i), None) => time_now > i, (Some(i), None) => time_now > i,
@@ -127,7 +137,7 @@ fn is_inside_time_range(
/// ///
/// If one of the ranges is invalid or not provided, then the corresponding field in the output /// If one of the ranges is invalid or not provided, then the corresponding field in the output
/// tuple is None /// tuple is None
fn parse_time_range(time_range: &str) -> (Option<NaiveTime>, Option<NaiveTime>) { fn parse_time_range(time_range: &str) -> (Option<Time>, Option<Time>) {
let value = String::from(time_range); let value = String::from(time_range);
// Check if there is exactly one hyphen, and fail otherwise // Check if there is exactly one hyphen, and fail otherwise
@@ -140,8 +150,8 @@ fn parse_time_range(time_range: &str) -> (Option<NaiveTime>, Option<NaiveTime>)
let end = &end[1..]; let end = &end[1..];
// Parse the ranges // Parse the ranges
let start_time = NaiveTime::parse_from_str(start, "%H:%M:%S").ok(); let start_time = start.parse::<Time>().ok();
let end_time = NaiveTime::parse_from_str(end, "%H:%M:%S").ok(); let end_time = end.parse::<Time>().ok();
(start_time, end_time) (start_time, end_time)
} }
@@ -152,140 +162,151 @@ tests become extra important */
mod tests { mod tests {
use super::*; use super::*;
use crate::test::ModuleRenderer; use crate::test::ModuleRenderer;
use chrono::offset::TimeZone; use jiff::civil::date;
const FMT_12: &str = "%r"; const FMT_12: &str = "%r";
const FMT_24: &str = "%T"; const FMT_24: &str = "%T";
#[test] #[test]
fn test_midnight_12hr() { fn test_midnight_12hr() {
let time = Local.with_ymd_and_hms(2014, 7, 8, 0, 0, 0).unwrap(); let time = date(2014, 7, 8)
.at(0, 0, 0, 0)
.to_zoned(TimeZone::system())
.unwrap();
let formatted = format_time(FMT_12, time); let formatted = format_time(FMT_12, time);
assert_eq!(formatted, "12:00:00 AM"); assert_eq!(formatted, "12:00:00 AM");
} }
#[test] #[test]
fn test_midnight_24hr() { fn test_midnight_24hr() {
let time = Local.with_ymd_and_hms(2014, 7, 8, 0, 0, 0).unwrap(); let time = date(2014, 7, 8)
.at(0, 0, 0, 0)
.to_zoned(TimeZone::system())
.unwrap();
let formatted = format_time(FMT_24, time); let formatted = format_time(FMT_24, time);
assert_eq!(formatted, "00:00:00"); assert_eq!(formatted, "00:00:00");
} }
#[test] #[test]
fn test_noon_12hr() { fn test_noon_12hr() {
let time = Local.with_ymd_and_hms(2014, 7, 8, 12, 0, 0).unwrap(); let time = date(2014, 7, 8)
.at(12, 0, 0, 0)
.to_zoned(TimeZone::system())
.unwrap();
let formatted = format_time(FMT_12, time); let formatted = format_time(FMT_12, time);
assert_eq!(formatted, "12:00:00 PM"); assert_eq!(formatted, "12:00:00 PM");
} }
#[test] #[test]
fn test_noon_24hr() { fn test_noon_24hr() {
let time = Local.with_ymd_and_hms(2014, 7, 8, 12, 0, 0).unwrap(); let time = date(2014, 7, 8)
.at(12, 0, 0, 0)
.to_zoned(TimeZone::system())
.unwrap();
let formatted = format_time(FMT_24, time); let formatted = format_time(FMT_24, time);
assert_eq!(formatted, "12:00:00"); assert_eq!(formatted, "12:00:00");
} }
#[test] #[test]
fn test_arbtime_12hr() { fn test_arbtime_12hr() {
let time = Local.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::system())
.unwrap();
let formatted = format_time(FMT_12, time); let formatted = format_time(FMT_12, time);
assert_eq!(formatted, "03:36:47 PM"); assert_eq!(formatted, "3:36:47 PM");
} }
#[test] #[test]
fn test_arbtime_24hr() { fn test_arbtime_24hr() {
let time = Local.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::system())
.unwrap();
let formatted = format_time(FMT_24, time); let formatted = format_time(FMT_24, time);
assert_eq!(formatted, "15:36:47"); assert_eq!(formatted, "15:36:47");
} }
#[test] #[test]
fn test_format_with_paren() { fn test_format_with_paren() {
let time = Local.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::system())
.unwrap();
let formatted = format_time("[%T]", time); let formatted = format_time("[%T]", time);
assert_eq!(formatted, "[15:36:47]"); assert_eq!(formatted, "[15:36:47]");
} }
#[test] #[test]
fn test_midnight_12hr_fixed_offset() { fn test_midnight_12hr_fixed_offset() {
let timezone_offset = FixedOffset::east_opt(0).unwrap(); let timezone_offset = Offset::from_seconds(0).unwrap();
let time = Utc let tz = TimeZone::fixed(timezone_offset);
.with_ymd_and_hms(2014, 7, 8, 0, 0, 0) let time = date(2014, 7, 8).at(0, 0, 0, 0).to_zoned(tz).unwrap();
.unwrap()
.with_timezone(&timezone_offset);
let formatted = format_time_fixed_offset(FMT_12, time); let formatted = format_time_fixed_offset(FMT_12, time);
assert_eq!(formatted, "12:00:00 AM"); assert_eq!(formatted, "12:00:00 AM");
} }
#[test] #[test]
fn test_midnight_24hr_fixed_offset() { fn test_midnight_24hr_fixed_offset() {
let timezone_offset = FixedOffset::east_opt(0).unwrap(); let timezone_offset = Offset::from_seconds(0).unwrap();
let time = Utc let tz = TimeZone::fixed(timezone_offset);
.with_ymd_and_hms(2014, 7, 8, 0, 0, 0) let time = date(2014, 7, 8).at(0, 0, 0, 0).to_zoned(tz).unwrap();
.unwrap()
.with_timezone(&timezone_offset);
let formatted = format_time_fixed_offset(FMT_24, time); let formatted = format_time_fixed_offset(FMT_24, time);
assert_eq!(formatted, "00:00:00"); assert_eq!(formatted, "00:00:00");
} }
#[test] #[test]
fn test_noon_12hr_fixed_offset() { fn test_noon_12hr_fixed_offset() {
let timezone_offset = FixedOffset::east_opt(0).unwrap(); let timezone_offset = Offset::from_seconds(0).unwrap();
let time = Utc let tz = TimeZone::fixed(timezone_offset);
.with_ymd_and_hms(2014, 7, 8, 12, 0, 0) let time = date(2014, 7, 8).at(12, 0, 0, 0).to_zoned(tz).unwrap();
.unwrap()
.with_timezone(&timezone_offset);
let formatted = format_time_fixed_offset(FMT_12, time); let formatted = format_time_fixed_offset(FMT_12, time);
assert_eq!(formatted, "12:00:00 PM"); assert_eq!(formatted, "12:00:00 PM");
} }
#[test] #[test]
fn test_noon_24hr_fixed_offset() { fn test_noon_24hr_fixed_offset() {
let timezone_offset = FixedOffset::east_opt(0).unwrap(); let timezone_offset = Offset::from_seconds(0).unwrap();
let time = Utc let tz = TimeZone::fixed(timezone_offset);
.with_ymd_and_hms(2014, 7, 8, 12, 0, 0) let time = date(2014, 7, 8).at(12, 0, 0, 0).to_zoned(tz).unwrap();
.unwrap()
.with_timezone(&timezone_offset);
let formatted = format_time_fixed_offset(FMT_24, time); let formatted = format_time_fixed_offset(FMT_24, time);
assert_eq!(formatted, "12:00:00"); assert_eq!(formatted, "12:00:00");
} }
#[test] #[test]
fn test_arbtime_12hr_fixed_offset() { fn test_arbtime_12hr_fixed_offset() {
let timezone_offset = FixedOffset::east_opt(0).unwrap(); let timezone_offset = Offset::from_seconds(0).unwrap();
let time = Utc let tz = TimeZone::fixed(timezone_offset);
.with_ymd_and_hms(2014, 7, 8, 15, 36, 47) let time = date(2014, 7, 8).at(15, 36, 47, 0).to_zoned(tz).unwrap();
.unwrap()
.with_timezone(&timezone_offset);
let formatted = format_time_fixed_offset(FMT_12, time); let formatted = format_time_fixed_offset(FMT_12, time);
assert_eq!(formatted, "03:36:47 PM"); assert_eq!(formatted, "3:36:47 PM");
} }
#[test] #[test]
fn test_arbtime_24hr_fixed_offset() { fn test_arbtime_24hr_fixed_offset() {
let timezone_offset = FixedOffset::east_opt(0).unwrap(); let timezone_offset = Offset::from_seconds(0).unwrap();
let time = Utc let tz = TimeZone::fixed(timezone_offset);
.with_ymd_and_hms(2014, 7, 8, 15, 36, 47) let time = date(2014, 7, 8).at(15, 36, 47, 0).to_zoned(tz).unwrap();
.unwrap()
.with_timezone(&timezone_offset);
let formatted = format_time_fixed_offset(FMT_24, time); let formatted = format_time_fixed_offset(FMT_24, time);
assert_eq!(formatted, "15:36:47"); assert_eq!(formatted, "15:36:47");
} }
#[test] #[test]
fn test_format_with_paren_fixed_offset() { fn test_format_with_paren_fixed_offset() {
let timezone_offset = FixedOffset::east_opt(0).unwrap(); let timezone_offset = Offset::from_seconds(0).unwrap();
let time = Utc let tz = TimeZone::fixed(timezone_offset);
.with_ymd_and_hms(2014, 7, 8, 15, 36, 47) let time = date(2014, 7, 8).at(15, 36, 47, 0).to_zoned(tz).unwrap();
.unwrap()
.with_timezone(&timezone_offset);
let formatted = format_time_fixed_offset("[%T]", time); let formatted = format_time_fixed_offset("[%T]", time);
assert_eq!(formatted, "[15:36:47]"); assert_eq!(formatted, "[15:36:47]");
} }
#[test] #[test]
fn test_create_formatted_time_string_with_minus_3() { fn test_create_formatted_time_string_with_minus_3() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "-3"; let utc_time_offset_str = "-3";
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap(); let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
@@ -294,34 +315,50 @@ mod tests {
#[test] #[test]
fn test_create_formatted_time_string_with_plus_5() { fn test_create_formatted_time_string_with_plus_5() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "+5"; let utc_time_offset_str = "+5";
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap(); let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
assert_eq!(actual, "08:36:47 PM"); assert_eq!(actual, "8:36:47 PM");
} }
#[test] #[test]
fn test_create_formatted_time_string_with_plus_9_30() { fn test_create_formatted_time_string_with_plus_9_30() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "+9.5"; let utc_time_offset_str = "+9.5";
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap(); let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
assert_eq!(actual, "01:06:47 AM"); assert_eq!(actual, "1:06:47 AM");
} }
#[test] #[test]
fn test_create_formatted_time_string_with_plus_5_45() { fn test_create_formatted_time_string_with_plus_5_45() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "+5.75"; let utc_time_offset_str = "+5.75";
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap(); let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
assert_eq!(actual, "09:21:47 PM"); assert_eq!(actual, "9:21:47 PM");
} }
#[test] #[test]
fn test_create_formatted_time_string_with_plus_24() { fn test_create_formatted_time_string_with_plus_24() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "+24"; let utc_time_offset_str = "+24";
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12) create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
@@ -330,7 +367,11 @@ mod tests {
#[test] #[test]
fn test_create_formatted_time_string_with_minus_24() { fn test_create_formatted_time_string_with_minus_24() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "-24"; let utc_time_offset_str = "-24";
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12) create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
@@ -339,7 +380,11 @@ mod tests {
#[test] #[test]
fn test_create_formatted_time_string_with_plus_9001() { fn test_create_formatted_time_string_with_plus_9001() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "+9001"; let utc_time_offset_str = "+9001";
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12) create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
@@ -348,7 +393,11 @@ mod tests {
#[test] #[test]
fn test_create_formatted_time_string_with_minus_4242() { fn test_create_formatted_time_string_with_minus_4242() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "-4242"; let utc_time_offset_str = "-4242";
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12) create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
@@ -357,7 +406,11 @@ mod tests {
#[test] #[test]
fn test_create_formatted_time_string_with_invalid_string() { fn test_create_formatted_time_string_with_invalid_string() {
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap(); let utc_time = date(2014, 7, 8)
.at(15, 36, 47, 0)
.to_zoned(TimeZone::UTC)
.unwrap()
.timestamp();
let utc_time_offset_str = "completely wrong config"; let utc_time_offset_str = "completely wrong config";
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12) create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
@@ -379,7 +432,7 @@ mod tests {
assert_eq!( assert_eq!(
parse_time_range(time_range), parse_time_range(time_range),
(Some(NaiveTime::from_hms_opt(10, 00, 00).unwrap()), None) (Some(Time::new(10, 0, 0, 0).unwrap()), None)
); );
} }
@@ -389,7 +442,7 @@ mod tests {
assert_eq!( assert_eq!(
parse_time_range(time_range), parse_time_range(time_range),
(None, Some(NaiveTime::from_hms_opt(22, 00, 00).unwrap())) (None, Some(Time::new(22, 0, 0, 0).unwrap()))
); );
} }
@@ -400,8 +453,8 @@ mod tests {
assert_eq!( assert_eq!(
parse_time_range(time_range), parse_time_range(time_range),
( (
Some(NaiveTime::from_hms_opt(10, 00, 00).unwrap()), Some(Time::new(10, 0, 0, 0).unwrap()),
Some(NaiveTime::from_hms_opt(16, 00, 00).unwrap()) Some(Time::new(16, 0, 0, 0).unwrap())
) )
); );
} }
@@ -410,16 +463,16 @@ mod tests {
fn test_is_inside_time_range_with_no_range() { fn test_is_inside_time_range_with_no_range() {
let time_start = None; let time_start = None;
let time_end = None; let time_end = None;
let time_now = NaiveTime::from_hms_opt(10, 00, 00).unwrap(); let time_now = Time::new(10, 0, 0, 0).unwrap();
assert!(is_inside_time_range(time_now, time_start, time_end)); assert!(is_inside_time_range(time_now, time_start, time_end));
} }
#[test] #[test]
fn test_is_inside_time_range_with_start_range() { fn test_is_inside_time_range_with_start_range() {
let time_start = Some(NaiveTime::from_hms_opt(10, 00, 00).unwrap()); let time_start = Some(Time::new(10, 0, 0, 0).unwrap());
let time_now = NaiveTime::from_hms_opt(12, 00, 00).unwrap(); let time_now = Time::new(12, 0, 0, 0).unwrap();
let time_now2 = NaiveTime::from_hms_opt(8, 00, 00).unwrap(); let time_now2 = Time::new(8, 0, 0, 0).unwrap();
assert!(is_inside_time_range(time_now, time_start, None)); assert!(is_inside_time_range(time_now, time_start, None));
assert!(!is_inside_time_range(time_now2, time_start, None)); assert!(!is_inside_time_range(time_now2, time_start, None));
@@ -427,9 +480,9 @@ mod tests {
#[test] #[test]
fn test_is_inside_time_range_with_end_range() { fn test_is_inside_time_range_with_end_range() {
let time_end = Some(NaiveTime::from_hms_opt(16, 00, 00).unwrap()); let time_end = Some(Time::new(16, 0, 0, 0).unwrap());
let time_now = NaiveTime::from_hms_opt(15, 00, 00).unwrap(); let time_now = Time::new(15, 0, 0, 0).unwrap();
let time_now2 = NaiveTime::from_hms_opt(19, 00, 00).unwrap(); let time_now2 = Time::new(19, 0, 0, 0).unwrap();
assert!(is_inside_time_range(time_now, None, time_end)); assert!(is_inside_time_range(time_now, None, time_end));
assert!(!is_inside_time_range(time_now2, None, time_end)); assert!(!is_inside_time_range(time_now2, None, time_end));
@@ -437,11 +490,11 @@ mod tests {
#[test] #[test]
fn test_is_inside_time_range_with_complete_range() { fn test_is_inside_time_range_with_complete_range() {
let time_start = Some(NaiveTime::from_hms_opt(9, 00, 00).unwrap()); let time_start = Some(Time::new(9, 0, 0, 0).unwrap());
let time_end = Some(NaiveTime::from_hms_opt(18, 00, 00).unwrap()); let time_end = Some(Time::new(18, 0, 0, 0).unwrap());
let time_now = NaiveTime::from_hms_opt(3, 00, 00).unwrap(); let time_now = Time::new(3, 0, 0, 0).unwrap();
let time_now2 = NaiveTime::from_hms_opt(13, 00, 00).unwrap(); let time_now2 = Time::new(13, 0, 0, 0).unwrap();
let time_now3 = NaiveTime::from_hms_opt(20, 00, 00).unwrap(); let time_now3 = Time::new(20, 0, 0, 0).unwrap();
assert!(!is_inside_time_range(time_now, time_start, time_end)); assert!(!is_inside_time_range(time_now, time_start, time_end));
assert!(is_inside_time_range(time_now2, time_start, time_end)); assert!(is_inside_time_range(time_now2, time_start, time_end));
@@ -450,11 +503,11 @@ mod tests {
#[test] #[test]
fn test_is_inside_time_range_with_complete_range_passing_midnight() { fn test_is_inside_time_range_with_complete_range_passing_midnight() {
let time_start = Some(NaiveTime::from_hms_opt(19, 00, 00).unwrap()); let time_start = Some(Time::new(19, 0, 0, 0).unwrap());
let time_end = Some(NaiveTime::from_hms_opt(12, 00, 00).unwrap()); let time_end = Some(Time::new(12, 0, 0, 0).unwrap());
let time_now = NaiveTime::from_hms_opt(3, 00, 00).unwrap(); let time_now = Time::new(3, 0, 0, 0).unwrap();
let time_now2 = NaiveTime::from_hms_opt(13, 00, 00).unwrap(); let time_now2 = Time::new(13, 0, 0, 0).unwrap();
let time_now3 = NaiveTime::from_hms_opt(20, 00, 00).unwrap(); let time_now3 = Time::new(20, 0, 0, 0).unwrap();
assert!(is_inside_time_range(time_now, time_start, time_end)); assert!(is_inside_time_range(time_now, time_start, time_end));
assert!(!is_inside_time_range(time_now2, time_start, time_end)); assert!(!is_inside_time_range(time_now2, time_start, time_end));
@@ -503,4 +556,98 @@ mod tests {
assert!(actual.starts_with(&col_prefix)); assert!(actual.starts_with(&col_prefix));
assert!(actual.ends_with(&col_suffix)); assert!(actual.ends_with(&col_suffix));
} }
#[test]
fn config_check_invalid_tz() {
let actual = ModuleRenderer::new("time")
.config(toml::toml! {
[time]
disabled = false
time_format = "%T"
utc_time_offset = "invalid"
})
.collect();
assert!(actual.is_some(), "Falls back to local time");
}
#[test]
fn module_tz() {
use nu_ansi_term::Color;
let actual = ModuleRenderer::new("time")
.config(toml::toml! {
[time]
disabled = false
time_format = "%:z"
utc_time_offset = "Asia/Kolkata"
})
.collect();
let expected = Some(format!("at {} ", Color::Yellow.bold().paint("+05:30")));
assert_eq!(actual, expected, "Uses timezone");
}
#[test]
fn module_offset() {
use nu_ansi_term::Color;
let actual = ModuleRenderer::new("time")
.config(toml::toml! {
[time]
disabled = false
time_format = "%:z"
utc_time_offset = "-1.75"
})
.collect();
let expected = Some(format!("at {} ", Color::Yellow.bold().paint("-01:45")));
assert_eq!(actual, expected, "Uses timezone offset");
}
#[test]
fn module_tz_abbreviation() {
let actual = ModuleRenderer::new("time")
.config(toml::toml! {
[time]
disabled = false
time_format = "%Z"
utc_time_offset = "America/New_York"
})
.collect();
// Should output a timezone abbreviation like "EST" or "EDT"
assert!(
actual.is_some(),
"Timezone abbreviation should be displayed"
);
let output = actual.unwrap();
assert!(
output.contains("EST") || output.contains("EDT"),
"Should contain timezone abbreviation"
);
}
#[test]
fn module_tz_identifier() {
use nu_ansi_term::Color;
let actual = ModuleRenderer::new("time")
.config(toml::toml! {
[time]
disabled = false
time_format = "%Q"
utc_time_offset = "America/New_York"
})
.collect();
let expected = Some(format!(
"at {} ",
Color::Yellow.bold().paint("America/New_York")
));
assert_eq!(actual, expected, "Uses IANA timezone identifier");
}
} }