{"record":{"id":"41bdbdb044a48871","repo":"hcengineering/platform","slug":"unable-to-calculate-negative-position-pos","errorCode":null,"errorMessage":"Unable to calculate negative position ${pos}","messagePattern":"Unable to calculate negative position (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"plugins/calendar/src/utils.ts","lineNumber":139,"sourceCode":"    }\n  }\n\n  return false\n}\n\nfunction getNegativePosition (date: Date, weekday: string, pos: number): number | undefined {\n  const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate()\n  const occurrences = []\n  for (let day = lastDayOfMonth; day >= 1; day--) {\n    const tempDate = new Date(date.getFullYear(), date.getMonth(), day)\n    if (getWeekday(tempDate) === weekday) {\n      occurrences.push(day)\n    }\n    if (occurrences.length === Math.abs(pos)) {\n      return occurrences.pop()\n    }\n  }\n  throw new Error(`Unable to calculate negative position ${pos}`)\n}\n\nfunction generateMonthlyValues (rule: RecurringRule, currentDate: Date, from: Timestamp, to: Timestamp): Timestamp[] {\n  const values: Timestamp[] = []\n  const { count, endDate, interval } = rule\n  let { byDay, byMonthDay, bySetPos } = rule\n  let i = 0\n\n  if (byDay == null && byMonthDay == null) {\n    byMonthDay = [currentDate.getDate()]\n  }\n\n  while (true) {\n    const next = new Date(currentDate).setMonth(currentDate.getMonth() + (interval ?? 1), 1)\n    const end = new Date(new Date(currentDate).setMonth(currentDate.getMonth() + 1, 1))\n    let date = currentDate\n    const candidates: Date[] = []\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/plugins/calendar/src/utils.ts#L121-L157","documentation":"getNegativePosition computes the Nth occurrence of a weekday within a month for BYDAY/BYSETPOS handling. If it iterates through the whole period without collecting Math.abs(pos) occurrences, the requested negative position does not exist in that month and the error is thrown.","triggerScenarios":"A recurring rule requests e.g. the 5th-last Monday (pos=-5) of a month that has fewer than 5 Mondays, or byDay/byMonthDay data is inconsistent with the month being expanded.","commonSituations":"Recurrence rules imported from external calendars using large negative set positions; hand-crafted rules like 'last fifth weekday'; month-length edge cases (February) during event expansion.","solutions":["Clamp/validate negative positions in rules to values that exist for every affected month (e.g. -1 to -4 for weekly positions)","Catch the error in expansion code and skip non-existent occurrences for that month","Normalize imported iCalendar BYSETPOS values against month feasibility at import"],"exampleFix":"// before: rule with bySetPos: -5 for MONTHLY on Monday, February throws\n// after: clamp before expansion\nconst maxPos = Math.floor(31 / 7) // 4 weekly occurrences guaranteed\nconst pos = Math.max(rule.bySetPos, -maxPos)","handlingStrategy":"try-catch","validationCode":"const possible = Math.floor(daysInMonth(currentDate) / 7)\nif (Math.abs(rule.bySetPos ?? -1) > possible) {\n  // position does not exist in this month — skip occurrence\n}","typeGuard":null,"tryCatchPattern":"try {\n  day = getNegativePosition(rule, currentDate, pos)\n} catch (err) {\n  if ((err as Error).message.startsWith('Unable to calculate negative position')) {\n    continue // occurrence does not exist in this month\n  } else throw err\n}","preventionTips":["Constrain negative BYSETPOS values to -1..-4 when authoring rules","Validate imported iCalendar BYSETPOS against month feasibility","Test recurrence expansion on short months like February"],"tags":["calendar","recurrence","edge-case"],"backgroundTag":"invalid-recurring-rule-position","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}