0

I am using the solution in this answer to create an accurate javascript timer. The code works fine. But I want it to have a fixed interval option. like it need to output 100, 200, 300, so on..,

Math.round(timer.getTime() / 1000); this code rounds the value to the nearest second so timer goes 1, 2, 3, 4, 5...

I tried adjusting the duration of setInterval to 50 and rounding to nearest 100, it works almost fine to my needs. but it is repeating numbers and sometimes skips few numbers.

And also I need some timer to start from a specific number, say from 400 to 10000. the class has 0 as default. How do I go about implementing that?

please find the codepen for more code and details: https://codepen.io/gurgoon32/pen/KKzYYKG

class Timer {
  constructor() {
    this.isRunning = false;
    this.startTime = 0;
    this.overallTime = 0;
  }

  _getTimeElapsedSinceLastStart() {
    if (!this.startTime) {
      return 0;
    }

    return Date.now() - this.startTime;
  }

  start() {
    if (this.isRunning) {
      return console.error('Timer is already running');
    }

    this.isRunning = true;

    this.startTime = Date.now();
  }

  stop() {
    if (!this.isRunning) {
      return console.error('Timer is already stopped');
    }

    this.isRunning = false;

    this.overallTime = this.overallTime + this._getTimeElapsedSinceLastStart();
  }

  reset() {
    this.overallTime = 0;

    if (this.isRunning) {
      this.startTime = Date.now();
      return;
    }

    this.startTime = 0;
  }

  getTime() {
    if (!this.startTime) {
      return 0;
    }

    if (this.isRunning) {
      return this.overallTime + this._getTimeElapsedSinceLastStart();
    }

    return this.overallTime;
  }
}

let the_interval;

function round_nearest_hundred(num) {
  return Math.round(num / 100) * 100;
}

function onUpdateTimer(duration) {
  const timeInSeconds = Math.round(timer.getTime() / 1000);
  document.getElementById('time').innerText = timeInSeconds;

  document.getElementById('accTime').innerText = round_nearest_hundred(timer.getTime());

  console.log(round_nearest_hundred(timer.getTime()));

  if (round_nearest_hundred(timer.getTime()) >= duration) {
    document.getElementById('status').innerText = 'complete';
    timer.stop();
    timer_manager(false);
  }
}

function timer_manager(flag, updateFunction, time, duration) {
  if (flag) {
    the_interval = setInterval(function() {
      updateFunction(duration);
    }, time);
  } else {
    clearInterval(the_interval);
  }
}



const timer = new Timer();
//timer.start();

timer_manager(true, onUpdateTimer, 50, 10000);

document.getElementById('start').addEventListener('click', function() {
  timer.start();
});

document.getElementById('stop').addEventListener('click', function() {
  timer.stop();
});

document.getElementById('restart').addEventListener('click', function() {
  timer_manager(true, onUpdateTimer, 100, 10000);
  timer.reset();
  timer.start();
});
<p>Elapsed time: <span id="time">0</span>s</p>
<p id="accTime"></p>
<p id="status"></p>
<button id="start">start</button>
<button id="stop">pause</button>
<button id="restart">restart</button>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
toddash
  • 167
  • 2
  • 17

2 Answers2

1

In order to provide a start time value, you simply have to modify the constructor so that it accepts the start time as a parameter:

class Timer {
  constructor (startTime = 0) {
    this.isRunning = false;
    this.startTime = startTime;
    this.overallTime = startTime;
  }
  // ...   
}

const timer1 = new Timer(); // timer1 will start from 0
const timer2 = new Timer(500); // timer2 will start from 500

For what concerns the interval, if you want to display hundredths of seconds rounded to hundreds (e.g. 100, 200, 300, ...), you have to set the interval time to 10ms and use the function "round_nearest_hundred()" you already have:

// ...

function onUpdateTimer(duration) {
  //const timeInSeconds = Math.round(timer.getTime() / 1000);
  //document.getElementById('time').innerText = timeInSeconds;
  const timeInHundredthsOfSeconds = round_nearest_hundred(timer.getTime());
  document.getElementById('time').innerText = HundredthsOf;

  document.getElementById('accTime').innerText = round_nearest_hundred(timer.getTime());

  console.log(round_nearest_hundred(timer.getTime()));

  if (round_nearest_hundred(timer.getTime()) >= duration) {
    document.getElementById('status').innerText = 'complete';
    timer.stop();
    timer_manager(false);
  }
}

