commit 1c652410814e5b69b9e09c12180e06343e2fee53
parent a0660bbdd206cc0596ca38acd4149fc2658501c6
Author: Demonstrandum <samuel@knutsen.co>
Date: Mon, 9 Dec 2024 21:57:08 +0000
Added :timezone offset to %date macro.
Diffstat:
5 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -51,12 +51,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
+name = "az"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
+
+[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
+name = "bytemuck"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
+
+[[package]]
name = "cc"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -102,6 +114,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
name = "descape"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -123,6 +141,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
+name = "fixed"
+version = "1.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c6e0b89bf864acd20590dbdbad56f69aeb898abfc9443008fd7bd48b2cc85a"
+dependencies = [
+ "az",
+ "bytemuck",
+ "half",
+ "typenum",
+]
+
+[[package]]
name = "formatx"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -135,6 +165,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
+name = "half"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -302,6 +342,7 @@ dependencies = [
"chrono",
"colored",
"descape",
+ "fixed",
"formatx",
"glob",
"markdown",
@@ -402,6 +443,12 @@ dependencies = [
]
[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
name = "unicode-id"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/seam/Cargo.toml b/crates/seam/Cargo.toml
@@ -25,6 +25,7 @@ path = "src/bin.rs"
seam_argparse_proc_macro = { version = "0.1.1", path = "../seam_argparse_proc_macro" }
colored = "2.1"
chrono = "0.4"
+fixed = "1.28.0"
unicode-width = "0.2.0"
descape = "2.0.3"
formatx = "0.2.2"
diff --git a/crates/seam/src/parse/expander.rs b/crates/seam/src/parse/expander.rs
@@ -15,6 +15,7 @@ use std::{
};
use colored::*;
+use fixed;
use formatx;
use glob::glob;
use unicode_width::UnicodeWidthStr;
@@ -878,7 +879,18 @@ impl<'a> Expander<'a> {
let (_, args) = arguments! { [¶ms]
mandatory(1): literal,
optional(2): number,
+ optional("timezone"): number,
}?;
+ let timezone_offset: i32 = match args.timezone {
+ Some(ref offset) => match offset.value.parse::<fixed::types::I32F32>() {
+ Ok(offset) => (offset * 60 * 60).round().to_num(),
+ Err(_) => return Err(ExpansionError::new(
+ "Invalid (decimal) timezone offset in hours.",
+ &offset.site,
+ )),
+ },
+ None => 0,
+ };
let date_format = args.number.1.value;
let time = if let Some(time) = args.number.2 {
let Ok(secs) = time.value.parse::<i64>() else {
@@ -894,6 +906,14 @@ impl<'a> Expander<'a> {
} else {
chrono::Utc::now()
};
+
+ let Some(timezone) = chrono::FixedOffset::east_opt(timezone_offset) else {
+ return Err(ExpansionError(
+ format!("Failed to compute UTC+(east) offset of {} seconds", timezone_offset),
+ args.timezone.map_or(node.owned_site(), |node| node.site),
+ ));
+ };
+ let time = time.with_timezone(&timezone);
let formatted = time.format(&date_format).to_string();
let date_string_node = ParseNode::String(Node {
value: formatted,
diff --git a/crates/seam/src/parse/lexer.rs b/crates/seam/src/parse/lexer.rs
@@ -36,7 +36,7 @@ fn character_kind(character: char)
'(' => Some(tokens::Kind::LParen),
')' => Some(tokens::Kind::RParen),
'0'..='9' => Some(tokens::Kind::Number),
- '-' => Some(tokens::Kind::Number),
+ '+' | '-' => Some(tokens::Kind::Number),
':' => Some(tokens::Kind::Keyword),
'"' => Some(tokens::Kind::String),
_ => Some(tokens::Kind::Symbol)
diff --git a/crates/seam/src/tests.rs b/crates/seam/src/tests.rs
@@ -64,4 +64,20 @@ mod tests {
"\"2024-12-09 20:42:46\""
);
}
+
+ #[test]
+ fn test_date_timezones() {
+ assert_output_eq(
+ "(%date \"%Y-%m-%d %H:%M:%S%Z\" 999999999 :timezone +1)",
+ "\"2001-09-09 02:46:39+01:00\""
+ );
+ assert_output_eq(
+ "(%date \"%Y-%m-%d %H:%M:%S%Z\" 999999999 :timezone +3.5)",
+ "\"2001-09-09 05:16:39+03:30\""
+ );
+ assert_output_eq(
+ "(%date \"%Y-%m-%d %H:%M:%S%Z\" 999999999 :timezone -2.25)",
+ "\"2001-09-08 23:31:39-02:15\""
+ );
+ }
}