0

I am creating a timed testing product in PHP. I am facing a problem in keeping the exam time properly. When the exam is started, and as long as I am in the same screen, the timer is working properly. But when I open other screens while the test is on (say for 10 minutes), and after sometime comeback to the test, the timer is showing wrong time and showing as if only 2 minutes of the test time consumed in previous 10 minutes.

Just want to know why this erratic behavior of the timer of the test when I move out of the screen while the test is on and come back after sometime.

Could anyone help me with this?

PHP CODE

<?php
/*Timer Code*/
        $dateFormat = "d F Y -- g:i a";
        $targetDate = time() + (240*60);//Change the 240 to however many minutes you want to countdown
        $actualDate = time();
        $secondsDiff = $targetDate - $actualDate;
        $remainingDay     = floor($secondsDiff/60/60/24);
        $remainingHour    = floor(($secondsDiff-($remainingDay*60*60*24))/60/60);
        $remainingMinutes = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))/60);
        $remainingSeconds = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))-($remainingMinutes*60));
        $actualDateDisplay = date($dateFormat,$actualDate);
        $targetDateDisplay = date($dateFormat,$targetDate);
?>

JavaScript

/*Timer Code*/
  var days = <?php echo $remainingDay; ?>  
  var hours = <?php echo $remainingHour; ?>  
  var minutes = <?php echo $remainingMinutes; ?>  
  var seconds = <?php echo $remainingSeconds; ?> 

function setCountDown ()
{
  seconds--;
  if (seconds < 0){
      minutes--;
      seconds = 59
  }
  if (minutes < 0){
      hours--;
      minutes = 59
  }
  if (hours < 0){
      days--;
      hours = 23
  }
  document.getElementById("remain").innerHTML = "Time "+hours+" H : "+minutes+" M : "+seconds+" S";
  SD=window.setTimeout( "setCountDown()", 1000 );
  if (hours == '0' && minutes == '00' && seconds == '00') 
  { 
      seconds = "00"; window.clearTimeout(SD);
      timeStatus = 1;
      var response = window.alert("Time is up. Press OK to continue."); // change timeout message as required         
  } } // here is the end of setCountDown
nani0077
  • 119
  • 14
  • 3
    Is it really ` – Qirel Sep 06 '17 at 15:14
  • 2
    not sure what the issue is, but something to be aware of - Javascript can be bypassed/manipulated by any user just by opening the browser tools or simply switching it off. So on the server, keep a timestamp record of when this user started the test. When the user submits their results, check the submission time against the timestamp to see if they completed it on time. If you don't do that, you have no guarantee at all that they didn't go over time. You can't rely on any code in the browser to actually get executed. like I said this is just a side point, not the answer to the specific question – ADyson Sep 06 '17 at 15:14
  • @Qirel I wrote php functions to calculate time and assigned those values to javascript. – nani0077 Sep 06 '17 at 15:18
  • No, but look at the PHP "opening tag": do you really have ` – Qirel Sep 06 '17 at 15:19
  • Are you actually navigating away from this page, going to other pages in the same window, and then later loading it again? Or are opening _new_ windows, with this one still open, and when switching back to this open window noticing that the timer has not gone down as much as expected? – Patrick Q Sep 06 '17 at 15:19
  • when user present in same window timer is matched with system time. for example user surfing something an other window in the same browser for 10 to 15 min. after coming back to exam window, exam timer is not matching with system time. – nani0077 Sep 06 '17 at 15:21
  • 2
    you can bypass the whole issue just by passing the ending date in js, then on every tick of setTimeout you recalculate what the timer displays – maioman Sep 06 '17 at 15:21
  • Also, there is no need for you to have setTimeout on a window level. – ren.rocks Sep 06 '17 at 15:23
  • @PatrickQ Exactly, that is my problem – nani0077 Sep 06 '17 at 15:23
  • @Qirel I edited please check out – nani0077 Sep 06 '17 at 15:26

3 Answers3

1

This works for me with the listed changes:

PHP:

<?php
/*Timer Code*/
        $dateFormat = "d F Y -- g:i a";
        $targetDate = time() + (240*60);//Change the 240 to however many minutes you want to countdown
        $actualDate = time();
        $secondsDiff = $targetDate - $actualDate;
        $remainingDay     = floor($secondsDiff/60/60/24);
        $remainingHour    = floor(($secondsDiff-($remainingDay*60*60*24))/60/60);
        $remainingMinutes = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))/60);
        $remainingSeconds = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))-($remainingMinutes*60));
        $actualDateDisplay = date($dateFormat,$actualDate);
        $targetDateDisplay = date($dateFormat,$targetDate);