// ...

timer_manager(true, onUpdateTimer, 10, 10000);

// ...
secan
  • 2,622
  • 1
  • 7
  • 24
  • is using setInterval to 10 ms a good Idea? Like wouldn't it be consuming more resources. I'm not sure i'm new. – toddash Oct 01 '20 at 09:03
  • @toddash of course the smaller the interval is, the higher the number of executions is... but if you want/need the counter to update every centisecond you have no choice as 1cs is equal to 10ms. If you are happy with updating every decisecond (which would make sense in this case, as centiseconds are anyway rounded to deciseconds) then by all means set the interval to 100ms. It all boils down on what the requirements are. – secan Oct 01 '20 at 09:32
1

Give this a try:

class Timer {
  constructor () {
    this.isRunning = false;
    this.startTime = 0;
    this.overallTime = 0;
  }

  _getTimeElapsedSinceLastStart () {
    if (!this.startTime) {
      return 0;
    }
  
    return Date.now() - this.startTime;
  }

  start () {
    if (this.isRunning) {
      return console.error('Timer is already running');
    }

    this.isRunning = true;

    this.startTime = Date.now();
  }

  stop () {
    if (!this.isRunning) {
      return console.error('Timer is already stopped');
    }

    this.isRunning = false;

    this.overallTime = this.overallTime + this._getTimeElapsedSinceLastStart();
  }

  reset () {
    this.overallTime = 0;

    if (this.isRunning) {
      this.startTime = Date.now();
      return;
    }

    this.startTime = 0;
  }

  getTime () {
    if (!this.startTime) {
      return 0;
    }

    if (this.isRunning) {
      return this.overallTime + this._getTimeElapsedSinceLastStart();
    }

    return this.overallTime;
  }
}

let the_interval;

function round_nearest_hundred(num){
        return Math.floor(num / 100)*100;
}

function onUpdateTimer(start, duration){
  const startTime = timer.getTime() + start;
  const timeInSeconds = Math.floor(timer.getTime() / 1000);
  document.getElementById('time').innerText = timeInSeconds;
  
  document.getElementById('accTime').innerText = round_nearest_hundred(startTime);
  
  console.log(round_nearest_hundred(startTime));
  
  if(round_nearest_hundred(timer.getTime()) >= (duration - start)){
    document.getElementById('status').innerText = 'complete';
    timer.stop();
    timer_manager(false);
  }
}

function timer_manager(flag, updateFunction, time, start, duration){
  if(flag){
    the_interval =  setInterval(function(){updateFunction(start, duration);}, time);
  } else {
    clearInterval(the_interval);
  }   
}



const timer = new Timer();
//timer.start();

timer_manager(true, onUpdateTimer, 50, 1000, 10000);

document.getElementById('start').addEventListener('click', function(){
  timer.start();
});

document.getElementById('stop').addEventListener('click', function(){
  timer.stop();
});

document.getElementById('restart').addEventListener('click', function(){
  timer_manager(false);
  timer_manager(true, onUpdateTimer, 100, 0, 10000);
  timer.reset();
  timer.start();
});
<p>Elapsed time: <span id="time">0</span>s</p>
<p id="accTime"></p>
<p id="status"></p>
<button id="start">start</button>
<button id="stop">pause</button>
<button id="restart">restart</button>
  1. Math.round() is a little confusing in this context because it means 500ms rounds up to 1s, making you think you've reached that time before you really have. It's better to use Math.floor() (rounds down) so that you only see the number of seconds that have really elapsed. I think this should solve the skipping issue.

  2. In the Reset function, it's good practice to clear the last interval before setting a new one. It seems that otherwise you would be running two setInterval functions, with only the latest one being referred to by variable the_interval.

  3. To start from a specific number, I've added an argument (start) to the timer_manager function. This gets passed to the onUpdateTimer function, because that is where your logic decides if the timer has finished or not (clearly, that would depend on which value it started from). Finally, also in that function, it's up to you whether you want to display to the user the actual number of seconds elapsed (e.g. start: 1s, end: 10s, elapsed: 9s), or that plus the starting point (e.g. start: 1s, end: 10s, elapsed: 10s).

sbgib
  • 5,580
  • 3
  • 19
  • 26