Skip to content

Commit 1570fa0

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 40753e2 commit 1570fa0

File tree

5 files changed

+135
-44
lines changed

5 files changed

+135
-44
lines changed

frontend/src/main.ts

Lines changed: 2 additions & 39 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

@@ -115,45 +116,7 @@ document.addEventListener('click', function (event) {
115116

116117
document.addEventListener("DOMContentLoaded", function () {
117118
theme.init();
118-
119-
let n = setInterval(
120-
() => {
121-
let whichOne = 0;
122-
// document.getElementById('active-timer').querySelectorAll('span.timer-duration')[0]
123-
let dd = document.getElementById('active-timer');
124-
if (dd === undefined || dd === null) {
125-
return
126-
}
127-
let timeBox = dd.children[1].children[whichOne];
128-
let s = timeBox.textContent.split(":");
129-
let second = parseInt(s.pop());
130-
let minute = parseInt(s.pop());
131-
if (isNaN(minute)) {
132-
minute = 0;
133-
}
134-
let hour = parseInt(s.pop());
135-
if (isNaN(hour)) {
136-
hour = 0;
137-
}
138-
second += 1;
139-
if (second >= 60) {
140-
second = 0;
141-
minute += 1;
142-
if (minute > 60) {
143-
hour += 1;
144-
}
145-
}
146-
timeBox.textContent = hour.toString()
147-
// @ts-ignore
148-
.padStart(2, "0") + ":" +
149-
minute.toString()
150-
// @ts-ignore
151-
.padStart(2, "0") + ":" +
152-
second.toString()
153-
// @ts-ignore
154-
.padStart(2, "0");
155-
}, 1000
156-
)
119+
timer.init();
157120
let day_progress = setInterval(
158121
() => {
159122
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());
@@ -303,6 +308,17 @@ fn update_tag_bar_key_comb() -> impl tera::Filter {
303308
)
304309
}
305310

311+
#[cfg(not(test))]
312+
#[allow(dead_code)]
313+
fn get_utc_now() -> DateTime<Utc> {
314+
chrono::prelude::Utc::now()
315+
}
316+
#[cfg(test)]
317+
#[allow(dead_code)]
318+
fn get_utc_now() -> DateTime<Utc> {
319+
chrono::Utc.with_ymd_and_hms(2025, 5, 1, 3, 55, 0).unwrap()
320+
}
321+
306322
pub struct DeltaNow {
307323
pub now: DateTime<chrono::Utc>,
308324
pub delta: TimeDelta,
@@ -317,7 +333,7 @@ impl DeltaNow {
317333
chrono::prelude::NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M:%SZ").unwrap()
318334
)
319335
.and_utc();
320-
let now = chrono::prelude::Utc::now();
336+
let now = get_utc_now();
321337
let delta = now - time;
322338
Self { now, delta, time }
323339
}
@@ -361,6 +377,22 @@ fn get_date_proper() -> impl tera::Function {
361377
)
362378
}
363379

380+
fn get_datetime_iso() -> impl tera::Function {
381+
Box::new(
382+
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
383+
let date_time_str = args.get("datetime").unwrap().as_str().unwrap();
384+
// we are working with utc time
385+
let date_time = chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y%m%dT%H%M%SZ")
386+
.unwrap_or_else(|_|
387+
// Try taskchampions variant.
388+
chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y-%m-%dT%H:%M:%SZ").unwrap()
389+
)
390+
.and_utc();
391+
Ok(tera::to_value(date_time.to_rfc3339()).unwrap())
392+
},
393+
)
394+
}
395+
364396
fn get_date() -> impl tera::Function {
365397
Box::new(
366398
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
@@ -422,7 +454,7 @@ fn get_timer() -> impl tera::Function {
422454

423455
let s = if delta.num_hours() > 0 {
424456
format!(
425-
"{:>02}:{:>02}",
457+
"{:>02}:{:>02}:00",
426458
delta.num_hours(),
427459
delta.num_minutes() - (delta.num_hours() * 60)
428460
)
@@ -434,7 +466,7 @@ fn get_timer() -> impl tera::Function {
434466
num_seconds % 60
435467
)
436468
} else {
437-
format!("{}s", num_seconds)
469+
format!("00:00:{:>02}", num_seconds)
438470
};
439471
Ok(tera::to_value(s).unwrap())
440472
},

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)