Why do my milliseconds look wrong in js Stopwatch

Viewed 103

I am making a stopwatch with js, but my milliseconds look wrong, like it is showing the wrong thing. Thought something is wrong with my editor, but it looks the same on repl.it ... I think seconds are showing up in milliseconds or something.

var startTimerButton = document.querySelector('.startTimer');
var pauseTimerButton = document.querySelector('.pauseTimer');
var timerDisplay = document.querySelector('.timer');
var startTime;
var updatedTime;
var difference;
var tInterval;
var savedTime;
var paused = 0;
var running = 0;

function startTimer() {
  if (!running) {
    startTime = new Date().getTime();
    tInterval = setInterval(getShowTime, 1);
    paused = 0;
    running = 1;
    startTimerButton.style.cursor = "auto";
    pauseTimerButton.style.cursor = "pointer";
  }
}

function pauseTimer() {
  if (!difference) {
    // if timer never started, don't allow pause button to do anything
  } else if (!paused) {
    clearInterval(tInterval);
    savedTime = difference;
    paused = 1;
    running = 0;
    pauseTimerButton.classList.add('lighter');
    startTimerButton.style.cursor = "pointer";
    pauseTimerButton.style.cursor = "auto";
  } else {}
}

function resetTimer() {
  document.location.reload();
}

