Skip to content

Commit 3c832cf

Browse files
committed
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
1 parent 7ac8fa5 commit 3c832cf

File tree

5 files changed

+135
-45
lines changed

5 files changed

+135
-45
lines changed

frontend/src/main.ts

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'hyperscript.org';
33
import * as _hyperscript from "hyperscript.org";
44
import hotkeys from "hotkeys-js";
55
import * as theme from "./theme.ts";
6+
import * as timer from "./timer.ts";
67

78
_hyperscript.browserInit();
89

@@ -136,46 +137,7 @@ document.addEventListener('click', function (event) {
136137

137138
document.addEventListener("DOMContentLoaded", function () {
138139
theme.init();
139-
140-
let n = setInterval(
141-
() => {
142-
let whichOne = 0;
143-
// document.getElementById('active-timer').querySelectorAll('span.timer-duration')[0]
144-
let dd = document.getElementById('active-timer');
145-
if (dd === undefined || dd === null) {
146-
return
147-
}
148-
let timeBox = dd.children[1].children[whichOne];
149-
let s = timeBox.textContent.split(":");
150-
let second = parseInt(s.pop());
151-
let minute = parseInt(s.pop());
152-
if (isNaN(minute)) {
153-
minute = 0;
154-
}
155-
let hour = parseInt(s.pop());
156-
if (isNaN(hour)) {
157-
hour = 0;
158-
}
159-
second += 1;
160-
if (second >= 60) {
161-
second = 0;
162-
minute += 1;
163-
if (minute > 60) {
164-
hour += 1;
165-
}
166-
}
167-
timeBox.textContent = hour.toString()
168-
// @ts-ignore
169-
.padStart(2, "0") + ":" +
170-
minute.toString()
171-
// @ts-ignore
172-
.padStart(2, "0") + ":" +
173-
second.toString()
174-
// @ts-ignore
175-
.padStart(2, "0");
176-
}, 1000
177-
)
178-
140+
timer.init();
179141
let day_progress = setInterval(
180142
() => {
181143
const dd = document.getElementById('time_of_the_day');

frontend/src/timer.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
var global_twk_timer: number|null = null;
2+
3+
function updateTimer() {
4+
let timer_dom = document.getElementById('active-timer');
5+
if (timer_dom === null) {
6+
return;
7+
}
8+
9+
let timer_box_dom = timer_dom.querySelector(".timer-duration");
10+
if (timer_box_dom === null) {
11+
return;
12+
}
13+
14+
let task_start = timer_dom?.getAttribute("data-task-start");
15+
if (task_start === null || task_start === undefined) {
16+
return;
17+
}
18+
let task_start_dt = Date.parse(task_start);
19+
let now_utc = Date.now();
20+
let diff_s = Math.floor((now_utc - task_start_dt)/1000);
21+
let hours = Math.floor(diff_s/3600);
22+
let minutes = Math.floor((diff_s - (hours*3600))/60);
23+
let seconds = Math.floor((diff_s - (hours*3600)) - (minutes*60));
24+
timer_box_dom.textContent =
25+
hours.toString().padStart(2, "0") + ":" +
26+
minutes.toString().padStart(2, "0") + ":" +
27+
seconds.toString().padStart(2, "0")
28+
}
29+
30+
export function init() {
31+
global_twk_timer = setInterval(
32+
updateTimer,
33+
1000
34+
)
35+
}
36+
37+
export function stop() {
38+
if (global_twk_timer != null && global_twk_timer != undefined) {
39+
clearInterval(global_twk_timer);
40+
global_twk_timer = null;
41+
}
42+
}

frontend/templates/active_task.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
</div>
66
<div class="pl-2.5 pr-6 py-1.5 bg-green-900 text-green-200 shadow-xl rounded-sm flex flex-wrap " id="active-timer"
7-
data-task-id="{{ active_task.uuid }}" data-task-start="{{ active_task.start }}">
7+
data-task-id="{{ active_task.uuid }}" data-task-start="{{ datetime_iso(datetime=active_task.start) }}">
88
<span class="flex-grow">{{ active_task.description }}</span>
99
<div class="join">
1010
<span class="badge badge-success badge-md badge-soft join-item timer-duration">

src/lib.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ use std::str::FromStr;
77

88
use crate::endpoints::tasks::task_query_builder::{TaskQuery, TaskReport};
99
use crate::endpoints::tasks::{is_a_tag, is_tag_keyword};
10-
use chrono::{DateTime, TimeDelta};
10+
use chrono::{DateTime, TimeDelta, Utc};
1111
use rand::distr::{Alphanumeric, SampleString};
1212
use serde::{de, Deserialize, Deserializer, Serialize};
1313
use taskchampion::Uuid;
1414
use tera::Context;
1515
use tracing::warn;
16+
#[cfg(test)]
17+
mod tests;
18+
#[cfg(test)]
19+
use chrono::TimeZone;
1620

1721
lazy_static::lazy_static! {
1822
pub static ref TEMPLATES: tera::Tera = {
@@ -26,6 +30,7 @@ lazy_static::lazy_static! {
2630
tera.register_function("project_name", get_project_name_link());
2731
tera.register_function("date_proper", get_date_proper());
2832
tera.register_function("timer_value", get_timer());
33+
tera.register_function("datetime_iso", get_datetime_iso());
2934
tera.register_function("date", get_date());
3035
tera.register_function("obj", obj());
3136
tera.register_function("remove_project_tag", remove_project_from_tag());
@@ -319,6 +324,17 @@ fn update_tag_bar_key_comb() -> impl tera::Filter {
319324
)
320325
}
321326

327+
#[cfg(not(test))]
328+
#[allow(dead_code)]
329+
fn get_utc_now() -> DateTime<Utc> {
330+
chrono::prelude::Utc::now()
331+
}
332+
#[cfg(test)]
333+
#[allow(dead_code)]
334+
fn get_utc_now() -> DateTime<Utc> {
335+
chrono::Utc.with_ymd_and_hms(2025, 5, 1, 3, 55, 0).unwrap()
336+
}
337+
322338
pub struct DeltaNow {
323339
pub now: DateTime<chrono::Utc>,
324340
pub delta: TimeDelta,
@@ -332,7 +348,7 @@ impl DeltaNow {
332348
// Try taskchampions variant.
333349
chrono::prelude::NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M:%SZ").unwrap())
334350
.and_utc();
335-
let now = chrono::prelude::Utc::now();
351+
let now = get_utc_now();
336352
let delta = now - time;
337353
Self { now, delta, time }
338354
}
@@ -376,6 +392,22 @@ fn get_date_proper() -> impl tera::Function {
376392
)
377393
}
378394

395+
fn get_datetime_iso() -> impl tera::Function {
396+
Box::new(
397+
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
398+
let date_time_str = args.get("datetime").unwrap().as_str().unwrap();
399+
// we are working with utc time
400+
let date_time = chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y%m%dT%H%M%SZ")
401+
.unwrap_or_else(|_|
402+
// Try taskchampions variant.
403+
chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y-%m-%dT%H:%M:%SZ").unwrap()
404+
)
405+
.and_utc();
406+
Ok(tera::to_value(date_time.to_rfc3339()).unwrap())
407+
},
408+
)
409+
}
410+
379411
fn get_date() -> impl tera::Function {
380412
Box::new(
381413
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
@@ -437,7 +469,7 @@ fn get_timer() -> impl tera::Function {
437469

438470
let s = if delta.num_hours() > 0 {
439471
format!(
440-
"{:>02}:{:>02}",
472+
"{:>02}:{:>02}:00",
441473
delta.num_hours(),
442474
delta.num_minutes() - (delta.num_hours() * 60)
443475
)
@@ -449,7 +481,7 @@ fn get_timer() -> impl tera::Function {
449481
num_seconds % 60
450482
)
451483
} else {
452-
format!("{}s", num_seconds)
484+
format!("00:00:{:>02}", num_seconds)
453485
};
454486
Ok(tera::to_value(s).unwrap())
455487
},

src/tests.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use tera::Function;
2+
3+
use super::*;
4+
5+
#[test]
6+
fn test_get_timer() {
7+
let get_timer_func = get_timer();
8+
let time_seconds: HashMap<String, tera::Value> = HashMap::from([
9+
("date".to_string(), serde_json::to_value("20250501T035408Z").unwrap())
10+
]);
11+
12+
let result = get_timer_func.call(&time_seconds);
13+
assert_eq!(result.is_ok(), true);
14+
let result_value = result.unwrap();
15+
assert_eq!(result_value, serde_json::to_value("00:00:52").unwrap());
16+
17+
let time_seconds: HashMap<String, tera::Value> = HashMap::from([
18+
("date".to_string(), serde_json::to_value("20250501T033408Z").unwrap())
19+
]);
20+
21+
let result = get_timer_func.call(&time_seconds);
22+
assert_eq!(result.is_ok(), true);
23+
let result_value = result.unwrap();
24+
assert_eq!(result_value, serde_json::to_value("00:20:52").unwrap());
25+
26+
let time_seconds: HashMap<String, tera::Value> = HashMap::from([
27+
("date".to_string(), serde_json::to_value("20250501T023408Z").unwrap())
28+
]);
29+
30+
let result = get_timer_func.call(&time_seconds);
31+
assert_eq!(result.is_ok(), true);
32+
let result_value = result.unwrap();
33+
assert_eq!(result_value, serde_json::to_value("01:20:00").unwrap());
34+
}
35+
36+
#[test]
37+
fn test_get_datetime_iso() {
38+
let get_datetime_iso_func = get_datetime_iso();
39+
let datetime: HashMap<String, tera::Value> = HashMap::from([
40+
("datetime".to_string(), serde_json::to_value("20250501T035408Z").unwrap())
41+
]);
42+
let result = get_datetime_iso_func.call(&datetime);
43+
assert_eq!(result.is_ok(), true);
44+
let result_value = result.unwrap();
45+
assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap());
46+
47+
let datetime: HashMap<String, tera::Value> = HashMap::from([
48+
("datetime".to_string(), serde_json::to_value("2025-05-01T03:54:08Z").unwrap())
49+
]);
50+
let result = get_datetime_iso_func.call(&datetime);
51+
assert_eq!(result.is_ok(), true);
52+
let result_value = result.unwrap();
53+
assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap());
54+
}

0 commit comments

Comments
 (0)