Skip to content

Commit

Permalink
Added %timestamp macro with timezone capability.
Browse files Browse the repository at this point in the history
  • Loading branch information
Demonstrandum committed Dec 9, 2024
1 parent 1c65241 commit c100c7a
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 1 deletion.
65 changes: 64 additions & 1 deletion crates/seam/src/parse/expander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::{
},
};

use chrono::TimeZone;
use colored::*;
use fixed;
use formatx;
Expand Down Expand Up @@ -889,7 +890,7 @@ impl<'a> Expander<'a> {
&offset.site,
)),
},
None => 0,
None => chrono::Local::now().offset().local_minus_utc(),
};
let date_format = args.number.1.value;
let time = if let Some(time) = args.number.2 {
Expand Down Expand Up @@ -923,6 +924,67 @@ impl<'a> Expander<'a> {
Ok(Box::new([date_string_node]))
}

fn expand_timestamp_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-> Result<ParseTree<'a>, ExpansionError<'a>> {
let params = self.expand_nodes(params)?;
let (_, args) = arguments! { [&params]
mandatory(1): literal,
mandatory(2): literal,
optional("timezone"): number,
}?;
let format = args.number.1.value;
let date_string = args.number.2.value;

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 => chrono::Local::now().offset().local_minus_utc(),
};

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 timestamp = if let Ok(datetime) = chrono::DateTime::parse_from_str(&date_string, &format) {
// Timezone information already given. Ignore any `:timezone` attribute.
datetime.timestamp()
} else {
// Parse NaiveDateTime instead and get timzone from attribute, or default to local time.
let datetime = match chrono::NaiveDateTime::parse_from_str(&date_string, &format) {
Ok(datetime) => datetime,
Err(err) => return Err(ExpansionError(
format!("Failed to parse date: {}", err),
node.owned_site(),
)),
};
let datetime = timezone.from_local_datetime(&datetime);
let datetime = match datetime.earliest() {
Some(local) => local,
None => return Err(ExpansionError::new(
"Timezone: local time falls in a gap in local time.",
node.site(),
))
};
datetime.timestamp()
};

Ok(Box::new([
ParseNode::Number(Node {
value: format!("{}", timestamp),
site: node.owned_site(),
leading_whitespace: node.leading_whitespace().to_owned(),
})
]))
}

/// `(%log ...)` logs to `STDERR` when called and leaves *no* node behind.
/// This means whitespace preceding `(%log ...)` will be removed!
fn expand_log_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
Expand Down Expand Up @@ -1658,6 +1720,7 @@ impl<'a> Expander<'a> {
"sort" => self.expand_sort_macro(node, params),
"reverse" => self.expand_reverse_macro(node, params),
"date" => self.expand_date_macro(node, params),
"timestamp" => self.expand_timestamp_macro(node, params),
"join" => self.expand_join_macro(node, params),
"concat" => self.expand_concat_macro(node, params),
"map" => self.expand_map_macro(node, params),
Expand Down
23 changes: 23 additions & 0 deletions crates/seam/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,27 @@ mod tests {
"\"2001-09-08 23:31:39-02:15\""
);
}

#[test]
fn test_timestamp_macro() {
let timestamp = "419083754"; // 13 Apr 1983.
assert_output_eq(
"(%timestamp \"%Y %b %d %H:%M:%S%.3f %z\" \"1983 Apr 13 12:09:14.274 +0000\")",
timestamp
);
assert_output_eq(
"(%timestamp \"%Y %b %d %H:%M:%S%.3f\" \"1983 Apr 13 12:09:14.274\" :timezone 0)",
timestamp
);
// Same time *locally* but in a timezone of -02:00.
let timestamp_minus_2h = "419090954";
assert_output_eq(
"(%timestamp \"%Y %b %d %H:%M:%S%.3f %z\" \"1983 Apr 13 12:09:14.274 -0200\")",
timestamp_minus_2h
);
assert_output_eq(
"(%timestamp \"%Y %b %d %H:%M:%S%.3f\" \"1983 Apr 13 12:09:14.274\" :timezone -2)",
timestamp_minus_2h
);
}
}

0 comments on commit c100c7a

Please sign in to comment.