?>
  • PHP opening should be

JavaScript:

/*Timer Code*/
  var days = 0;
  var hours = 4;
  var minutes = 0;
  var seconds = 0;

  setCountDown();

function setCountDown ()
{
  seconds--;
  if (seconds < 0){
      minutes--;
      seconds = 59
  }
  if (minutes < 0){
      hours--;
      minutes = 59
  }
  if (hours < 0){
      days--;
      hours = 23
  }
  if (hours == '0' && minutes == '00' && seconds == '00') 
  { 
      seconds = "00"; 
      window.clearTimeout(SD);
      timeStatus = 1;
      var response = window.alert("Time is up. Press OK to continue."); // change timeout message as required         
  } 
  console.log(days+'/'+hours+'/'+minutes+'/'+seconds);
  SD=setTimeout(setCountDown, 1000);
}
  • setCountDown was missing it's closing bracket
  • setTimeout should be called with just the function's name. Calling it with () after the name is passing the returned value into setTimeout, not the function itself. (This last point is not relevant to your question as you had the function call in quotes)
  • The re-calling of the function should really come after you check if you're at 0.

For a more accurate timer system, consider using timestamps from the server and user's computer instead of variables that can be manipulated. Then you can continuously pull the current time from the user's computer to see how much time has passed.

John Denver
  • 362
  • 1
  • 12
  • ReferenceError: SD is not defined. I found this error in your code – nani0077 Sep 06 '17 at 15:52
  • Yes, it should be SD = setTimeout(setCountDown, 1000); – John Denver Sep 06 '17 at 16:08
  • Abiut the parentheses after setTimeout : the OP code needed them as it was within a string. But passing a string is considered bad practice. – trincot Sep 06 '17 at 17:02
  • How do you mean? @trincot – John Denver Sep 06 '17 at 17:05
  • @MatthewSabatini, this works fine: `setTimeout("setCountDown()", 1000)` and this also: `setTimeout(setCountDown, 1000)`. What would not have worked, but was not anywhere in the original code, is `setTimeout(setCountDown(), 1000)`. So your one-but-last bullet point is not really accurate concerning the OP's code. – trincot Sep 06 '17 at 17:23
1

JavaScript will not always process timer ticks when not running in the foreground, so that is why you get slow-downs.

It would be more reliable if you would:

  • Use a target deadline in JS, which you translate to the remaining time every time you handle a timer event. This way it will always relate to the actual deadline.
  • Refresh the deadline information from the server from time to time (not every second, but maybe every 5 minutes). This means you'll have to use some kind of persistence on the server-side, like a session scope variable that has the deadline with which you can return the number of seconds left.
  • Verify at the server side that a submission of answers does not happen beyond the deadline (if someone tampered with the client). Again, you would need some session scope usage for implementing this.

I will not provide the code for the second or third idea, but here is the code adapted to the first idea (which may be enough for your purposes?):

/*Timer Code*/
var totalSeconds = 240*60; //<?php echo 240*60; ?>  
var deadlineMillis = Date.now() + totalSeconds * 1000;

function setCountDown () {
    var secondsRemaining = Math.max(0, Math.round((deadlineMillis - Date.now()) / 1000)),
        minutes = Math.floor(secondsRemaining / 60),
        hours = Math.floor(minutes / 60),
        days = Math.floor(hours / 24);
    hours = hours % 24;
    minutes = ('0' + (minutes % 60)).substr(-2);
    seconds = ('0' + (secondsRemaining % 60)).substr(-2);
    document.getElementById("remain").textContent = 
        "Time "+hours+" H : "+minutes+" M : "+seconds+" S";
    if (secondsRemaining === 0) { 
        timeStatus = 1;
        alert("Time is up. Press OK to continue."); // change timeout message as required
        return;
    }
    setTimeout(setCountDown, 1000);
}

setCountDown();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="remain"></div>

Replace the 240*60 by a PHP generated value.

trincot
  • 317,000
  • 35
  • 244
  • 286
0

Most modern browser intentionally put lower priority on inactive tabs/windows. Thus the timer will be slowed down. So what you face is not erratic behavior. It is by design to increase browser performance.

https://developers.google.com/web/updates/2017/03/background_tabs#budget-based_background_timer_throttling

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Timeouts_in_inactive_tabs_throttled_to_>1000ms

Solution: use web worker

Kamal
  • 1,922
  • 2
  • 23
  • 37