---
title: How Scheduling Works
description: "The calendar rules behind every target the plugin calculates — including the awkward cases: daylight saving, month ends, leap days and disallowed weekdays. --- ## Who Owns a Schedule An active recurring feed starts **one series per parent/feed pair** when…"
url: "https://brightleafdigital.io/docs/how-scheduling-works/"
updated: "2026-10-18"
category: "Recurring Form Submissions for Gravity Forms / Explanations & Concepts"
---

# How Scheduling Works

The calendar rules behind every target the plugin calculates — including the awkward cases: daylight saving, month ends, leap days and disallowed weekdays.

---

## Who Owns a Schedule

An active recurring feed starts **one series per parent/feed pair** when its initial conditional logic passes.

At that moment, the feed's settings are copied into a **normalized schedule snapshot** on the series. From then on:

| Action | Effect on existing series |
| --- | --- |
| Ordinary feed save | None. New series only. |
| **Change next submission** | Moves that one series' target; the cadence continues from it. |
| **Apply saved feed schedule** | Copies schedule settings to the series you select, after a preview and confirmation. |

Recurrence changes belong in feed settings. Date moves belong in Queue. The separation is the point: neither one can surprise you with the other's effect.

---

## Local Time, Stored as UTC

Calendar intervals are calculated in the schedule's **selected local timezone**, and stored as **UTC epoch targets**.

That combination is what makes "every day at 11:00" mean 11:00 all year, while the stored value remains unambiguous.

---

## Daylight Saving

| Case | Behaviour |
| --- | --- |
| **Ordinary DST change** | A daily 11:00 schedule stays at 11:00. |
| **Spring-forward gap** | A time that doesn't exist moves to the **first valid instant after** the gap. |
| **Autumn overlap** | A time that happens twice uses the **first** occurrence. |
| **Fixed UTC offset** | Cannot follow DST at all. An 11:00 schedule effectively shifts by an hour for half the year. |

Use an IANA timezone name — `America/New_York`, `Europe/London` — rather than an offset. The feed editor links to your WordPress timezone settings when it detects an offset.

---

## Month and Year Recurrence

Monthly and yearly recurrence keeps an **anchor day**.

- If the anchor day doesn't exist in a given period, the target **clamps to the last day** of that period.
- When the anchor day exists again, the schedule **returns to it**.

| Anchor | January | February | March |
| --- | --- | --- | --- |
| 31st | Jan 31 | Feb 28 (clamped) | Mar 31 (restored) |
| 29th | Jan 29 | Feb 29 in a leap year, Feb 28 otherwise | Mar 29 |

Clamping never permanently drags a schedule earlier.

---

## Allowed Weekdays

The **Monday–Sunday** checkboxes filter *when a slot may land*, without changing the underlying calendar anchor.

- A slot landing on a disallowed weekday **moves forward** to the next allowed weekday.
- The anchor itself is unchanged, so the schedule doesn't drift permanently.
- If multiple slots coincide on the same eligible day, they produce **one** occurrence — never a burst.

At least one weekday must remain selected.

---

## End Conditions

| Condition | Rule |
| --- | --- |
| **Number of successful submissions** | Counts **successful children only**. The parent isn't counted; failures, conditional skips and skipped missed runs don't count. |
| **End date** | **Inclusive** upper bound, evaluated in the schedule's timezone. |
| **Number from parent form field** | The same counting rule, with the limit read from a Number field on the parent. |

---

## The First Occurrence

A new schedule applies the **interval first**, then the clock time.

> Daily feed. Parent submitted **September 14 at 01:14**. Specified time **01:20**. First run: **September 15 at 01:20**.

Not six minutes later — a full day later, at the specified time.

This avoids a surprise submission moments after someone fills in a form. When you want the earlier first run, **Change next submission** sets it explicitly.

---

## Run Time Modes

| Mode | Behaviour |
| --- | --- |
| **Preserve original submission local time** | Each occurrence uses the parent's own submission time-of-day. Feeds saved before this setting existed behave this way. |
| **Specified local time** | Every occurrence uses the `HH:MM` you set, in the schedule's timezone. |

In *preserve* mode, the feed **preview** has to estimate — it uses "a parent submitted now". Once a series actually starts, it uses that parent's real timestamp.

---

## One Target at a Time

The single most consequential rule in the plugin:

> **A series holds one next target. It never accumulates a queue.**

The next target is calculated **after** each attempt resolves. There is no list of pending occurrences, so there is nothing that could ever replay.

This is why:

- downtime never produces a backlog,
- lateness never compounds,
- and moving one target simply re-anchors everything after it.

---

## Manual Actions and the Anchor

| Action | Effect on the rhythm |
| --- | --- |
| **Run Next Submission Now** | Consumes the next slot, **preserving** the long-term anchor. |
| **Retry Failed Submission** | Re-runs the failed target. The anchor is unchanged. |
| **Change next submission** | **Re-anchors** the cadence to the date and time you chose. |
| **Resume** | Picks the next eligible **future** slot; the paused interval is skipped entirely. |

Optionally shifting the cadence based on a manual run's time is on the roadmap.
