1

I have made a Javascript clock, when the seconds and minutes hits 60 it will count over 60 and keeps going...

function increment(){
  if(running == 1){
    setTimeout(function(){
      Dtime++;
      var hours = Math.floor(Dtime / 10 / 3600);
      if(hours <= 9){
        hours = "0" + hours;
      }
      var mins = Math.floor(Dtime / 10 / 60);
      if(mins <= 9){
        mins = "0" + mins;
      }
      var secs = Math.floor(Dtime / 10);
      if(secs <= 9){
        secs = "0" + secs;
      }
      document.getElementById("outputt").innerHTML = hours + ":" + mins + ":" + secs;
      increment();
    }, 100);
  }
}

var Dtime=0;
var running = 1;
increment();
<div id="outputt" class="timerClock" value="00:00:00">00:00:00</div>

Basically the clock counts past 60 for minutes and seconds, I would like this to reset to 0 instead of hitting 60. I have not had the time to test hours although I'm sure it would go past 24 as well.

Please can someone explain how this would be done? I am certain its very simple but I have managed to struggle with it for a bit too long and am getting frustrated.

iAmOren
  • 2,760
  • 2
  • 11
  • 23
srcomptn
  • 81
  • 1
  • 8
  • Dtime++; is not accurate way to keep track of time since setTimeout is not accurate – epascarello Sep 29 '20 at 13:48
  • If you wait long enough, the minutes will also spill into and over 60. – iAmOren Sep 29 '20 at 14:22
  • @epascarello It works fine, can you explain how it is not accurate? I have checked the timings are accurate against 2 systems. – srcomptn Sep 29 '20 at 14:34
  • If you move away from page, the clock paused - I just happened to notice that. – iAmOren Sep 29 '20 at 14:38
  • `function fix(x){ var y = x.toString().split('.'); return y[0] + '.' + (y[1] || '').padEnd(3, '0'); } var x = Date.now(); var time = 0; window.setInterval(() => { time++; console.log(fix(time/100), fix((Date.now() - x)/1000)) }, 10);` Run this code for while in the console, leave the tab and come back after a minute or so. Numbers should be equal..... but are they? – epascarello Sep 29 '20 at 14:47
  • @epascarello they arent the same, one is counting really fast and the other is counting accurately – srcomptn Sep 29 '20 at 15:13
  • @iAmOren it is always left on that tab in a seperate window; however, I had not noticed before. How would I fix that? – srcomptn Sep 29 '20 at 15:13
  • As others recommended, and I join them, get `Date.now()` and compare to that. – iAmOren Sep 29 '20 at 15:27

4 Answers4

2

You can use the remainder operator (%) to wrap back to 0 when a number is passing 60.

For example

  • 59 % 60 === 59
  • 60 % 60 === 0
  • 61 % 60 === 1

So your code would look like this:

function increment() {
  if (running == 1) {
    setTimeout(function() {
      Dtime++;
      var hours = Math.floor(Dtime / 10 / 3600);
      if (hours <= 9) {
        hours = "0" + hours;
      }
      var mins = Math.floor(Dtime / 10 / 60) % 60; // Remainder operator
      if (mins <= 9) {
        mins = "0" + mins;
      }
      var secs = Math.floor(Dtime / 10) % 60; // Remainder operator
      if (secs <= 9) {
        secs = "0" + secs;
      }
      document.getElementById("outputt").innerHTML = hours + ":" + mins + ":" + secs;
      increment();
    }, 100);
  }
}

var running = 1;
var Dtime = 35980; //Setting it just under 1 hour for testing purposes
increment();
<div id="outputt" class="timerClock" value="00:00:00">00:00:00</div>

Do note that this timer might drift over time. For why and how you can solve that see How to create an accurate timer in javascript?

