phacility/phabricator · error · Exception

RRULE specifies BYSETPOS with FREQ "%s", but this is invalid

Error message

RRULE specifies BYSETPOS with FREQ "%s", but this is invalid.

What it means

Evaluating BYSETPOS requires the rule engine to rewind its cursor one interval, and PhutilCalendarRecurrenceRule only implements rewinding down to MINUTELY. The default branch of the rewind switch therefore throws for FREQ=SECONDLY (or any unrecognized frequency) rather than silently mis-evaluating the set positions.

Source

Thrown at src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php:671

        case self::FREQUENCY_WEEKLY:
          $this->cursorWeek -= $interval;
          $this->rewindWeek();
          break;
        case self::FREQUENCY_DAILY:
          $this->cursorDay -= $interval;
          $this->rewindDay();
          break;
        case self::FREQUENCY_HOURLY:
          $this->cursorHour -= $interval;
          $this->rewindHour();
          break;
        case self::FREQUENCY_MINUTELY:
          $this->cursorMinute -= $interval;
          $this->rewindMinute();
          break;
        case self::FREQUENCY_SECONDLY:
        default:
          throw new Exception(
            pht(
              'RRULE specifies BYSETPOS with FREQ "%s", but this is invalid.',
              $frequency));
      }
    }

    // We can generate events from before the cursor when evaluating rules
    // with BYSETPOS or FREQ=WEEKLY.
    $this->minimumEpoch = $this->getStartDateTime()->getEpoch();

    $cursor_state = array(
      'year' => $this->cursorYear,
      'month' => $this->cursorMonth,
      'week' => $this->cursorWeek,
      'day' => $this->cursorDay,
      'hour' => $this->cursorHour,
    );

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Use a coarser frequency (MINUTELY or larger) when BYSETPOS is required
  2. Drop the BYSETPOS part if second-level recurrence is the real requirement
  3. Reject FREQ=SECONDLY + BYSETPOS combinations during RRULE pre-validation before import

Example fix

// before
$rrule = 'FREQ=SECONDLY;BYSECOND=0,30;BYSETPOS=1';

// after
$rrule = 'FREQ=MINUTELY;BYSECOND=0,30;BYSETPOS=1';
Defensive patterns

Strategy: validation

Validate before calling

if (isset($parts['BYSETPOS']) && idx($parts, 'FREQ') === 'SECONDLY') {
  throw new Exception('BYSETPOS cannot be combined with FREQ=SECONDLY.');
}

Try / catch

try {
  $events = $rule->getEvents();
} catch (Exception $ex) {
  if (strpos($ex->getMessage(), 'BYSETPOS') !== false) {
    $events = $rule->setBySetPosition(array())->getEvents();
  }
}

Prevention

When it happens

Trigger: An RRULE that combines BYSETPOS with a sub-minute frequency, e.g. 'FREQ=SECONDLY;BYSETPOS=1', or code that sets frequency to FREQUENCY_SECONDLY together with setBySetPosition(...) and then triggers evaluation/rewind.

Common situations: Rules synthesized by test harnesses or tooling that sweep all frequencies; malformed imported RRULEs where FREQ was truncated (e.g. 'FREQ=SECOND;...' parsed into an unexpected value).

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/3c94364fd38999f7. Report an issue: GitHub.