code: public static function checkTimedSectionExit(RunnerServiceContext $context, $nextPosition)
      {
          $timerConfig = $context->getTestConfig()->getConfigValue('timer');
          if (empty($timerConfig['keepUpToTimeout'])) {
              /* @var AssessmentTestSession $session */
              $session = $context->getTestSession();
              $route = $session->getRoute();
              $section = $session->getCurrentAssessmentSection();
              $limits = $section->getTimeLimits();
  
              // As we have only one identifier for the whole adaptive section it will consider a jump of section on the first item
              if(!($context instanceof QtiRunnerServiceContext) || !$context->isAdaptive() ){
                  $isJumpOutOfSection = false;
                  if (($nextPosition >= 0) && ($nextPosition < $route->count())) {
                      $nextSection = $route->getRouteItemAt($nextPosition);
  
                      $isJumpOutOfSection = ($section->getIdentifier() !== $nextSection->getAssessmentSection()->getIdentifier());
                  }
  
                  if ($isJumpOutOfSection && $limits != null && $limits->hasMaxTime()) {
                      $assessmentItemRefs = $section->getComponentsByClassName('assessmentItemRef');
                      foreach ($assessmentItemRefs as $assessmentItemRef) {
                          $itemSessions = $session->getAssessmentItemSessions($assessmentItemRef->getIdentifier());
  
                          if($itemSessions !== false){
                              foreach ($itemSessions as $itemSession) {
                                  $itemSession->endItemSession();
                              }
                          }
                      }
                  }
  
              }
          }
      }
docs: Check if a timed section is exited
  @param RunnerServiceContext $context
  @param int $nextPosition