Ivar
  • 6,138
  • 12
  • 49
  • 61
  • Nice edit (Setting it just under 1 hour for testing purposes)! I would use `var Dtime = 10 * (0 * 3600 + 59 * 60 + 58); // Set to 0 hours, 59 minutes, and 58 seconds for testing purposes` to be clear(er). I've up-voted your answer (and, as you probably noticed, made it into a snippet). – iAmOren Sep 29 '20 at 14:36
  • If you move away from page, the clock paused - I just happened to notice that. That's not a reflection on your answer - it's on the implementation used by the op - your answer works fine. – iAmOren Sep 29 '20 at 14:40
  • @iAmOren Thanks for the suggestion, but that feels a bit excessive. It's only there for testing purposes and the comment explains what it means. The equation and comment might actually take longer to process than just that one comment. (In production code that might of course be a different story, as it _does_ make it easier to change.) – Ivar Sep 29 '20 at 14:42
  • @iAmOren To your second comment, yes that's one of the downsides of relying on `setTimeout`/`setInterval`. That's why I added a link to [this question](https://stackoverflow.com/questions/29971898/how-to-create-an-accurate-timer-in-javascript) at the end of my answer. – Ivar Sep 29 '20 at 14:43
  • Hi, thank you for your response, that fixed the problem. Ive tested the timer, its perfectly accurate for the purpose will be used. Thanks for the help – srcomptn Sep 29 '20 at 15:00
2

You can't trust that either setTimeout or setInterval will execute in exactly X milliseconds. Check out the following video about event loops to understand why. It's something you should know when programming javascript.

What you instead need to do is to update the output element every time the screen repaints, which you can do with requestAnimationFrame. You need to store when the clock starts, which can be done in milliseconds, and then calculate from that the difference between the starting time and the current time (with Date.now()) when the window is about to do a repaint. requestAnimationFrame takes a callback as a parameter, which should be the method that updates your output element.

EDIT: I sped up the timer to show case the minute and second reset.

const outputElement = document.getElementById("output");
var startTime = 0;

function startTimer() {
  startTime = Date.now();
  updateTimer();
}

function updateTimer() {
  let differenceInMillis = Date.now() - startTime;
  let {hours, minutes, seconds} = calculateTime(differenceInMillis);
  let timeStr = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;

  outputElement.innerText = timeStr;

  requestAnimationFrame(updateTimer);
}

function calculateTime(timeInMillis) {
  const SECONDS = 10; // should be 1000 - only 10 to speed up the timer
  const MINUTES = 60;
  const HOURS   = 60;
  const RESET   = 60;
  
  let hours   = Math.floor(timeInMillis / SECONDS / MINUTES / HOURS);
  let minutes = Math.floor(timeInMillis / SECONDS / MINUTES) % RESET;
  let seconds = Math.floor(timeInMillis / SECONDS) % RESET;
  
  return {hours, minutes, seconds};
}

function pad(time) {
  return time.toString().padStart(2, '0');
}

startTimer();
<div id="output" class="timerClock" value="00:00:00">00:00:00</div>
Rickard Elimää
  • 7,107
  • 3
  • 14
  • 30
  • I updated my post to point out that I deliberately increased the speed of the timer. – Rickard Elimää Sep 29 '20 at 14:45
  • 1
    Excellent! (deleted my comments, up-voted your answer). – iAmOren Sep 29 '20 at 14:47
  • Cheers, mate. :) – Rickard Elimää Sep 29 '20 at 14:48
  • With this code, I was having a go at understanding how it works, its pretty clear to me. Sorry I am pretty new to JS, there is 1 thing I cant seem to implement, I added a start/stop button, the only issue is if I stop it and start it again it will reset to 00:00:00, I have been trying to get it to save and I cant figure it out? Thank you for your answer! – srcomptn Sep 29 '20 at 15:31
  • That's beyond what you asked for. Create a new question for that. :) But I would store the `differenceInMillis` value for each recursive `updateTimer` loop somewhere (like outputElement.value or a global variable) and then subtract that value from `startTime = Date.now()` whenever you (re)start your timer. – Rickard Elimää Sep 29 '20 at 15:53
  • @RickardElimää if you would not mind, please can you check out my new question for this: https://stackoverflow.com/questions/64135439/how-can-i-fix-the-stop-start-process-within-this-javascript-stopwatch-clock - Thank you – srcomptn Sep 30 '20 at 10:29