function getShowTime() {
  updatedTime = new Date().getTime();
  if (savedTime) {
    difference = (updatedTime - startTime) + savedTime;
  } else {
    difference = updatedTime - startTime;
  }
  var hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
  var seconds = Math.floor((difference % (1000 * 60)) / 1000);
  var milliseconds = Math.floor((difference % (1000 * 60)) / 100);
  hours = (hours < 10) ? "0" + hours : hours;
  minutes = (minutes < 10) ? "0" + minutes : minutes;
  seconds = (seconds < 10) ? "0" + seconds : seconds;
  milliseconds = (milliseconds < 100) ? (milliseconds < 10) ? "00" + milliseconds : "0" + milliseconds : milliseconds;
  timerDisplay.innerHTML = hours + ':' + minutes + ':' + seconds + ':' + milliseconds;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<h1>Stopwatch</h1>
<div class="timer">00:00:00:00</div>
<div class="startTimer reset" onclick="startTimer()"> Start
  <i class="fas fa-play"></i>
</div>
<div class="pauseTimer reset" onclick="pauseTimer()"> Stop
  <i class="fas fa-pause"></i>
</div>
<div class="resetTimer reset" onclick="resetTimer()">Reset</div>

6 Answers

Your calculation of milliseconds is wrong. Since difference is the total number of milliseconds, all you need to do to get the milliseconds portion of the result is take the modulus of 1000.

var milliseconds = difference % 1000;

var startTimerButton = document.querySelector('.startTimer');
var pauseTimerButton = document.querySelector('.pauseTimer');
var timerDisplay = document.querySelector('.timer');
var startTime;
var updatedTime;
var difference;
var tInterval;
var savedTime;
var paused = 0;
var running = 0;

function startTimer() {
  if (!running) {
    startTime = new Date().getTime();
    tInterval = setInterval(getShowTime, 1);
    paused = 0;
    running = 1;
    startTimerButton.style.cursor = "auto";
    pauseTimerButton.style.cursor = "pointer";
  }
}

function pauseTimer() {
  if (!difference) {
    // if timer never started, don't allow pause button to do anything
  } else if (!paused) {
    clearInterval(tInterval);
    savedTime = difference;
    paused = 1;
    running = 0;
    pauseTimerButton.classList.add('lighter');
    startTimerButton.style.cursor = "pointer";
    pauseTimerButton.style.cursor = "auto";
  } else {}
}

function resetTimer() {
  document.location.reload();
}

function getShowTime() {
  updatedTime = new Date().getTime();
  if (savedTime) {
    difference = (updatedTime - startTime) + savedTime;
  } else {
    difference = updatedTime - startTime;
  }
  var hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
  var seconds = Math.floor((difference % (1000 * 60)) / 1000);
  var milliseconds = difference % 1000;
  hours = (hours < 10) ? "0" + hours : hours;
  minutes = (minutes < 10) ? "0" + minutes : minutes;
  seconds = (seconds < 10) ? "0" + seconds : seconds;
  milliseconds = (milliseconds < 100) ? (milliseconds < 10) ? "00" + milliseconds : "0" + milliseconds : milliseconds;
  timerDisplay.innerHTML = hours + ':' + minutes + ':' + seconds + ':' + milliseconds;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<h1>Stopwatch</h1>
<div class="timer">00:00:00:00</div>
<div class="startTimer reset" onclick="startTimer()"> Start
  <i class="fas fa-play"></i>
</div>
<div class="pauseTimer reset" onclick="pauseTimer()"> Stop
  <i class="fas fa-pause"></i>
</div>
<div class="resetTimer reset" onclick="resetTimer()">Reset</div>

For the milliseconds, you do not need to divide anything, you just mod it by 1,000 because that is how many milliseconds are in a second.

var milliseconds = Math.floor(difference % 1000);

Also, you start time of 00:00:00:00 is missing the final zero i.e. :000.

Creating constants removes a lot of parsing with your brain.

const MS_PER_SEC  = 1000
const MS_PER_MIN  = MS_PER_SEC  * 60
const MS_PER_HOUR = MS_PER_MIN  * 60
const MS_PER_DAY  = MS_PER_HOUR * 24

This makes the formatting function self-documenting.

function formatTime(ms) {
  var hours        = Math.floor((ms % MS_PER_DAY)  / MS_PER_HOUR);
  var minutes      = Math.floor((ms % MS_PER_HOUR) / MS_PER_MIN);
  var seconds      = Math.floor((ms % MS_PER_MIN)  / MS_PER_SEC);
  var milliseconds = Math.floor( ms % MS_PER_SEC);

  return [
    ('' + hours).padStart(2, '0'),
    ('' + minutes).padStart(2, '0'),
    ('' + seconds).padStart(2, '0'),
    ('' + milliseconds).padStart(3, '0')
  ].join(':');
}

Demo

const MS_PER_SEC  = 1000;
const MS_PER_MIN  = MS_PER_SEC  * 60;
const MS_PER_HOUR = MS_PER_MIN  * 60;
const MS_PER_DAY  = MS_PER_HOUR * 24;

var startTimerButton = document.querySelector('.startTimer');
var pauseTimerButton = document.querySelector('.pauseTimer');
var timerDisplay = document.querySelector('.timer');
var startTime;
var updatedTime;
var difference;
var tInterval;
var savedTime;
var paused = 0;
var running = 0;

function startTimer() {
  if (!running) {
    startTime = new Date().getTime();
    tInterval = setInterval(getShowTime, 1);
    paused = 0;
    running = 1;
    startTimerButton.style.cursor = "auto";
    pauseTimerButton.style.cursor = "pointer";
  }
}

function pauseTimer() {
  if (!difference) {
    // if timer never started, don't allow pause button to do anything
  } else if (!paused) {
    clearInterval(tInterval);
    savedTime = difference;
    paused = 1;
    running = 0;
    pauseTimerButton.classList.add('lighter');
    startTimerButton.style.cursor = "pointer";
    pauseTimerButton.style.cursor = "auto";
  } else {}
}

function resetTimer() {
  document.location.reload();
}

function getShowTime() {
  updatedTime = new Date().getTime();
  if (savedTime) {
    difference = (updatedTime - startTime) + savedTime;
  } else {
    difference = updatedTime - startTime;
  }
  
  timerDisplay.innerHTML = formatTime(difference);
}

function formatTime(ms) {
  var hours        = Math.floor((ms % MS_PER_DAY)  / MS_PER_HOUR);
  var minutes      = Math.floor((ms % MS_PER_HOUR) / MS_PER_MIN);
  var seconds      = Math.floor((ms % MS_PER_MIN)  / MS_PER_SEC);
  var milliseconds = Math.floor( ms % MS_PER_SEC);

  return [
    ('' + hours).padStart(2, '0'),
    ('' + minutes).padStart(2, '0'),
    ('' + seconds).padStart(2, '0'),
    ('' + milliseconds).padStart(3, '0')
  ].join(':');
}
.timer {
  font-family: monospace;
  font-size: 2em;
}

.reset {
  display: inline-block;
  border: thin solid grey;
  padding: 0.25em;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<h1>Stopwatch</h1>
<div class="timer">00:00:00:000</div>
<div class="startTimer reset" onclick="startTimer()"> Start
  <i class="fas fa-play"></i>
</div>
<div class="pauseTimer reset" onclick="pauseTimer()"> Stop
  <i class="fas fa-pause"></i>
</div>
<div class="resetTimer reset" onclick="resetTimer()">Reset</div>

your line var milliseconds = Math.floor((difference % (1000 * 60)) / 100); has the wrong calculation. The Date object's getTime method already returns a value in milliseconds.

var startTimerButton = document.querySelector('.startTimer');
var pauseTimerButton = document.querySelector('.pauseTimer');
var timerDisplay = document.querySelector('.timer');
var startTime;
var updatedTime;
var difference;
var tInterval;
var savedTime;
var paused = 0;
var running = 0;

function startTimer() {
  if (!running) {
    startTime = new Date().getTime();
    tInterval = setInterval(getShowTime, 1);
    paused = 0;
    running = 1;
    startTimerButton.style.cursor = "auto";
    pauseTimerButton.style.cursor = "pointer";
  }
}

function pauseTimer() {
  if (!difference) {
    // if timer never started, don't allow pause button to do anything
  } else if (!paused) {
    clearInterval(tInterval);
    savedTime = difference;
    paused = 1;
    running = 0;
    pauseTimerButton.classList.add('lighter');
    startTimerButton.style.cursor = "pointer";
    pauseTimerButton.style.cursor = "auto";
  } else {}
}

function resetTimer() {
  document.location.reload();
}

function getShowTime() {
  updatedTime = new Date().getTime();
  if (savedTime) {
    difference = (updatedTime - startTime) + savedTime;
  } else {
    difference = updatedTime - startTime;
  }
  var hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
  var seconds = Math.floor((difference % (1000 * 60)) / 1000);

  //here!
  var milliseconds = difference % 1000; //<<<<<  this line
  //var milliseconds = Math.floor((difference % (1000 * 60)) / 100); //<<<<<  instead of this
  
  hours = (hours < 10) ? "0" + hours : hours;
  minutes = (minutes < 10) ? "0" + minutes : minutes;
  seconds = (seconds < 10) ? "0" + seconds : seconds;
  milliseconds = (milliseconds < 100) ? (milliseconds < 10) ? "00" + milliseconds : "0" + milliseconds : milliseconds;
  timerDisplay.innerHTML = hours + ':' + minutes + ':' + seconds + ':' + milliseconds;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<h1>Stopwatch</h1>
<div class="timer">00:00:00:00</div>
<div class="startTimer reset" onclick="startTimer()"> Start
  <i class="fas fa-play"></i>
</div>
<div class="pauseTimer reset" onclick="pauseTimer()"> Stop
  <i class="fas fa-pause"></i>
</div>
<div class="resetTimer reset" onclick="resetTimer()">Reset</div>

I changed the code as little as possible, but this is far from being an optimal solution, the code is rather hard to read and might have too many steps.

The main takeaway here is that you don't need to transform a time value in milliseconds if it already is in miliseconds.

Here is a piece of a code sample which would be more readable :

//*********************************
//constants, state variables and object declarations
//*********************************
const MS_PER_SEC = 1000;
const MS_PER_MIN = MS_PER_SEC * 60;
const MS_PER_HOUR = MS_PER_MIN * 60;
const MS_PER_DAY = MS_PER_HOUR * 24;

var running = false;
var startTime = null;
var pausedTimer = null; //elapsed milliseconds already in timer

var timerObject = { //encapsulating data and methods (preferrably in a different file)
  hours: 0,
  minutes: 0,
  seconds: 0,
  milliseconds: 0,

  clearTimer: function() {
    this.hours = 0;
    this.minutes = 0;
    this.seconds = 0;
    this.milliseconds = 0;
  },
  
  setTimer: function(ms) {
    this.hours = Math.floor(ms % (MS_PER_DAY) / MS_PER_HOUR);
    this.minutes = Math.floor(ms % (MS_PER_HOUR) / MS_PER_MIN);
    this.seconds = Math.floor(ms % (MS_PER_MIN) / MS_PER_SEC);
    this.milliseconds = ms % MS_PER_SEC;
  },

  formatTime: function() {
    return [
        ("" + this.hours).padStart(2, '0'),
        ("" + this.minutes).padStart(2, '0'),
        ("" + this.seconds).padStart(2, '0'),
      ].join(":") +
      "." + ('' + this.milliseconds).padStart(3, '0');
  }
};


//*********************************
//Actual functions used in the page
//*********************************
function startTimer() {
  if (!running) { //ignore if already running
    if (startTime != null && pausedTimer != null) {
      //if timer was paused, both values should be != null

      startTime.setTime((new Date()).getTime() - pausedTimer);
      pausedTimer = null; //not necessary, but I like to make stuff crash when I make a mistake
    } else {
      //consider the timer as new or reset otherwise
      startTime = new Date();
    }

    running = true;
    updateTimer();
  }
}

function pauseTimer() {
  if (running) {
    running = false;
    var currentTime = new Date();
    pausedTimer = currentTime.getTime() - startTime.getTime();
  }
  updateTimer();
}

function resetTimer() {
  running = false;
  startTime = null;
  pausedTimer = null;
  timerObject.clearTimer();
  updateTimer();
}

function updateTimer() {
  if (running) {
    var currentTime = new Date();
    var difference = currentTime.getTime() - startTime.getTime();
    timerObject.setTimer(difference);
    setTimeout(updateTimer, 31);
  }


  var timerDisplay = document.getElementById("timer");
  timerDisplay.innerHTML = timerObject.formatTime();
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<h1>Stopwatch</h1>
<div class="timer" id="timer">
  00:00:00:000
</div>
<div class="startTimer reset" onclick="startTimer()">
  Start <i class="fas fa-play"></i>
</div>
<div class="pauseTimer reset" onclick="pauseTimer()">
  Pause <i class="fas fa-pause"></i>
</div>
<div class="resetTimer reset" onclick="resetTimer()">
  Reset
</div>

In this code, it seems like you are setting milliseconds to be one order of magnitude different from seconds, when it should be three orders of magnitude different:

var seconds = Math.floor((difference % (1000 * 60)) / 1000);
var milliseconds = Math.floor((difference % (1000 * 60)) / 100);

First thing:

Use stuff like jsbin.

  var hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
  var seconds = Math.floor((difference % (1000 * 60)) / 1000);
  var milliseconds = Math.floor((difference % (1000 * 60)) % 1e3 );

This should work: https://jsbin.com/yihelay/3/edit?html,js,output

  • You are calculating this times very strange.
  • It is better to use setTimeout instead setInterval (risk of overwriting tid and memory leak)
  • You should not use 1ms interval (its too fast) for user who is using monitor and eyes 30ms-50ms is enough
  • You should not depend on intervals when calculating elapsed time - you should use new Date();

The problem is that the code that calculates the milliseconds.

Instead of...

Math.floor((difference % (1000 * 60)) / 100);

...it should just be...

Math.floor(difference % 1000);

Like this:

var startTimerButton = document.querySelector('.startTimer');
var pauseTimerButton = document.querySelector('.pauseTimer');
var timerDisplay = document.querySelector('.timer');
var startTime;
var updatedTime;
var difference;
var tInterval;
var savedTime;
var paused = 0;
var running = 0;

function startTimer() {
  if (!running) {
    startTime = new Date().getTime();
    tInterval = setInterval(getShowTime, 1);
    paused = 0;
    running = 1;
    startTimerButton.style.cursor = "auto";
    pauseTimerButton.style.cursor = "pointer";
  }
}

function pauseTimer() {
  if (!difference) {
    // if timer never started, don't allow pause button to do anything
  } else if (!paused) {
    clearInterval(tInterval);
    savedTime = difference;
    paused = 1;
    running = 0;
    pauseTimerButton.classList.add('lighter');
    startTimerButton.style.cursor = "pointer";
    pauseTimerButton.style.cursor = "auto";
  } else {}
}

function resetTimer() {
  document.location.reload();
}

function getShowTime() {
  updatedTime = new Date().getTime();
  if (savedTime) {
    difference = (updatedTime - startTime) + savedTime;
  } else {
    difference = updatedTime - startTime;
  }
  var hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  var minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
  var seconds = Math.floor((difference % (1000 * 60)) / 1000);
  var milliseconds = Math.floor(difference % 1000);
  hours = (hours < 10) ? "0" + hours : hours;
  minutes = (minutes < 10) ? "0" + minutes : minutes;
  seconds = (seconds < 10) ? "0" + seconds : seconds;
  milliseconds = (milliseconds < 100) ? (milliseconds < 10) ? "00" + milliseconds : "0" + milliseconds : milliseconds;
  timerDisplay.innerHTML = hours + ':' + minutes + ':' + seconds + ':' + milliseconds;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" rel="stylesheet" />
<h1>Stopwatch</h1>
<div class="timer">00:00:00:00</div>
<div class="startTimer reset" onclick="startTimer()"> Start
  <i class="fas fa-play"></i>
</div>
<div class="pauseTimer reset" onclick="pauseTimer()"> Stop
  <i class="fas fa-pause"></i>
</div>
<div class="resetTimer reset" onclick="resetTimer()">Reset</div>

Related