docs: function(schedule, useSeconds) {
      var textParts = [];
  
      // A later.js schedules contains no member for time units where an asterisk is used,
      // but schedules that means the same (e.g 0/1 is essentially the same as *) are
      // returned with populated members.
      // Remove all members that are fully populated to reduce complexity of code
      removeFromSchedule( schedule, 'M', 12 );
      removeFromSchedule( schedule, 'D', 31 );
      removeFromSchedule( schedule, 'd', 7 );
      removeFromSchedule( schedule, 'h', 24 );
      removeFromSchedule( schedule, 'm', 60 );
      removeFromSchedule( schedule, 's', 60 );
  
      var everySecond = useSeconds && schedule['s'] === undefined,
          everyMinute = schedule['m'] === undefined,
          everyHour = schedule['h'] === undefined
          everyWeekday = schedule['d'] === undefined
          everyDayInMonth = schedule['D'] === undefined,
          everyMonth = schedule['M'] === undefined;
  
      var oneOrTwoSecondsPerMinute = schedule['s'] && schedule['s'].length <= 2;
      var oneOrTwoMinutesPerHour = schedule['m'] && schedule['m'].length <= 2;
      var oneOrTwoHoursPerDay = schedule['h'] && schedule['h'].length <= 2;
      var onlySpecificDaysOfMonth = schedule['D'] && schedule['D'].length !== 31;
      if ( oneOrTwoHoursPerDay && oneOrTwoMinutesPerHour && oneOrTwoSecondsPerMinute ) {
        // If there are only one or two specified values for
        // hour or minute, print them in HH:MM format, or HH:MM:ss if seconds are used
        // If seconds are not used, later.js returns one element for the seconds (set to zero)
  
        var hm = [];
        var m = moment();
        for (var i=0; i < schedule['h'].length; i++) {
          for (var j=0; j < schedule['m'].length; j++) {
            for (var k=0; k < schedule['s'].length; k++) {
              m.hour(schedule['h'][i]);
              m.minute(schedule['m'][j]);
              m.second(schedule['s'][k]);
              hm.push(m.format( useSeconds ? 'HH:mm:ss' : 'HH:mm'));
            }
          }
        }
        if (hm.length < 2) {
          textParts.push( hm[0] );
        } else {
          var last_val = hm.pop();
          textParts.push( hm.join(', ') + ' and ' + last_val );
        }
        if (everyWeekday && everyDayInMonth) {
          textParts.push('every day');
        }
  
      } else {
        var seconds = getSecondsTextParts(schedule['s']);
        var minutes = getMinutesTextParts(schedule['m']);
        var beginning = '';
        var end = '';
  
        textParts.push('Every');
  
        // Otherwise, list out every specified hour/minute value.
        var hasSpecificSeconds = schedule['s'] && (
            schedule['s'].length > 1 && schedule['s'].length < 60 ||
            schedule['s'].length === 1 && schedule['s'][0] !== 0 );
        if(hasSpecificSeconds) {
          beginning = seconds.beginning;
          end = seconds.text;
        }
  
        if(schedule['h']) { // runs only at specific hours
          if( hasSpecificSeconds ) {
            end += ' on the ';
          }
          if (schedule['m']) { // and only at specific minutes
            var hours = numberList(schedule['h']) + ' hour';
            if( !hasSpecificSeconds && isOnTheHour(schedule['m']) ) {
              textParts = [ 'On the' ];
              end += hours;
            } else {
              beginning = minutes.beginning;
              end += minutes.text + ' past the ' + hours;
            }
          } else { // specific hours, but every minute
            end += 'minute of ' + numberList(schedule['h']) + ' hour';
          }
        } else if(schedule['m']) { // every hour, but specific minutes
          beginning = minutes.beginning;
          end += minutes.text;
          if( !isOnTheHour(schedule['m']) && ( onlySpecificDaysOfMonth || schedule['d'] || schedule['M'] ) ) {
            end += ' past every hour';
          }
        } else if( !schedule['s'] && !schedule['m'] ) {
          beginning = seconds.beginning;
        } else if( !useSeconds || !hasSpecificSeconds) { // cronspec has "*" for both hour and minute
          beginning += minutes.beginning;
        }
        textParts.push(beginning);
        textParts.push(end);
      }
  
      if (onlySpecificDaysOfMonth) { // runs only on specific day(s) of month
        textParts.push('on the ' + numberList(schedule['D']));
        if (!schedule['M']) {
          textParts.push('of every month');
        }
      }
  
      if (schedule['d']) { // runs only on specific day(s) of week
        if (schedule['D']) {
          // if both day fields are specified, cron uses both; superuser.com/a/348372
          textParts.push('and every');
        } else {
          textParts.push('on');
        }
        textParts.push(dateList(schedule['d'], 'dow'));
      }
  
      if (schedule['M']) {
        if( schedule['M'].length === 12 ) {
          textParts.push('day of every month');
        } else {
          // runs only in specific months; put this output last
          textParts.push('in ' + dateList(schedule['M'], 'mon'));
        }
      }
  
      return textParts.filter(function(p) { return p; }).join(' ');
    }