- Status: Draft
- Last Updated: 2025-12-27
Bills repeat. Rent comes monthly. Insurance renews yearly. The recurrence engine handles this predictable rhythm by calculating when payments come due next and tracking their status as time passes.
Without automatic due date advancement, you'd manually edit every bill after each payment. The recurrence engine eliminates this busywork while preserving the "Active Payer" philosophy; it calculates dates, but you still decide when to log payments.
When you log a payment and keep the "Update Due Date" toggle on (the default), the recurrence engine calculates the next due date based on the bill's repeat interval.
| Interval | Behavior |
|---|---|
| Every Week | Advances 7 days |
| Every 2 Weeks | Advances 14 days |
| Twice Per Month | Advances to the 15th or 1st (e.g., Jan 1 -> Jan 15 -> Feb 1) |
| Every Month | Advances to the same day next month |
| Every 2 Months | Advances to the same day in 2 months |
| Every 3 Months | Advances to the same day in 3 months |
| Every Year | Advances to the same day next year |
| Never | No next date; bill marked as paid |
Examples:
- A weekly bill due January 1 moves to January 8 after payment.
- A bi-weekly bill due January 1 moves to January 15.
- A twice-monthly bill due January 1 moves to January 15; if due January 15, it moves to February 1.
- A monthly bill due January 15 moves to February 15.
- A quarterly bill due January 10 moves to April 10.
- A bill with "Never" interval due March 1 becomes "paid" with no future due date.
At midnight each day, a background job scans all pending bills. Any bill with a due date before today gets marked "overdue." This happens automatically without user action.
Status determination:
- Due date is today or in the future → pending
- Due date is in the past → overdue
The bill list displays status badges based on these calculations:
- Pending: Due date hasn't passed yet
- Overdue: Due date passed without payment logged
- Paid: One-time bill (Interval: Never) fully paid
The due date shown in the bill list always reflects the current billing cycle. After you log a payment with "Update Due Date" enabled, the displayed date jumps to the next occurrence.
Bill forms and the payment logging modal interact with these services through server actions. The UI never calculates dates directly.
The system uses "anchor dates" for recurrence calculations to prevent date drift. When a bill's due date falls on a weekend, the system stores the anchor date (the actual weekend date) but displays an adjusted payment date based on your weekend adjustment strategy.
Example: A monthly bill due January 15 (Saturday) with "Move to Previous Business Day" strategy:
- Anchor date stored: January 15 (used for recurrence calculations)
- Payment date displayed: January 14 (Friday, adjusted for display)
- After payment, next anchor date: February 15 (preserves the monthly pattern)
- Next payment date displayed: February 15, adjusted if it falls on a weekend
This separation ensures that monthly bills stay anchored to the same day of the month across all months, regardless of weekend adjustments. The recurrence engine always calculates next due dates using anchor dates, preventing drift over time.
For details on weekend adjustment strategies and configuration, see Weekend Payment Date Adjustment.
When a bill is due on the 31st, months without 31 days get skipped.
Example: A bill due January 31 skips February (28/29 days) and moves to March 31.
The engine uses strict date matching. It doesn't "roll back" to February 28; it waits for the next month that has a 31st.
Yearly bills due February 29 skip non-leap years.
Example: A bill due February 29, 2024 jumps to February 29, 2028 (the next leap year), not February 28, 2025.
If you need annual renewal on the last day of February every year, set the due date to February 28 instead.
If you set a custom start date (not the 1st or 15th), the system calculates the second occurrence by adding 14 days. For example, a bill starting on the 5th will recur on the 19th.
Server functions normalize dates to midnight before comparison. This avoids issues where time-of-day affects overdue detection. A bill due "today" remains pending until tomorrow.
These bills (formerly called "one-time") transition to "paid" status permanently. They don't advance because there's no next occurrence. The "Amount Due" zeroes out, and the bill stays in history.
If you log a payment at the same moment the daily status check runs, the system handles this gracefully. Your payment takes priority, and the bill won't be incorrectly marked as overdue.
The recurrence engine treats variable and fixed bills identically. The "Variable Amount" flag affects UI display (showing "estimate" labels) but doesn't change how due dates advance.
To confirm the recurrence engine works:
- Create a monthly bill due today.
- Log a payment with "Update Due Date" enabled.
- The bill's due date should advance exactly one month.
- Create a bill with "Every 2 Weeks" interval; due date should advance 14 days after payment.
- Create a bill with "Never" interval, log payment; status should show "paid".
- Create a bill due yesterday without logging payment; status should show "overdue".
- Logging Payments - Recording payments, partial payments, and historical payment detection
- Background Jobs - Automated system tasks
- Weekend Payment Date Adjustment - How weekend due dates are adjusted for banking reality