1

You can use String.padStart to insert 0 symbol for digital values.

And instead of calling function inside setTimeout for recursive, you can use setInterval. To clear the time handler, you can use clearInterval.

And the second param of setInterval is miliseconds value so to count the time by seconds, it will be good to set 1000 - 1 second.

let Dtime = 0;
setInterval(function() {
  Dtime++;
  const hours = Math.floor(Dtime / 3600).toString();
  const mins = Math.floor((Dtime % 3600) / 60).toString();
  const secs = (Dtime % 60).toString();
  document.getElementById("outputt").innerHTML = hours.padStart(2, '0') + ":" + mins.padStart(2, '0') + ":" + secs.padStart(2, '0');
}, 1000);
<div id="outputt" class="timerClock" value="00:00:00">00:00:00</div>
Derek Wang
  • 10,098
  • 4
  • 18
  • 39
0

This is a bit overkill for what you are doing, but it has a nice method for formatting a time string. It also shows how to use Date.now() to keep track of time difference.

class StopWatch { 

  startTime = 0
  previousDuration = 0
  currentDuration = 0
  timer = null;
  listeners = {
    update: [],
    mark: [],
    reset: []
  }
  marks = []
  
  start() {
    if (this.timer) return;
    this.previousDuration += this.currentDuration;
    this.currentDuration = 0;
    this.startTime = Date.now();
    this.run();
  }
  
  stop() {
    if (!this.timer) return;
    this.setDuration();
    window.clearTimeout(this.timer);
    this.timer = null;
  }
  
  run() {
    this.setDuration();
    this.timer = window.setTimeout(this.run.bind(this), 10);
  }
  
  setDuration () {
    this.currentDuration = Date.now() - this.startTime;
    this.trigger('update');
  }
  
  trigger (evtName) {
    const details = {
      time: this.msToTime(this.currentDuration + this.previousDuration),
      marks: [...this.marks]
    };
    this.listeners[evtName].forEach(cb => cb(details));
  }
  
  mark () {
    this.marks.push(this.msToTime(this.currentDuration + this.previousDuration));
    this.trigger('mark');
  }
  
  reset() {
    if (this.timer) window.clearTimeout(this.timer);
    this.currentDuration = 0;
    this.previousDuration = 0;
    this.marks = [];
    this.trigger('update');
    this.trigger('reset');
  }
  
  on (event, callback) {
    var arr = this.listeners[event];
    if (!arr) throw(`unknown event ${event}`);
    arr.push(callback);
    return this;
  }
  
  msToTime (duration) {
    let milliseconds = parseInt((duration % 1000));
    let seconds = Math.floor((duration / 1000) % 60);
    let minutes = Math.floor((duration / (1000 * 60)) % 60);
    let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);

    hours = hours.toString().padStart(2, '0');
    minutes = minutes.toString().padStart(2, '0');
    seconds = seconds.toString().padStart(2, '0');
    milliseconds = milliseconds.toString().padStart(3, '0');

    return hours + ":" + minutes + ":" + seconds + "." + milliseconds;
  }
}

const out = document.querySelector("#out");
const list = document.querySelector("#list");

const myStopWatch = new StopWatch();
myStopWatch
  .on('update', function(data) {
    out.textContent = data.time;
  })
  .on('mark', function(data) {
    var li = document.createElement("li");
    li.textContent = data.marks.pop();
    list.appendChild(li);
  }).on('reset', function () {
    list.innerHTML = '';
  });

const wrapper = document.querySelector("#actions");
wrapper.addEventListener("click", function (evt) {
  const action = evt.target.dataset.action;
  myStopWatch[action]();
});
<div id="out"></div>
<div id="actions">
  <button data-action="start">Start</button>
  <button data-action="stop">Stop</button>
  <button data-action="mark">Mark Time</button>
  <button data-action="reset">Reset</button>
</div>

<ol id="list"></ol>
epascarello
  • 204,599
  • 20
  • 195
  • 236