docs: def eval(self, now=None):
          '''
          Evaluate and execute the schedule
  
          :param datetime now: Override current time with a datetime object instance``
  
          '''
  
          log.trace('==== evaluating schedule now %s =====', now)
  
          loop_interval = self.opts['loop_interval']
          if not isinstance(loop_interval, datetime.timedelta):
              loop_interval = datetime.timedelta(seconds=loop_interval)
  
          def _splay(splaytime):
              '''
              Calculate splaytime
              '''
              splay_ = None
              if isinstance(splaytime, dict):
                  if splaytime['end'] >= splaytime['start']:
                      splay_ = random.randint(splaytime['start'],
                                              splaytime['end'])
                  else:
                      log.error('schedule.handle_func: Invalid Splay, '
                                'end must be larger than start. Ignoring splay.')
              else:
                  splay_ = random.randint(1, splaytime)
              return splay_
  
          def _handle_time_elements(data):
              '''
              Handle schedule item with time elements
              seconds, minutes, hours, days
              '''
              if '_seconds' not in data:
                  interval = int(data.get('seconds', 0))
                  interval += int(data.get('minutes', 0)) * 60
                  interval += int(data.get('hours', 0)) * 3600
                  interval += int(data.get('days', 0)) * 86400
  
                  data['_seconds'] = interval
  
                  if not data['_next_fire_time']:
                      data['_next_fire_time'] = now + datetime.timedelta(seconds=data['_seconds'])
  
                  if interval < self.loop_interval:
                      self.loop_interval = interval
  
                  data['_next_scheduled_fire_time'] = now + datetime.timedelta(seconds=data['_seconds'])
  
          def _handle_once(data, loop_interval):
              '''
              Handle schedule item with once
              '''
              if data['_next_fire_time']:
                  if data['_next_fire_time'] < now - loop_interval or \
                     data['_next_fire_time'] > now and \
                     not data['_splay']:
                      data['_continue'] = True
  
              if not data['_next_fire_time'] and \
                      not data['_splay']:
                  once = data['once']
                  if not isinstance(once, datetime.datetime):
                      once_fmt = data.get('once_fmt', '%Y-%m-%dT%H:%M:%S')
                      try:
                          once = datetime.datetime.strptime(data['once'],
                                                            once_fmt)
                      except (TypeError, ValueError):
                          data['_error'] = ('Date string could not '
                                            'be parsed: {0}, {1}. '
                                            'Ignoring job {2}.'.format(
                                                data['once'],
                                                once_fmt,
                                                data['name']))
                          log.error(data['_error'])
                          return
                  data['_next_fire_time'] = once
                  data['_next_scheduled_fire_time'] = once
                  # If _next_fire_time is less than now, continue
                  if once < now - loop_interval:
                      data['_continue'] = True
  
          def _handle_when(data, loop_interval):
              '''
              Handle schedule item with when
              '''
              if not _WHEN_SUPPORTED:
                  data['_error'] = ('Missing python-dateutil. '
                                    'Ignoring job {0}.'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              if not isinstance(data['when'], list):
                  _when_data = [data['when']]
              else:
                  _when_data = data['when']
  
              _when = []
              for i in _when_data:
                  if ('pillar' in self.opts and 'whens' in self.opts['pillar'] and
                          i in self.opts['pillar']['whens']):
                      if not isinstance(self.opts['pillar']['whens'],
                                        dict):
                          data['_error'] = ('Pillar item "whens" '
                                            'must be a dict. '
                                            'Ignoring job {0}.'.format(data['name']))
                          log.error(data['_error'])
                          return
                      when_ = self.opts['pillar']['whens'][i]
                  elif ('whens' in self.opts['grains'] and
                        i in self.opts['grains']['whens']):
                      if not isinstance(self.opts['grains']['whens'],
                                        dict):
                          data['_error'] = ('Grain "whens" must be a dict. '
                                            'Ignoring job {0}.'.format(data['name']))
                          log.error(data['_error'])
                          return
                      when_ = self.opts['grains']['whens'][i]
                  else:
                      when_ = i
  
                  if not isinstance(when_, datetime.datetime):
                      try:
                          when_ = dateutil_parser.parse(when_)
                      except ValueError:
                          data['_error'] = ('Invalid date string {0}. '
                                            'Ignoring job {1}.'.format(i, data['name']))
                          log.error(data['_error'])
                          return
  
                  _when.append(when_)
  
              if data['_splay']:
                  _when.append(data['_splay'])
  
              # Sort the list of "whens" from earlier to later schedules
              _when.sort()
  
              # Copy the list so we can loop through it
              for i in copy.deepcopy(_when):
                  if len(_when) > 1:
                      if i < now - loop_interval:
                          # Remove all missed schedules except the latest one.
                          # We need it to detect if it was triggered previously.
                          _when.remove(i)
  
              if _when:
                  # Grab the first element, which is the next run time or
                  # last scheduled time in the past.
                  when = _when[0]
  
                  if when < now - loop_interval and \
                          not data.get('_run', False) and \
                          not data.get('run', False) and \
                          not data['_splay']:
                      data['_next_fire_time'] = None
                      data['_continue'] = True
                      return
  
                  if '_run' not in data:
                      # Prevent run of jobs from the past
                      data['_run'] = bool(when >= now - loop_interval)
  
                  if not data['_next_fire_time']:
                      data['_next_fire_time'] = when
  
                  data['_next_scheduled_fire_time'] = when
  
                  if data['_next_fire_time'] < when and \
                          not run and \
                          not data['_run']:
                      data['_next_fire_time'] = when
                      data['_run'] = True
  
              elif not data.get('_run', False):
                  data['_next_fire_time'] = None
                  data['_continue'] = True
  
          def _handle_cron(data, loop_interval):
              '''
              Handle schedule item with cron
              '''
              if not _CRON_SUPPORTED:
                  data['_error'] = ('Missing python-croniter. '
                                    'Ignoring job {0}.'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              if data['_next_fire_time'] is None:
                  # Get next time frame for a "cron" job if it has been never
                  # executed before or already executed in the past.
                  try:
                      data['_next_fire_time'] = croniter.croniter(data['cron'], now).get_next(datetime.datetime)
                      data['_next_scheduled_fire_time'] = croniter.croniter(data['cron'], now).get_next(datetime.datetime)
                  except (ValueError, KeyError):
                      data['_error'] = ('Invalid cron string. '
                                        'Ignoring job {0}.'.format(data['name']))
                      log.error(data['_error'])
                      return
  
                  # If next job run is scheduled more than 1 minute ahead and
                  # configured loop interval is longer than that, we should
                  # shorten it to get our job executed closer to the beginning
                  # of desired time.
                  interval = (now - data['_next_fire_time']).total_seconds()
                  if interval >= 60 and interval < self.loop_interval:
                      self.loop_interval = interval
  
          def _handle_run_explicit(data, loop_interval):
              '''
              Handle schedule item with run_explicit
              '''
              _run_explicit = []
              for _run_time in data['run_explicit']:
                  if isinstance(_run_time, datetime.datetime):
                      _run_explicit.append(_run_time)
                  else:
                      _run_explicit.append(datetime.datetime.strptime(_run_time['time'],
                                                                      _run_time['time_fmt']))
              data['run'] = False
  
              # Copy the list so we can loop through it
              for i in copy.deepcopy(_run_explicit):
                  if len(_run_explicit) > 1:
                      if i < now - loop_interval:
                          _run_explicit.remove(i)
  
              if _run_explicit:
                  if _run_explicit[0] <= now < _run_explicit[0] + loop_interval:
                      data['run'] = True
                      data['_next_fire_time'] = _run_explicit[0]
  
          def _handle_skip_explicit(data, loop_interval):
              '''
              Handle schedule item with skip_explicit
              '''
              data['run'] = False
  
              _skip_explicit = []
              for _skip_time in data['skip_explicit']:
                  if isinstance(_skip_time, datetime.datetime):
                      _skip_explicit.append(_skip_time)
                  else:
                      _skip_explicit.append(datetime.datetime.strptime(_skip_time['time'],
                                                                       _skip_time['time_fmt']))
  
              # Copy the list so we can loop through it
              for i in copy.deepcopy(_skip_explicit):
                  if i < now - loop_interval:
                      _skip_explicit.remove(i)
  
              if _skip_explicit:
                  if _skip_explicit[0] <= now <= (_skip_explicit[0] + loop_interval):
                      if self.skip_function:
                          data['run'] = True
                          data['func'] = self.skip_function
                      else:
                          data['_skip_reason'] = 'skip_explicit'
                          data['_skipped_time'] = now
                          data['_skipped'] = True
                          data['run'] = False
              else:
                  data['run'] = True
  
          def _handle_skip_during_range(data, loop_interval):
              '''
              Handle schedule item with skip_explicit
              '''
              if not _RANGE_SUPPORTED:
                  data['_error'] = ('Missing python-dateutil. '
                                    'Ignoring job {0}.'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              if not isinstance(data['skip_during_range'], dict):
                  data['_error'] = ('schedule.handle_func: Invalid, range '
                                    'must be specified as a dictionary. '
                                    'Ignoring job {0}.'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              start = data['skip_during_range']['start']
              end = data['skip_during_range']['end']
              if not isinstance(start, datetime.datetime):
                  try:
                      start = dateutil_parser.parse(start)
                  except ValueError:
                      data['_error'] = ('Invalid date string for start in '
                                        'skip_during_range. Ignoring '
                                        'job {0}.'.format(data['name']))
                      log.error(data['_error'])
                      return
  
              if not isinstance(end, datetime.datetime):
                  try:
                      end = dateutil_parser.parse(end)
                  except ValueError:
                      data['_error'] = ('Invalid date string for end in '
                                        'skip_during_range. Ignoring '
                                        'job {0}.'.format(data['name']))
                      log.error(data['_error'])
                      return
  
              # Check to see if we should run the job immediately
              # after the skip_during_range is over
              if 'run_after_skip_range' in data and \
                 data['run_after_skip_range']:
                  if 'run_explicit' not in data:
                      data['run_explicit'] = []
                  # Add a run_explicit for immediately after the
                  # skip_during_range ends
                  _run_immediate = (end + loop_interval).strftime('%Y-%m-%dT%H:%M:%S')
                  if _run_immediate not in data['run_explicit']:
                      data['run_explicit'].append({'time': _run_immediate,
                                                   'time_fmt': '%Y-%m-%dT%H:%M:%S'})
  
              if end > start:
                  if start <= now <= end:
                      if self.skip_function:
                          data['run'] = True
                          data['func'] = self.skip_function
                      else:
                          data['_skip_reason'] = 'in_skip_range'
                          data['_skipped_time'] = now
                          data['_skipped'] = True
                          data['run'] = False
                  else:
                      data['run'] = True
              else:
                  data['_error'] = ('schedule.handle_func: Invalid '
                                    'range, end must be larger than '
                                    'start. Ignoring job {0}.'.format(data['name']))
                  log.error(data['_error'])
  
          def _handle_range(data):
              '''
              Handle schedule item with skip_explicit
              '''
              if not _RANGE_SUPPORTED:
                  data['_error'] = ('Missing python-dateutil. '
                                    'Ignoring job {0}'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              if not isinstance(data['range'], dict):
                  data['_error'] = ('schedule.handle_func: Invalid, range '
                                    'must be specified as a dictionary.'
                                    'Ignoring job {0}.'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              start = data['range']['start']
              end = data['range']['end']
              if not isinstance(start, datetime.datetime):
                  try:
                      start = dateutil_parser.parse(start)
                  except ValueError:
                      data['_error'] = ('Invalid date string for start. '
                                        'Ignoring job {0}.'.format(data['name']))
                      log.error(data['_error'])
                      return
  
              if not isinstance(end, datetime.datetime):
                  try:
                      end = dateutil_parser.parse(end)
                  except ValueError:
                      data['_error'] = ('Invalid date string for end.'
                                        ' Ignoring job {0}.'.format(data['name']))
                      log.error(data['_error'])
                      return
  
              if end > start:
                  if 'invert' in data['range'] and data['range']['invert']:
                      if now <= start or now >= end:
                          data['run'] = True
                      else:
                          data['_skip_reason'] = 'in_skip_range'
                          data['run'] = False
                  else:
                      if start <= now <= end:
                          data['run'] = True
                      else:
                          if self.skip_function:
                              data['run'] = True
                              data['func'] = self.skip_function
                          else:
                              data['_skip_reason'] = 'not_in_range'
                              data['run'] = False
              else:
                  data['_error'] = ('schedule.handle_func: Invalid '
                                    'range, end must be larger '
                                    'than start. Ignoring job {0}.'.format(data['name']))
                  log.error(data['_error'])
  
          def _handle_after(data):
              '''
              Handle schedule item with after
              '''
              if not _WHEN_SUPPORTED:
                  data['_error'] = ('Missing python-dateutil. '
                                    'Ignoring job {0}'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              after = data['after']
              if not isinstance(after, datetime.datetime):
                  after = dateutil_parser.parse(after)
  
              if after >= now:
                  log.debug(
                      'After time has not passed skipping job: %s.',
                      data['name']
                  )
                  data['_skip_reason'] = 'after_not_passed'
                  data['_skipped_time'] = now
                  data['_skipped'] = True
                  data['run'] = False
              else:
                  data['run'] = True
  
          def _handle_until(data):
              '''
              Handle schedule item with until
              '''
              if not _WHEN_SUPPORTED:
                  data['_error'] = ('Missing python-dateutil. '
                                    'Ignoring job {0}'.format(data['name']))
                  log.error(data['_error'])
                  return
  
              until = data['until']
              if not isinstance(until, datetime.datetime):
                  until = dateutil_parser.parse(until)
  
              if until <= now:
                  log.debug(
                      'Until time has passed skipping job: %s.',
                      data['name']
                  )
                  data['_skip_reason'] = 'until_passed'
                  data['_skipped_time'] = now
                  data['_skipped'] = True
                  data['run'] = False
              else:
                  data['run'] = True
  
          def _chop_ms(dt):
              '''
              Remove the microseconds from a datetime object
              '''
              return dt - datetime.timedelta(microseconds=dt.microsecond)
  
          schedule = self._get_schedule()
          if not isinstance(schedule, dict):
              raise ValueError('Schedule must be of type dict.')
          if 'skip_function' in schedule:
              self.skip_function = schedule['skip_function']
          if 'skip_during_range' in schedule:
              self.skip_during_range = schedule['skip_during_range']
          if 'enabled' in schedule:
              self.enabled = schedule['enabled']
          if 'splay' in schedule:
              self.splay = schedule['splay']
  
          _hidden = ['enabled',
                     'skip_function',
                     'skip_during_range',
                     'splay']
          for job, data in six.iteritems(schedule):
  
              # Skip anything that is a global setting
              if job in _hidden:
                  continue
  
              # Clear these out between runs
              for item in ['_continue',
                           '_error',
                           '_enabled',
                           '_skipped',
                           '_skip_reason',
                           '_skipped_time']:
                  if item in data:
                      del data[item]
              run = False
  
              if 'name' in data:
                  job_name = data['name']
              else:
                  job_name = data['name'] = job
  
              if not isinstance(data, dict):
                  log.error(
                      'Scheduled job "%s" should have a dict value, not %s',
                      job_name, type(data)
                  )
                  continue
              if 'function' in data:
                  func = data['function']
              elif 'func' in data:
                  func = data['func']
              elif 'fun' in data:
                  func = data['fun']
              else:
                  func = None
              if not isinstance(func, list):
                  func = [func]
              for _func in func:
                  if _func not in self.functions:
                      log.info(
                          'Invalid function: %s in scheduled job %s.',
                          _func, job_name
                      )
  
              if '_next_fire_time' not in data:
                  data['_next_fire_time'] = None
  
              if '_splay' not in data:
                  data['_splay'] = None
  
              if 'run_on_start' in data and \
                      data['run_on_start'] and \
                      '_run_on_start' not in data:
                  data['_run_on_start'] = True
  
              if not now:
                  now = datetime.datetime.now()
  
              # Used for quick lookups when detecting invalid option
              # combinations.
              schedule_keys = set(data.keys())
  
              time_elements = ('seconds', 'minutes', 'hours', 'days')
              scheduling_elements = ('when', 'cron', 'once')
  
              invalid_sched_combos = [
                  set(i) for i in itertools.combinations(scheduling_elements, 2)
              ]
  
              if any(i <= schedule_keys for i in invalid_sched_combos):
                  log.error(
                      'Unable to use "%s" options together. Ignoring.',
                      '", "'.join(scheduling_elements)
                  )
                  continue
  
              invalid_time_combos = []
              for item in scheduling_elements:
                  all_items = itertools.chain([item], time_elements)
                  invalid_time_combos.append(
                      set(itertools.combinations(all_items, 2)))
  
              if any(set(x) <= schedule_keys for x in invalid_time_combos):
                  log.error(
                      'Unable to use "%s" with "%s" options. Ignoring',
                      '", "'.join(time_elements),
                      '", "'.join(scheduling_elements)
                  )
                  continue
  
              if 'run_explicit' in data:
                  _handle_run_explicit(data, loop_interval)
                  run = data['run']
  
              if True in [True for item in time_elements if item in data]:
                  _handle_time_elements(data)
              elif 'once' in data:
                  _handle_once(data, loop_interval)
              elif 'when' in data:
                  _handle_when(data, loop_interval)
              elif 'cron' in data:
                  _handle_cron(data, loop_interval)
              else:
                  continue
  
              # Something told us to continue, so we continue
              if '_continue' in data and data['_continue']:
                  continue
  
              # An error occurred so we bail out
              if '_error' in data and data['_error']:
                  continue
  
              seconds = int((_chop_ms(data['_next_fire_time']) - _chop_ms(now)).total_seconds())
  
              # If there is no job specific splay available,
              # grab the global which defaults to None.
              if 'splay' not in data:
                  data['splay'] = self.splay
  
              if 'splay' in data and data['splay']:
                  # Got "splay" configured, make decision to run a job based on that
                  if not data['_splay']:
                      # Try to add "splay" time only if next job fire time is
                      # still in the future. We should trigger job run
                      # immediately otherwise.
                      splay = _splay(data['splay'])
                      if now < data['_next_fire_time'] + datetime.timedelta(seconds=splay):
                          log.debug('schedule.handle_func: Adding splay of '
                                    '%s seconds to next run.', splay)
                          data['_splay'] = data['_next_fire_time'] + datetime.timedelta(seconds=splay)
                          if 'when' in data:
                              data['_run'] = True
                      else:
                          run = True
  
                  if data['_splay']:
                      # The "splay" configuration has been already processed, just use it
                      seconds = (data['_splay'] - now).total_seconds()
                      if 'when' in data:
                          data['_next_fire_time'] = data['_splay']
  
              if '_seconds' in data:
                  if seconds <= 0:
                      run = True
              elif 'when' in data and data['_run']:
                  if data['_next_fire_time'] <= now <= (data['_next_fire_time'] + loop_interval):
                      data['_run'] = False
                      run = True
              elif 'cron' in data:
                  # Reset next scheduled time because it is in the past now,
                  # and we should trigger the job run, then wait for the next one.
                  if seconds <= 0:
                      data['_next_fire_time'] = None
                      run = True
              elif 'once' in data:
                  if data['_next_fire_time'] <= now <= (data['_next_fire_time'] + loop_interval):
                      run = True
              elif seconds == 0:
                  run = True
  
              if '_run_on_start' in data and data['_run_on_start']:
                  run = True
                  data['_run_on_start'] = False
              elif run:
                  if 'range' in data:
                      _handle_range(data)
  
                      # An error occurred so we bail out
                      if '_error' in data and data['_error']:
                          continue
  
                      run = data['run']
                      # Override the functiton if passed back
                      if 'func' in data:
                          func = data['func']
  
                  # If there is no job specific skip_during_range available,
                  # grab the global which defaults to None.
                  if 'skip_during_range' not in data and self.skip_during_range:
                      data['skip_during_range'] = self.skip_during_range
  
                  if 'skip_during_range' in data and data['skip_during_range']:
                      _handle_skip_during_range(data, loop_interval)
  
                      # An error occurred so we bail out
                      if '_error' in data and data['_error']:
                          continue
  
                      run = data['run']
                      # Override the functiton if passed back
                      if 'func' in data:
                          func = data['func']
  
                  if 'skip_explicit' in data:
                      _handle_skip_explicit(data, loop_interval)
  
                      # An error occurred so we bail out
                      if '_error' in data and data['_error']:
                          continue
  
                      run = data['run']
                      # Override the functiton if passed back
                      if 'func' in data:
                          func = data['func']
  
                  if 'until' in data:
                      _handle_until(data)
  
                      # An error occurred so we bail out
                      if '_error' in data and data['_error']:
                          continue
  
                      run = data['run']
  
                  if 'after' in data:
                      _handle_after(data)
  
                      # An error occurred so we bail out
                      if '_error' in data and data['_error']:
                          continue
  
                      run = data['run']
  
                  # If args is a list and less than the number of functions
                  # run is set to False.
                  if 'args' in data and isinstance(data['args'], list):
                      if len(data['args']) < len(func):
                          data['_error'] = ('Number of arguments is less than '
                                            'the number of functions. Ignoring job.')
                          log.error(data['_error'])
                          run = False
  
              # If the job item has continue, then we set run to False
              # so the job does not run but we still get the important
              # information calculated, eg. _next_fire_time
              if '_continue' in data and data['_continue']:
                  run = False
  
              # If there is no job specific enabled available,
              # grab the global which defaults to True.
              if 'enabled' not in data:
                  data['enabled'] = self.enabled
  
              # If globally disabled, disable the job
              if not self.enabled:
                  data['enabled'] = self.enabled
                  data['_skip_reason'] = 'disabled'
                  data['_skipped_time'] = now
                  data['_skipped'] = True
                  run = False
  
              # Job is disabled, set run to False
              if 'enabled' in data and not data['enabled']:
                  data['_enabled'] = False
                  data['_skip_reason'] = 'disabled'
                  data['_skipped_time'] = now
                  data['_skipped'] = True
                  run = False
  
              miss_msg = ''
              if seconds < 0:
                  miss_msg = ' (runtime missed ' \
                             'by {0} seconds)'.format(abs(seconds))
  
              try:
                  if run:
                      # Job is disabled, continue
                      if 'enabled' in data and not data['enabled']:
                          log.debug('Job: %s is disabled', job_name)
                          data['_skip_reason'] = 'disabled'
                          data['_skipped_time'] = now
                          data['_skipped'] = True
                          continue
  
                      if 'jid_include' not in data or data['jid_include']:
                          data['jid_include'] = True
                          log.debug('schedule: Job %s was scheduled with jid_include, '
                                    'adding to cache (jid_include defaults to True)',
                                    job_name)
                          if 'maxrunning' in data:
                              log.debug('schedule: Job %s was scheduled with a max '
                                        'number of %s', job_name, data['maxrunning'])
                          else:
                              log.info('schedule: maxrunning parameter was not specified for '
                                       'job %s, defaulting to 1.', job_name)
                              data['maxrunning'] = 1
  
                      if not self.standalone:
                          data['run'] = run
                          data = self._check_max_running(func,
                                                         data,
                                                         self.opts,
                                                         now)
                          run = data['run']
  
                  # Check run again, just in case _check_max_running
                  # set run to False
                  if run:
                      log.info('Running scheduled job: %s%s', job_name, miss_msg)
                      self._run_job(func, data)
  
              finally:
                  # Only set _last_run if the job ran
                  if run:
                      data['_last_run'] = now
                      data['_splay'] = None
                  if '_seconds' in data:
                      if self.standalone:
                          data['_next_fire_time'] = now + datetime.timedelta(seconds=data['_seconds'])
                      elif '_skipped' in data and data['_skipped']:
                          data['_next_fire_time'] = now + datetime.timedelta(seconds=data['_seconds'])
                      elif run:
                          data['_next_fire_time'] = now + datetime.timedelta(seconds=data['_seconds'])