Skip to content
Snippets Groups Projects
Select Git revision
21 results Searching

index.ts

Blame
  • schedule.js 5.23 KiB
    const later = require('later');
    const moment = require('moment-timezone');
    
    module.exports = {
      hasValidTimezone,
      hasValidSchedule,
      isScheduledNow,
    };
    
    function fixShortHours(input) {
      return input.replace(/( \d?\d)((a|p)m)/g, '$1:00$2');
    }
    
    function hasValidTimezone(timezone) {
      if (!moment.tz.zone(timezone)) {
        return [false, `Invalid timezone: ${timezone}`];
      }
      return [true];
    }
    
    function hasValidSchedule(schedule) {
      let message;
      // check if any of the schedules fail to parse
      const hasFailedSchedules = schedule.some(scheduleText => {
        const massagedText = fixShortHours(scheduleText);
        const parsedSchedule = later.parse.text(massagedText);
        if (parsedSchedule.error !== -1) {
          message = `Failed to parse schedule "${scheduleText}"`;
          // It failed to parse
          return true;
        }
        if (parsedSchedule.schedules.some(s => s.m)) {
          message = `Schedule "${scheduleText}" should not specify minutes`;
          return true;
        }
        if (
          !parsedSchedule.schedules.some(
            s => s.d !== undefined || s.D || s.t_a !== undefined || s.t_b
          )
        ) {
          message = `Schedule "${scheduleText}" has no days of week or time of day`;
          return true;
        }
        // It must be OK
        return false;
      });
      if (hasFailedSchedules) {
        // If any fail then we invalidate the whole thing
        return [false, message];
      }
      return [true];
    }
    
    function isScheduledNow(config) {
      let configSchedule = config.schedule;
      logger.debug(
        { schedule: configSchedule, timezone: config.timezone },
        `Checking schedule`
      );
      if (
        !configSchedule ||
        configSchedule.length === 0 ||
        configSchedule[0] === ''
      ) {
        logger.debug('No schedule defined');
        return true;
      }
      if (!Array.isArray(configSchedule)) {
        logger.warn(
          `config schedule is not an array: ${JSON.stringify(configSchedule)}`
        );
        configSchedule = [configSchedule];
      }
      const [validSchedule, errorMessage] = hasValidSchedule(configSchedule);
      if (!validSchedule) {
        logger.warn(errorMessage);
        return true;
      }
      let now = moment();
      logger.debug(`now=${now.format()}`);
      // Adjust the time if repo is in a different timezone to renovate
      if (config.timezone) {
        logger.debug({ timezone: config.timezone }, 'Found timezone');
        const [validTimezone, error] = hasValidTimezone(config.timezone);
        if (!validTimezone) {
          logger.warn(error);
          return true;
        }
        logger.debug('Adjusting now for timezone');
        now = now.tz(config.timezone);
        logger.debug(`now=${now.format()}`);
      }
      // Get today in text form, e.g. "Monday";
      const currentDay = now.format('dddd');
      logger.debug(`currentDay=${currentDay}`);
      // Get the number of seconds since midnight
      const currentSeconds =
        now.hours() * 3600 + now.minutes() * 60 + now.seconds();
      logger.debug(`currentSeconds=${currentSeconds}`);
      // Support a single string but massage to array for processing
      logger.debug(`Checking ${configSchedule.length} schedule(s)`);
      // We run if any schedule matches
      const isWithinSchedule = configSchedule.some(scheduleText => {
        const parsedSchedule = later.parse.text(fixShortHours(scheduleText));
        logger.debug({ parsedSchedule }, `Checking schedule "${scheduleText}"`);
        // Later library returns array of schedules
        return parsedSchedule.schedules.some(schedule => {
          // Check if days are defined
          if (schedule.d) {
            logger.debug({ schedule_d: schedule.d }, `schedule.d`);
            // We need to compare text instead of numbers because
            // 'moment' adjusts day of week for locale while 'later' does not
            // later days run from 1..7
            const dowMap = [
              null,
              'Sunday',
              'Monday',
              'Tuesday',
              'Wednesday',
              'Thursday',
              'Friday',
              'Saturday',
            ];
            const scheduledDays = schedule.d.map(day => dowMap[day]);
            logger.debug({ scheduledDays }, `scheduledDays`);
            if (scheduledDays.indexOf(currentDay) === -1) {
              logger.debug(
                `Does not match schedule because ${currentDay} is not in ${scheduledDays}`
              );
              return false;
            }
          }
          if (schedule.D) {
            logger.debug({ schedule_D: schedule.D }, `schedule.D`);
            // moment outputs as string but later outputs as integer
            const currentDayOfMonth = parseInt(now.format('D'), 10);
            if (!schedule.D.includes(currentDayOfMonth)) {
              return false;
            }
          }
          // Check for start time
          if (schedule.t_a) {
            const startSeconds = schedule.t_a[0];
            if (currentSeconds < startSeconds) {
              logger.debug(
                `Does not match schedule because ${currentSeconds} is earlier than ${startSeconds}`
              );
              return false;
            }
          }
          // Check for end time
          if (schedule.t_b) {
            const endSeconds = schedule.t_b[0];
            if (currentSeconds > endSeconds) {
              logger.debug(
                `Does not match schedule because ${currentSeconds} is later than ${endSeconds}`
              );
              return false;
            }
          }
          logger.debug(`Matches schedule ${scheduleText}`);
          return true;
        });
      });
      if (!isWithinSchedule) {
        logger.debug('Package not scheduled');
        return false;
      }
      return true;
    }