From 3c832cf6b1c0fb8a8946d9b15c4eaacac7748bb4 Mon Sep 17 00:00:00 2001 From: Lukas Schreiner Date: Wed, 30 Apr 2025 22:10:34 +0200 Subject: [PATCH] Fix: Timer can handle long than one hour This commit ensures, that a timer can run longer than an hour by not increasing manual values for hours, minutes and seconds, but taking the tasks start date and is comparing against `now`. There might be a sudden jump on a leap time, but its neglecated. Close #23 --- frontend/src/main.ts | 42 ++-------------------- frontend/src/timer.ts | 42 ++++++++++++++++++++++ frontend/templates/active_task.html | 2 +- src/lib.rs | 40 ++++++++++++++++++--- src/tests.rs | 54 +++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 45 deletions(-) create mode 100644 frontend/src/timer.ts create mode 100644 src/tests.rs diff --git a/frontend/src/main.ts b/frontend/src/main.ts index a06af3f..30deae6 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -3,6 +3,7 @@ import 'hyperscript.org'; import * as _hyperscript from "hyperscript.org"; import hotkeys from "hotkeys-js"; import * as theme from "./theme.ts"; +import * as timer from "./timer.ts"; _hyperscript.browserInit(); @@ -136,46 +137,7 @@ document.addEventListener('click', function (event) { document.addEventListener("DOMContentLoaded", function () { theme.init(); - - let n = setInterval( - () => { - let whichOne = 0; - // document.getElementById('active-timer').querySelectorAll('span.timer-duration')[0] - let dd = document.getElementById('active-timer'); - if (dd === undefined || dd === null) { - return - } - let timeBox = dd.children[1].children[whichOne]; - let s = timeBox.textContent.split(":"); - let second = parseInt(s.pop()); - let minute = parseInt(s.pop()); - if (isNaN(minute)) { - minute = 0; - } - let hour = parseInt(s.pop()); - if (isNaN(hour)) { - hour = 0; - } - second += 1; - if (second >= 60) { - second = 0; - minute += 1; - if (minute > 60) { - hour += 1; - } - } - timeBox.textContent = hour.toString() - // @ts-ignore - .padStart(2, "0") + ":" + - minute.toString() - // @ts-ignore - .padStart(2, "0") + ":" + - second.toString() - // @ts-ignore - .padStart(2, "0"); - }, 1000 - ) - + timer.init(); let day_progress = setInterval( () => { const dd = document.getElementById('time_of_the_day'); diff --git a/frontend/src/timer.ts b/frontend/src/timer.ts new file mode 100644 index 0000000..da600f9 --- /dev/null +++ b/frontend/src/timer.ts @@ -0,0 +1,42 @@ +var global_twk_timer: number|null = null; + +function updateTimer() { + let timer_dom = document.getElementById('active-timer'); + if (timer_dom === null) { + return; + } + + let timer_box_dom = timer_dom.querySelector(".timer-duration"); + if (timer_box_dom === null) { + return; + } + + let task_start = timer_dom?.getAttribute("data-task-start"); + if (task_start === null || task_start === undefined) { + return; + } + let task_start_dt = Date.parse(task_start); + let now_utc = Date.now(); + let diff_s = Math.floor((now_utc - task_start_dt)/1000); + let hours = Math.floor(diff_s/3600); + let minutes = Math.floor((diff_s - (hours*3600))/60); + let seconds = Math.floor((diff_s - (hours*3600)) - (minutes*60)); + timer_box_dom.textContent = + hours.toString().padStart(2, "0") + ":" + + minutes.toString().padStart(2, "0") + ":" + + seconds.toString().padStart(2, "0") +} + +export function init() { + global_twk_timer = setInterval( + updateTimer, + 1000 + ) +} + +export function stop() { + if (global_twk_timer != null && global_twk_timer != undefined) { + clearInterval(global_twk_timer); + global_twk_timer = null; + } +} \ No newline at end of file diff --git a/frontend/templates/active_task.html b/frontend/templates/active_task.html index 255a250..42229a5 100644 --- a/frontend/templates/active_task.html +++ b/frontend/templates/active_task.html @@ -4,7 +4,7 @@
+ data-task-id="{{ active_task.uuid }}" data-task-start="{{ datetime_iso(datetime=active_task.start) }}"> {{ active_task.description }}
diff --git a/src/lib.rs b/src/lib.rs index 85823d4..7757fcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,12 +7,16 @@ use std::str::FromStr; use crate::endpoints::tasks::task_query_builder::{TaskQuery, TaskReport}; use crate::endpoints::tasks::{is_a_tag, is_tag_keyword}; -use chrono::{DateTime, TimeDelta}; +use chrono::{DateTime, TimeDelta, Utc}; use rand::distr::{Alphanumeric, SampleString}; use serde::{de, Deserialize, Deserializer, Serialize}; use taskchampion::Uuid; use tera::Context; use tracing::warn; +#[cfg(test)] +mod tests; +#[cfg(test)] +use chrono::TimeZone; lazy_static::lazy_static! { pub static ref TEMPLATES: tera::Tera = { @@ -26,6 +30,7 @@ lazy_static::lazy_static! { tera.register_function("project_name", get_project_name_link()); tera.register_function("date_proper", get_date_proper()); tera.register_function("timer_value", get_timer()); + tera.register_function("datetime_iso", get_datetime_iso()); tera.register_function("date", get_date()); tera.register_function("obj", obj()); tera.register_function("remove_project_tag", remove_project_from_tag()); @@ -319,6 +324,17 @@ fn update_tag_bar_key_comb() -> impl tera::Filter { ) } +#[cfg(not(test))] +#[allow(dead_code)] +fn get_utc_now() -> DateTime { + chrono::prelude::Utc::now() +} +#[cfg(test)] +#[allow(dead_code)] +fn get_utc_now() -> DateTime { + chrono::Utc.with_ymd_and_hms(2025, 5, 1, 3, 55, 0).unwrap() +} + pub struct DeltaNow { pub now: DateTime, pub delta: TimeDelta, @@ -332,7 +348,7 @@ impl DeltaNow { // Try taskchampions variant. chrono::prelude::NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M:%SZ").unwrap()) .and_utc(); - let now = chrono::prelude::Utc::now(); + let now = get_utc_now(); let delta = now - time; Self { now, delta, time } } @@ -376,6 +392,22 @@ fn get_date_proper() -> impl tera::Function { ) } +fn get_datetime_iso() -> impl tera::Function { + Box::new( + move |args: &HashMap| -> tera::Result { + let date_time_str = args.get("datetime").unwrap().as_str().unwrap(); + // we are working with utc time + let date_time = chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y%m%dT%H%M%SZ") + .unwrap_or_else(|_| + // Try taskchampions variant. + chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y-%m-%dT%H:%M:%SZ").unwrap() + ) + .and_utc(); + Ok(tera::to_value(date_time.to_rfc3339()).unwrap()) + }, + ) +} + fn get_date() -> impl tera::Function { Box::new( move |args: &HashMap| -> tera::Result { @@ -437,7 +469,7 @@ fn get_timer() -> impl tera::Function { let s = if delta.num_hours() > 0 { format!( - "{:>02}:{:>02}", + "{:>02}:{:>02}:00", delta.num_hours(), delta.num_minutes() - (delta.num_hours() * 60) ) @@ -449,7 +481,7 @@ fn get_timer() -> impl tera::Function { num_seconds % 60 ) } else { - format!("{}s", num_seconds) + format!("00:00:{:>02}", num_seconds) }; Ok(tera::to_value(s).unwrap()) }, diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..6180007 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,54 @@ +use tera::Function; + +use super::*; + +#[test] +fn test_get_timer() { + let get_timer_func = get_timer(); + let time_seconds: HashMap = HashMap::from([ + ("date".to_string(), serde_json::to_value("20250501T035408Z").unwrap()) + ]); + + let result = get_timer_func.call(&time_seconds); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("00:00:52").unwrap()); + + let time_seconds: HashMap = HashMap::from([ + ("date".to_string(), serde_json::to_value("20250501T033408Z").unwrap()) + ]); + + let result = get_timer_func.call(&time_seconds); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("00:20:52").unwrap()); + + let time_seconds: HashMap = HashMap::from([ + ("date".to_string(), serde_json::to_value("20250501T023408Z").unwrap()) + ]); + + let result = get_timer_func.call(&time_seconds); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("01:20:00").unwrap()); +} + +#[test] +fn test_get_datetime_iso() { + let get_datetime_iso_func = get_datetime_iso(); + let datetime: HashMap = HashMap::from([ + ("datetime".to_string(), serde_json::to_value("20250501T035408Z").unwrap()) + ]); + let result = get_datetime_iso_func.call(&datetime); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap()); + + let datetime: HashMap = HashMap::from([ + ("datetime".to_string(), serde_json::to_value("2025-05-01T03:54:08Z").unwrap()) + ]); + let result = get_datetime_iso_func.call(&datetime); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap()); +}